]> git.sur5r.net Git - i3/i3/commitdiff
Merge branch 'tree' into next
authorMichael Stapelberg <michael@stapelberg.de>
Sun, 31 Jul 2011 19:56:02 +0000 (21:56 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Sun, 31 Jul 2011 19:56:02 +0000 (21:56 +0200)
214 files changed:
.gitignore
DEPENDS
Makefile
PACKAGE-MAINTAINER
RELEASE-NOTES-3.d-bf1 [deleted file]
RELEASE-NOTES-3.e [deleted file]
RELEASE-NOTES-4.0 [new file with mode: 0644]
RELEASE-NOTES-tree-pr1 [new file with mode: 0644]
RELEASE-NOTES-tree-pr2 [new file with mode: 0644]
RELEASE-NOTES-tree-pr3 [new file with mode: 0644]
RELEASE-NOTES-tree-pr4 [new file with mode: 0644]
common.mk
debian/changelog
debian/control
debian/rules
docs/Makefile
docs/debugging
docs/hacking-howto
docs/ipc
docs/tree-migrating [new file with mode: 0644]
docs/userguide
dump-asy.pl [new file with mode: 0755]
gtk-tree-watch.pl [new file with mode: 0755]
i3-config-wizard/Makefile [new file with mode: 0644]
i3-config-wizard/atoms.xmacro [new file with mode: 0644]
i3-config-wizard/cfgparse.l [new file with mode: 0644]
i3-config-wizard/cfgparse.y [new file with mode: 0644]
i3-config-wizard/ipc.c [new file with mode: 0644]
i3-config-wizard/ipc.h [new file with mode: 0644]
i3-config-wizard/main.c [new file with mode: 0644]
i3-config-wizard/xcb.c [new file with mode: 0644]
i3-config-wizard/xcb.h [new file with mode: 0644]
i3-input/Makefile
i3-input/i3-input.h
i3-input/main.c
i3-input/xcb.c
i3-migrate-config-to-v4.pl [new file with mode: 0755]
i3-msg/Makefile
i3-msg/main.c
i3-nagbar/Makefile [new file with mode: 0644]
i3-nagbar/atoms.xmacro [new file with mode: 0644]
i3-nagbar/i3-nagbar.h [new file with mode: 0644]
i3-nagbar/main.c [new file with mode: 0644]
i3-nagbar/xcb.c [new file with mode: 0644]
i3.config
i3.config.keycodes [new file with mode: 0644]
include/all.h [new file with mode: 0644]
include/assignments.h [new file with mode: 0644]
include/atoms.xmacro [new file with mode: 0644]
include/click.h
include/client.h [deleted file]
include/cmdparse.h [new file with mode: 0644]
include/commands.h [deleted file]
include/con.h [new file with mode: 0644]
include/config.h
include/container.h [deleted file]
include/data.h
include/ewmh.h
include/floating.h
include/handlers.h
include/i3.h
include/i3/ipc.h
include/ipc.h
include/layout.h [deleted file]
include/load_layout.h [new file with mode: 0644]
include/log.h
include/manage.h
include/match.h [new file with mode: 0644]
include/move.h [new file with mode: 0644]
include/output.h [new file with mode: 0644]
include/queue.h
include/randr.h
include/render.h [new file with mode: 0644]
include/resize.h
include/sighandler.h
include/table.h [deleted file]
include/tree.h [new file with mode: 0644]
include/util.h
include/window.h [new file with mode: 0644]
include/workspace.h
include/x.h [new file with mode: 0644]
include/xcb.h
include/xcb_compat.h [new file with mode: 0644]
include/xcursor.h [new file with mode: 0644]
include/xinerama.h
man/Makefile
man/asciidoc.conf
man/i3-input.man
man/i3-msg.man
man/i3-nagbar.man [new file with mode: 0644]
man/i3.man
render-tree/Con.pm [new file with mode: 0644]
render-tree/render.pl [new file with mode: 0755]
src/assignments.c [new file with mode: 0644]
src/cfgparse.l
src/cfgparse.y
src/click.c
src/client.c [deleted file]
src/cmdparse.l [new file with mode: 0644]
src/cmdparse.y [new file with mode: 0644]
src/commands.c [deleted file]
src/con.c [new file with mode: 0644]
src/config.c
src/container.c [deleted file]
src/debug.c
src/ewmh.c
src/floating.c
src/handlers.c
src/ipc.c
src/layout.c [deleted file]
src/load_layout.c [new file with mode: 0644]
src/log.c
src/main.c [new file with mode: 0644]
src/mainx.c [deleted file]
src/manage.c
src/match.c [new file with mode: 0644]
src/move.c [new file with mode: 0644]
src/output.c [new file with mode: 0644]
src/randr.c
src/render.c [new file with mode: 0644]
src/resize.c
src/sighandler.c
src/table.c [deleted file]
src/tree.c [new file with mode: 0644]
src/util.c
src/window.c [new file with mode: 0644]
src/workspace.c
src/x.c [new file with mode: 0644]
src/xcb.c
src/xcursor.c [new file with mode: 0644]
src/xinerama.c
testcases/Makefile
testcases/Xdummy [new file with mode: 0755]
testcases/complete-run.pl [new file with mode: 0755]
testcases/i3-test.config [new file with mode: 0644]
testcases/t/01-tile.t
testcases/t/02-fullscreen.t
testcases/t/03-unmanaged.t
testcases/t/04-floating.t
testcases/t/05-ipc.t
testcases/t/06-focus.t
testcases/t/07-move.t
testcases/t/08-focus-stack.t
testcases/t/09-stacking.t
testcases/t/10-dock.t
testcases/t/11-goto.t
testcases/t/12-floating-resize.t
testcases/t/13-urgent.t
testcases/t/14-client-leader.t
testcases/t/15-ipc-workspaces.t
testcases/t/16-nestedcons.t [new file with mode: 0644]
testcases/t/17-workspace.t [new file with mode: 0644]
testcases/t/18-openkill.t [new file with mode: 0644]
testcases/t/19-match.t [new file with mode: 0644]
testcases/t/20-multiple-cmds.t [new file with mode: 0644]
testcases/t/21-next-prev.t [new file with mode: 0644]
testcases/t/22-split.t [new file with mode: 0644]
testcases/t/24-move.t [new file with mode: 0644]
testcases/t/26-regress-close.t [new file with mode: 0644]
testcases/t/27-regress-floating-parent.t [new file with mode: 0644]
testcases/t/28-open-order.t [new file with mode: 0644]
testcases/t/29-focus-after-close.t [new file with mode: 0644]
testcases/t/30-close-empty-split.t [new file with mode: 0644]
testcases/t/31-stacking-order.t [new file with mode: 0644]
testcases/t/32-move-workspace.t [new file with mode: 0644]
testcases/t/33-size-hints.t [new file with mode: 0644]
testcases/t/34-invalid-command.t [new file with mode: 0644]
testcases/t/35-floating-focus.t [new file with mode: 0644]
testcases/t/36-floating-ws-empty.t [new file with mode: 0644]
testcases/t/37-floating-unmap.t [new file with mode: 0644]
testcases/t/38-floating-attach.t [new file with mode: 0644]
testcases/t/39-ws-numbers.t [new file with mode: 0644]
testcases/t/40-focus-lost.t [new file with mode: 0644]
testcases/t/41-resize.t [new file with mode: 0644]
testcases/t/42-regress-move-floating.t [new file with mode: 0644]
testcases/t/43-regress-floating-restart.t [new file with mode: 0644]
testcases/t/44-regress-floating-resize.t [new file with mode: 0644]
testcases/t/45-flattening.t [new file with mode: 0644]
testcases/t/46-floating-reinsert.t [new file with mode: 0644]
testcases/t/47-regress-floatingmove.t [new file with mode: 0644]
testcases/t/48-regress-floatingmovews.t [new file with mode: 0644]
testcases/t/50-regress-dock-restart.t [new file with mode: 0644]
testcases/t/51-regress-float-size.t [new file with mode: 0644]
testcases/t/52-regress-level-up.t [new file with mode: 0644]
testcases/t/53-floating-originalsize.t [new file with mode: 0644]
testcases/t/54-regress-multiple-dock.t [new file with mode: 0644]
testcases/t/55-floating-split-size.t [new file with mode: 0644]
testcases/t/56-fullscreen-focus.t [new file with mode: 0644]
testcases/t/57-regress-fullscreen-level-up.t [new file with mode: 0644]
testcases/t/58-wm_take_focus.t [new file with mode: 0644]
testcases/t/59-socketpaths.t [new file with mode: 0644]
testcases/t/61-regress-borders-restart.t [new file with mode: 0644]
testcases/t/62-regress-dock-urgent.t [new file with mode: 0644]
testcases/t/63-wm-state.t [new file with mode: 0644]
testcases/t/64-kill-win-vs-client.t [new file with mode: 0644]
testcases/t/65-for_window.t [new file with mode: 0644]
testcases/t/66-assign.t [new file with mode: 0644]
testcases/t/67-workspace_layout.t [new file with mode: 0644]
testcases/t/68-regress-fullscreen-restart.t [new file with mode: 0644]
testcases/t/69-border-toggle.t [new file with mode: 0644]
testcases/t/70-force_focus_wrapping.t [new file with mode: 0644]
testcases/t/71-config-migrate.t [new file with mode: 0644]
testcases/t/lib/i3test.pm
tests/queue.h [new file with mode: 0644]
tests/swap.c [new file with mode: 0644]
website/contact/index.html
website/docs/building_ubuntu_9.04.html
website/docs/index.html
website/downloads/index.html
website/favicon.png [deleted file]
website/i3lock/index.html
website/i3status/index.html
website/index.html
website/screenshots/index.html

index eec7fb0f1683d1a093a5a44c714b04f8e7021eef..454b2e3ac3b74ecfac12936862d1ed203e993e6d 100644 (file)
@@ -1,19 +1,37 @@
 *.o
-i3
-i3-input/i3-input
-i3-msg/i3-msg
+tags
 include/loglevels.h
 loglevels.tmp
-src/*.output
-src/*.tab.*
-src/*.yy.c
+*.swp
+*.gcda
+*.gcno
+testcases/testsuite-*
+testcases/latest
+*.output
+*.tab.*
+*.yy.c
 man/i3-msg.1
 man/i3-msg.xml
 man/i3-msg.html
+man/i3-nagbar.1
+man/i3-nagbar.xml
+man/i3-nagbar.html
 man/i3-wsbar.1
 man/i3-wsbar.xml
 man/i3-wsbar.html
+man/i3-input.1
+man/i3-input.xml
+man/i3-input.html
 man/i3.1
 man/i3.xml
 man/i3.html
-tags
+*.tar.bz2*
+i3
+i3-input/i3-input
+i3-nagbar/i3-nagbar
+i3-msg/i3-msg
+i3-config-wizard/i3-config-wizard
+docs/*.html
+docs/*.aux
+docs/*.out
+docs/*.pdf
diff --git a/DEPENDS b/DEPENDS
index b7a6fefb6a2bb74c2f973fb73de5d68fdb8786f3..71abc3b2858e5cb001d904badc0f1b9349699ff1 100644 (file)
--- a/DEPENDS
+++ b/DEPENDS
@@ -1,32 +1,39 @@
-You need the following libraries. The version given is to be understood as the
-minimum version required. However, if any of these libraries changes the API,
-i3 may not compile anymore. In that case, please try using the versions
-mentioned below until a fix is provided.
-
- * xcb-proto-1.3 (2008-12-10)
- * libxcb-1.1.93 (2008-12-11)
- * xcb-util-0.3.3 (2009-01-31)
- * libev
- * flex and bison
- * yajl (the IPC interface uses JSON to serialize data)
- * asciidoc >= 8.3.0 for docs/hacking-howto
- * asciidoc, xmlto, docbook-xml for man/i3.man
- * Xlib, the one that comes with your X-Server
- * x11-utils for xmessage (only for displaying the welcome message, so this is
-   mainly interesting for distributors)
-
-Recommendations:
- * i3lock for locking your screen
- * dmenu for launching applications
-
-Get the libraries from:
-http://xcb.freedesktop.org/dist/xcb-proto-1.5.tar.bz2
-http://xcb.freedesktop.org/dist/libxcb-1.1.93.tar.bz2
-http://xcb.freedesktop.org/dist/xcb-util-0.3.5.tar.bz2
-http://libev.schmorp.de/
-http://flex.sourceforge.net/
-http://www.gnu.org/software/bison/
-http://lloyd.github.com/yajl/
-
-http://i3.zekjur.net/i3lock/
-http://tools.suckless.org/dmenu
+
+ i3 has the following dependencies:
+
+   "min" means minimum required version
+   "lkgv" means last known good version
+
+┌─────────────┬────────┬────────┬────────────────────────────────────────┐
+│ dependency  │ min.   │ lkgv   │ URL                                    │
+├─────────────┼────────┼────────┼────────────────────────────────────────┤
+│ pkg-config  │ 0.25   │ 0.26   │ http://pkgconfig.freedesktop.org/      │
+│ xcb-proto   │ 1.3    │ 1.6    │ http://xcb.freedesktop.org/dist/       │
+│ libxcb      │ 1.1.93 │ 1.7    │ http://xcb.freedesktop.org/dist/       │
+│ xcb-util    │ 0.3.3  │ 0.3.8  │ http://xcb.freedesktop.org/dist/       │
+│ libev       │ 3.0    │ 4.04   │ http://libev.schmorp.de/               │
+│ flex        │ 2.5.35 │ 2.5.35 │ http://flex.sourceforge.net/           │
+│ bison       │ 2.4.1  │ 2.4.1  │ http://www.gnu.org/software/bison/     │
+│ yajl        │ 1.0.8  │ 2.0.1  │ http://lloyd.github.com/yajl/          │
+│ asciidoc    │ 8.3.0  │ 8.6.4  │ http://www.methods.co.nz/asciidoc/     │
+│ xmlto       │ 0.0.23 │ 0.0.23 │ http://www.methods.co.nz/asciidoc/     │
+│ docbook-xml │ 4.5    │ 4.5    │ http://www.methods.co.nz/asciidoc/     │
+│ libxcursor  │ 1.1.11 │ 1.1.11 │ http://ftp.x.org/pub/current/src/lib/  │
+│ Xlib        │ 1.3.3  │ 1.4.3  │ http://ftp.x.org/pub/current/src/lib/  │
+└─────────────┴────────┴────────┴────────────────────────────────────────┘
+
+ i3-msg, i3-input, i3-nagbar and i3-config-wizard do not introduce any new
+ dependencies.
+
+ i3-wsbar is implemented in Perl and has the following dependencies:
+
+   • IPC::Run
+   • Try::Tiny
+   • AnyEvent
+   • AnyEvent::I3
+
+ All of them are available at CPAN, see http://search.cpan.org/
+ Use your distribution’s packages or cpan(1) to install them.
+
+ i3-migrate-config-to-v4.pl is implemented in Perl, but it has no dependencies
+ besides Perl 5.10.
index 9e615d6d8ed9163d8fcef6f3aa7c7b94fc3c7503..a29fe59e577f85bd5833d767cb988c45722cec70 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@ 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
+AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c
 FILES:=$(filter-out $(AUTOGENERATED),$(wildcard src/*.c))
 FILES:=$(FILES:.c=.o)
 HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h))
@@ -13,24 +13,30 @@ HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h))
 # updated if necessary, but we also want to save rebuilds of the object
 # files, so we cannot let the object files depend on loglevels.h.
 ifeq ($(MAKECMDGOALS),loglevels.h)
-UNUSED:=$(warning Generating loglevels.h)
+#UNUSED:=$(warning Generating loglevels.h)
 else
 UNUSED:=$(shell $(MAKE) loglevels.h)
 endif
 
+SUBDIRS=i3-msg i3-input i3-nagbar i3-config-wizard
+
 # Depend on the specific file (.c for each .o) and on all headers
 src/%.o: src/%.c ${HEADERS}
        echo "CC $<"
-       $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/$(shell basename $< .c)/ { print NR }' loglevels.tmp))" -c -o $@ $<
+       $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR; exit 0; }' loglevels.tmp))" -c -o $@ $<
+
+all: i3 subdirs
 
-all: src/cfgparse.y.o src/cfgparse.yy.o ${FILES}
+i3: src/cfgparse.y.o src/cfgparse.yy.o src/cmdparse.y.o src/cmdparse.yy.o ${FILES}
        echo "LINK i3"
-       $(CC) -o i3 ${FILES} src/cfgparse.y.o src/cfgparse.yy.o $(LDFLAGS)
-       echo ""
-       echo "SUBDIR i3-msg"
-       $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg
-       echo "SUBDIR i3-input"
-       $(MAKE) TOPDIR=$(TOPDIR) -C i3-input
+       $(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+
+subdirs:
+       for dir in $(SUBDIRS); do \
+               echo ""; \
+               echo "MAKE $$dir"; \
+               $(MAKE) -C $$dir; \
+       done
 
 loglevels.h:
        echo "LOGLEVELS"
@@ -47,12 +53,24 @@ loglevels.h:
 src/cfgparse.yy.o: src/cfgparse.l src/cfgparse.y.o ${HEADERS}
        echo "LEX $<"
        flex -i -o$(@:.o=.c) $<
-       $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c)
+       $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c)
+
+src/cmdparse.yy.o: src/cmdparse.l src/cmdparse.y.o ${HEADERS}
+       echo "LEX $<"
+       flex -Pcmdyy -i -o$(@:.o=.c) $<
+       $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c)
+
 
 src/cfgparse.y.o: src/cfgparse.y ${HEADERS}
        echo "YACC $<"
        bison --debug --verbose -b $(basename $< .y) -d $<
-       $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c)
+       $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c)
+
+src/cmdparse.y.o: src/cmdparse.y ${HEADERS}
+       echo "YACC $<"
+       bison -p cmdyy --debug --verbose -b $(basename $< .y) -d $<
+       $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c)
+
 
 install: all
        echo "INSTALL"
@@ -61,20 +79,22 @@ install: all
        $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/include/i3
        $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/xsessions
        $(INSTALL) -m 0755 i3 $(DESTDIR)$(PREFIX)/bin/
-       $(INSTALL) -m 0755 i3-wsbar $(DESTDIR)$(PREFIX)/bin/
+       $(INSTALL) -m 0755 i3-migrate-config-to-v4.pl $(DESTDIR)$(PREFIX)/bin/
        test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config
+       test -e $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes || $(INSTALL) -m 0644 i3.config.keycodes $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes
        $(INSTALL) -m 0644 i3.welcome $(DESTDIR)$(SYSCONFDIR)/i3/welcome
        $(INSTALL) -m 0644 i3.desktop $(DESTDIR)$(PREFIX)/share/xsessions/
        $(INSTALL) -m 0644 include/i3/ipc.h $(DESTDIR)$(PREFIX)/include/i3/
-       $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg install
-       $(MAKE) TOPDIR=$(TOPDIR) -C i3-input install
+       for dir in $(SUBDIRS); do \
+               $(MAKE) -C $$dir install; \
+       done
 
 dist: distclean
        [ ! -d i3-${VERSION} ] || rm -rf i3-${VERSION}
        [ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2
        mkdir i3-${VERSION}
-       cp DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome i3-wsbar pseudo-doc.doxygen Makefile i3-${VERSION}
-       cp -r src i3-msg include man i3-${VERSION}
+       cp i3-migrate-config-to-v4.pl i3.config.keycodes DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome pseudo-doc.doxygen i3-wsbar Makefile i3-${VERSION}
+       cp -r src i3-msg i3-nagbar i3-config-wizard yajl-fallback include man i3-${VERSION}
        # Only copy toplevel documentation (important stuff)
        mkdir i3-${VERSION}/docs
        # Pre-generate documentation
@@ -83,21 +103,32 @@ dist: distclean
        # Only copy source code from i3-input
        mkdir i3-${VERSION}/i3-input
        find i3-input -maxdepth 1 -type f \( -name "*.c" -or -name "*.h" -or -name "Makefile" \) -exec cp '{}' i3-${VERSION}/i3-input \;
-       sed -e 's/^GIT_VERSION:=\(.*\)/GIT_VERSION=${GIT_VERSION}/g;s/^VERSION:=\(.*\)/VERSION=${VERSION}/g' common.mk > i3-${VERSION}/common.mk
+       sed -e 's/^GIT_VERSION:=\(.*\)/GIT_VERSION:=$(shell echo '${GIT_VERSION}' | sed 's/\\/\\\\/g')/g;s/^VERSION:=\(.*\)/VERSION:=${VERSION}/g' common.mk > i3-${VERSION}/common.mk
        # Pre-generate a manpage to allow distributors to skip this step and save some dependencies
-       make -C man
+       $(MAKE) -C man
        cp man/*.1 i3-${VERSION}/man/
        tar cfj i3-${VERSION}.tar.bz2 i3-${VERSION}
        rm -rf i3-${VERSION}
 
 clean:
-       rm -f src/*.o src/cfgparse.tab.{c,h} src/cfgparse.{output,dot} src/cfgparse.yy.c loglevels.tmp include/loglevels.h
+       rm -f src/*.o src/*.gcno src/cfgparse.tab.{c,h} src/cfgparse.yy.c src/cfgparse.{output,dot} src/cmdparse.tab.{c,h} src/cmdparse.yy.c src/cmdparse.{output,dot} loglevels.tmp include/loglevels.h
+       (which lcov >/dev/null && lcov -d . --zerocounters) || true
        $(MAKE) -C docs clean
        $(MAKE) -C man clean
        $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg clean
        $(MAKE) TOPDIR=$(TOPDIR) -C i3-input clean
+       $(MAKE) TOPDIR=$(TOPDIR) -C i3-nagbar clean
+       $(MAKE) TOPDIR=$(TOPDIR) -C i3-config-wizard clean
 
 distclean: clean
        rm -f i3
        $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg distclean
        $(MAKE) TOPDIR=$(TOPDIR) -C i3-input distclean
+       $(MAKE) TOPDIR=$(TOPDIR) -C i3-nagbar distclean
+       $(MAKE) TOPDIR=$(TOPDIR) -C i3-config-wizard distclean
+
+coverage:
+       rm -f /tmp/i3-coverage.info
+       rm -rf /tmp/i3-coverage
+       lcov -d . -b . --capture -o /tmp/i3-coverage.info
+       genhtml -o /tmp/i3-coverage/ /tmp/i3-coverage.info
index 40222803800dd7c49eee1d475eeb9eb9b4b78949..633b2d7e4af08eed3cc45127cb95c2bfdf0a14d1 100644 (file)
@@ -10,16 +10,27 @@ packages for them.
 Please make sure the manpage for i3 will be properly created and installed
 in your package.
 
+Also please provide the path to a suitable terminal emulator which is installed
+as a dependency of your package (e.g. urxvt). On systems which have a special
+commend to launch the best available terminal emulator, please use this one
+(e.g. x-terminal-emulator on debian).
+
 On debian, this looks like this:
 
        # Compilation
-       $(MAKE)
+       $(MAKE) TERM_EMU=x-terminal-emulator
        $(MAKE) -C man
 
        # Installation
        $(MAKE) DESTDIR=$(CURDIR)/debian/i3-wm/ install
        mkdir -p $(CURDIR)/debian/i3-wm/usr/share/man/man1
-       cp man/i3.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
+       cp man/*.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
+
+Please make sure that i3-migrate-config-to-v4.pl and i3-config-wizard are
+installed with i3. The Perl script is necessary to (automatically) convert v3
+configs to v4. The wizard provides the possibility to create a keysym-based
+config with the user’s preferred modifier and should be started on the first
+start of i3 (it will automatically exit if it finds a config file).
 
 If you have any questions, ideas, hints, problems or whatever, please do not
 hesitate to contact me. I will help you out. Just drop me an E-Mail (find the
diff --git a/RELEASE-NOTES-3.d-bf1 b/RELEASE-NOTES-3.d-bf1
deleted file mode 100644 (file)
index 58c9a90..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-Release notes for i3 v3.δ-bf1
------------------------------
-
-This is the first bugfix release (bf1) for version 3.δ (transcribed 3.d) of
-i3. Because many bugs were fixed after the release of version 3.δ, we thought
-users of the stable releases might profit from this additional bugfix release.
-
-Thanks for this release go out to msi, merovius, Grauwolf, jace, Syntropy,
-Mirko, helgiks and Moredread.
-
-A list of changes follows:
-
-  * Bugfix: Don’t draw window title when titlebar is disabled
-  * Bugfix: Correctly switch border types for floating windows
-  * Bugfix: Correctly replay pointer if the click handler does not trigger
-  * Bugfix: Also allow WORDs as workspace names
-  * Bugfix: Correctly clear the urgency hint if a window gets unmapped without
-    clearing it
-  * Bugfix: Fix resizing of floating windows in borderless/1-px-border mode
-  * Bugfix: Accept underscores in bindsym
-  * Bugfix: Don’t set the urgency flag if the window is focused
-  * Bugfix: Handle stack-limit cols on tabbed containers
-  * Bugfix: Resize client after updating base_width/base_height
-  * Bugfix: Force render containers after setting the client active
-  * Bugfix: Fix two problems in resizing floating windows with right mouse
-  * Bugfix: Use more precise floating point arithmetics
-  * Bugfix: Correctly place new windows below fullscreen windows
-
- -- Michael Stapelberg, 2009-12-21
diff --git a/RELEASE-NOTES-3.e b/RELEASE-NOTES-3.e
deleted file mode 100644 (file)
index 4bf590f..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-Release notes for i3 v3.ε
------------------------------
-
-This is the fifth version (3.ε, transcribed 3.e) of i3. It is considered
-stable.
-
-A really big change in this release is the support of RandR instead of
-Xinerama. The Xinerama API is a subset of RandR and its limitations clearly
-showed when you reconfigured outputs using xrandr(1) during runtime (it was
-not designed to handle such changes). The implementation of RandR fixes some
-long-standing bugs (workspaces were messed up when reconfiguring outputs)
-and cleans up some code. Furthermore, you are now able to assign workspaces
-to outputs (like LVDS1, VGA1, …) instead of the formerly used heuristics
-like "the screen at position (x, y)" or "the second screen in the list".
-
-Furthermore, another big change is the separation of debug output (the
-so-called logfile): you now need to enable verbose output (parameter -V)
-and you need to specify which (if any) debug output you want to see (parameter
--d <loglevels>). When starting without -V, i3 will only log errors. This is
-what you usually want for a production system. When enabling verbose output,
-you will see the names and window classes of new windows (useful for creating
-assignments in your configuration file) and other useful messages. For an
-explanation of the debuglevels, please see the "How to debug" document (for
-the impatient: "-d all" gives you full output).
-
-In 3.δ, a new parser/lexer was introduced and available using the -l option.
-The old parser/lexer has been removed in the meantime, so in 3.ε, the "new"
-parser/lexer is always used and you do not need the -l option anymore. To
-make debugging errors in your configuration easier, the error messages have
-been very much improved. Also, the parser tries to skip invalid lines (though
-it may not always succeed, it usually works and does not crash i3).
-
-Starting from version 3.ε, i3 obeys the XDG base directory specification,
-meaning that you can now put your configuration file into ~/.config/i3/config,
-which might be useful if you manage your ~/.config directory in some way (git,
-…). The old configuration file path is still supported (there are no plans
-to change this), but using ~/.config seems reasonable for clean setups.
-
-You can disable the internal workspace bar in this release. Instead of the
-internal bar, you can use dzen2 (or similar) in dock mode (-dock for dzen2,
-but you need an svn revision). The sample implementation i3-wsbar takes
-stdin, generates a combined bar (workspaces + stdin) and starts dzen2 on
-your outputs as needed (does the right thing when you reconfigure your
-monitors dynamically).
-
-To accomplish the external workspace bar feature, the IPC interface has
-seen much love: requests and replies now use JSON for serialization of
-data structures and provide a nice and simple way to get information (like
-the current workspaces or outputs) from i3 or send commands to it. You can
-also subscribe to certain types of events (workspace or output changes).
-See the AnyEvent::I3 module for a sample implementation of a library.
-
-Thanks for this release go out to Merovius, badboy, xeen, Atsutane, Ciprian,
-dirkson, Mirko, sur5r, artoj, Scytale, fallen, Thomas, Sasha, dothebart, msi
-and all other people who reported bugs/made suggestions.
-
-A complete list of changes follows:
-
- * Implement RandR instead of Xinerama
- * Obey the XDG Base Directory Specification for config file paths
- * lexer/parser: proper error messages
- * Add new options -V for verbose mode and -d <loglevel> for debug log levels
- * Implement resize command for floating clients
- * Include date of the last commit in version string
- * Fixed cursor orientation when resizing
- * Added focus_follows_mouse config option
- * Feature: Cycle through workspaces
- * Fix bindings using the cursor keys in default config
- * added popup for handling SIGSEGV or SIGFPE
- * Correctly exit when another window manager is already running
- * Take into account the window’s base_{width,height} when resizing
- * Disable XKB instead of quitting with an error
- * Make containers containing exactly one window behave like default containers
- * Also warp the pointer when moving a window to a another visible workspace
- * work around clients setting 0xFFFF as resize increments
- * Move autostart after creating the IPC socket in start process
- * Restore geometry of all windows before exiting/restarting
- * When in fullscreen mode, focus whole screens instead of denying to focus
- * draw consistent borders for each frame in a tabbed/stacked container
- * Update fullscreen client position/size when an output changes
- * i3-input: Bugfix: repeatedly grab the keyboard if it does not succeed
- * put windows with WM_CLIENT_LEADER on the workspace of their leader
- * use real functions instead of nested functions (enables compilation with
-   llvm-clang)
- * implement screen-spanning fullscreen mode
- * floating resize now uses arbitrary corners
- * floating resize now works proportionally when pressing shift
- * Don’t use SYNC key bindings for mode_switch but re-grab keys
- * support PREFIX and SYSCONFDIR in Makefile
- * make pointer follow the focus when moving to a different screen also for
-   floating clients
- * start dock clients on the output they request to be started on according
-   to their geometry
- * handle destroy notify events like unmap notify events
- * ewmh: correctly set _NET_CURRENT_DESKTOP to the number of the active
-   workspace
- * ewmh: correctly set _NET_ACTIVE_WINDOW
- * ewmh: implement support for _NET_WORKAREA (rdesktop can use that)
- * default ipc-socket path is now ~/.i3/ipc.sock, enabled in the default config
- * Bugfix: Containers could lose their snap state
- * Bugfix: Use ev_loop_new to not block SIGCHLD
- * Bugfix: if a font provides no per-char info for width, fall back to default
- * Bugfix: lexer: return to INITIAL state after floating_modifier
- * Bugfix: Don’t leak IPC socket to launched processes
- * Bugfix: Use both parts of WM_CLASS (it contains instance and class)
- * Bugfix: Correctly do boundary checking/moving to other workspaces when
-   moving floating clients via keyboard
- * Bugfix: checked for wrong flag in size hints
- * Bugfix: Correctly render workspace names containing some non-ascii chars
- * Bugfix: Correctly position floating windows sending configure requests
- * Bugfix: Don’t remap stack windows errnously when changing workspaces
- * Bugfix: configure floating windows above tiling windows when moving them
-   to another workspace
- * Bugfix: Take window out of fullscreen mode before entering floating mode
- * Bugfix: Don’t enter BIND_A2WS_COND state too early
- * Bugfix: only restore focus if the workspace is focused, not if it is visible
- * Bugfix: numlock state will now be filtered in i3-input and signal handler
- * Bugfix: Don’t unmap windows when current workspace gets reassigned
- * Bugfix: correctly translate coordinates for floating windows when outputs
-   change
- * Bugfix: Correctly switch workspace when using the "jump" command
- * Bugfix: Fix rendering of workspace names after "reload"
- * Bugfix: Correctly ignore clicks when in fullscreen mode
- * Bugfix: Don’t allow fullscreen floating windows to be moved
- * Bugfix: Don’t render containers which are not visible on hint changes
- * Some memory leaks/invalid accesses have been fixed
-
- -- Michael Stapelberg, 2010-03-30
diff --git a/RELEASE-NOTES-4.0 b/RELEASE-NOTES-4.0
new file mode 100644 (file)
index 0000000..38d2088
--- /dev/null
@@ -0,0 +1,135 @@
+
+ ┌────────────────────────────┐
+ │ Release notes for i3 v4.0  │
+ └────────────────────────────┘
+
+This is the first release of the new major version of i3, v4.0. It has been a
+long time since v3.ε was released (over one year). A lot has been happening
+since then, we made 736 commits – compare that to the total number of 1664
+commits for i3.
+
+The reason for the high number of commits and long time for this release is the
+big refactoring we have been doing. Instead of using several lists and a table
+as data structures, we now use a single tree of containers. These containers
+represent invisible entities like your X11 root window, your different monitors
+and workspaces, but also visible entities like actual windows.
+
+Using a tree has made a lot of things cleaner and easier – in the code *and* in
+the user interface. Admittedly though, you will probably need a day or two to
+get used to a few more advanced movement commands if you are used to v3.ε right
+now.
+
+ ┌────────────────────────────┐
+ │ New features               │
+ └────────────────────────────┘
+
+ • In addition to the proper flex/bison based parser for the config file
+   introduced in 3.δ, we now also have a flex/bison parser for commands. What
+   this means is that we can have more human-readable, beautiful command names
+   instead of cryptic commands like 'f' for fullscreen or 'mh' for move left.
+   In fact, the commands for the aforementioned functions *are* 'fullscreen'
+   and 'move left'!
+
+ • You can now chain commands using ';' (a semicolon). One example for that is
+   'workspace 3 ; exec /usr/bin/urxvt' to switch to a new workspace and open a
+   terminal.
+
+ • You can specify which windows should be affected by your command by using
+   different criteria. A good example is '[class="Firefox"] kill' to get rid
+   of all Firefox windows.
+
+ • As the configuration file needs new commands (and a few options are
+   obsolete), you need to change it. To make this process a little bit easier
+   for you, this release comes with the script i3-migrate-config-to-v4.pl. Just
+   run it on your current config file and it will spit out a v4 config file to
+   stdout. To make things even better, i3 automatically detects v3 config files
+   and calls that script, so you never end up with a non-working config :).
+
+ • Similarly to the criteria when using commands, we now have a 'for_window'
+   configuration directive, which lets you automatically apply certain commands
+   to certain windows. Use it to set border styles per window, for example with
+   'for_window [class="XTerm"] border 1pixel'.
+
+ • Since dock clients (like dzen2) are now part of the layout tree (as opposed
+   to a custom data structure as before), it was easy to implement top and
+   bottom dock areas. Programs which properly specify the dock hint get placed
+   on the edge of the screen they request. i3bar has the -dtop and -dbottom
+   parameters, for example.
+
+ • The internal workspace bar is obsolete. Use i3bar instead.
+
+ • Resizing now works between all windows!
+
+ • Fullscreen now works for everything!
+
+ • Floating now works for everything!
+
+ • Your layout is now preserved when doing an inplace restart.
+
+ • When you have an error in your config file, a new program called i3-nagbar
+   will tell you so. It offers you two buttons: One to view the error in your
+   $PAGER and one to edit your config in your $EDITOR.
+
+ • The default config used key symbols (like 'bind Mod1+f fullscreen') instead
+   of key codes. If you use a non-qwerty layout, the program i3-config-wizard
+   can create a key symbol based config file based on your current layout. You
+   can also chose between Windows (Mod4) and Alt (Mod1) as your default
+   modifier. i3-config-wizard will automatically be started as long as you
+   don’t have a configuration file for i3.
+
+ • Custom X cursor themes are now supported.
+
+ • The RandR backend now respects the primary output.
+
+ • A wrong 'font' configuration in your config file will no longer make i3
+   exit. Instead, it will fall back to a different font and tell you about the
+   error in its log.
+
+ • The default split direction (whether a new window gets placed right next to
+   the current one or below the current one) is now automatically set to
+   horizontal if you have a monitor that is wider than high or vertical if you
+   a monitor which is higher than wide. This works great with rotated monitors.
+
+ • Sockets and temporary files are now placed in XDG_RUNTIME_DIR, if set (this
+   is used on systemd based systems).
+
+ • Tools like i3bar, i3-msg etc. use the I3_SOCKET_PATH property which is set
+   to the X11 root window, so you don’t have to configure your socket path
+   anywhere.
+
+ • The kill command kills single windows by default now. To kill a whole
+   application, use 'kill client'.
+
+ • IPC: Commands can now have custom replies. When the parser encounters an
+   error, a proper error reply is sent.
+
+ • There is now an 'exec_always' configuration directive which works like
+   'exec' but will also be run when restarting.
+
+ ┌────────────────────────────┐
+ │ Future features            │
+ └────────────────────────────┘
+
+Our plans were big but our time and manpower is limited. Therefore, the
+following features did not make it into this release. However, the foundation
+is now in place and implementing them is possible, so stay tuned!
+
+ • Saving/Restoring specific parts of your layout
+
+ • Session saving
+
+ • Sticky windows
+
+ ┌────────────────────────────┐
+ │ Thanks!                    │
+ └────────────────────────────┘
+
+Thanks for testing, bugfixes, discussions and everything I forgot go out to:
+
+  aniou, artoj, badboy, cloud, cradle, David Coppa, dbp, dothebart, eeemsi,
+  eelvex, f8l, fernando, jan, jimdigriz, jon, julien, kacper, ktosiek,
+  lexszero, litemotiv, lourens, madroach, marcus, merovius, mike, mirko, mseed,
+  mxf, phnom, quaec, rogutes, sardemff7, smartass, thepub, tiago, tucos,
+  woddf2, xpt, ys
+
+-- Michael Stapelberg, 2011-07-24
diff --git a/RELEASE-NOTES-tree-pr1 b/RELEASE-NOTES-tree-pr1
new file mode 100644 (file)
index 0000000..dec817f
--- /dev/null
@@ -0,0 +1,45 @@
+Release notes for i3 tree-pr1
+-----------------------------
+
+This is a PREVIEW RELEASE for the tree branch. It is *NOT* part of i3’s regular
+releases and should *NOT* be packaged in the usual way for distributions.
+Instead, provide a separate, unofficial package if possible.
+
+The so called tree branch is the place where the next version of i3 is
+developed. This time, we did a major code refactoring bringing many changes.
+The idea is to use a tree as datastructure instead of separate lists (like one
+for outputs, workspaces and a table for storing your window layout).
+
+Quite a few advantages arise from this new data structure. The most prominent
+ones will be a slightly different look and feel, the possibility to store your
+layout and restore it later, correct resizing, a much cleaner command parser
+and more little improvements.
+
+As this is a preview release, some things are not working yet. Generally,
+though, the core developers are using it already and think it’s good enough to
+try it out. With this release, we want to gather feedback from you, so please
+report any bugs you encounter in our bugtracker at http://i3.zekjur.net/bugs
+
+What should be working in this release?
+---------------------------------------
+
+ • Basic window management, navigation, moving
+ • Fullscreen mode, correct aspect ratio
+ • Stacked/Tabbed layout, floating mode
+ • Splitting (for fancy layouts), resizing
+ • Restarts, preserving the layout
+ • i3bar, get it from http://git.merovius.de/
+
+If any of these features do not work (correctly), please file a bugreport.
+
+What is not working in this release?
+------------------------------------
+
+ • RandR changes (i3 needs to be restarted)
+ • Assignments
+ • Configfile compatibility
+ • Workspace switching is sometimes not working. If you find a pattern, please
+   report it.
+ • There are still some bugs in resizing. Please report!
+
+ -- Michael Stapelberg, 2010-12-06
diff --git a/RELEASE-NOTES-tree-pr2 b/RELEASE-NOTES-tree-pr2
new file mode 100644 (file)
index 0000000..533c3c7
--- /dev/null
@@ -0,0 +1,43 @@
+Release notes for i3 tree-pr2
+-----------------------------
+
+This is the second PREVIEW RELEASE for the tree branch. It is *NOT* part of
+i3’s regular releases and should *NOT* be packaged in the usual way for
+distributions.  Instead, provide a separate, unofficial package if possible.
+
+The so called tree branch is the place where the next version of i3 is
+developed. This time, we did a major code refactoring bringing many changes.
+The idea is to use a tree as datastructure instead of separate lists (like one
+for outputs, workspaces and a table for storing your window layout).
+
+Quite a few advantages arise from this new data structure. The most prominent
+ones will be a slightly different look and feel, the possibility to store your
+layout and restore it later, correct resizing, a much cleaner command parser
+and more little improvements.
+
+As this is a preview release, some things are not working yet. Generally,
+though, the core developers are using it already and think it’s good enough to
+try it out. With this release, we want to gather feedback from you, so please
+report any bugs you encounter in our bugtracker at http://i3.zekjur.net/bugs
+
+What should be working in this release?
+---------------------------------------
+
+ • Basic window management, navigation, moving
+ • Fullscreen mode, correct aspect ratio
+ • Stacked/Tabbed layout, floating mode
+ • Splitting (for fancy layouts), resizing
+ • Restarts (preserving the layout), crash handler
+ • RandR changes, keyboard layout changes
+ • Dock clients
+ • i3bar, get it from http://git.merovius.de/
+
+If any of these features do not work (correctly), please file a bugreport.
+
+What is not working in this release?
+------------------------------------
+
+ • Assignments
+ • Configfile compatibility
+
+ -- Michael Stapelberg, 2011-03-07
diff --git a/RELEASE-NOTES-tree-pr3 b/RELEASE-NOTES-tree-pr3
new file mode 100644 (file)
index 0000000..fd8f4c3
--- /dev/null
@@ -0,0 +1,43 @@
+Release notes for i3 tree-pr3
+-----------------------------
+
+This is the third PREVIEW RELEASE for the tree branch. It is *NOT* part of
+i3’s regular releases and should *NOT* be packaged in the usual way for
+distributions. Instead, provide a separate, unofficial package if possible.
+
+The so called tree branch is the place where the next version of i3 is
+developed. This time, we did a major code refactoring bringing many changes.
+The idea is to use a tree as datastructure instead of separate lists (like one
+for outputs, workspaces and a table for storing your window layout).
+
+Quite a few advantages arise from this new data structure. The most prominent
+ones will be a slightly different look and feel, the possibility to store your
+layout and restore it later, correct resizing, a much cleaner command parser
+and more little improvements.
+
+As this is a preview release, some things are not working yet. Generally,
+though, the core developers are using it already and think it’s good enough to
+try it out. With this release, we want to gather feedback from you, so please
+report any bugs you encounter in our bugtracker at http://bugs.i3wm.org/
+
+What should be working in this release?
+---------------------------------------
+
+ • Basic window management, navigation, moving
+ • Fullscreen mode, correct aspect ratio
+ • Stacked/Tabbed layout, floating mode
+ • Splitting (for fancy layouts), resizing
+ • Restarts (preserving the layout), crash handler
+ • RandR changes, keyboard layout changes
+ • Dock clients
+ • Assignments
+ • i3bar, get it from http://git.merovius.de/
+
+If any of these features do not work (correctly), please file a bugreport.
+
+What is not working in this release?
+------------------------------------
+
+ • Configfile compatibility
+
+ -- Michael Stapelberg, 2011-05-28
diff --git a/RELEASE-NOTES-tree-pr4 b/RELEASE-NOTES-tree-pr4
new file mode 100644 (file)
index 0000000..9316bba
--- /dev/null
@@ -0,0 +1,37 @@
+Release notes for i3 tree-pr4
+-----------------------------
+
+This is the fourth PREVIEW RELEASE for the tree branch. It is *NOT* part of
+i3’s regular releases and should *NOT* be packaged in the usual way for
+distributions. Instead, provide a separate, unofficial package if possible.
+
+The so called tree branch is the place where the next version of i3 is
+developed. This time, we did a major code refactoring bringing many changes.
+The idea is to use a tree as datastructure instead of separate lists (like one
+for outputs, workspaces and a table for storing your window layout).
+
+Quite a few advantages arise from this new data structure. The most prominent
+ones will be a slightly different look and feel, the possibility to store your
+layout and restore it later, correct resizing, a much cleaner command parser
+and more little improvements.
+
+This release is considered a release candidate for i3 v4.0. We will not make
+big changes and plan to release v4.0 in a few weeks.
+With this release, we want to gather feedback from you, so please
+report any bugs you encounter in our bugtracker at http://bugs.i3wm.org/
+
+What should be working in this release?
+---------------------------------------
+
+ • Basic window management, navigation, moving
+ • Fullscreen mode, correct aspect ratio
+ • Stacked/Tabbed layout, floating mode
+ • Splitting (for fancy layouts), resizing
+ • Restarts (preserving the layout), crash handler
+ • RandR changes, keyboard layout changes
+ • Dock clients
+ • Assignments
+ • Config file compatibility
+ • i3bar, get it from http://git.merovius.de/
+
+ -- Michael Stapelberg, 2011-07-15
index d45286a8f63b81e886646ee3aeaee46f6701d4da..0a695e7dc3205f185c7f94f016dd152a151dcd6d 100644 (file)
--- a/common.mk
+++ b/common.mk
@@ -1,5 +1,6 @@
 UNAME=$(shell uname)
 DEBUG=1
+COVERAGE=0
 INSTALL=install
 ifndef PREFIX
   PREFIX=/usr
@@ -11,10 +12,20 @@ ifndef SYSCONFDIR
     SYSCONFDIR=$(PREFIX)/etc
   endif
 endif
+TERM_EMU=xterm
 # The escaping is absurd, but we need to escape for shell, sed, make, define
-GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch $(shell [ -f .git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' .git/HEAD || echo 'unknown'))"
+GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch $(shell [ -f $(TOPDIR)/.git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' $(TOPDIR)/.git/HEAD || echo 'unknown'))"
 VERSION:=$(shell git describe --tags --abbrev=0)
 
+ifeq ($(shell which pkg-config 2>/dev/null 1>/dev/null || echo 1),1)
+$(error "pkg-config was not found")
+endif
+
+# An easier way to get CFLAGS and LDFLAGS falling back in case there's
+# no pkg-config support for certain libraries
+cflags_for_lib = $(shell pkg-config --silence-errors --cflags $(1))
+ldflags_for_lib = $(shell pkg-config --exists $(1) && pkg-config --libs $(1) || echo -l$(2))
+
 CFLAGS += -std=c99
 CFLAGS += -pipe
 CFLAGS += -Wall
@@ -22,40 +33,43 @@ CFLAGS += -Wall
 # We don’t want unused-parameter because of the use of many callbacks
 CFLAGS += -Wunused-value
 CFLAGS += -Iinclude
-CFLAGS += -I/usr/local/include
-CFLAGS += -DI3_VERSION=\"${GIT_VERSION}\"
-CFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\"
-
-# Check if pkg-config is installed, because without pkg-config, the following
-# check for the version of libxcb cannot be done.
-ifeq ($(shell which pkg-config 2>/dev/null 1>/dev/null || echo 1),1)
-$(error "pkg-config was not found")
-endif
-
-ifeq ($(shell pkg-config --exists xcb-keysyms || echo 1),1)
-$(error "pkg-config could not find xcb-keysyms.pc")
+CFLAGS += $(call cflags_for_lib, xcb-keysyms)
+ifeq ($(shell pkg-config --exists xcb-util || echo 1),1)
+CPPFLAGS += -DXCB_COMPAT
+CFLAGS += $(call cflags_for_lib, xcb-atom)
+CFLAGS += $(call cflags_for_lib, xcb-aux)
+else
+CFLAGS += $(call cflags_for_lib, xcb-util)
 endif
+CFLAGS += $(call cflags_for_lib, xcb-icccm)
+CFLAGS += $(call cflags_for_lib, xcb-xinerama)
+CFLAGS += $(call cflags_for_lib, xcb-randr)
+CFLAGS += $(call cflags_for_lib, xcb)
+CFLAGS += $(call cflags_for_lib, xcursor)
+CFLAGS += $(call cflags_for_lib, x11)
+CFLAGS += $(call cflags_for_lib, yajl)
+CFLAGS += $(call cflags_for_lib, libev)
+CPPFLAGS += -DI3_VERSION=\"${GIT_VERSION}\"
+CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\"
+CPPFLAGS += -DTERM_EMU=\"$(TERM_EMU)\"
 
-ifeq ($(shell pkg-config --exact-version=0.3.3 xcb-keysyms && echo 1),1)
-# xcb-keysyms fixed API from 0.3.3 to 0.3.4, so for some months, we will
-# have this here. Distributions should upgrade their libxcb in the meantime.
-CFLAGS += -DOLD_XCB_KEYSYMS_API
+LIBS += -lm
+LIBS += $(call ldflags_for_lib, xcb-event, xcb-event)
+LIBS += $(call ldflags_for_lib, xcb-keysyms, xcb-keysyms)
+ifeq ($(shell pkg-config --exists xcb-util || echo 1),1)
+LIBS += $(call ldflags_for_lib, xcb-atom, xcb-atom)
+LIBS += $(call ldflags_for_lib, xcb-aux, xcb-aux)
+else
+LIBS += $(call ldflags_for_lib, xcb-util)
 endif
-
-LDFLAGS += -lm
-LDFLAGS += -lxcb-event
-LDFLAGS += -lxcb-property
-LDFLAGS += -lxcb-keysyms
-LDFLAGS += -lxcb-atom
-LDFLAGS += -lxcb-aux
-LDFLAGS += -lxcb-icccm
-LDFLAGS += -lxcb-xinerama
-LDFLAGS += -lxcb-randr
-LDFLAGS += -lxcb
-LDFLAGS += -lyajl
-LDFLAGS += -lX11
-LDFLAGS += -lev
-LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib
+LIBS += $(call ldflags_for_lib, xcb-icccm, xcb-icccm)
+LIBS += $(call ldflags_for_lib, xcb-xinerama, xcb-xinerama)
+LIBS += $(call ldflags_for_lib, xcb-randr, xcb-randr)
+LIBS += $(call ldflags_for_lib, xcb, xcb)
+LIBS += $(call ldflags_for_lib, xcursor, Xcursor)
+LIBS += $(call ldflags_for_lib, x11, X11)
+LIBS += $(call ldflags_for_lib, yajl, yajl)
+LIBS += $(call ldflags_for_lib, libev, ev)
 
 ifeq ($(UNAME),NetBSD)
 # We need -idirafter instead of -I to prefer the system’s iconv over GNU libiconv
@@ -65,12 +79,16 @@ endif
 
 ifeq ($(UNAME),OpenBSD)
 CFLAGS += -I${X11BASE}/include
-LDFLAGS += -liconv
+LIBS += -liconv
 LDFLAGS += -L${X11BASE}/lib
 endif
 
 ifeq ($(UNAME),FreeBSD)
-LDFLAGS += -liconv
+LIBS += -liconv
+endif
+
+ifeq ($(UNAME),Darwin)
+LIBS += -liconv
 endif
 
 # Fallback for libyajl 1 which did not include yajl_version.h. We need
@@ -78,7 +96,7 @@ endif
 CFLAGS += -idirafter yajl-fallback
 
 ifneq (,$(filter Linux GNU GNU/%, $(UNAME)))
-CFLAGS += -D_GNU_SOURCE
+CPPFLAGS += -D_GNU_SOURCE
 endif
 
 ifeq ($(DEBUG),1)
@@ -87,6 +105,12 @@ CFLAGS += -gdwarf-2
 CFLAGS += -g3
 else
 CFLAGS += -O2
+CFLAGS += -freorder-blocks-and-partition
+endif
+
+ifeq ($(COVERAGE),1)
+CFLAGS += -fprofile-arcs -ftest-coverage
+LIBS += -lgcov
 endif
 
 # Don’t print command lines which are run
index e20d269076db470822e3815dcda2aeb04809ebc3..a638750e3fa805b157377a2a7db139b078106012 100644 (file)
@@ -1,3 +1,9 @@
+i3-wm (4.0-0) unstable; urgency=low
+
+  * NOT YET RELEASED
+
+ -- Michael Stapelberg <michael@stapelberg.de>  Sun, 24 Jul 2011 00:10:30 +0200
+
 i3-wm (3.e-bf1-3) unstable; urgency=low
 
   * include keyboard-layer{1,2}.png in docs (Closes: #595295)
index d396ea77ebb4bcb30b586a0eb590adb901e3667f..a2a837269fbfa1af8efd204119aff5a6bb3b6960 100644 (file)
@@ -3,7 +3,7 @@ Section: utils
 Priority: extra
 Maintainer: Michael Stapelberg <michael@stapelberg.de>
 DM-Upload-Allowed: yes
-Build-Depends: debhelper (>= 6), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, perl
+Build-Depends: debhelper (>= 6), libx11-dev, libxcb-util0-dev (>= 0.3.8), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-icccm4-dev, libxcursor-dev, asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, perl, texlive-latex-base, texlive-latex-recommended, texlive-latex-extra
 Standards-Version: 3.9.1
 Homepage: http://i3.zekjur.net/
 
@@ -27,12 +27,12 @@ Provides: x-window-manager
 Suggests: rxvt-unicode | x-terminal-emulator
 Recommends: xfonts-base, libanyevent-i3-perl, libanyevent-perl, libipc-run-perl
 Description: an improved dynamic tiling window manager
- Key features of i3 are good support of multi-monitor setups (workspaces are
+ Key features of i3 are correct implementation of Xinerama (workspaces are
  assigned to virtual screens, i3 does the right thing when attaching new
- monitors), XRandR support, horizontal and vertical columns (think of a table)
- in tiling. Also, special focus is on writing clean, readable and well
documented code. i3 uses XCB for asynchronous communication with X11, and has
- several measures to be very fast.
+ monitors), XrandR support (not done yet), horizontal and vertical columns
+ (think of a table) in tiling. Also, special focus is on writing clean,
readable and well documented code. i3 uses xcb for asynchronous
communication with X11, and has several measures to be very fast.
  .
  Please be aware i3 is primarily targeted at advanced users and developers.
 
index 464722c0634ddefcee1e29cd1982d7587b16a541..d639398050b026a5301d6e5a63533615abb0264c 100755 (executable)
@@ -45,7 +45,6 @@ install: build
        cp man/i3.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
        cp man/i3-msg.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
        cp man/i3-input.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
-       cp man/i3-wsbar.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
 
 
 # Build architecture-independent files here.
index 379f07d1fe4696de60380ec03d12851d2c10f7b8..90abed28aff3a4f11c4f8318205d54b1ec1f8757 100644 (file)
@@ -1,5 +1,5 @@
 
-all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html wsbar.html refcard.pdf
+all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html wsbar.html tree-migrating.html refcard.pdf
 
 hacking-howto.html: hacking-howto
        asciidoc -a toc -n $<
@@ -10,6 +10,10 @@ debugging.html: debugging
 userguide.html: userguide
        asciidoc -a toc -n $<
 
+tree-migrating.html: tree-migrating
+       asciidoc -a toc -n $<
+
+
 ipc.html: ipc
        asciidoc -a toc -n $<
 
@@ -23,5 +27,4 @@ refcard.pdf: refcard.tex
        pdflatex refcard.tex && pdflatex refcard.tex
 
 clean:
-       rm -f */*.{aux,log,toc,bm,pdf,dvi}
-       rm -f *.log *.html
+       find . -regex ".*\.\(aux\|out\|log\|toc\|bm\|pdf\|dvi\|log\|html\)" -exec rm '{}' \;
index b4bda6709c621dd2e3b3d442effeca92417f6f6d..5e71ecf0c84cca936ec419787ac275eb009cdb1c 100644 (file)
@@ -1,7 +1,7 @@
 Debugging i3: How To
 ====================
 Michael Stapelberg <michael+i3@stapelberg.de>
-March 2010
+July 2011
 
 This document describes how to debug i3 suitably for sending us useful bug
 reports, even if you have no clue of C programming.
@@ -12,14 +12,21 @@ debugging and/or need further help, do not hesitate to contact us!
 
 == Enabling logging
 
-i3 spits out much information onto stdout, if told so. To have a clearly
-defined place where log files will be saved, you should redirect stdout and
-stderr in xsession. While you’re at it, putting each run of i3 in a seperate
-log file with date/time in it is a good idea to not get confused about the
-different log files later on.
+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 -V -d all >/home/michael/i3/i3log-$(date +'%F-%k-%M-%S') 2>&1
+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
@@ -29,7 +36,7 @@ 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):
+use the following command (in your +~/.xsession+, before starting i3):
 
 -------------------
 ulimit -c unlimited
@@ -50,9 +57,9 @@ process id (%p) in it. You can save this setting across reboots using
 == 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 they are not stripped during the
-build process. You can check whether your executable contains symbols by
-issuing the following command:
+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)
index 8a778546d18512d4407162773cda652cc33e8775..9a7ec9d4547fd1ce421bd81e14b1bcbc5c3ff74d 100644 (file)
@@ -1,7 +1,7 @@
 Hacking i3: How To
 ==================
 Michael Stapelberg <michael+i3@stapelberg.de>
-December 2009
+July 2011
 
 This document is intended to be the first thing you read before looking and/or
 touching i3’s source code. It should contain all important information to help
@@ -65,6 +65,13 @@ the layout you need at the moment.
 
 === 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* :).
+*********************************************************************************
+
+/////////////////////////////////////////////////////////////////////////////////
 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
@@ -106,9 +113,15 @@ window).
 |========
 
 Furthermore, you can freely resize table cells.
+/////////////////////////////////////////////////////////////////////////////////
 
 == Files
 
+include/atoms.xmacro::
+A file containing all X11 atoms which i3 uses. This file will be included
+various times (for defining, requesting and receiving the atoms), each time
+with a different definition of xmacro().
+
 include/data.h::
 Contains data definitions used by nearly all files. You really need to read
 this first.
@@ -128,19 +141,27 @@ src/click.c::
 Contains all functions which handle mouse button clicks (right mouse button
 clicks initiate resizing and thus are relatively complex).
 
-src/client.c::
-Contains all functions which are specific to a certain client (make it
-fullscreen, see if its class/name matches a pattern, kill it, …).
+src/cmdparse.l::
+Contains the lexer for i3 commands, written for +flex(1)+.
+
+src/cmdparse.y::
+Contains the parser for i3 commands, written for +bison(1)+.
 
-src/commands.c::
-Parsing commands and actually executing them (focusing, moving, …).
+src/con.c::
+Contains all functions which deal with containers directly (creating
+containers, searching containers, getting specific properties from containers,
+…).
 
 src/config.c::
-Parses the configuration file.
+Contains all functions handling the configuration file (calling the parser
+(src/cfgparse.y) with the correct path, switching key bindings mode).
 
 src/debug.c::
 Contains debugging functions to print unhandled X events.
 
+src/ewmh.c::
+iFunctions to get/set certain EWMH properties easily.
+
 src/floating.c::
 Contains functions for floating mode (mostly resizing/dragging).
 
@@ -151,88 +172,162 @@ unmapping, key presses, button presses, …).
 src/ipc.c::
 Contains code for the IPC interface.
 
-src/layout.c::
-Renders your layout (screens, workspaces, containers).
+src/load_layout.c::
+Contains code for loading layouts from JSON files.
+
+src/log.c::
+Handles the setting of loglevels, contains the logging functions.
 
-src/mainx.c::
+src/main.c::
 Initializes the window manager.
 
 src/manage.c::
 Looks at existing or new windows and decides whether to manage them. If so, it
 reparents the window and inserts it into our data structures.
 
+src/match.c::
+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.
+
+src/move.c::
+Contains code to move a container in a specific direction.
+
+src/output.c::
+Functions to handle CT_OUTPUT cons.
+
+src/randr.c::
+The RandR API is used to get (and re-query) the configured outputs (monitors,
+…).
+
+src/render.c::
+Renders the tree data structure by assigning coordinates to every node. These
+values will later be pushed to X11 in +src/x.c+.
+
 src/resize.c::
-Contains the functions to resize columns/rows in the table.
+Contains the functions to resize containers.
+
+src/sighandler.c::
+Handles +SIGSEGV+, +SIGABRT+ and +SIGFPE+ by showing a dialog that i3 crashed.
+You can chose to let it dump core, to restart it in-place or to restart it
+in-place but forget about the layout.
 
-src/table.c::
-Manages the most important internal data structure, the design table.
+src/tree.c::
+Contains functions which open or close containers in the tree, change focus or
+cleanup ("flatten") the tree. See also +src/move.c+ for another similar
+function, which was moved into its own file because it is so long.
 
 src/util.c::
 Contains useful functions which are not really dependant on anything.
 
+src/window.c::
+Handlers to update X11 window properties like +WM_CLASS+, +_NET_WM_NAME+,
++CLIENT_LEADER+, etc.
+
 src/workspace.c::
 Contains all functions related to workspaces (displaying, hiding, renaming…)
 
+src/x.c::
+Transfers our in-memory tree (see +src/render.c+) to X11.
+
 src/xcb.c::
 Contains wrappers to use xcb more easily.
 
+src/xcursor.c::
+XCursor functions (for cursor themes).
+
 src/xinerama.c::
-(Re-)initializes the available screens and converts them to virtual screens
-(see below).
+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.
 
 image:bigpicture.png[The Big Picture]
 
+/////////////////////////////////////////////////////////////////////////////////
+
 So, the hierarchy is:
 
+. *X11 root window*, the root container
 . *Virtual screens* (Screen 0 in this example)
-. *Workspaces* (Workspace 1 in this example)
-. *Table* (There can only be one table per Workspace)
-. *Container* (left and right in this example)
-. *Client* (The two clients in the left container)
+. *Content container* (there are also containers for dock windows)
+. *Workspaces* (Workspace 1 in this example, with horizontal orientation)
+. *Split container* (vertically split)
+. *X11 window containers*
+
+The data type is +Con+, in all cases.
 
 === Virtual screens
 
-A virtual screen (type `i3Screen`) is generated from the connected screens
-obtained through Xinerama. The difference to the raw Xinerama monitors as seen
+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 logical screens.
+the actual enabled outputs.
 
-For example, if your notebook has 1280x800 and you connect a video projector
-with 1024x768, set up in clone mode (+xrandr \--output VGA \--mode 1024x768
-\--same-as LVDS+), i3 will have one virtual screen.
+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.
 
-However, if you configure it using +xrandr \--output VGA \--mode 1024x768
-\--right-of LVDS+, i3 will generate two virtual screens. For each virtual
+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.
 
 === Workspace
 
-A workspace is identified by its number. Basically, you could think of
+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
-seperate of each other. Other window managers also call this ``Virtual
+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* :).
+*********************************************************************************
+
+/////////////////////////////////////////////////////////////////////////////////
+
 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).
 
+/////////////////////////////////////////////////////////////////////////////////
+
 === 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* :).
+*********************************************************************************
+
+/////////////////////////////////////////////////////////////////////////////////
+
 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.
@@ -244,11 +339,11 @@ ensure that the operating system on which i3 is compiled has all the expected
 features, i3 comes with `include/queue.h`. On BSD systems, you can use man
 `queue(3)`. On Linux, you have to use google (or read the source).
 
-The lists used are `SLIST` (single linked lists), `CIRCLEQ` (circular
-queues) and TAILQ (tail queues). Usually, only forward traversal is necessary,
+The lists used are +SLIST+ (single linked lists), +CIRCLEQ+ (circular
+queues) and +TAILQ+ (tail queues). Usually, only forward traversal is necessary,
 so an `SLIST` works fine. If inserting elements at arbitrary positions or at
-the end of a list is necessary, a `TAILQ` is used instead. However, for the
-windows inside a container, a `CIRCLEQ` is necessary to go from the currently
+the end of a list is necessary, a +TAILQ+ is used instead. However, for the
+windows inside a container, a +CIRCLEQ+ is necessary to go from the currently
 selected window to the window above/below.
 
 == Naming conventions
@@ -258,14 +353,14 @@ should be chosen for those:
 
  * ``conn'' is the xcb_connection_t
  * ``event'' is the event of the particular type
- * ``container'' names a container
- * ``client'' names a client, for example when using a +CIRCLEQ_FOREACH+
+ * ``con'' names a container
+ * ``current'' is a loop variable when using +TAILQ_FOREACH+ etc.
 
 == Startup (src/mainx.c, main())
 
  * Establish the xcb connection
- * Check for XKB extension on the seperate X connection
- * Check for Xinerama screens
+ * Check for XKB extension on the separate X connection, load Xcursor
+ * Check for RandR screens (with a fall-back to Xinerama)
  * Grab the keycodes for which bindings exist
  * Manage all existing windows
  * Enter the event loop
@@ -303,9 +398,10 @@ the correct state.
 Then, it looks through all bindings and gets the one which matches the received
 event.
 
-The bound command is parsed directly in command mode.
+The bound command is parsed by the cmdparse lexer/parser, see +parse_cmd+ in
++src/cmdparse.y+.
 
-== Manage windows (src/mainx.c, manage_window() and reparent_window())
+== Manage windows (src/main.c, manage_window() and reparent_window())
 
 `manage_window()` does some checks to decide whether the window should be
 managed at all:
@@ -325,7 +421,7 @@ 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 needsd to be reserved for the window,
+of the screen. 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
@@ -339,7 +435,7 @@ i3 does not care for applications. All it notices is when new windows are
 mapped (see `src/handlers.c`, `handle_map_request()`). The window is then
 reparented (see section "Manage windows").
 
-After reparenting the window, `render_layout()` is called which renders the
+After reparenting the window, `render_tree()` is called which renders the
 internal layout table. The new window has been placed in the currently focused
 container and therefore the new window and the old windows (if any) need to be
 moved/resized so that the currently active layout (default/stacking/tabbed mode)
@@ -388,6 +484,15 @@ 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
@@ -460,7 +565,18 @@ floating windows:
 * The new width_factor for each involved column (respectively row) will be
   calculated.
 
-== User commands / commandmode (src/commands.c)
+/////////////////////////////////////////////////////////////////////////////////
+
+== 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
@@ -485,6 +601,148 @@ 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.
 
+/////////////////////////////////////////////////////////////////////////////////
+
+== Moving containers
+
+The movement code is pretty delicate. You need to consider all cases before
+making any changes or before being able to fully understand how it works.
+
+=== Case 1: Moving inside the same container
+
+The reference layout for this case is a single workspace in horizontal
+orientation with two containers on it. Focus is on the left container (1).
+
+
+[width="15%",cols="^,^"]
+|========
+| 1 | 2
+|========
+
+When moving the left window to the right (command +move right+), tree_move will
+look for a container with horizontal orientation and finds the parent of the
+left container, that is, the workspace. Afterwards, it runs the code branch
+commented with "the easy case": it calls TAILQ_NEXT to get the container right
+of the current one and swaps both containers.
+
+=== Case 2: Move a container into a split container
+
+The reference layout for this case is a horizontal workspace with two
+containers. The right container is a v-split with two containers. Focus is on
+the left container (1).
+
+[width="15%",cols="^,^"]
+|========
+1.2+^.^| 1 | 2
+| 3
+|========
+
+When moving to the right (command +move right+), i3 will work like in case 1
+("the easy case"). However, as the right container is not a leaf container, but
+a v-split, the left container (1) will be inserted at the right position (below
+2, assuming that 2 is focused inside the v-split) by calling +insert_con_into+.
+
++insert_con_into+ detaches the container from its parent and inserts it
+before/after the given target container. Afterwards, the on_remove_child
+callback is called on the old parent container which will then be closed, if
+empty.
+
+Afterwards, +con_focus+ will be called to fix the focus stack and the tree will
+be flattened.
+
+=== Case 3: Moving to non-existant top/bottom
+
+Like in case 1, the reference layout for this case is a single workspace in
+horizontal orientation with two containers on it. Focus is on the left
+container:
+
+[width="15%",cols="^,^"]
+|========
+| 1 | 2
+|========
+
+This time however, the command is +move up+ or +move down+. tree_move will look
+for a container with vertical orientation. As it will not find any,
++same_orientation+ is NULL and therefore i3 will perform a forced orientation
+change on the workspace by creating a new h-split container, moving the
+workspace contents into it and then changing the workspace orientation to
+vertical. Now it will again search for parent containers with vertical
+orientation and it will find the workspace.
+
+This time, the easy case code path will not be run as we are not moving inside
+the same container. Instead, +insert_con_into+ will be called with the focused
+container and the container above/below the current one (on the level of
++same_orientation+).
+
+Now, +con_focus+ will be called to fix the focus stack and the tree will be
+flattened.
+
+=== Case 4: Moving to existant top/bottom
+
+The reference layout for this case is a vertical workspace with two containers.
+The bottom one is a h-split containing two containers (1 and 2). Focus is on
+the bottom left container (1).
+
+[width="15%",cols="^,^"]
+|========
+2+| 3
+| 1 | 2
+|========
+
+This case is very much like case 3, only this time the forced workspace
+orientation change does not need to be performed because the workspace already
+is in vertical orientation.
+
+=== Case 5: Moving in one-child h-split
+
+The reference layout for this case is a horizontal workspace with two
+containers having a v-split on the left side with a one-child h-split on the
+bottom. Focus is on the bottom left container (2(h)):
+
+[width="15%",cols="^,^"]
+|========
+| 1 1.2+^.^| 3
+| 2(h)
+|========
+
+In this case, +same_orientation+ will be set to the h-split container around
+the focused container. However, when trying the easy case, the next/previous
+container +swap+ will be NULL. Therefore, i3 will search again for a
++same_orientation+ container, this time starting from the parent of the h-split
+container.
+
+After determining a new +same_orientation+ container (if it is NULL, the
+orientation will be force-changed), this case is equivalent to case 2 or case
+4.
+
+
+=== Case 6: Floating containers
+
+The reference layout for this case is a horizontal workspace with two
+containers plus one floating h-split container. Focus is on the floating
+container.
+
+TODO: nice illustration. table not possible?
+
+When moving up/down, the container needs to leave the floating container and it
+needs to be placed on the workspace (at workspace level). This is accomplished
+by calling the function +attach_to_workspace+.
+
+== Click handling
+
+Without much ado, here is the list of cases which need to be considered:
+
+* click to focus (tiling + floating) and raise (floating)
+* click to focus/raise when in stacked/tabbed mode
+* floating_modifier + left mouse button to drag a floating con
+* floating_modifier + right mouse button to resize a floating con
+* click on decoration in a floating con to either initiate a resize (if there
+  is more than one child in the floating con) or to drag the
+  floating con (if it’s the one at the top).
+* click on border in a floating con to resize the floating con
+* floating_modifier + right mouse button to resize a tiling con
+* click on border/decoration to resize a tiling con
+
 == Gotchas
 
 * Forgetting to call `xcb_flush(conn);` after sending a request. This usually
index 36a5d2b9dc5de5c8f2b4d38ba796f45615653791..7e71326022606aa2b4d1abf93d4d77d3c448fe1a 100644 (file)
--- a/docs/ipc
+++ b/docs/ipc
@@ -3,16 +3,19 @@ IPC interface (interprocess communication)
 Michael Stapelberg <michael+i3@stapelberg.de>
 March 2010
 
-This document describes how to interface with i3 from a seperate process. This
+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
 to get various information like the current workspaces to implement an external
 workspace bar.
 
 The method of choice for IPC in our case is a unix socket because it has very
 little overhead on both sides and is usually available without headaches in
-most languages. In the default configuration file, no ipc-socket path is
-specified and thus no socket is created. The standard path (which +i3-msg+ and
-+i3-input+ use) is +~/.i3/ipc.sock+.
+most languages. In the default configuration file, the ipc-socket gets created
+in +/tmp/i3-%u/ipc-socket.%p+ where +%u+ is your UNIX username and +%p+ is the
+PID of i3.
+
+All i3 utilities, like +i3-msg+ and +i3-input+ will read the +I3_SOCKET_PATH+
+X11 property, stored on the X11 root window.
 
 == Establishing a connection
 
@@ -21,7 +24,7 @@ snippet illustrates this in Perl:
 
 -------------------------------------------------------------
 use IO::Socket::UNIX;
-my $sock = IO::Socket::UNIX->new(Peer => '~/.i3/ipc.sock');
+my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
 -------------------------------------------------------------
 
 == Sending messages to i3
@@ -52,6 +55,10 @@ SUBSCRIBE (2)::
 GET_OUTPUTS (3)::
        Gets the current outputs. The reply will be a JSON-encoded list of outputs
        (see the reply section).
+GET_TREE (4)::
+       Gets the layout tree. i3 uses a tree as data structure which includes
+       every container. The reply will be the JSON-encoded tree (see the reply
+       section).
 
 So, a typical message could look like this:
 --------------------------------------------------
@@ -101,6 +108,8 @@ SUBSCRIBE (2)::
        Confirmation/Error code for the SUBSCRIBE message.
 GET_OUTPUTS (3)::
        Reply to the GET_OUTPUTS message.
+GET_TREE (4)::
+       Reply to the GET_TREE message.
 
 === COMMAND reply
 
@@ -226,6 +235,190 @@ rect (map)::
 ]
 -------------------
 
+=== GET_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
+have more properties, please do not use any properties which are not documented
+here. They are not yet finalized and will probably change!
+
+id (integer)::
+       The internal ID (actually a C pointer value) of this container. Do not
+       make any assumptions about it. You can use it to (re-)identify and
+       address containers when talking to i3.
+name (string)::
+       The internal name of this container. For all containers which are part
+       of the tree structure down to the workspace contents, this is set to a
+       nice human-readable name of the container.
+       For all other containers, the content is not defined (yet).
+border (string)::
+       Can be either "normal", "none" or "1pixel", dependending on the
+       container’s border style.
+layout (string)::
+       Can be either "default", "stacked", "tabbed", "dockarea" or "output".
+       Other values might be possible in the future, should we add new
+       layouts.
+orientation (string)::
+       Can be either "none" (for non-split containers), "horizontal" or
+       "vertical".
+percent (float)::
+       The percentage which this container takes in its parent. A value of
+       +null+ means that the percent property does not make sense for this
+       container, for example for the root container.
+rect (map)::
+       The absolute display coordinates for this container. Display
+       coordinates means that when you have two 1600x1200 monitors on a single
+       X11 Display (the standard way), the coordinates of the first window on
+       the second monitor are +{ "x": 1600, "y": 0, "width": 1600, "height":
+       1200 }+.
+window_rect (map)::
+       The coordinates of the *actual client window* inside its container.
+       These coordinates are relative to the container and do not include the
+       window decoration (which is actually rendered on the parent container).
+       So, when using the +default+ layout, you will have a 2 pixel border on
+       each side, making the window_rect +{ "x": 2, "y": 0, "width": 632,
+       "height": 366 }+ (for example).
+geometry (map)::
+       The original geometry the window specified when i3 mapped it. Used when
+       switching a window to floating mode, for example.
+urgent (bool)::
+       Whether this container (window or workspace) has the urgency hint set.
+focused (bool)::
+       Whether this container is currently focused.
+
+Please note that in the following example, I have left out some keys/values
+which are not relevant for the type of the node. Otherwise, the example would
+be by far too long (it already is quite long, despite showing only 1 window and
+one dock window).
+
+It is useful to have an overview of the structure before taking a look at the
+JSON dump:
+
+* root
+** LVDS1
+*** topdock
+*** content
+**** workspace 1
+***** window 1
+*** bottomdock
+**** dock window 1
+** VGA1
+
+*Example:*
+-----------------------
+{
+ "id": 6875648,
+ "name": "root",
+ "rect": {
+   "x": 0,
+   "y": 0,
+   "width": 1280,
+   "height": 800
+ },
+ "nodes": [
+
+   {
+    "id": 6878320,
+    "name": "LVDS1",
+    "layout": "output",
+    "rect": {
+      "x": 0,
+      "y": 0,
+      "width": 1280,
+      "height": 800
+    },
+    "nodes": [
+
+      {
+       "id": 6878784,
+       "name": "topdock",
+       "layout": "dockarea",
+       "orientation": "vertical",
+       "rect": {
+         "x": 0,
+        "y": 0,
+        "width": 1280,
+        "height": 0
+       },
+      },
+
+      {
+       "id": 6879344,
+       "name": "content",
+       "rect": {
+         "x": 0,
+        "y": 0,
+        "width": 1280,
+        "height": 782
+       },
+       "nodes": [
+
+         {
+          "id": 6880464,
+         "name": "1",
+         "orientation": "horizontal",
+         "rect": {
+            "x": 0,
+           "y": 0,
+           "width": 1280,
+           "height": 782
+         },
+         "floating_nodes": [],
+         "nodes": [
+
+            {
+             "id": 6929968,
+            "name": "#aa0000",
+            "border": "normal",
+            "percent": 1,
+            "rect": {
+               "x": 0,
+              "y": 18,
+              "width": 1280,
+              "height": 782
+            }
+           }
+
+         ]
+        }
+
+       ]
+      },
+
+      {
+       "id": 6880208,
+       "name": "bottomdock",
+       "layout": "dockarea",
+       "orientation": "vertical",
+       "rect": {
+         "x": 0,
+        "y": 782,
+        "width": 1280,
+        "height": 18
+       },
+       "nodes": [
+
+         {
+          "id": 6931312,
+         "name": "#00aa00",
+         "percent": 1,
+         "rect": {
+            "x": 0,
+           "y": 782,
+           "width": 1280,
+           "height": 18
+         }
+        }
+
+       ]
+      }
+    ]
+   }
+ ]
+}
+------------------------
+
+
 == Events
 
 [[events]]
@@ -242,7 +435,7 @@ situation can happen: You send a GET_WORKSPACES request but you receive a
 "workspace" event before receiving the reply to GET_WORKSPACES. If your
 program does not want to cope which such kinds of race conditions (an
 event based library may not have a problem here), I suggest you create a
-seperate connection to receive events.
+separate connection to receive events.
 
 === Subscribing to events
 
@@ -290,8 +483,8 @@ if ($is_event) {
 === workspace event
 
 This event consists of a single serialized map containing a property
-+change (string)+ which indicates the type of the change ("focus", "create",
-"init", "empty", "urgent").
++change (string)+ which indicates the type of the change ("focus", "init",
+"empty", "urgent").
 
 *Example:*
 ---------------------
diff --git a/docs/tree-migrating b/docs/tree-migrating
new file mode 100644 (file)
index 0000000..d356bbe
--- /dev/null
@@ -0,0 +1,192 @@
+Tree branch: Migrating
+======================
+Michael Stapelberg <michael+i3@stapelberg.de>
+November 2010
+
+== Introduction
+
+The tree branch (referring to a branch of i3 in the git repository) is the new
+version of i3. Due to the very deep changes and heavy refactoring of the source
+source, we decided to develop it in a separate branch (instead of using the
+next/master-branch system like before).
+
+== Current status
+
+Currently, the code is mostly working. Some of the i3 core developers have been
+using the tree branch version for a few weeks now. So, if you are eager to try
+out the new features and help us find bugs, give it a try!
+
+At the same time, a word of warning is appropriate: This version of i3 might
+crash unexpectedly, so please be careful with important data (do not work for
+two days without saving…).
+
+== Getting the latest tree branch version
+
+Check out the latest version:
+---------------------------------------------
+$ git clone -b tree git://code.stapelberg.de/i3
+---------------------------------------------
+
+Then build and install it (has the same dependencies as the latest stable i3
+version):
+-----------------------------
+$ cd i3
+$ make
+$ sudo cp i3 /usr/bin/i3-tree
+-----------------------------
+
+…and execute +i3-tree+ instead of +i3+ in your Xsession.
+
+*IMPORTANT:* Please note that configuration file compatibility is not yet done.
+So, make sure you use/customize the provided +i3.config+ file.
+
+== Tree
+
+The most important change and reason for the name is that i3 stores all
+information about the X11 outputs, workspaces and layout of the windows on them
+in a tree. The root node is the X11 root window, followed by the X11 outputs,
+then workspaces and finally the windows themselve. In previous versions of i3
+we had multiple lists (of outputs, workspaces) and a table for each workspace.
+That approach turned out to be complicated to use (snapping), understand and
+implement.
+
+=== The tree consists of Containers
+
+The building blocks of our tree are so called +Containers+. A +Container+ can
+host a window (meaning an X11 window, one that you can actually see and use,
+like a browser). Alternatively, it could contain one or more +Containers+. A
+simple example is the workspace: When you start i3 with a single monitor, a
+single workspace and you open two terminal windows, you will end up with a tree
+like this:
+
+image::tree-layout2.png["layout2",float="right"]
+image::tree-shot4.png["shot4",title="Two terminals on standard workspace"]
+
+=== Orientation and Split Containers
+
+[[OrientationSplit]]
+
+It is only natural to use so-called +Split Containers+ in order to build a
+layout when using a tree as data structure. In i3, every +Container+ has an
+orientation (horizontal, vertical or unspecified). So, in our example with the
+workspace, the default orientation of the workspace +Container+ is horizontal
+(most monitors are widescreen nowadays). If you change the orientation to
+vertical (+Alt+v+ in the default config) and *then* open two terminals, i3 will
+configure your windows like this:
+
+image::tree-shot2.png["shot2",title="Vertical Workspace Orientation"]
+
+An interesting new feature of the tree branch is the ability to split anything:
+Let’s assume you have two terminals on a workspace (with horizontal
+orientation), focus is on the right terminal. Now you want to open another
+terminal window below the current one. If you would just open a new terminal
+window, it would show up to the right due to the horizontal workspace
+orientation. Instead, press +Alt+v+ to create a +Vertical Split Container+ (to
+open a +Horizontal Split Container+, use +Alt+h+). Now you can open a new
+terminal and it will open below the current one:
+
+image::tree-layout1.png["Layout",float="right"]
+image::tree-shot1.png["shot",title="Vertical Split Container"]
+
+unfloat::[]
+
+You probably guessed it already: There is no limit on how deep your hierarchy
+of splits can be.
+
+=== Level up
+
+Let’s stay with our example from above. We have a terminal on the left and two
+vertically split terminals on the right, focus is on the bottom right one. When
+you open a new terminal, it will open below the current one.
+
+So, how can you open a new terminal window to the *right* of the current one?
+The solution is to use +level up+, which will focus the +Parent Container+ of
+the current +Container+. In this case, you would focus the +Vertical Split
+Container+ which is *inside* the horizontally oriented workspace. Thus, now new
+windows will be opened to the right of the +Vertical Split Container+:
+
+image::tree-shot3.png["shot3",title="Level Up, then open new terminal"]
+
+== Commands
+
+The authoritive reference for commands is +src/cmdparse.y+. You can also find
+most commands in +i3.config+. Here comes a short overview over the important
+commands:
+
+=== Manipulating layout
+
+-------------------------------
+layout <default|stacked|tabbed>
+-------------------------------
+
+=== Changing Focus
+
+--------------------------
+next <horizontal|vertical>
+prev <horizontal|vertical>
+--------------------------
+
+.Examples:
+-------------------------
+bindsym Mod1+Left prev h
+bindsym Mod1+Right next h
+bindsym Mod1+Down next v
+bindsym Mod1+Up prev v
+-------------------------
+
+=== Moving
+
+-----------------------------------------
+move <before|after> <horizontal|vertical>
+-----------------------------------------
+
+.Examples:
+-----------------------------------------
+bindsym Mod1+Shift+Left move before h
+bindsym Mod1+Shift+Right move after h
+bindsym Mod1+Shift+Down move before v
+bindsym Mod1+Shift+Up move after v
+-----------------------------------------
+
+=== Changing workspace
+
+---------------------------
+workspace <name>
+---------------------------
+
+.Examples:
+---------------------------
+bindsym Mod1+1 workspace 1
+bindsym Mod1+2 workspace 2
+…
+---------------------------
+
+=== Moving Containers to workspaces
+
+---------------------
+move workspace <name>
+---------------------
+
+-------------------------------------
+bindsym Mod1+Shift+1 move workspace 1
+bindsym Mod1+Shift+2 move workspace 2
+…
+-------------------------------------
+
+=== Changing border style
+
+---------------------------
+border <normal|none|1pixel>
+---------------------------
+
+=== Changing container mode
+
+-----------------------------
+mode <tiling|floating|toggle>
+-----------------------------
+
+== The rest
+
+What is not mentioned here explicitly is either unchanged and can be read in
+the http://i3.zekjur.net/docs/userguide.html[i3 User’s Guide] or it is not yet
+implemented.
index 7402f64bce95bfa247580d8dc199c625b0badff1..5c24dbf28ca0f75ee1a364ed52c1a31f8e36dbe3 100644 (file)
@@ -1,24 +1,30 @@
 i3 User’s Guide
 ===============
 Michael Stapelberg <michael+i3@stapelberg.de>
-March 2010
+July 2011
+
+*********************************************************************************
+This document is not yet finished. The tree branch is still in development. The
+information provided here should be correct, just not complete yet.
+*********************************************************************************
 
 This document contains all the information you need to configure and use the i3
-window manager. If it does not, please contact me on IRC, Jabber or E-Mail and
-I’ll help you out.
+window manager. If it does not, please contact us on IRC (preferred) or post your
+question(s) on the mailing list.
 
+//////////////////////////////////////////////////////////////////////////////
 == Default keybindings
 
 For the "too long; didn’t read" people, here is an overview of the default
 keybindings (click to see the full size image):
 
-*Keys to use with Mod1 (alt):*
+*Keys to use with mod (alt):*
 
-image:keyboard-layer1.png["Keys to use with Mod1 (alt)",width=600,link="keyboard-layer1.png"]
+image:keyboard-layer1.png["Keys to use with mod (alt)",width=600,link="keyboard-layer1.png"]
 
-*Keys to use with Shift+Mod1:*
+*Keys to use with Shift+mod:*
 
-image:keyboard-layer2.png["Keys to use with Shift+Mod1",width=600,link="keyboard-layer2.png"]
+image:keyboard-layer2.png["Keys to use with Shift+mod",width=600,link="keyboard-layer2.png"]
 
 As i3 uses keycodes in the default configuration, it does not matter which
 keyboard layout you actually use. The key positions are what matters (of course
@@ -26,46 +32,52 @@ you can also use keysymbols, see <<keybindings>>).
 
 The red keys are the modifiers you need to press (by default), the blue keys
 are your homerow.
+//////////////////////////////////////////////////////////////////////////////
 
 == Using i3
 
+Throughout this guide, the keyword +mod+ will be used to refer to the
+configured modifier. This is the alt key (Mod1) by default, with windows (Mod4)
+being a popular alternative.
+
 === Opening terminals and moving around
 
 One very basic operation is opening a new terminal. By default, the keybinding
-for this is Mod1+Enter, that is Alt+Enter in the default configuration. By
-pressing Mod1+Enter, a new terminal will be opened.  It will fill the whole
+for this is mod+Enter, that is Alt+Enter in the default configuration. By
+pressing mod+Enter, a new terminal will be opened.  It will fill the whole
 space available on your screen.
 
 image:single_terminal.png[Single terminal]
 
-It is important to keep in mind that i3 uses a table to manage your windows. At
-the moment, you have exactly one column and one row which leaves you with one
-cell. In this cell there is a container, which is where your new terminal is
-opened.
-
-If you now open another terminal, you still have only one cell. However, the
-container in that cell holds both of your terminals. So, a container is just a
-group of clients with a specific layout. Containers can be resized by adjusting
-the size of the cell that holds them.
+If you now open another terminal, i3 will place it next to the current one,
+splitting the screen size in half. Depending on your monitor, i3 will put the
+created window beside the existing window (on wide displays) or below the
+existing window (rotated displays).
 
 image:two_terminals.png[Two terminals]
 
-To move the focus between the two terminals, you use the direction keys which
-you may know from the editor +vi+. However, in i3, your homerow is used for
-these keys (in +vi+, the keys are shifted to the left by one for compatibility
-with most keyboard layouts). Therefore, +Mod1+J+ is left, +Mod1+K+ is down,
-+Mod1+L+ is up and `Mod1+;` is right. So, to switch between the terminals,
-use +Mod1+K+ or +Mod1+L+.
+To move the focus between the two terminals, you can use the direction keys
+which you may know from the editor +vi+. However, in i3, your homerow is used
+for these keys (in +vi+, the keys are shifted to the left by one for
+compatibility with most keyboard layouts). Therefore, +mod+J+ is left, +mod+K+
+is down, +mod+L+ is up and `mod+;` is right. So, to switch between the
+terminals, use +mod+K+ or +mod+L+. Of course, you can also use the arrow keys.
+
+At the moment, your workspace is split (it contains two terminals) in a
+specific direction (horizontal by default). Every window can be split
+horizontally or vertically again, just like the workspace. The terminology is
+"window" for a container that actually contains an X11 window (like a terminal
+or browser) and "split container" for containers that consist of one or more
+windows.
 
-To create a new row/column (and a new cell), you can simply move a terminal (or
-any other window) in the direction you want to expand your table. So, let’s
-expand the table to the right by pressing `Mod1+Shift+;`.
+TODO: picture of the tree
 
-image:two_columns.png[Two columns]
+To split a window vertically, press +mod+v+. To split it horizontally, press
++mod+h+.
 
-=== Changing container modes
+=== Changing the container layout
 
-A container can have the following modes:
+A split container can have one of the following layouts:
 
 default::
 Windows are sized so that every window gets an equal amount of space in the
@@ -77,35 +89,35 @@ tabbed::
 The same principle as +stacking+, but the list of windows at the top is only
 a single line which is vertically split.
 
-To switch modes, press +Mod1+e+ for default, +Mod1+h+ for stacking and
-+Mod1+w+ for tabbed.
+To switch modes, press +mod+e+ for default, +mod+h+ for stacking and
++mod+w+ for tabbed.
 
 image:modes.png[Container modes]
 
 === Toggling fullscreen mode for a window
 
-To display a window fullscreen or to go out of fullscreen mode again, press
-+Mod1+f+.
+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 use all
+There is also a global fullscreen mode in i3 in which the client will span all
 available outputs.
 
 === Opening other applications
 
 Aside from opening applications from a terminal, you can also use the handy
-+dmenu+ which is opened by pressing +Mod1+v+ by default. Just type the name
-(or a part of it) of the application which you want to open. The application
-typed has to be in your +$PATH+ for this to work.
++dmenu+ which is opened by pressing +mod+d+ by default. Just type the name
+(or a part of it) of the application which you want to open. The corresponding
+application has to be in your +$PATH+ for this to work.
 
 Additionally, if you have applications you open very frequently, you can
 create a keybinding for starting the application directly. See the section
-"Configuring i3" for details.
+<<configuring>> for details.
 
 === Closing windows
 
 If an application does not provide a mechanism for closing (most applications
 provide a menu, the escape key or a shortcut like +Control+W+ to close), you
-can press +Mod1+Shift+q+ to kill a window. For applications which support
+can press +mod+Shift+q+ to kill a window. For applications which support
 the WM_DELETE protocol, this will correctly close the application (saving
 any modifications or doing other cleanup). If the application doesn’t support
 the WM_DELETE protocol your X server will kill the window and the behaviour
@@ -115,7 +127,7 @@ depends on the application.
 
 Workspaces are an easy way to group a set of windows. By default, you are on
 the first workspace, as the bar on the bottom left indicates. To switch to
-another workspace, press +Mod1+num+ where +num+ is the number of the workspace
+another workspace, press +mod+num+ where +num+ is the number of the workspace
 you want to use. If the workspace does not exist yet, it will be created.
 
 A common paradigm is to put the web browser on one workspace, communication
@@ -129,18 +141,15 @@ focus to that screen.
 
 === Moving windows to workspaces
 
-To move a window to another workspace, simply press +Mod1+Shift+num+ where
+To move a window to another workspace, simply press +mod+Shift+num+ where
 +num+ is (like when switching workspaces) the number of the target workspace.
 Similarly to switching workspaces, the target workspace will be created if
 it does not yet exist.
 
-=== Resizing columns/rows
+=== Resizing
 
-To resize columns or rows, just grab the border between the two columns/rows
-and move it to the wanted size. Please keep in mind that each cell of the table
-holds a +container+ and thus you cannot horizontally resize single windows.  If
-you need applications with different horizontal sizes, place them in seperate
-cells one above the other.
+The easiest way to resize a container is by using the mouse: Grab the border
+and move it to the wanted size.
 
 See <<resizingconfig>> for how to configure i3 to be able to resize
 columns/rows with your keyboard.
@@ -148,35 +157,21 @@ columns/rows with your keyboard.
 === Restarting i3 inplace
 
 To restart i3 inplace (and thus get into a clean state if there is a bug, or
-to upgrade to a newer version of i3) you can use +Mod1+Shift+r+. Be aware,
-though, that this kills your current layout and all the windows you have opened
-will be put in a default container in only one cell. Saving layouts will be
-implemented in a later version.
+to upgrade to a newer version of i3) you can use +mod+Shift+r+.
 
 === Exiting i3
 
-To cleanly exit i3 without killing your X server, you can use +Mod1+Shift+e+.
-
-=== Snapping
-
-Snapping is a mechanism to increase/decrease the colspan/rowspan of a container.
-Colspan/rowspan is the number of columns/rows a specific cell of the table
-consumes. This is easier explained by giving an example, so take the following
-layout:
-
-image:snapping.png[Snapping example]
-
-To use the full size of your screen, you can now snap container 3 downwards
-by pressing +Mod1+Control+k+ (or snap container 2 rightwards).
+To cleanly exit i3 without killing your X server, you can use +mod+Shift+e+.
 
 === Floating
 
 Floating mode is the opposite of tiling mode. The position and size of a window
 are not managed by i3, but by you. Using this mode violates the tiling
 paradigm but can be useful for some corner cases like "Save as" dialog
-windows, or toolbar windows (GIMP or similar).
+windows, or toolbar windows (GIMP or similar). Those windows usually set the
+appropriate hint and are opened in floating mode by default.
 
-You can enable floating mode for a window by pressing +Mod1+Shift+Space+. By
+You can enable floating mode for a window by pressing +mod+Shift+Space+. By
 dragging the window’s titlebar with your mouse you can move the window
 around. By grabbing the borders and moving them you can resize the window. You
 can also do that by using the <<floating_modifier>>.
@@ -185,6 +180,73 @@ For resizing floating windows with your keyboard, see <<resizingconfig>>.
 
 Floating windows are always on top of tiling windows.
 
+== Tree
+
+i3 stores all information about the X11 outputs, workspaces and layout of the
+windows on them in a tree. The root node is the X11 root window, followed by
+the X11 outputs, then dock areas and a content container, then workspaces and
+finally the windows themselve. In previous versions of i3 we had multiple lists
+(of outputs, workspaces) and a table for each workspace. That approach turned
+out to be complicated to use (snapping), understand and implement.
+
+=== The tree consists of Containers
+
+The building blocks of our tree are so called +Containers+. A +Container+ can
+host a window (meaning an X11 window, one that you can actually see and use,
+like a browser). Alternatively, it could contain one or more +Containers+. A
+simple example is the workspace: When you start i3 with a single monitor, a
+single workspace and you open two terminal windows, you will end up with a tree
+like this:
+
+image::tree-layout2.png["layout2",float="right"]
+image::tree-shot4.png["shot4",title="Two terminals on standard workspace"]
+
+=== Orientation and Split Containers
+
+[[OrientationSplit]]
+
+It is only natural to use so-called +Split Containers+ in order to build a
+layout when using a tree as data structure. In i3, every +Container+ has an
+orientation (horizontal, vertical or unspecified). So, in our example with the
+workspace, the default orientation of the workspace +Container+ is horizontal
+(most monitors are widescreen nowadays). If you change the orientation to
+vertical (+mod+v+ in the default config) and *then* open two terminals, i3 will
+configure your windows like this:
+
+image::tree-shot2.png["shot2",title="Vertical Workspace Orientation"]
+
+An interesting new feature of the tree branch is the ability to split anything:
+Let’s assume you have two terminals on a workspace (with horizontal
+orientation), focus is on the right terminal. Now you want to open another
+terminal window below the current one. If you would just open a new terminal
+window, it would show up to the right due to the horizontal workspace
+orientation. Instead, press +mod+v+ to create a +Vertical Split Container+ (to
+open a +Horizontal Split Container+, use +mod+h+). Now you can open a new
+terminal and it will open below the current one:
+
+image::tree-layout1.png["Layout",float="right"]
+image::tree-shot1.png["shot",title="Vertical Split Container"]
+
+unfloat::[]
+
+You probably guessed it already: There is no limit on how deep your hierarchy
+of splits can be.
+
+=== Focus parent
+
+Let’s stay with our example from above. We have a terminal on the left and two
+vertically split terminals on the right, focus is on the bottom right one. When
+you open a new terminal, it will open below the current one.
+
+So, how can you open a new terminal window to the *right* of the current one?
+The solution is to use +focus parent+, which will focus the +Parent Container+ of
+the current +Container+. In this case, you would focus the +Vertical Split
+Container+ which is *inside* the horizontally oriented workspace. Thus, now new
+windows will be opened to the right of the +Vertical Split Container+:
+
+image::tree-shot3.png["shot3",title="Focus parent, then open new terminal"]
+
+[[configuring]]
 == Configuring i3
 
 This is where the real fun begins ;-). Most things are very dependant on your
@@ -203,6 +265,14 @@ To change the configuration of i3, copy +/etc/i3/config+ to +\~/.i3/config+
 (or +~/.config/i3/config+ if you like the XDG directory scheme) and edit it
 with a text editor.
 
+On first start (and on all following starts, unless you have a configuration
+file), i3 will offer you to create a configuration file. You can tell the
+wizard to use either Alt (Mod1) or Windows (Mod4) as modifier in the config
+file. Also, the created config file will use the key symbols of your current
+keyboard layout. To start the wizard, use the command +i3-config-wizard+.
+Please note that you must not have +~/.i3/config+, otherwise the wizard will
+exit.
+
 === Comments
 
 It is possible and recommended to use comments in your configuration file to
@@ -216,10 +286,12 @@ a # and can only be used at the beginning of a line:
 
 === Fonts
 
-i3 uses X core fonts (not Xft) for rendering window titles and the internal
-workspace bar. You can use +xfontsel(1)+ to generate such a font description.
-To see special characters (Unicode), you need to use a font which supports
-the ISO-10646 encoding.
+i3 uses X core fonts (not Xft) for rendering window titles. You can use
++xfontsel(1)+ to generate such a font description. To see special characters
+(Unicode), you need to use a font which supports the ISO-10646 encoding.
+
+If i3 cannot open the configured font, it will output an error in the logfile
+and fall back to a working font.
 
 *Syntax*:
 ------------------------------
@@ -244,9 +316,9 @@ also mix your bindings, though i3 will not protect you from overlapping ones).
   are the ones you use in Xmodmap to remap your keys. To get the current
   mapping of your keys, use +xmodmap -pke+.
 
-* Keycodes do not need to have a symbol assigned (handy for some hotkeys
-  on some notebooks) and they will not change their meaning as you switch to a
-  different keyboard layout (when using +xmodmap+).
+* Keycodes do not need to have a symbol assigned (handy for custom vendor
+  hotkeys on some notebooks) and they will not change their meaning as you
+  switch to a different keyboard layout (when using +xmodmap+).
 
 My recommendation is: If you often switch keyboard layouts but you want to keep
 your bindings in the same physical location on the keyboard, use keycodes.
@@ -256,19 +328,19 @@ keysyms.
 *Syntax*:
 ----------------------------------
 bindsym [Modifiers+]keysym command
-bind [Modifiers+]keycode command
+bindcode [Modifiers+]keycode command
 ----------------------------------
 
 *Examples*:
 --------------------------------
 # Fullscreen
-bindsym Mod1+f f
+bindsym mod+f f
 
 # Restart
-bindsym Mod1+Shift+r restart
+bindsym mod+Shift+r restart
 
 # Notebook-specific hotkeys
-bind 214 exec /home/michael/toggle_beamer.sh
+bindcode 214 exec /home/michael/toggle_beamer.sh
 --------------------------------
 
 Available Modifiers:
@@ -303,41 +375,63 @@ you hold the shift button as well, the resize will be proportional.
 floating_modifier <Modifiers>
 --------------------------------
 
-*Examples*:
+*Example*:
 --------------------------------
 floating_modifier Mod1
 --------------------------------
 
+=== Orientation for new workspaces
+
+New workspaces get a reasonable default orientation: Wide-screen monitors
+(anything wider than high) get horizontal orientation, rotated monitors
+(anything higher than wide) get vertical orientation.
+
+With the +default_orientation+ configuration directive, you can override that
+behaviour.
+
+*Syntax*:
+----------------------------------------------
+default_orientation <horizontal|vertical|auto>
+----------------------------------------------
+
+*Example*:
+----------------------------
+default_orientation vertical
+----------------------------
+
 === Layout mode for new containers
 
-This option determines in which mode new containers will start. See also
-<<stack-limit>>.
+This option determines in which mode new containers on workspace level will
+start.
+///////////////////////////////
+See also <<stack-limit>>.
+//////////////////////////////
 
 *Syntax*:
 ---------------------------------------------
-new_container <default|stacking|tabbed>
-new_container stack-limit <cols|rows> <value>
+workspace_layout <default|stacking|tabbed>
 ---------------------------------------------
+/////////////////////////////////////////////
+new_container stack-limit <cols|rows> <value>
+/////////////////////////////////////////////
 
-*Examples*:
+*Example*:
 ---------------------
-new_container tabbed
+workspace_layout tabbed
 ---------------------
 
 === Border style for new windows
 
-This option determines which border style new windows will have: +bn+ for
-the normal border (including window title), +bp+ for a 1-pixel border (no
-window title) and +bb+ to make the window borderless.
+This option determines which border style new windows will have.
 
 *Syntax*:
 ---------------------------------------------
-new_window <bp|bn|bb>
+new_window <normal|1pixel|borderless>
 ---------------------------------------------
 
-*Examples*:
+*Example*:
 ---------------------
-new_window bp
+new_window 1pixel
 ---------------------
 
 === Variables
@@ -352,7 +446,7 @@ variables can be handy.
 set $name value
 --------------
 
-*Examples*:
+*Example*:
 ------------------------
 set $m Mod1
 bindsym $m+Shift+r restart
@@ -362,19 +456,20 @@ Variables are directly replaced in the file when parsing. There is no fancy
 handling and there are absolutely no plans to change this. If you need a more
 dynamic configuration you should create a little script which generates a
 configuration file and run it before starting i3 (for example in your
-+.xsession+ file).
++~/.xsession+ file).
 
 === Automatically putting clients on specific workspaces
 
 [[assign_workspace]]
 
-It is recommended that you match on window classes wherever possible because
-some applications first create their window, and then worry about setting the
-correct title. Firefox with Vimperator comes to mind. The window starts up
-being named Firefox, and only when Vimperator is loaded does the title change.
-As i3 will get the title as soon as the application maps the window (mapping
-means actually displaying it on the screen), you’d need to have to match on
-'Firefox' in this case.
+Specific windows can be matched by window class and/or window title. It is
+recommended that you match on window classes instead of window titles whenever
+possible because some applications first create their window, and then worry
+about setting the correct title. Firefox with Vimperator comes to mind. The
+window starts up being named Firefox, and only when Vimperator is loaded does
+the title change. As i3 will get the title as soon as the application maps the
+window (mapping means actually displaying it on the screen), you’d need to have
+to match on 'Firefox' in this case.
 
 You can prefix or suffix workspaces with a `~` to specify that matching clients
 should be put into floating mode. If you specify only a `~`, the client will
@@ -382,36 +477,40 @@ not be put onto any workspace, but will be set floating on the current one.
 
 *Syntax*:
 ------------------------------------------------------------
-assign ["]window class[/window title]["] [→] [~ | workspace]
+assign ["]window class[/window title]["] [→] [workspace]
 ------------------------------------------------------------
 
 *Examples*:
 ----------------------
 assign urxvt 2
 assign urxvt → 2
+assign urxvt → work
 assign "urxvt" → 2
 assign "urxvt/VIM" → 3
-assign "gecko" → ~4
-assign "xv/MPlayer" → ~
+assign "gecko" → 4
 ----------------------
 
 Note that the arrow is not required, it just looks good :-). If you decide to
-use it, it has to be a UTF-8 encoded arrow, not "->" or something like that.
+use it, it has to be a UTF-8 encoded arrow, not `->` or something like that.
 
 === Automatically starting applications on i3 startup
 
-By using the +exec+ keyword outside a keybinding, you can configure which
-commands will be performed by i3 on initial startup (not when restarting i3
-in-place however). These commands will be run in order.
+By using the +exec+ keyword outside a keybinding, you can configure
+which commands will be performed by i3 on initial startup. +exec+
+commands will not run when restarting i3, if you need a command to run
+also when restarting i3 you should use the +exec_always+
+keyword. These commands will be run in order.
 
 *Syntax*:
-------------
+-------------------
 exec command
-------------
+exec_always command
+-------------------
 
 *Examples*:
 --------------------------------
-exec sudo i3status | dzen2 -dock
+exec i3status | dzen2 -dock
+exec_always ~/my_script.sh
 --------------------------------
 
 [[workspace_screen]]
@@ -439,30 +538,9 @@ workspace 1 output LVDS1
 workspace 5 output VGA1
 ---------------------------
 
-=== Named workspaces
-
-If you always have a certain arrangement of workspaces, you might want to give
-them names (of course UTF-8 is supported):
-
-*Syntax*:
----------------------------------------
-workspace <number> <name>
-workspace <number> output <output> name
----------------------------------------
-
-For more details about the 'output' part of this command, see above.
-
-*Examples*:
---------------------------
-workspace 1 www
-workspace 2 work
-workspace 3 i ♥ workspaces
---------------------------
-
 === Changing colors
 
-You can change all colors which i3 uses to draw the window decorations and the
-bottom bar.
+You can change all colors which i3 uses to draw the window decorations.
 
 *Syntax*:
 --------------------------------------------
@@ -520,18 +598,22 @@ i3 uses unix sockets to provide an IPC interface. This allows third-party
 programs to get information from i3, such as the current workspaces
 (to display a workspace bar), and to control i3.
 
-To enable it, you have to configure a path where the unix socket will be
-stored. The default path is +~/.i3/ipc.sock+.
+The IPC socket is enabled by default and will be created in
++/tmp/i3-%u/ipc-socket.%p+ where +%u+ is your UNIX username and +%p+ is the PID
+of i3.
+
+You can override the default path through the environment-variable +I3SOCK+ or
+by specifying the +ipc-socket+ directive.
 
 *Examples*:
 ----------------------------
-ipc-socket ~/.i3/ipc.sock
+ipc-socket /tmp/i3-ipc.sock
 ----------------------------
 
 You can then use the +i3-msg+ application to perform any command listed in
 the next section.
 
-=== Disable focus follows mouse
+=== Focus follows mouse
 
 If you have a setup where your mouse usually is in your way (like a touchpad
 on your laptop which you do not want to disable completely), you might want
@@ -544,119 +626,146 @@ to click on links in your browser window).
 focus_follows_mouse <yes|no>
 ----------------------------
 
-*Examples*:
+*Example*:
 ----------------------
 focus_follows_mouse no
 ----------------------
 
-=== Internal workspace bar
+=== Popups during fullscreen mode
+
+When you are in fullscreen mode, some applications still open popup windows
+(take Xpdf for example). This is because these applications may not be aware
+that they are in fullscreen mode (they do not check the corresponding hint).
+There are two things which are possible to do in this situation:
 
-The internal workspace bar (the thing at the bottom of your screen) is very
-simple -- it does not provide a way to display custom text and it does not
-offer advanced customization features. This is intended because we do not
-want to duplicate functionality of tools like +dzen2+, +xmobar+ and so on
-(they render bars, we manage windows). Instead, there is an option which will
-turn off the internal bar completely, so that you can use a seperate program to
-display it (see +i3-wsbar+, a sample implementation of such a program):
+1. Just ignore the popup (don’t map it). This won’t interrupt you while you are
+   in fullscreen. However, some apps might react badly to this (deadlock until
+   you go out of fullscreen).
+2. Leave fullscreen mode. This is the default.
 
 *Syntax*:
-----------------------
-workspace_bar <yes|no>
-----------------------
+-------------------------------------------------
+popup_during_fullscreen <ignore|leave_fullscreen>
+-------------------------------------------------
 
-*Examples*:
-----------------
-workspace_bar no
-----------------
+*Example*:
+------------------------------
+popup_during_fullscreen ignore
+------------------------------
 
 == List of commands
 
+=== Splitting containers
+
+The split command makes the current window a split container. Split containers
+can contain multiple windows. Every split container has an orientation, it is
+either split horizontally (a new window gets placed to the right of the current
+one) or vertically (a new window gets placed below the current one).
+
+If you apply this command to a split container with the same orientation,
+nothing will happen. If you use a different orientation, the split container’s
+orientation will be changed (if it does not have more than one window).
+
+*Syntax*:
+---------------------------
+split <vertical|horizontal>
+---------------------------
+
+*Example*:
+--------------
+split vertical
+--------------
+
 === Manipulating layout
 
-To change the layout of the current container to stacking, use +s+, for default
-use +d+ and for tabbed, use +T+. To make the current client (!) fullscreen,
-use +f+, to make it span all outputs, use +fg+, to make it floating (or
-tiling again) use +t+:
+Use +layout default+, +layout stacking+ or +layout tabbed+ to change the
+current container layout to default, stacking or tabbed layout, respectively.
+
+To make the current window (!) fullscreen, use +fullscreen+, to make
+it floating (or tiling again) use +floating enable+ respectively +floating disable+
+(or +floating toggle+):
 
 *Examples*:
 --------------
-bindsym Mod1+s s
-bindsym Mod1+l d
-bindsym Mod1+w T
+bindsym mod+s layout stacking
+bindsym mod+l layout default
+bindsym mod+w layout tabbed
 
 # Toggle fullscreen
-bindsym Mod1+f f
-
-# Toggle global fullscreen
-bindsym Mod1+Shift+f fg
+bindsym mod+f fullscreen
 
 # Toggle floating/tiling
-bindsym Mod1+t t
+bindsym mod+t floating toggle
 --------------
 
-=== Focusing/Moving/Snapping clients/containers/screens
+=== 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 one of the +h+, +j+, +k+ and +l+ commands, meaning
-left, down, up, right (respectively). To focus a container, prefix it with
-+wc+. To focus a screen, prefix it with +ws+.
+There are a few special parameters you can use for the focus command:
+
+parent::
+       Sets focus to the +Parent Container+ of the current +Container+.
+child::
+       The opposite of +focus parent+, sets the focus to the last focused
+       child container.
+floating::
+       Sets focus to the last focused floating container.
+tiling::
+       Sets focus to the last focused tiling container.
+mode_toggle::
+       Toggles between floating/tiling containers.
 
-The same principle applies for moving and snapping: just prefix the command
-with +m+ when moving and with +s+ when snapping:
+For moving, use +move left+, +move right+, +move down+ and +move up+.
 
 *Examples*:
 ----------------------
 # Focus clients on the left, bottom, top, right:
-bindsym Mod1+j h
-bindsym Mod1+k j
-bindsym Mod1+j k
-bindsym Mod1+semicolon l
+bindsym mod+j focus left
+bindsym mod+k focus down
+bindsym mod+l focus up
+bindsym mod+semicolon focus right
+
+# Focus parent container
+bindsym mod+u focus parent
+
+# Focus last floating/tiling container
+bindsym mod+g focus mode_toggle
 
 # Move client to the left, bottom, top, right:
-bindsym Mod1+j mh
-bindsym Mod1+k mj
-bindsym Mod1+j mk
-bindsym Mod1+semicolon ml
-
-# Snap client to the left, bottom, top, right:
-bindsym Mod1+j sh
-bindsym Mod1+k sj
-bindsym Mod1+j sk
-bindsym Mod1+semicolon sl
-
-# Focus container on the left, bottom, top, right:
-bindsym Mod3+j wch
-…
+bindsym mod+j move left
+bindsym mod+k move down
+bindsym mod+l move up
+bindsym mod+semicolon move right
 ----------------------
 
-=== Changing workspaces/moving clients to workspaces
+=== Changing workspaces/moving containers to workspaces
 
-To change to a specific workspace, the command is just the number of the
-workspace, e.g. +1+ or +3+. To move the current client to a specific workspace,
-prefix the number with an +m+.
+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+.
 
-You can also switch to the next and previous workspace with the commands +nw+
-and +pw+, 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.
+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.
 
 *Examples*:
 -------------------------
-bindsym Mod1+1 1
-bindsym Mod1+2 2
+bindsym mod+1 workspace 1
+bindsym mod+2 workspace 2
 ...
 
-bindsym Mod1+Shift+1 m1
-bindsym Mod1+Shift+2 m2
+bindsym mod+Shift+1 move workspace 1
+bindsym mod+Shift+2 move workspace 2
 ...
-
-bindsym Mod1+o nw
-bindsym Mod1+p pw
 -------------------------
 
 [[resizingconfig]]
 
-=== Resizing columns/rows
+=== Resizing containers/windows
 
-If you want to resize columns/rows using your keyboard, you can use the
+If you want to resize containers/windows using your keyboard, you can use the
 +resize+ command, I recommend using it inside a so called +mode+:
 
 .Example: Configuration file, defining a mode for resizing
@@ -668,23 +777,23 @@ mode "resize" {
         # when pressing left, the window is resized so that it has
         # more space on its left
 
-        bindsym n resize left -10
-        bindsym Shift+n resize left +10
+        bindsym j resize shrink left
+        bindsym Shift+j resize grow left
 
-        bindsym r resize bottom +10
-        bindsym Shift+r resize bottom -10
+        bindsym k resize grow bottom
+        bindsym Shift+k resize shrink bottom
 
-        bindsym t resize top -10
-        bindsym Shift+t resize top +10
+        bindsym l resize shrink top
+        bindsym Shift+l resize grow top
 
-        bindsym d resize right +10
-        bindsym Shift+d resize right -10
+        bindsym semicolon resize grow right
+        bindsym Shift+semicolon resize shrink right
 
-        bind 36 mode default
+        bindcode 36 mode default
 }
 
 # Enter resize mode
-bindsym Mod1+r mode resize
+bindsym mod+r mode "resize"
 ----------------------------------------------------------------------
 
 === Jumping to specific windows
@@ -693,23 +802,20 @@ Often when in a multi-monitor environment, you want to quickly jump to a
 specific window. For example, while working on workspace 3 you may want to
 jump to your mail client to email your boss that you’ve achieved some
 important goal. Instead of figuring out how to navigate to your mailclient,
-it would be more convenient to have a shortcut.
+it would be more convenient to have a shortcut. You can use the +focus+ command
+with criteria for that.
 
 *Syntax*:
 ----------------------------------------------------
-jump ["]window class[/window title]["]
-jump workspace [ column row ]
+[class="class"] focus
+[title="title"] focus
 ----------------------------------------------------
 
-You can either use the same matching algorithm as in the +assign+ command
-(see above) or you can specify the position of the client if you always use
-the same layout.
-
 *Examples*:
---------------------------------------
+------------------------------------------------
 # Get me to the next open VIM instance
-bindsym Mod1+a jump "urxvt/VIM"
---------------------------------------
+bindsym mod+a [class="urxvt" title="VIM"] focus
+------------------------------------------------
 
 === VIM-like marks (mark/goto)
 
@@ -728,68 +834,57 @@ for this purpose: It lets you input a command and sends the command to i3. It
 can also prefix this command and display a custom prompt for the input dialog.
 
 *Syntax*:
------------------
-mark <identifier>
-goto <identifier>
------------------
+------------------------------
+mark identifier
+[con_mark="identifier"] focus
+------------------------------
+
+*Example (in a terminal)*:
+------------------------------
+$ i3-msg mark irssi
+$ i3-msg '[con_mark="irssi"] focus'
+------------------------------
 
+///////////////////////////////////////////////////////////////////
+TODO: make i3-input replace %s
 *Examples*:
 ---------------------------------------
 # Read 1 character and mark the current window with this character
-bindsym Mod1+m exec i3-input -p 'mark ' -l 1 -P 'Mark: '
+bindsym mod+m exec i3-input -p 'mark ' -l 1 -P 'Mark: '
 
 # Read 1 character and go to the window with the character
-bindsym Mod1+g exec i3-input -p 'goto ' -l 1 -P 'Goto: '
+bindsym mod+g exec i3-input -p 'goto ' -l 1 -P 'Goto: '
 ---------------------------------------
 
 Alternatively, if you do not want to mess with +i3-input+, you could create
 seperate bindings for a specific set of labels and then only use those labels.
-
-=== Traveling the focus stack
-
-This mechanism can be thought of as the opposite of the +jump+ command.
-It travels the focus stack and jumps to the window which had focus previously.
-
-*Syntax*:
---------------
-focus [number] | floating | tiling | ft
---------------
-
-Where +number+ by default is 1 meaning that the next client in the focus stack
-will be selected.
-
-The special values have the following meaning:
-
-floating::
-       The next floating window is selected.
-tiling::
-       The next tiling window is selected.
-ft::
-       If the current window is floating, the next tiling window will be
-       selected; and vice-versa.
+///////////////////////////////////////////////////////////////////
 
 === Changing border style
 
-To change the border of the current client, you can use +bn+ to use the normal
-border (including window title), +bp+ to use a 1-pixel border (no window title)
-and +bb+ to make the client borderless. There is also +bt+ which will toggle
-the different border styles.
+To change the border of the current client, you can use +border normal+ to use the normal
+border (including window title), +border 1pixel+ to use a 1-pixel border (no window title)
+and +border none+ to make the client borderless.
+
+There is also +border toggle+ which will toggle the different border styles.
 
 *Examples*:
-------------------
-bindsym Mod1+t bn
-bindsym Mod1+y bp
-bindsym Mod1+u bb
-------------------
+----------------------------
+bindsym mod+t border normal
+bindsym mod+y border 1pixel
+bindsym mod+u border none
+----------------------------
 
 [[stack-limit]]
 
+///////////////////////////////////////////////////////////////////////////////
+TODO: not yet implemented
 === Changing the stack-limit of a container
 
 If you have a single container with a lot of windows inside it (say, more than
 10), the default layout of a stacking container can get a little unhandy.
-Depending on your screen’s size, you might end up seeing only half of the
-titlebars for each window in the container.
+Depending on your screen’s size, you might end up with only half of the title
+lines being actually used, wasting a lot of screen space.
 
 Using the +stack-limit+ command, you can limit the number of rows or columns
 in a stacking container. i3 will create columns or rows (depending on what
@@ -810,22 +905,21 @@ stack-limit rows 5
 -------------------
 
 image:stacklimit.png[Container limited to two columns]
+///////////////////////////////////////////////////////////////////////////////
 
 === Reloading/Restarting/Exiting
 
 You can make i3 reload its configuration file with +reload+. You can also
 restart i3 inplace with the +restart+ command to get it out of some weird state
 (if that should ever happen) or to perform an upgrade without having to restart
-your X session. However, your layout is not preserved at the moment, meaning
-that all open windows will end up in a single container in default layout
-after the restart. To exit i3 properly, you can use the +exit+ command,
+your X session. To exit i3 properly, you can use the +exit+ command,
 however you don’t need to (simply killing your X session is fine as well).
 
 *Examples*:
 ----------------------------
-bindsym Mod1+Shift+r restart
-bindsym Mod1+Shift+w reload
-bindsym Mod1+Shift+e exit
+bindsym mod+Shift+r restart
+bindsym mod+Shift+w reload
+bindsym mod+Shift+e exit
 ----------------------------
 
 [[multi_monitor]]
@@ -947,24 +1041,16 @@ approach you have in the task bar of a traditional desktop environment.
 If you don’t already have your favorite way of generating such a status line
 (self-written scripts, conky, …), then i3status is the recommended tool for
 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.
-
-Regardless of which application you use to generate the status line, you
-want to make sure that the application does one of the following things:
-
-1. Register as a dock window using EWMH hints. This will make i3 position the
-   window above the workspace bar but below every other client. This is the
-   recommended way, but in case of dzen2, for example, you need to check out
-   the source of dzen2 from subversion, as the -dock option is not present
-   in the released versions.
-2. Overlay the internal workspace bar. This method will not waste any space
-   on the workspace bar, however, it is rather hackish. Just configure
-   the output window to be over the workspace bar (say -x 200 and -y 780 if
-   your screen is 800 px height).
-
-The planned solution for this problem is to make the workspace bar optional
-and switch to a third party application completely (dzen2 for example)
-which will then contain the workspace bar.
+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+.
+
+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.
 
 === Giving presentations (multi-monitor)
 
diff --git a/dump-asy.pl b/dump-asy.pl
new file mode 100755 (executable)
index 0000000..a8eab04
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/bin/env perl
+# vim:ts=4:sw=4:expandtab
+# renders the layout tree using asymptote
+
+use strict;
+use warnings;
+use Data::Dumper;
+use AnyEvent::I3;
+use File::Temp;
+use v5.10;
+
+my $i3 = i3("/tmp/nestedcons");
+
+my $tree = $i3->get_tree->recv;
+
+my $tmp = File::Temp->new(UNLINK => 0, SUFFIX => '.asy');
+
+say $tmp "import drawtree;";
+
+say $tmp "treeLevelStep = 2cm;";
+
+sub dump_node {
+       my ($n, $parent) = @_;
+
+    my $o = ($n->{orientation} eq 'none' ? "u" : ($n->{orientation} eq 'horizontal' ? "h" : "v"));
+    my $w = (defined($n->{window}) ? $n->{window} : "N");
+    my $na = $n->{name};
+    $na =~ s/#/\\#/g;
+    my $name = "($na, $o, $w)";
+
+    print $tmp "TreeNode n" . $n->{id} . " = makeNode(";
+
+    print $tmp "n" . $parent->{id} . ", " if defined($parent);
+    print $tmp "\"" . $name . "\");\n";
+
+       dump_node($_, $n) for @{$n->{nodes}};
+}
+
+dump_node($tree);
+say $tmp "draw(n" . $tree->{id} . ", (0, 0));";
+
+close($tmp);
+my $rep = "$tmp";
+$rep =~ s/asy$/eps/;
+system("cd /tmp && asy $tmp && gv $rep && rm $rep");
diff --git a/gtk-tree-watch.pl b/gtk-tree-watch.pl
new file mode 100755 (executable)
index 0000000..f15d0c1
--- /dev/null
@@ -0,0 +1,219 @@
+#!/usr/bin/env perl
+# vim:ts=4:sw=4:expandtab
+# renders the layout tree using asymptote
+
+use strict;
+use warnings;
+
+use JSON::XS;
+use Data::Dumper;
+use AnyEvent::I3;
+use v5.10;
+
+use Gtk2 '-init';
+use Gtk2::SimpleMenu;
+use Glib qw/TRUE FALSE/;
+
+my $window = Gtk2::Window->new('toplevel');
+$window->signal_connect('delete_event' => sub { Gtk2->main_quit; });
+
+my $tree_store = Gtk2::TreeStore->new(qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/);
+
+my $i3 = i3("/tmp/nestedcons");
+
+my $tree_view = Gtk2::TreeView->new($tree_store);
+
+my $layout_box = undef;
+
+sub copy_node {
+    my ($n, $parent, $piter, $pbox) = @_;
+
+    my $o = ($n->{orientation} == 0 ? "u" : ($n->{orientation} == 1 ? "h" : "v"));
+    my $w = (defined($n->{window}) ? $n->{window} : "N");
+
+    # convert a rectangle struct to X11 notation (WxH+X+Y)
+    my $r = $n->{rect};
+    my $x = $r->{x};
+    my $y = $r->{y};
+    my $dim = $r->{width}."x".$r->{height}.($x<0?$x:"+$x").($y<0?$y:"+$y");
+
+    # add node to the tree with all known properties
+    my $iter = $tree_store->append($piter);
+    $tree_store->set($iter, 0 => $n->{name}, 1 => $w, 2 => $o, 3 => sprintf("0x%08x", $n->{id}), 4 => $n->{urgent}, 5 => $n->{focused}, 6 => $n->{layout}, 7 => $dim);
+
+    # also create a box for the node, each node has a vbox
+    # for combining the title (and properties) with the
+    # container itself, the container will be empty in case
+    # of no children, a vbox or hbox
+    my $box;
+    if($n->{orientation} == 1) {
+        $box = Gtk2::HBox->new(1, 5);
+    } else {
+        $box = Gtk2::VBox->new(1, 5);
+    }
+
+    # combine label and container
+    my $node = Gtk2::Frame->new($n->{name}.",".$o.",".$w);
+    $node->set_shadow_type('etched-out');
+    $node->add($box);
+
+    # the parent is added onto a scrolled window, so add it with a viewport
+    if(defined($pbox)) {
+       $pbox->pack_start($node, 1, 1, 0);
+    } else {
+       $layout_box = $node;
+    }
+
+    # recurse into children
+    copy_node($_, $n, $iter, $box) for @{$n->{nodes}};
+
+    # if it is a window draw a nice color
+    if(defined($n->{window})) {
+       # use a drawing area to fill a colored rectangle
+       my $area = Gtk2::DrawingArea->new();
+
+       # the color is stored as hex in the name
+       $area->{"user-data"} = $n->{name};
+
+       $area->signal_connect(expose_event => sub {
+           my ($widget, $event) = @_;
+
+           # fetch a cairo context and it width/height to start drawing nodes
+           my $cr = Gtk2::Gdk::Cairo::Context->create($widget->window());
+
+           my $w = $widget->allocation->width;
+           my $h = $widget->allocation->height;
+
+           my $hc  = $widget->{"user-data"};
+           my $r = hex(substr($hc, 1, 2)) / 255.0;
+           my $g = hex(substr($hc, 3, 2)) / 255.0;
+           my $b = hex(substr($hc, 5, 2)) / 255.0;
+
+           $cr->set_source_rgb($r, $g, $b);
+           $cr->rectangle(0, 0, $w, $h);
+           $cr->fill();
+
+        return FALSE;
+       });
+
+    $box->pack_end($area, 1, 1, 0);
+    }
+}
+
+# Replaced by Gtk2 Boxes:
+#sub draw_node {
+#    my ($n, $cr, $x, $y, $w, $h) = @_;
+#
+#    $cr->set_source_rgb(1.0, 1.0, 1.0);
+#    $cr->rectangle($x, $y, $w/2, $h/2);
+#    $cr->fill();
+#}
+
+my $json_prev = "";
+
+my $layout_sw = Gtk2::ScrolledWindow->new(undef, undef);
+my $layout_container = Gtk2::HBox->new(0, 0);
+$layout_sw->add_with_viewport($layout_container);
+
+sub copy_tree {
+    my $tree = $i3->get_tree->recv;
+
+    # convert the tree back to json so we only rebuild/redraw when the tree is changed
+    my $json = encode_json($tree);
+    if ($json ne $json_prev) {
+        $json_prev = $json;
+
+        # rebuild the tree and the layout
+        $tree_store->clear();
+        if(defined($layout_box)) {
+            $layout_container->remove($layout_box);
+        }
+        copy_node($tree);
+        $layout_container->add($layout_box);
+        $layout_container->show_all();
+
+        # keep things expanded, otherwise the tree collapses every reload which is more annoying then this :-)
+        $tree_view->expand_all();
+    }
+
+    return(TRUE);
+}
+
+sub new_column {
+    my $tree_column = Gtk2::TreeViewColumn->new();
+    $tree_column->set_title(shift);
+
+    my $renderer = Gtk2::CellRendererText->new();
+    $tree_column->pack_start($renderer, FALSE);
+    $tree_column->add_attribute($renderer, text => shift);
+
+    return($tree_column);
+}
+
+my $col = 0;
+$tree_view->append_column(new_column("Name", $col++));
+$tree_view->append_column(new_column("Window", $col++));
+$tree_view->append_column(new_column("Orientation", $col++));
+$tree_view->append_column(new_column("ID", $col++));
+$tree_view->append_column(new_column("Urgent", $col++));
+$tree_view->append_column(new_column("Focused", $col++));
+$tree_view->append_column(new_column("Layout", $col++));
+$tree_view->append_column(new_column("Rect", $col++));
+
+$tree_view->set_grid_lines("both");
+
+my $tree_sw = Gtk2::ScrolledWindow->new(undef, undef);
+$tree_sw->add($tree_view);
+
+# Replaced by Gtk2 Boxes:
+#my $area = Gtk2::DrawingArea->new();
+#$area->signal_connect(expose_event => sub {
+#    my ($widget, $event) = @_;
+#
+#    # fetch a cairo context and it width/height to start drawing nodes
+#    my $cr = Gtk2::Gdk::Cairo::Context->create($widget->window());
+#
+#    my $w = $widget->allocation->width;
+#    my $h = $widget->allocation->height;
+#
+#    draw_node($gtree, $cr, 0, 0, $w, $h);
+#
+#    return FALSE;
+#});
+
+sub menu_export {
+    print("TODO: EXPORT\n");
+}
+
+my $menu_tree = [
+       _File => {
+               item_type => '<Branch>',
+               children => [
+                       _Export => {
+                               callback => \&menu_export,
+                               accelerator => '<ctrl>E',
+                       },
+                       _Quit => {
+                               callback => sub { Gtk2->main_quit; },
+                               accelerator => '<ctrl>Q',
+                       },
+               ],
+       },
+];
+
+my $menu = Gtk2::SimpleMenu->new(menu_tree => $menu_tree);
+
+my $vbox = Gtk2::VBox->new(0, 0);
+$vbox->pack_start($menu->{widget}, 0, 0, 0);
+$vbox->pack_end($tree_sw, 1, 1, 0);
+$vbox->pack_end($layout_sw, 1, 1, 0);
+
+$window->add($vbox);
+$window->show_all();
+$window->set_size_request(500,500);
+
+Glib::Timeout->add(1000, "copy_tree", undef, Glib::G_PRIORITY_DEFAULT);
+copy_tree();
+
+Gtk2->main();
+
diff --git a/i3-config-wizard/Makefile b/i3-config-wizard/Makefile
new file mode 100644 (file)
index 0000000..43c3a1f
--- /dev/null
@@ -0,0 +1,42 @@
+# Default value so one can compile i3-input standalone
+TOPDIR=..
+
+include $(TOPDIR)/common.mk
+
+# Depend on the object files of all source-files in src/*.c and on all header files
+AUTOGENERATED:=cfgparse.tab.c cfgparse.yy.c
+FILES:=$(patsubst %.c,%.o,$(filter-out $(AUTOGENERATED),$(wildcard *.c)))
+HEADERS:=$(wildcard *.h)
+
+# Depend on the specific file (.c for each .o) and on all headers
+%.o: %.c ${HEADERS}
+       echo "CC $<"
+       $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+
+all: i3-config-wizard
+
+i3-config-wizard: cfgparse.y.o cfgparse.yy.o ${FILES}
+       echo "LINK i3-config-wizard"
+       $(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+
+cfgparse.yy.o: cfgparse.l cfgparse.y.o ${HEADERS}
+       echo "LEX $<"
+       flex -i -o$(@:.o=.c) $<
+       $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(@:.o=.c)
+
+cfgparse.y.o: cfgparse.y ${HEADERS}
+       echo "YACC $<"
+       bison --debug --verbose -b $(basename $< .y) -d $<
+       $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(<:.y=.tab.c)
+
+
+install: all
+       echo "INSTALL"
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
+       $(INSTALL) -m 0755 i3-config-wizard $(DESTDIR)$(PREFIX)/bin/
+
+clean:
+       rm -f *.o cfgparse.tab.{c,h} cfgparse.output cfgparse.yy.c
+
+distclean: clean
+       rm -f i3-config-wizard
diff --git a/i3-config-wizard/atoms.xmacro b/i3-config-wizard/atoms.xmacro
new file mode 100644 (file)
index 0000000..8f94cea
--- /dev/null
@@ -0,0 +1,6 @@
+xmacro(_NET_WM_NAME)
+xmacro(_NET_WM_WINDOW_TYPE)
+xmacro(_NET_WM_WINDOW_TYPE_DIALOG)
+xmacro(ATOM)
+xmacro(CARDINAL)
+xmacro(UTF8_STRING)
diff --git a/i3-config-wizard/cfgparse.l b/i3-config-wizard/cfgparse.l
new file mode 100644 (file)
index 0000000..772b847
--- /dev/null
@@ -0,0 +1,105 @@
+%option nounput
+%option noinput
+%option noyy_top_state
+%option stack
+
+%{
+/*
+ * vim:ts=8:expandtab
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include "cfgparse.tab.h"
+
+int yycolumn = 1;
+
+struct context {
+        int line_number;
+        char *line_copy;
+
+        char *compact_error;
+
+        /* These are the same as in YYLTYPE */
+        int first_column;
+        int last_column;
+};
+
+
+#define YY_DECL int yylex (struct context *context)
+
+#define YY_USER_ACTION { \
+        context->first_column = yycolumn; \
+        context->last_column = yycolumn+yyleng-1; \
+        yycolumn += yyleng; \
+}
+
+%}
+
+EOL    (\r?\n)
+
+%s BINDCODE_COND
+%s BIND_AWS_COND
+%s BIND_A2WS_COND
+%x BUFFER_LINE
+
+%%
+
+       {
+               /* This is called when a new line is lexed. We only want the
+                * first line to match to go into state BUFFER_LINE */
+               if (context->line_number == 0) {
+                       context->line_number = 1;
+                       BEGIN(INITIAL);
+                       yy_push_state(BUFFER_LINE);
+               }
+       }
+
+<BUFFER_LINE>^[^\r\n]*/{EOL}? {
+       /* save whole line */
+       context->line_copy = strdup(yytext);
+
+       yyless(0);
+       yy_pop_state();
+       yy_set_bol(true);
+       yycolumn = 1;
+}
+
+
+<BIND_A2WS_COND>[^\n]+          { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR; }
+[0-9]+                          { yylval.number = atoi(yytext); return NUMBER; }
+bind                            { BEGIN(BINDCODE_COND); return TOKBINDCODE; }
+bindcode                        { BEGIN(BINDCODE_COND); return TOKBINDCODE; }
+Mod1                            { yylval.number = (1 << 3); return MODIFIER; }
+Mod2                            { yylval.number = (1 << 4); return MODIFIER; }
+Mod3                            { yylval.number = (1 << 5); return MODIFIER; }
+Mod4                            { yylval.number = (1 << 6); return MODIFIER; }
+Mod5                            { yylval.number = (1 << 7); return MODIFIER; }
+Mode_switch                     { yylval.number = (1 << 8); return MODIFIER; }
+$mod                           { yylval.number = (1 << 9); return TOKMODVAR; }
+control                         { return TOKCONTROL; }
+ctrl                            { return TOKCONTROL; }
+shift                           { return TOKSHIFT; }
+{EOL}                           {
+                                 if (context->line_copy) {
+                                    free(context->line_copy);
+                                   context->line_copy = NULL;
+                                 }
+                                  context->line_number++;
+                                  BEGIN(INITIAL);
+                                  yy_push_state(BUFFER_LINE);
+                                }
+<BINDCODE_COND>[ \t]+           { BEGIN(BIND_AWS_COND); return WHITESPACE; }
+<BIND_AWS_COND>[ \t]+           { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
+[ \t]+                          { return WHITESPACE; }
+.                               { return (int)yytext[0]; }
+
+<<EOF>> {
+        while (yy_start_stack_ptr > 0)
+                yy_pop_state();
+        yyterminate();
+}
+
+%%
diff --git a/i3-config-wizard/cfgparse.y b/i3-config-wizard/cfgparse.y
new file mode 100644 (file)
index 0000000..018b37b
--- /dev/null
@@ -0,0 +1,161 @@
+%{
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <X11/Xlib.h>
+
+extern Display *dpy;
+
+struct context {
+        int line_number;
+        char *line_copy;
+
+        char *compact_error;
+
+        /* These are the same as in YYLTYPE */
+        int first_column;
+        int last_column;
+
+        char *result;
+};
+
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+extern int yylex(struct context *context);
+extern int yyparse(void);
+extern FILE *yyin;
+YY_BUFFER_STATE yy_scan_string(const char *);
+
+static struct context *context;
+
+/* We don’t need yydebug for now, as we got decent error messages using
+ * yyerror(). Should you ever want to extend the parser, it might be handy
+ * to just comment it in again, so it stays here. */
+//int yydebug = 1;
+
+void yyerror(const char *error_message) {
+    fprintf(stderr, "\n");
+    fprintf(stderr, "CONFIG: %s\n", error_message);
+    fprintf(stderr, "CONFIG: line %d:\n",
+        context->line_number);
+    fprintf(stderr, "CONFIG:   %s\n", context->line_copy);
+    fprintf(stderr, "CONFIG:   ");
+    for (int c = 1; c <= context->last_column; c++)
+        if (c >= context->first_column)
+            fprintf(stderr, "^");
+        else fprintf(stderr, " ");
+    fprintf(stderr, "\n");
+    fprintf(stderr, "\n");
+}
+
+int yywrap() {
+    return 1;
+}
+
+char *rewrite_binding(const char *bindingline) {
+    char *result = NULL;
+
+    context = calloc(sizeof(struct context), 1);
+
+    yy_scan_string(bindingline);
+
+    if (yyparse() != 0) {
+        fprintf(stderr, "Could not parse configfile\n");
+        exit(1);
+    }
+
+    result = context->result;
+
+    if (context->line_copy)
+        free(context->line_copy);
+    free(context);
+
+    return result;
+}
+
+/* XXX: does not work for combinations of modifiers yet */
+static char *modifier_to_string(int modifiers) {
+    //printf("should convert %d to string\n", modifiers);
+    if (modifiers == (1 << 3))
+        return strdup("$mod+");
+    else if (modifiers == ((1 << 3) | (1 << 0)))
+        return strdup("$mod+Shift+");
+    else if (modifiers == (1 << 9))
+        return strdup("$mod+");
+    else if (modifiers == ((1 << 9) | (1 << 0)))
+        return strdup("$mod+Shift+");
+    else if (modifiers == (1 << 0))
+        return strdup("Shift+");
+    else return strdup("");
+}
+
+%}
+
+%error-verbose
+%lex-param { struct context *context }
+
+%union {
+    int number;
+    char *string;
+}
+
+%token <number>NUMBER "<number>"
+%token <string>STR "<string>"
+%token TOKBINDCODE
+%token TOKMODVAR "$mod"
+%token MODIFIER "<modifier>"
+%token TOKCONTROL "control"
+%token TOKSHIFT "shift"
+%token WHITESPACE "<whitespace>"
+
+%%
+
+lines: /* empty */
+    | lines WHITESPACE bindcode
+    | lines error
+    | lines bindcode
+    ;
+
+bindcode:
+    TOKBINDCODE WHITESPACE binding_modifiers NUMBER WHITESPACE STR
+    {
+        //printf("\tFound keycode binding mod%d with key %d and command %s\n", $<number>3, $4, $<string>6);
+        int level = 0;
+        if (($<number>3 & (1 << 0))) {
+            /* When shift is included, we really need to use the second-level
+             * symbol (upper-case). The lower-case symbol could be on a
+             * different key than the upper-case one (unlikely for letters, but
+             * more likely for special characters). */
+            level = 1;
+        }
+        KeySym sym = XKeycodeToKeysym(dpy, $4, level);
+        char *str = XKeysymToString(sym);
+        char *modifiers = modifier_to_string($<number>3);
+        // TODO: modifier to string
+        asprintf(&(context->result), "bindsym %s%s %s\n", modifiers, str, $<string>6);
+        free(modifiers);
+    }
+    ;
+
+binding_modifiers:
+    /* NULL */                               { $<number>$ = 0; }
+    | binding_modifier
+    | binding_modifiers '+' binding_modifier { $<number>$ = $<number>1 | $<number>3; }
+    | binding_modifiers '+'                  { $<number>$ = $<number>1; }
+    ;
+
+binding_modifier:
+    MODIFIER        { $<number>$ = $<number>1; }
+    | TOKMODVAR     { $<number>$ = $<number>1; }
+    | TOKCONTROL    { $<number>$ = (1 << 2); }
+    | TOKSHIFT      { $<number>$ = (1 << 0); }
+    ;
diff --git a/i3-config-wizard/ipc.c b/i3-config-wizard/ipc.c
new file mode 100644 (file)
index 0000000..597a86e
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * vim:ts=8:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ *
+ * © 2009 Michael Stapelberg and contributors
+ *
+ * See file LICENSE for license information.
+ *
+ */
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <err.h>
+
+/*
+ * Formats a message (payload) of the given size and type and sends it to i3 via
+ * the given socket file descriptor.
+ *
+ */
+void ipc_send_message(int sockfd, uint32_t message_size,
+                      uint32_t message_type, uint8_t *payload) {
+        int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + sizeof(uint32_t) + message_size;
+        char msg[buffer_size];
+        char *walk = msg;
+
+        strcpy(walk, "i3-ipc");
+        walk += strlen("i3-ipc");
+        memcpy(walk, &message_size, sizeof(uint32_t));
+        walk += sizeof(uint32_t);
+        memcpy(walk, &message_type, sizeof(uint32_t));
+        walk += sizeof(uint32_t);
+        memcpy(walk, payload, message_size);
+
+        int sent_bytes = 0;
+        int bytes_to_go = buffer_size;
+        while (sent_bytes < bytes_to_go) {
+                int n = write(sockfd, msg + sent_bytes, bytes_to_go);
+                if (n == -1)
+                        err(EXIT_FAILURE, "write() failed");
+
+                sent_bytes += n;
+                bytes_to_go -= n;
+        }
+}
+
+/*
+ * Connects to the i3 IPC socket and returns the file descriptor for the
+ * socket. die()s if anything goes wrong.
+ *
+ */
+int connect_ipc(char *socket_path) {
+       int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
+        if (sockfd == -1)
+                err(EXIT_FAILURE, "Could not create socket");
+
+        struct sockaddr_un addr;
+        memset(&addr, 0, sizeof(struct sockaddr_un));
+        addr.sun_family = AF_LOCAL;
+        strcpy(addr.sun_path, socket_path);
+        if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
+                err(EXIT_FAILURE, "Could not connect to i3");
+
+       return sockfd;
+}
diff --git a/i3-config-wizard/ipc.h b/i3-config-wizard/ipc.h
new file mode 100644 (file)
index 0000000..c40c909
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef _IPC_H
+#define _IPC_H
+
+void ipc_send_message(int sockfd, uint32_t message_size,
+                      uint32_t message_type, uint8_t *payload);
+
+int connect_ipc(char *socket_path);
+
+#endif
diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c
new file mode 100644 (file)
index 0000000..d3a2672
--- /dev/null
@@ -0,0 +1,546 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ *
+ * © 2011 Michael Stapelberg and contributors
+ *
+ * See file LICENSE for license information.
+ *
+ * i3-config-wizard: Program to convert configs using keycodes to configs using
+ * keysyms.
+ *
+ */
+#include <ev.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <err.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <glob.h>
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_aux.h>
+#include <xcb/xcb_event.h>
+#include <xcb/xcb_keysyms.h>
+
+#include <X11/Xlib.h>
+#include <X11/keysym.h>
+
+/* We need SYSCONFDIR for the path to the keycode config template, so raise an
+ * error if it’s not defined for whatever reason */
+#ifndef SYSCONFDIR
+#error "SYSCONFDIR not defined"
+#endif
+
+#define FREE(pointer) do { \
+    if (pointer != NULL) { \
+        free(pointer); \
+        pointer = NULL; \
+    } \
+} \
+while (0)
+
+#include "xcb.h"
+#include "ipc.h"
+
+enum { STEP_WELCOME, STEP_GENERATE } current_step = STEP_WELCOME;
+enum { MOD_ALT, MOD_SUPER } modifier = MOD_SUPER;
+
+static char *config_path;
+static xcb_connection_t *conn;
+static uint32_t font_id;
+static uint32_t font_bold_id;
+static char *socket_path;
+static int font_height;
+static int font_bold_height;
+static xcb_window_t win;
+static xcb_pixmap_t pixmap;
+static xcb_gcontext_t pixmap_gc;
+static xcb_key_symbols_t *symbols;
+xcb_window_t root;
+Display *dpy;
+
+char *rewrite_binding(const char *bindingline);
+static void finish();
+
+#if defined(__APPLE__)
+
+/*
+ * Taken from FreeBSD
+ * Returns a pointer to a new string which is a duplicate of the
+ * string, but only copies at most n characters.
+ *
+ */
+char *strndup(const char *str, size_t n) {
+    size_t len;
+    char *copy;
+
+    for (len = 0; len < n && str[len]; len++)
+        continue;
+
+    if ((copy = malloc(len + 1)) == NULL)
+        return (NULL);
+    memcpy(copy, str, len);
+    copy[len] = '\0';
+    return (copy);
+}
+
+#endif
+
+/*
+ * This function resolves ~ in pathnames.
+ * It may resolve wildcards in the first part of the path, but if no match
+ * or multiple matches are found, it just returns a copy of path as given.
+ *
+ */
+static char *resolve_tilde(const char *path) {
+    static glob_t globbuf;
+    char *head, *tail, *result;
+
+    tail = strchr(path, '/');
+    head = strndup(path, tail ? tail - path : strlen(path));
+
+    int res = glob(head, GLOB_TILDE, NULL, &globbuf);
+    free(head);
+    /* no match, or many wildcard matches are bad */
+    if (res == GLOB_NOMATCH || globbuf.gl_pathc != 1)
+        result = strdup(path);
+    else if (res != 0) {
+        err(1, "glob() failed");
+    } else {
+        head = globbuf.gl_pathv[0];
+        result = calloc(1, strlen(head) + (tail ? strlen(tail) : 0) + 1);
+        strncpy(result, head, strlen(head));
+        if (tail)
+            strncat(result, tail, strlen(tail));
+    }
+    globfree(&globbuf);
+
+    return result;
+}
+
+/*
+ * Try to get the socket path from X11 and return NULL if it doesn’t work.
+ * As i3-msg is a short-running tool, we don’t bother with cleaning up the
+ * connection and leave it up to the operating system on exit.
+ *
+ */
+static char *socket_path_from_x11() {
+    xcb_connection_t *conn;
+    int screen;
+    if ((conn = xcb_connect(NULL, &screen)) == NULL ||
+        xcb_connection_has_error(conn))
+        return NULL;
+    xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen);
+    xcb_window_t root = root_screen->root;
+
+    xcb_intern_atom_cookie_t atom_cookie;
+    xcb_intern_atom_reply_t *atom_reply;
+
+    atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH");
+    atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL);
+    if (atom_reply == NULL)
+        return NULL;
+
+    xcb_get_property_cookie_t prop_cookie;
+    xcb_get_property_reply_t *prop_reply;
+    prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom,
+                                             XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX);
+    prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL);
+    if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0)
+        return NULL;
+    if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply),
+                 (char*)xcb_get_property_value(prop_reply)) == -1)
+        return NULL;
+    return socket_path;
+}
+
+/*
+ * Handles expose events, that is, draws the window contents.
+ *
+ */
+static int handle_expose() {
+    /* re-draw the background */
+    xcb_rectangle_t border = {0, 0, 300, (15*font_height) + 8};
+    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
+    xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
+
+    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
+
+#define txt(x, row, text) xcb_image_text_8(conn, strlen(text), pixmap, pixmap_gc, x, (row * font_height) + 2, text)
+
+    if (current_step == STEP_WELCOME) {
+        /* restore font color */
+        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
+
+        txt(10, 2, "You have not configured i3 yet.");
+        txt(10, 3, "Do you want me to generate ~/.i3/config?");
+        txt(85, 5, "Yes, generate ~/.i3/config");
+        txt(85, 7, "No, I will use the defaults");
+
+        /* green */
+        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#00FF00"));
+        txt(25, 5, "<Enter>");
+
+        /* red */
+        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000"));
+        txt(31, 7, "<ESC>");
+    }
+
+    if (current_step == STEP_GENERATE) {
+        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
+
+        txt(10, 2, "Please choose either:");
+        txt(85, 4, "Win as default modifier");
+        txt(85, 5, "Alt as default modifier");
+        txt(10, 7, "Afterwards, press");
+        txt(85, 9, "to write ~/.i3/config");
+        txt(85, 10, "to abort");
+
+        /* the not-selected modifier */
+        if (modifier == MOD_SUPER)
+            txt(31, 5, "<Alt>");
+        else txt(31, 4, "<Win>");
+
+        /* the selected modifier */
+        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_bold_id);
+        if (modifier == MOD_SUPER)
+            txt(31, 4, "<Win>");
+        else txt(31, 5, "<Alt>");
+
+        /* green */
+        uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_FONT;
+        uint32_t values[] = { get_colorpixel(conn, "#00FF00"), font_id };
+        xcb_change_gc(conn, pixmap_gc, mask, values);
+
+        txt(25, 9, "<Enter>");
+
+        /* red */
+        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000"));
+        txt(31, 10, "<ESC>");
+    }
+
+    /* Copy the contents of the pixmap to the real window */
+    xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, 500);
+    xcb_flush(conn);
+
+    return 1;
+}
+
+static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
+    printf("Keypress %d, state raw = %d\n", event->detail, event->state);
+
+    /* Remove the numlock bit, all other bits are modifiers we can bind to */
+    uint16_t state_filtered = event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK);
+    /* Only use the lower 8 bits of the state (modifier masks) so that mouse
+     * button masks are filtered out */
+    state_filtered &= 0xFF;
+
+    xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, state_filtered);
+
+    printf("sym = %c (%d)\n", sym, sym);
+
+    if (sym == XK_Return || sym == XK_KP_Enter) {
+        if (current_step == STEP_WELCOME) {
+            current_step = STEP_GENERATE;
+            /* Set window title */
+            xcb_change_property(conn,
+                XCB_PROP_MODE_REPLACE,
+                win,
+                A__NET_WM_NAME,
+                A_UTF8_STRING,
+                8,
+                strlen("i3: generate config"),
+                "i3: generate config");
+            xcb_flush(conn);
+        }
+        else finish();
+    }
+
+    /* cancel any time */
+    if (sym == XK_Escape)
+        exit(0);
+
+    if (sym == XK_Alt_L)
+        modifier = MOD_ALT;
+
+    if (sym == XK_Super_L)
+        modifier = MOD_SUPER;
+
+    handle_expose();
+    return 1;
+}
+
+/*
+ * Creates the config file and tells i3 to reload.
+ *
+ */
+static void finish() {
+    printf("creating \"%s\"...\n", config_path);
+
+    if (!(dpy = XOpenDisplay(NULL)))
+        errx(1, "Could not connect to X11");
+
+    FILE *kc_config = fopen(SYSCONFDIR "/i3/config.keycodes", "r");
+    if (kc_config == NULL)
+        err(1, "Could not open input file \"%s\"", SYSCONFDIR "/i3/config.keycodes");
+
+    FILE *ks_config = fopen(config_path, "w");
+    if (ks_config == NULL)
+        err(1, "Could not open output config file \"%s\"", config_path);
+
+    char *line = NULL;
+    size_t len = 0;
+#if !defined(__APPLE__)
+    ssize_t read;
+#endif
+    bool head_of_file = true;
+
+    /* write a header about auto-generation to the output file */
+    fputs("# This file has been auto-generated by i3-config-wizard(1).\n", ks_config);
+    fputs("# It will not be overwritten, so edit it as you like.\n", ks_config);
+    fputs("#\n", ks_config);
+    fputs("# Should you change your keyboard layout somewhen, delete\n", ks_config);
+    fputs("# this file and re-run i3-config-wizard(1).\n", ks_config);
+    fputs("#\n", ks_config);
+
+#if defined(__APPLE__)
+    while ((line = fgetln(kc_config, &len)) != NULL) {
+#else
+    while ((read = getline(&line, &len, kc_config)) != -1) {
+#endif
+        /* skip the warning block at the beginning of the input file */
+        if (head_of_file &&
+            strncmp("# WARNING", line, strlen("# WARNING")) == 0)
+            continue;
+
+        head_of_file = false;
+
+        /* Skip leading whitespace */
+        char *walk = line;
+        while (isspace(*walk) && walk < (line + len))
+            walk++;
+
+        /* Set the modifier the user chose */
+        if (strncmp(walk, "set $mod ", strlen("set $mod ")) == 0) {
+            if (modifier == MOD_ALT)
+                fputs("set $mod Mod1\n", ks_config);
+            else fputs("set $mod Mod4\n", ks_config);
+            continue;
+        }
+
+        /* Check for 'bindcode'. If it’s not a bindcode line, we
+         * just copy it to the output file */
+        if (strncmp(walk, "bindcode", strlen("bindcode")) != 0) {
+            fputs(line, ks_config);
+            continue;
+        }
+        char *result = rewrite_binding(walk);
+        fputs(result, ks_config);
+        free(result);
+    }
+
+    /* sync to do our best in order to have the file really stored on disk */
+    fflush(ks_config);
+    fsync(fileno(ks_config));
+
+    free(line);
+    fclose(kc_config);
+    fclose(ks_config);
+
+    /* tell i3 to reload the config file */
+    int sockfd = connect_ipc(socket_path);
+    ipc_send_message(sockfd, strlen("reload"), 0, (uint8_t*)"reload");
+    close(sockfd);
+
+    exit(0);
+}
+
+int main(int argc, char *argv[]) {
+    config_path = resolve_tilde("~/.i3/config");
+    socket_path = getenv("I3SOCK");
+    char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
+    char *patternbold = "-misc-fixed-bold-r-normal--13-120-75-75-C-70-iso10646-1";
+    int o, option_index = 0;
+
+    static struct option long_options[] = {
+        {"socket", required_argument, 0, 's'},
+        {"version", no_argument, 0, 'v'},
+        {"limit", required_argument, 0, 'l'},
+        {"prompt", required_argument, 0, 'P'},
+        {"prefix", required_argument, 0, 'p'},
+        {"font", required_argument, 0, 'f'},
+        {"help", no_argument, 0, 'h'},
+        {0, 0, 0, 0}
+    };
+
+    char *options_string = "s:vh";
+
+    while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
+        switch (o) {
+            case 's':
+                FREE(socket_path);
+                socket_path = strdup(optarg);
+                break;
+            case 'v':
+                printf("i3-config-wizard " I3_VERSION "\n");
+                return 0;
+            case 'h':
+                printf("i3-config-wizard " I3_VERSION "\n");
+                printf("i3-config-wizard [-s <socket>] [-v]\n");
+                return 0;
+        }
+    }
+
+    /* Check if the destination config file does not exist but the path is
+     * writable. If not, exit now, this program is not useful in that case. */
+    struct stat stbuf;
+    if (stat(config_path, &stbuf) == 0) {
+        printf("The config file \"%s\" already exists. Exiting.\n", config_path);
+        return 0;
+    }
+
+    /* Create ~/.i3 if it does not yet exist */
+    char *config_dir = resolve_tilde("~/.i3");
+    if (stat(config_dir, &stbuf) != 0)
+        if (mkdir(config_dir, 0755) == -1)
+            err(1, "mkdir(%s) failed", config_dir);
+    free(config_dir);
+
+    int fd;
+    if ((fd = open(config_path, O_CREAT | O_RDWR, 0644)) == -1) {
+        printf("Cannot open file \"%s\" for writing: %s. Exiting.\n", config_path, strerror(errno));
+        return 0;
+    }
+    close(fd);
+    unlink(config_path);
+
+    if (socket_path == NULL)
+        socket_path = socket_path_from_x11();
+
+    if (socket_path == NULL)
+        socket_path = "/tmp/i3-ipc.sock";
+
+    int screens;
+    if ((conn = xcb_connect(NULL, &screens)) == NULL ||
+        xcb_connection_has_error(conn))
+        errx(1, "Cannot open display\n");
+
+    /* Place requests for the atoms we need as soon as possible */
+    #define xmacro(atom) \
+        xcb_intern_atom_cookie_t atom ## _cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom);
+    #include "atoms.xmacro"
+    #undef xmacro
+
+    xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
+    root = root_screen->root;
+
+    xcb_get_numlock_mask(conn);
+
+    symbols = xcb_key_symbols_alloc(conn);
+
+    font_id = get_font_id(conn, pattern, &font_height);
+    font_bold_id = get_font_id(conn, patternbold, &font_bold_height);
+
+    /* Open an input window */
+    win = open_input_window(conn, 300, 205);
+
+    /* Setup NetWM atoms */
+    #define xmacro(name) \
+        do { \
+            xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, name ## _cookie, NULL); \
+            if (!reply) \
+                errx(EXIT_FAILURE, "Could not get atom " # name "\n"); \
+            \
+            A_ ## name = reply->atom; \
+            free(reply); \
+        } while (0);
+    #include "atoms.xmacro"
+    #undef xmacro
+
+    /* Set dock mode */
+    xcb_change_property(conn,
+        XCB_PROP_MODE_REPLACE,
+        win,
+        A__NET_WM_WINDOW_TYPE,
+        A_ATOM,
+        32,
+        1,
+        (unsigned char*) &A__NET_WM_WINDOW_TYPE_DIALOG);
+
+    /* Set window title */
+    xcb_change_property(conn,
+        XCB_PROP_MODE_REPLACE,
+        win,
+        A__NET_WM_NAME,
+        A_UTF8_STRING,
+        8,
+        strlen("i3: first configuration"),
+        "i3: first configuration");
+
+    /* Create pixmap */
+    pixmap = xcb_generate_id(conn);
+    pixmap_gc = xcb_generate_id(conn);
+    xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, 500);
+    xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
+
+    /* Grab the keyboard to get all input */
+    xcb_flush(conn);
+
+    /* Try (repeatedly, if necessary) to grab the keyboard. We might not
+     * get the keyboard at the first attempt because of the keybinding
+     * still being active when started via a wm’s keybinding. */
+    xcb_grab_keyboard_cookie_t cookie;
+    xcb_grab_keyboard_reply_t *reply = NULL;
+
+    int count = 0;
+    while ((reply == NULL || reply->status != XCB_GRAB_STATUS_SUCCESS) && (count++ < 500)) {
+        cookie = xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
+        reply = xcb_grab_keyboard_reply(conn, cookie, NULL);
+        usleep(1000);
+    }
+
+    if (reply->status != XCB_GRAB_STATUS_SUCCESS) {
+        fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status);
+        exit(-1);
+    }
+
+    xcb_flush(conn);
+
+    xcb_generic_event_t *event;
+    while ((event = xcb_wait_for_event(conn)) != NULL) {
+        if (event->response_type == 0) {
+            fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence);
+            continue;
+        }
+
+        /* Strip off the highest bit (set if the event is generated) */
+        int type = (event->response_type & 0x7F);
+
+        switch (type) {
+            case XCB_KEY_PRESS:
+                handle_key_press(NULL, conn, (xcb_key_press_event_t*)event);
+                break;
+
+            /* TODO: handle mappingnotify */
+
+            case XCB_EXPOSE:
+                handle_expose();
+                break;
+        }
+
+        free(event);
+    }
+
+    return 0;
+}
diff --git a/i3-config-wizard/xcb.c b/i3-config-wizard/xcb.c
new file mode 100644 (file)
index 0000000..d1753d1
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * vim:ts=8:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ *
+ * © 2009 Michael Stapelberg and contributors
+ *
+ * See file LICENSE for license information.
+ *
+ */
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <err.h>
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_keysyms.h>
+
+#include <X11/keysym.h>
+
+#include "xcb.h"
+
+extern xcb_window_t root;
+unsigned int xcb_numlock_mask;
+
+/*
+ * Convenience-wrapper around xcb_change_gc which saves us declaring a variable
+ *
+ */
+void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value) {
+        xcb_change_gc(conn, gc, mask, &value);
+}
+
+/*
+ * Returns the colorpixel to use for the given hex color (think of HTML).
+ *
+ * The hex_color has to start with #, for example #FF00FF.
+ *
+ * NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
+ * This has to be done by the caller.
+ *
+ */
+uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
+        char strgroups[3][3] = {{hex[1], hex[2], '\0'},
+                                {hex[3], hex[4], '\0'},
+                                {hex[5], hex[6], '\0'}};
+        uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
+                             (strtol(strgroups[1], NULL, 16)),
+                             (strtol(strgroups[2], NULL, 16))};
+
+        return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
+}
+
+/*
+ * Returns the mask for Mode_switch (to be used for looking up keysymbols by
+ * keycode).
+ *
+ */
+uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode) {
+       xcb_key_symbols_t *symbols = xcb_key_symbols_alloc(conn);
+
+       xcb_get_modifier_mapping_reply_t *modmap_r;
+       xcb_keycode_t *modmap, kc;
+       xcb_keycode_t *modeswitchcodes = xcb_key_symbols_get_keycode(symbols, keycode);
+       if (modeswitchcodes == NULL)
+               return 0;
+
+       modmap_r = xcb_get_modifier_mapping_reply(conn, xcb_get_modifier_mapping(conn), NULL);
+       modmap = xcb_get_modifier_mapping_keycodes(modmap_r);
+
+       for (int i = 0; i < 8; i++)
+               for (int j = 0; j < modmap_r->keycodes_per_modifier; j++) {
+                       kc = modmap[i * modmap_r->keycodes_per_modifier + j];
+                       for (xcb_keycode_t *ktest = modeswitchcodes; *ktest; ktest++) {
+                               if (*ktest != kc)
+                                       continue;
+
+                               free(modeswitchcodes);
+                               free(modmap_r);
+                               return (1 << i);
+                       }
+               }
+
+       return 0;
+}
+
+/*
+ * Opens the window we use for input/output and maps it
+ *
+ */
+xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height) {
+        xcb_window_t win = xcb_generate_id(conn);
+        //xcb_cursor_t cursor_id = xcb_generate_id(conn);
+
+#if 0
+        /* Use the default cursor (left pointer) */
+        if (cursor > -1) {
+                i3Font *cursor_font = load_font(conn, "cursor");
+                xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id,
+                                XCB_CURSOR_LEFT_PTR, XCB_CURSOR_LEFT_PTR + 1,
+                                0, 0, 0, 65535, 65535, 65535);
+        }
+#endif
+
+        uint32_t mask = 0;
+        uint32_t values[3];
+
+        mask |= XCB_CW_BACK_PIXEL;
+        values[0] = 0;
+
+       mask |= XCB_CW_EVENT_MASK;
+       values[1] = XCB_EVENT_MASK_EXPOSURE;
+
+        xcb_create_window(conn,
+                          XCB_COPY_FROM_PARENT,
+                          win, /* the window id */
+                          root, /* parent == root */
+                          490, 297, width, height, /* dimensions */
+                          0, /* border = 0, we draw our own */
+                          XCB_WINDOW_CLASS_INPUT_OUTPUT,
+                          XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
+                          mask,
+                          values);
+
+#if 0
+        if (cursor > -1)
+                xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
+#endif
+
+        /* Map the window (= make it visible) */
+        xcb_map_window(conn, win);
+
+       return win;
+}
+
+/*
+ * Returns the ID of the font matching the given pattern and stores the height
+ * of the font (in pixels) in *font_height. die()s if no font matches.
+ *
+ */
+int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height) {
+        xcb_void_cookie_t font_cookie;
+        xcb_list_fonts_with_info_cookie_t info_cookie;
+
+        /* Send all our requests first */
+        int result;
+        result = xcb_generate_id(conn);
+        font_cookie = xcb_open_font_checked(conn, result, strlen(pattern), pattern);
+        info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
+
+        xcb_generic_error_t *error = xcb_request_check(conn, font_cookie);
+        if (error != NULL) {
+                fprintf(stderr, "ERROR: Could not open font: %d\n", error->error_code);
+                exit(1);
+        }
+
+        /* Get information (height/name) for this font */
+        xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL);
+        if (reply == NULL)
+                errx(1, "Could not load font \"%s\"\n", pattern);
+
+        *font_height = reply->font_ascent + reply->font_descent;
+
+        return result;
+}
+
+/*
+ * Finds out which modifier mask is the one for numlock, as the user may change this.
+ *
+ */
+void xcb_get_numlock_mask(xcb_connection_t *conn) {
+    xcb_key_symbols_t *keysyms;
+    xcb_get_modifier_mapping_cookie_t cookie;
+    xcb_get_modifier_mapping_reply_t *reply;
+    xcb_keycode_t *modmap;
+    int mask, i;
+    const int masks[8] = { XCB_MOD_MASK_SHIFT,
+                           XCB_MOD_MASK_LOCK,
+                           XCB_MOD_MASK_CONTROL,
+                           XCB_MOD_MASK_1,
+                           XCB_MOD_MASK_2,
+                           XCB_MOD_MASK_3,
+                           XCB_MOD_MASK_4,
+                           XCB_MOD_MASK_5 };
+
+    /* Request the modifier map */
+    cookie = xcb_get_modifier_mapping_unchecked(conn);
+
+    /* Get the keysymbols */
+    keysyms = xcb_key_symbols_alloc(conn);
+
+    if ((reply = xcb_get_modifier_mapping_reply(conn, cookie, NULL)) == NULL) {
+        xcb_key_symbols_free(keysyms);
+        return;
+    }
+
+    modmap = xcb_get_modifier_mapping_keycodes(reply);
+
+    /* Get the keycode for numlock */
+#ifdef OLD_XCB_KEYSYMS_API
+    xcb_keycode_t numlock = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
+#else
+    /* For now, we only use the first keysymbol. */
+    xcb_keycode_t *numlock_syms = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
+    if (numlock_syms == NULL)
+        return;
+    xcb_keycode_t numlock = *numlock_syms;
+    free(numlock_syms);
+#endif
+
+    /* Check all modifiers (Mod1-Mod5, Shift, Control, Lock) */
+    for (mask = 0; mask < 8; mask++)
+        for (i = 0; i < reply->keycodes_per_modifier; i++)
+            if (modmap[(mask * reply->keycodes_per_modifier) + i] == numlock)
+                xcb_numlock_mask = masks[mask];
+
+    xcb_key_symbols_free(keysyms);
+    free(reply);
+}
+
diff --git a/i3-config-wizard/xcb.h b/i3-config-wizard/xcb.h
new file mode 100644 (file)
index 0000000..40aeec8
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef _XCB_H
+#define _XCB_H
+
+/* from X11/keysymdef.h */
+#define XCB_NUM_LOCK                    0xff7f
+
+#define xmacro(atom) xcb_atom_t A_ ## atom;
+#include "atoms.xmacro"
+#undef xmacro
+
+extern unsigned int xcb_numlock_mask;
+
+void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value);
+uint32_t get_colorpixel(xcb_connection_t *conn, char *hex);
+uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode);
+xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height);
+int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height);
+/**
+ * Finds out which modifier mask is the one for numlock, as the user may change this.
+ *
+ */
+void xcb_get_numlock_mask(xcb_connection_t *conn);
+
+#endif
index 74f3f8da7be77b40e03ca5e7f58d48b2d0b19efd..45653dadbc44884e61355f6dd66b91009ce07094 100644 (file)
@@ -10,11 +10,13 @@ HEADERS=$(wildcard *.h)
 # Depend on the specific file (.c for each .o) and on all headers
 %.o: %.c ${HEADERS}
        echo "CC $<"
-       $(CC) $(CFLAGS) -c -o $@ $<
+       $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
 
-all: ${FILES}
+all: i3-input
+
+i3-input: ${FILES}
        echo "LINK i3-input"
-       $(CC) -o i3-input ${FILES} $(LDFLAGS)
+       $(CC) $(LDFLAGS) -o $@ ${FILES} $(LIBS)
 
 install: all
        echo "INSTALL"
index 8d8b467f874af0faab5d6fd532494c0717bd3e88..581203d4b19682683954200142eb42852de5cd7b 100644 (file)
@@ -4,6 +4,15 @@
 #include <err.h>
 
 #define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
+#define FREE(pointer) do { \
+        if (pointer != NULL) { \
+                free(pointer); \
+                pointer = NULL; \
+        } \
+} \
+while (0)
+
+extern xcb_window_t root;
 
 char *convert_ucs_to_utf8(char *input);
 char *convert_utf8_to_ucs2(char *input, int *real_strlen);
index 581386d208dd0c9c8f90ec4be5d3a6a6a91beac4..fb2635a274024e1f41199bd3e71dd8a164fd9bb0 100644 (file)
@@ -3,7 +3,7 @@
  *
  * i3 - an improved dynamic tiling window manager
  *
- * © 2009-2010 Michael Stapelberg and contributors
+ * © 2009 Michael Stapelberg and contributors
  *
  * See file LICENSE for license information.
  *
@@ -22,7 +22,7 @@
 #include <err.h>
 #include <stdint.h>
 #include <getopt.h>
-#include <glob.h>
+#include <limits.h>
 
 #include <xcb/xcb.h>
 #include <xcb/xcb_aux.h>
@@ -35,6 +35,7 @@
 
 #include "i3-input.h"
 
+static char *socket_path;
 static int sockfd;
 static xcb_key_symbols_t *symbols;
 static int modeswitchmask;
@@ -51,20 +52,42 @@ static char *command_prefix;
 static char *prompt;
 static int prompt_len;
 static int limit;
+xcb_window_t root;
 
 /*
- * This function resolves ~ in pathnames (and more, see glob(3)).
+ * Try to get the socket path from X11 and return NULL if it doesn’t work.
+ * As i3-msg is a short-running tool, we don’t bother with cleaning up the
+ * connection and leave it up to the operating system on exit.
  *
  */
-static char *glob_path(const char *path) {
-        static glob_t globbuf;
-        if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0)
-                errx(EXIT_FAILURE, "glob() failed");
-        char *result = strdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
-        if (result == NULL)
-                err(EXIT_FAILURE, "malloc() failed");
-        globfree(&globbuf);
-        return result;
+static char *socket_path_from_x11() {
+        xcb_connection_t *conn;
+        int screen;
+        if ((conn = xcb_connect(NULL, &screen)) == NULL ||
+            xcb_connection_has_error(conn))
+                return NULL;
+        xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen);
+        xcb_window_t root = root_screen->root;
+
+        xcb_intern_atom_cookie_t atom_cookie;
+        xcb_intern_atom_reply_t *atom_reply;
+
+        atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH");
+        atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL);
+        if (atom_reply == NULL)
+                return NULL;
+
+        xcb_get_property_cookie_t prop_cookie;
+        xcb_get_property_reply_t *prop_reply;
+        prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom,
+                                                 XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX);
+        prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL);
+        if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0)
+                return NULL;
+        if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply),
+                     (char*)xcb_get_property_value(prop_reply)) == -1)
+                return NULL;
+        return socket_path;
 }
 
 /*
@@ -257,7 +280,7 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
 }
 
 int main(int argc, char *argv[]) {
-        char *socket_path = glob_path("~/.i3/ipc.sock");
+        socket_path = getenv("I3SOCK");
         char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
         int o, option_index = 0;
 
@@ -277,21 +300,25 @@ int main(int argc, char *argv[]) {
         while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
                 switch (o) {
                         case 's':
-                                socket_path = glob_path(optarg);
+                                FREE(socket_path);
+                                socket_path = strdup(optarg);
                                 break;
                         case 'v':
                                 printf("i3-input " I3_VERSION);
                                 return 0;
                         case 'p':
+                                FREE(command_prefix);
                                 command_prefix = strdup(optarg);
                                 break;
                         case 'l':
                                 limit = atoi(optarg);
                                 break;
                         case 'P':
+                                FREE(prompt);
                                 prompt = strdup(optarg);
                                 break;
                         case 'f':
+                                FREE(pattern);
                                 pattern = strdup(optarg);
                                 break;
                         case 'h':
@@ -301,6 +328,12 @@ int main(int argc, char *argv[]) {
                 }
         }
 
+        if (socket_path == NULL)
+                socket_path = socket_path_from_x11();
+
+        if (socket_path == NULL)
+                socket_path = "/tmp/i3-ipc.sock";
+
         sockfd = connect_ipc(socket_path);
 
         if (prompt != NULL)
@@ -311,13 +344,8 @@ int main(int argc, char *argv[]) {
         if (xcb_connection_has_error(conn))
                 die("Cannot open display\n");
 
-        /* Set up event handlers for key press and key release */
-        xcb_event_handlers_t evenths;
-        memset(&evenths, 0, sizeof(xcb_event_handlers_t));
-        xcb_event_handlers_init(conn, &evenths);
-        xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL);
-        xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL);
-        xcb_event_set_expose_handler(&evenths, handle_expose, NULL);
+        xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
+        root = root_screen->root;
 
         modeswitchmask = get_mod_mask(conn, XK_Mode_switch);
         numlockmask = get_mod_mask(conn, XK_Num_Lock);
@@ -329,8 +357,6 @@ int main(int argc, char *argv[]) {
         win = open_input_window(conn, 500, font_height + 8);
 
         /* Create pixmap */
-        xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
-
         pixmap = xcb_generate_id(conn);
         pixmap_gc = xcb_generate_id(conn);
         xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font_height + 8);
@@ -366,7 +392,32 @@ int main(int argc, char *argv[]) {
 
         xcb_flush(conn);
 
-        xcb_event_wait_for_event_loop(&evenths);
+        xcb_generic_event_t *event;
+        while ((event = xcb_wait_for_event(conn)) != NULL) {
+                if (event->response_type == 0) {
+                        fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence);
+                        continue;
+                }
+
+                /* Strip off the highest bit (set if the event is generated) */
+                int type = (event->response_type & 0x7F);
+
+                switch (type) {
+                        case XCB_KEY_PRESS:
+                                handle_key_press(NULL, conn, (xcb_key_press_event_t*)event);
+                                break;
+
+                        case XCB_KEY_RELEASE:
+                                handle_key_release(NULL, conn, (xcb_key_release_event_t*)event);
+                                break;
+
+                        case XCB_EXPOSE:
+                                handle_expose(NULL, conn, (xcb_expose_event_t*)event);
+                                break;
+                }
+
+                free(event);
+        }
 
         return 0;
 }
index 661d486370a8c2de63259b98e002a9cc5a1a10d3..3c1d99e1c21f758b4811c3f9b7fcea040fdc3eaf 100644 (file)
@@ -86,7 +86,6 @@ uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode) {
  *
  */
 xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height) {
-        xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
         xcb_window_t win = xcb_generate_id(conn);
         //xcb_cursor_t cursor_id = xcb_generate_id(conn);
 
diff --git a/i3-migrate-config-to-v4.pl b/i3-migrate-config-to-v4.pl
new file mode 100755 (executable)
index 0000000..5f20cba
--- /dev/null
@@ -0,0 +1,358 @@
+#!/usr/bin/env perl
+# vim:ts=4:sw=4:expandtab
+#
+# script to migrate an old config file (i3 < 4.0) to the new format (>= 4.0)
+# this script only uses modules which come with perl 5.10
+#
+# reads an i3 v3 config from stdin and spits out a v4 config on stdout
+# exit codes:
+#     0 = the input was a v3 config
+#     1 = the input was already a v4 config
+#         (the config is printed to stdout nevertheless)
+#
+# © 2011 Michael Stapelberg and contributors, see LICENSE
+
+use strict;
+use warnings;
+use Getopt::Long;
+use v5.10;
+
+# is this a version 3 config file? disables auto-detection
+my $v3 = 0;
+my $result = GetOptions('v3' => \$v3);
+
+# reads stdin
+sub slurp {
+    local $/;
+    <>;
+}
+
+my @unchanged = qw(
+    font
+    set
+    mode
+    exec
+    assign
+    floating_modifier
+    focus_follows_mouse
+    ipc-socket
+    ipc_socket
+    client.focused
+    client.focused_inactive
+    client.unfocused
+    client.urgent
+    client.background
+);
+
+my %workspace_names;
+my $workspace_bar = 1;
+
+my $input = slurp();
+my @lines = split /\n/, $input;
+
+# remove whitespaces in the beginning of lines
+@lines = map { s/^[ \t]*//g; $_ } @lines;
+
+# Try to auto-detect if this is a v3 config file.
+sub need_to_convert {
+    # If the user passed --v3, we need to convert in any case
+    return 1 if $v3;
+
+    for my $line (@lines) {
+        # only v4 configfiles can use bindcode or workspace_layout
+        return 0 if $line =~ /^bindcode/ ||
+                    $line =~ /^workspace_layout/ ||
+                    $line =~ /^force_focus_wrapping/;
+
+        # have a look at bindings
+        next unless $line =~ /^bind/;
+
+        my ($statement, $key, $command) = ($line =~ /([a-zA-Z_-]+)[ \t]+([^ \t]+)[ \t]+(.*)/);
+        return 0 if $command =~ /^layout/ ||
+                    $command =~ /^floating/ ||
+                    $command =~ /^workspace/ ||
+                    $command =~ /^focus (left|right|up|down)/ ||
+                    $command =~ /^border (normal|1pixel|none)/;
+    }
+
+    return 1;
+}
+
+if (!need_to_convert()) {
+    # If this is already a v4 config file, we will spit out the lines
+    # and exit with return code 1
+    print $input;
+    exit 1;
+}
+
+# first pass: get workspace names
+for my $line (@lines) {
+    next if $line =~ /^#/ || $line =~ /^$/;
+
+    my ($statement, $parameters) = ($line =~ /([a-zA-Z._-]+)(.*)/);
+
+    # skip everything but workspace lines
+    next unless defined($statement) and $statement eq 'workspace';
+
+    my ($number, $params) = ($parameters =~ /[ \t]+([0-9]+) (.+)/);
+
+    # save workspace name (unless the line is actually a workspace assignment)
+    $workspace_names{$number} = $params unless $params =~ /^output/;
+}
+
+for my $line (@lines) {
+    # directly use comments and empty lines
+    if ($line =~ /^#/ || $line =~ /^$/) {
+        print "$line\n";
+        next;
+    }
+
+    my ($statement, $parameters) = ($line =~ /([a-zA-Z._-]+)(.*)/);
+
+    # directly use lines which have not changed between 3.x and 4.x
+    if (!defined($statement) || (lc $statement ~~ @unchanged)) {
+        print "$line\n";
+        next;
+    }
+
+    # new_container changed only the statement name to workspace_layout
+    if ($statement eq 'new_container') {
+        # TODO: new_container stack-limit
+        print "workspace_layout$parameters\n";
+        next;
+    }
+
+    # workspace_bar is gone, you should use i3bar now
+    if ($statement eq 'workspace_bar') {
+        $workspace_bar = ($parameters =~ /[ \t+](yes|true|on|enable|active)/);
+        print "# XXX: REMOVED workspace_bar line. There is no internal workspace bar in v4.\n";
+        next;
+    }
+
+    # new_window changed the parameters from bb to none etc.
+    if ($statement eq 'new_window') {
+        if ($parameters =~ /bb/) {
+            print "new_window none\n";
+        } elsif ($parameters =~ /bp/) {
+            print "new_window 1pixel\n";
+        } elsif ($parameters =~ /bn/) {
+            print "new_window normal\n";
+        } else {
+            print "# XXX: Invalid parameter for new_window, not touching line:\n";
+            print "$line\n";
+        }
+        next;
+    }
+
+    # bar colors are obsolete, need to be configured in i3bar
+    if ($statement =~ /^bar\./) {
+        print "# XXX: REMOVED $statement, configure i3bar instead.\n";
+        print "# Old line: $line\n";
+        next;
+    }
+
+    # one form of this is still ok (workspace assignments), the other (named workspaces) isn’t
+    if ($statement eq 'workspace') {
+        my ($number, $params) = ($parameters =~ /[ \t]+([0-9]+) (.+)/);
+        if ($params =~ /^output/) {
+            print "$line\n";
+            next;
+        } else {
+            print "# XXX: workspace name will end up in the corresponding bindings.\n";
+            next;
+        }
+    }
+
+    if ($statement eq 'bind' || $statement eq 'bindsym') {
+        convert_command($line);
+        next;
+    }
+
+    print "# XXX: migration script could not handle line: $line\n";
+}
+
+#
+# Converts a command (after bind/bindsym)
+#
+sub convert_command {
+    my ($line) = @_;
+
+    my @unchanged_cmds = qw(
+        exec
+        mark
+        kill
+        restart
+        reload
+        exit
+        stack-limit
+    );
+
+    my ($statement, $key, $command) = ($line =~ /([a-zA-Z_-]+)[ \t]+([^ \t]+)[ \t]+(.*)/);
+
+    # turn 'bind' to 'bindcode'
+    $statement = 'bindcode' if $statement eq 'bind';
+
+    # check if it’s one of the unchanged commands
+    my ($cmd) = ($command =~ /([a-zA-Z_-]+)/);
+    if ($cmd ~~ @unchanged_cmds) {
+        print "$statement $key $command\n";
+        return;
+    }
+
+    # simple replacements
+    my @replace = (
+        qr/^s/ => 'layout stacking',
+        qr/^d/ => 'layout default',
+        qr/^T/ => 'layout tabbed',
+        qr/^f($|[^go])/ => 'fullscreen',
+        qr/^fg/ => 'fullscreen global',
+        qr/^t/ => 'floating toggle',
+        qr/^h/ => 'focus left',
+        qr/^j($|[^u])/ => 'focus down',
+        qr/^k/ => 'focus up',
+        qr/^l/ => 'focus right',
+        qr/^mh/ => 'move left',
+        qr/^mj/ => 'move down',
+        qr/^mk/ => 'move up',
+        qr/^ml/ => 'move right',
+        qr/^bn/ => 'border normal',
+        qr/^bp/ => 'border 1pixel',
+        qr/^bb/ => 'border none',
+        qr/^bt/ => 'border toggle',
+        qr/^pw/ => 'workspace prev',
+        qr/^nw/ => 'workspace next',
+    );
+
+    my $replaced = 0;
+    for (my $c = 0; $c < @replace; $c += 2) {
+        if ($command =~ $replace[$c]) {
+            $command = $replace[$c+1];
+            $replaced = 1;
+            last;
+        }
+    }
+
+    # goto command is now obsolete due to criteria + focus command
+    if ($command =~ /^goto/) {
+        my ($mark) = ($command =~ /^goto (.*)/);
+        print qq|$statement $key [con_mark="$mark"] focus\n|;
+        return;
+    }
+
+    # the jump command is also obsolete due to criteria + focus
+    if ($command =~ /^jump/) {
+        my ($params) = ($command =~ /^jump (.*)/);
+        if ($params =~ /^"/) {
+            # jump ["]window class[/window title]["]
+            ($params) = ($params =~ /^"([^"]+)"/);
+
+            # check if a window title was specified
+            if ($params =~ m,/,) {
+                my ($class, $title) = ($params =~ m,([^/]+)/(.+),);
+                print qq|$statement $key [class="$class" title="$title"] focus\n|;
+            } else {
+                print qq|$statement $key [class="$params"] focus\n|;
+            }
+            return;
+        } else {
+            # jump <workspace> [ column row ]
+            print "# XXX: jump workspace is obsolete in 4.x: $line\n";
+            return;
+        }
+    }
+
+    if (!$replaced && $command =~ /^focus/) {
+        my ($what) = ($command =~ /^focus (.*)/);
+        $what =~ s/[ \t]//g;
+        if ($what eq 'ft') {
+            $what = 'mode_toggle';
+        } elsif ($what eq 'floating' || $what eq 'tiling') {
+            # those are unchanged
+        } else {
+            print "# XXX: focus <number> is obsolete in 4.x: $line\n";
+            return;
+        }
+        print qq|$statement $key focus $what\n|;
+        return;
+    }
+
+    if ($command =~ /^mode/) {
+        my ($parameters) = ($command =~ /^mode (.*)/);
+        print qq|$statement $key mode "$parameters"\n|;
+        return;
+    }
+
+    # the parameters of the resize command have changed
+    if ($command =~ /^resize/) {
+        # OLD: resize <left|right|top|bottom> [+|-]<pixels>\n")
+        # NEW: resize <grow|shrink> <direction> [<px> px] [or <ppt> ppt]
+        my ($direction, $sign, $px) = ($command =~ /^resize (left|right|top|bottom) ([+-])([0-9]+)/);
+        my $cmd = 'resize ';
+        $cmd .= ($sign eq '+' ? 'grow' : 'shrink') . ' ';
+        $cmd .= "$direction ";
+        $cmd .= "$px px";
+        print qq|$statement $key $cmd\n|;
+        return;
+    }
+
+    # switch workspace
+    if ($command =~ /^[0-9]+/) {
+        my ($number) = ($command =~ /^([0-9]+)/);
+        if (exists $workspace_names{$number}) {
+            print qq|$statement $key workspace $workspace_names{$number}\n|;
+            return;
+        } else {
+            print qq|$statement $key workspace $number\n|;
+            return;
+        }
+    }
+
+    # move to workspace
+    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|;
+            return;
+        } else {
+            print qq|$statement $key move workspace $number\n|;
+            return;
+        }
+    }
+
+    # With Container-commands are now obsolete due to different abstraction
+    if ($command =~ /^wc/) {
+        $command =~ s/^wc//g;
+        my $wc_replaced = 0;
+        for (my $c = 0; $c < @replace; $c += 2) {
+            if ($command =~ $replace[$c]) {
+                $command = $replace[$c+1];
+                $wc_replaced = 1;
+                last;
+            }
+        }
+        if (!$wc_replaced) {
+            print "# XXX: migration script could not handle command: $line\n";
+        } else {
+            # NOTE: This is not 100% accurate, as it only works for one level
+            # of nested containers. As this is a common use case, we use 'focus
+            # parent; $command' nevertheless. For advanced use cases, the user
+            # has to modify his config.
+            print "$statement $key focus parent; $command\n";
+        }
+        return;
+    }
+
+    if ($replaced) {
+        print "$statement $key $command\n";
+    } else {
+        print "# XXX: migration script could not handle command: $line\n";
+    }
+}
+
+
+# add an i3bar invocation automatically if no 'workspace_bar no' was found
+if ($workspace_bar) {
+    print "\n";
+    print "# XXX: Automatically added a call to i3bar to provide a workspace bar\n";
+    print "exec i3status | i3bar -d\n";
+}
index d75d807c570eab8b38bf787f95501e1c9149070d..1b7c1c04a9bb092660f958af71ab52f2eb306603 100644 (file)
@@ -12,11 +12,13 @@ HEADERS=$(wildcard *.h)
 # Depend on the specific file (.c for each .o) and on all headers
 %.o: %.c ${HEADERS}
        echo "CC $<"
-       $(CC) $(CFLAGS) -c -o $@ $<
+       $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
 
-all: ${FILES}
+all: i3-msg
+
+i3-msg: ${FILES}
        echo "LINK i3-msg"
-       $(CC) -o i3-msg ${FILES} $(LDFLAGS)
+       $(CC) $(LDFLAGS) -o i3-msg ${FILES} $(LIBS)
 
 install: all
        echo "INSTALL"
index 33bedc7cbe198334cef13757911139bcdad0c430..630a345d63c6ce57dc611b19464d8a5b6da0f8cf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
  *
 #include <err.h>
 #include <stdint.h>
 #include <getopt.h>
-#include <glob.h>
+#include <limits.h>
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_aux.h>
 
 #include <i3/ipc.h>
 
+static char *socket_path;
+
 /*
- * This function resolves ~ in pathnames (and more, see glob(3)).
+ * Try to get the socket path from X11 and return NULL if it doesn’t work.
+ * As i3-msg is a short-running tool, we don’t bother with cleaning up the
+ * connection and leave it up to the operating system on exit.
  *
  */
-static char *glob_path(const char *path) {
-        static glob_t globbuf;
-        if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0)
-                errx(EXIT_FAILURE, "glob() failed");
-        char *result = strdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
-        if (result == NULL)
-                err(EXIT_FAILURE, "malloc() failed");
-        globfree(&globbuf);
-        return result;
+static char *socket_path_from_x11() {
+    xcb_connection_t *conn;
+    int screen;
+    if ((conn = xcb_connect(NULL, &screen)) == NULL ||
+        xcb_connection_has_error(conn))
+        return NULL;
+    xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen);
+    xcb_window_t root = root_screen->root;
+
+    xcb_intern_atom_cookie_t atom_cookie;
+    xcb_intern_atom_reply_t *atom_reply;
+
+    atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH");
+    atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL);
+    if (atom_reply == NULL)
+        return NULL;
+
+    xcb_get_property_cookie_t prop_cookie;
+    xcb_get_property_reply_t *prop_reply;
+    prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom,
+                                             XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX);
+    prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL);
+    if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0)
+        return NULL;
+    if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply),
+                 (char*)xcb_get_property_value(prop_reply)) == -1)
+        return NULL;
+    return socket_path;
 }
 
 /*
@@ -53,144 +79,174 @@ static char *glob_path(const char *path) {
  */
 static void ipc_send_message(int sockfd, uint32_t message_size,
                              uint32_t message_type, uint8_t *payload) {
-        int buffer_size = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t) + message_size;
-        char msg[buffer_size];
-        char *walk = msg;
-
-        strcpy(walk, I3_IPC_MAGIC);
-        walk += strlen(I3_IPC_MAGIC);
-        memcpy(walk, &message_size, sizeof(uint32_t));
-        walk += sizeof(uint32_t);
-        memcpy(walk, &message_type, sizeof(uint32_t));
-        walk += sizeof(uint32_t);
-        memcpy(walk, payload, message_size);
-
-        int sent_bytes = 0;
-        int bytes_to_go = buffer_size;
-        while (sent_bytes < bytes_to_go) {
-                int n = write(sockfd, msg + sent_bytes, bytes_to_go);
-                if (n == -1)
-                        err(EXIT_FAILURE, "write() failed");
-
-                sent_bytes += n;
-                bytes_to_go -= n;
-        }
+    int buffer_size = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t) + message_size;
+    char msg[buffer_size];
+    char *walk = msg;
+
+    strcpy(walk, I3_IPC_MAGIC);
+    walk += strlen(I3_IPC_MAGIC);
+    memcpy(walk, &message_size, sizeof(uint32_t));
+    walk += sizeof(uint32_t);
+    memcpy(walk, &message_type, sizeof(uint32_t));
+    walk += sizeof(uint32_t);
+    memcpy(walk, payload, message_size);
+
+    int sent_bytes = 0;
+    int bytes_to_go = buffer_size;
+    while (sent_bytes < bytes_to_go) {
+        int n = write(sockfd, msg + sent_bytes, bytes_to_go);
+        if (n == -1)
+            err(EXIT_FAILURE, "write() failed");
+
+        sent_bytes += n;
+        bytes_to_go -= n;
+    }
 }
 
 static void ipc_recv_message(int sockfd, uint32_t message_type,
                              uint32_t *reply_length, uint8_t **reply) {
-        /* Read the message header first */
-        uint32_t to_read = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t);
-        char msg[to_read];
-        char *walk = msg;
-
-        uint32_t read_bytes = 0;
-        while (read_bytes < to_read) {
-                int n = read(sockfd, msg + read_bytes, to_read);
-                if (n == -1)
-                        err(EXIT_FAILURE, "read() failed");
-                if (n == 0)
-                        errx(EXIT_FAILURE, "received EOF instead of reply");
-
-                read_bytes += n;
-                to_read -= n;
-        }
-
-        if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0)
-                errx(EXIT_FAILURE, "invalid magic in reply");
-
-        walk += strlen(I3_IPC_MAGIC);
-        *reply_length = *((uint32_t*)walk);
-        walk += sizeof(uint32_t);
-        if (*((uint32_t*)walk) != message_type)
-                errx(EXIT_FAILURE, "unexpected reply type (got %d, expected %d)", *((uint32_t*)walk), message_type);
-        walk += sizeof(uint32_t);
-
-        *reply = malloc(*reply_length);
-        if ((*reply) == NULL)
-                err(EXIT_FAILURE, "malloc() failed");
-
-        to_read = *reply_length;
-        read_bytes = 0;
-        while (read_bytes < to_read) {
-                int n = read(sockfd, *reply + read_bytes, to_read);
-                if (n == -1)
-                        err(EXIT_FAILURE, "read() failed");
-
-                read_bytes += n;
-                to_read -= n;
-        }
+    /* Read the message header first */
+    uint32_t to_read = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t);
+    char msg[to_read];
+    char *walk = msg;
+
+    uint32_t read_bytes = 0;
+    while (read_bytes < to_read) {
+        int n = read(sockfd, msg + read_bytes, to_read);
+        if (n == -1)
+            err(EXIT_FAILURE, "read() failed");
+        if (n == 0)
+            errx(EXIT_FAILURE, "received EOF instead of reply");
+
+        read_bytes += n;
+        to_read -= n;
+    }
+
+    if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0)
+        errx(EXIT_FAILURE, "invalid magic in reply");
+
+    walk += strlen(I3_IPC_MAGIC);
+    *reply_length = *((uint32_t*)walk);
+    walk += sizeof(uint32_t);
+    if (*((uint32_t*)walk) != message_type)
+        errx(EXIT_FAILURE, "unexpected reply type (got %d, expected %d)", *((uint32_t*)walk), message_type);
+    walk += sizeof(uint32_t);
+
+    *reply = malloc(*reply_length);
+    if ((*reply) == NULL)
+        err(EXIT_FAILURE, "malloc() failed");
+
+    to_read = *reply_length;
+    read_bytes = 0;
+    while (read_bytes < to_read) {
+        int n = read(sockfd, *reply + read_bytes, to_read);
+        if (n == -1)
+            err(EXIT_FAILURE, "read() failed");
+
+        read_bytes += n;
+        to_read -= n;
+    }
 }
 
 int main(int argc, char *argv[]) {
-        char *socket_path = glob_path("~/.i3/ipc.sock");
-        int o, option_index = 0;
-        int message_type = I3_IPC_MESSAGE_TYPE_COMMAND;
-        char *payload = "";
-        bool quiet = false;
-
-        static struct option long_options[] = {
-                {"socket", required_argument, 0, 's'},
-                {"type", required_argument, 0, 't'},
-                {"version", no_argument, 0, 'v'},
-                {"quiet", no_argument, 0, 'q'},
-                {"help", no_argument, 0, 'h'},
-                {0, 0, 0, 0}
-        };
-
-        char *options_string = "s:t:vhq";
-
-        while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
-                if (o == 's') {
-                        socket_path = glob_path(optarg);
-                } else if (o == 't') {
-                        if (strcasecmp(optarg, "command") == 0)
-                                message_type = I3_IPC_MESSAGE_TYPE_COMMAND;
-                        else if (strcasecmp(optarg, "get_workspaces") == 0)
-                                message_type = I3_IPC_MESSAGE_TYPE_GET_WORKSPACES;
-                        else {
-                                printf("Unknown message type\n");
-                                printf("Known types: command, get_workspaces\n");
-                                exit(EXIT_FAILURE);
-                        }
-                } else if (o == 'q') {
-                        quiet = true;
-                } else if (o == 'v') {
-                        printf("i3-msg " I3_VERSION);
-                        return 0;
-                } else if (o == 'h') {
-                        printf("i3-msg " I3_VERSION);
-                        printf("i3-msg [-s <socket>] [-t <type>] <message>\n");
-                        return 0;
-                }
+    socket_path = getenv("I3SOCK");
+    int o, option_index = 0;
+    int message_type = I3_IPC_MESSAGE_TYPE_COMMAND;
+    char *payload = NULL;
+    bool quiet = false;
+
+    static struct option long_options[] = {
+        {"socket", required_argument, 0, 's'},
+        {"type", required_argument, 0, 't'},
+        {"version", no_argument, 0, 'v'},
+        {"quiet", no_argument, 0, 'q'},
+        {"help", no_argument, 0, 'h'},
+        {0, 0, 0, 0}
+    };
+
+    char *options_string = "s:t:vhq";
+
+    while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
+        if (o == 's') {
+            if (socket_path != NULL)
+                free(socket_path);
+            socket_path = strdup(optarg);
+        } else if (o == 't') {
+            if (strcasecmp(optarg, "command") == 0)
+                message_type = I3_IPC_MESSAGE_TYPE_COMMAND;
+            else if (strcasecmp(optarg, "get_workspaces") == 0)
+                message_type = I3_IPC_MESSAGE_TYPE_GET_WORKSPACES;
+            else if (strcasecmp(optarg, "get_outputs") == 0)
+                message_type = I3_IPC_MESSAGE_TYPE_GET_OUTPUTS;
+            else if (strcasecmp(optarg, "get_tree") == 0)
+                message_type = I3_IPC_MESSAGE_TYPE_GET_TREE;
+            else {
+                printf("Unknown message type\n");
+                printf("Known types: command, get_workspaces, get_outputs, get_tree\n");
+                exit(EXIT_FAILURE);
+            }
+        } else if (o == 'q') {
+            quiet = true;
+        } else if (o == 'v') {
+            printf("i3-msg " I3_VERSION "\n");
+            return 0;
+        } else if (o == 'h') {
+            printf("i3-msg " I3_VERSION "\n");
+            printf("i3-msg [-s <socket>] [-t <type>] <message>\n");
+            return 0;
+        }
+    }
+
+    if (socket_path == NULL)
+        socket_path = socket_path_from_x11();
+
+    /* Fall back to the default socket path */
+    if (socket_path == NULL)
+        socket_path = strdup("/tmp/i3-ipc.sock");
+
+    /* Use all arguments, separated by whitespace, as payload.
+     * This way, you don’t have to do i3-msg 'mark foo', you can use
+     * i3-msg mark foo */
+    while (optind < argc) {
+        if (!payload) {
+            if (!(payload = strdup(argv[optind])))
+                err(EXIT_FAILURE, "strdup(argv[optind])");
+        } else {
+            char *both;
+            if (asprintf(&both, "%s %s", payload, argv[optind]) == -1)
+                err(EXIT_FAILURE, "asprintf");
+            free(payload);
+            payload = both;
         }
+        optind++;
+    }
 
-        if (optind < argc)
-                payload = argv[optind];
+    if (!payload)
+        payload = "";
 
-        int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
-        if (sockfd == -1)
-                err(EXIT_FAILURE, "Could not create socket");
+    int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
+    if (sockfd == -1)
+        err(EXIT_FAILURE, "Could not create socket");
 
-        struct sockaddr_un addr;
-        memset(&addr, 0, sizeof(struct sockaddr_un));
-        addr.sun_family = AF_LOCAL;
-        strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
-        if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
-                err(EXIT_FAILURE, "Could not connect to i3");
+    struct sockaddr_un addr;
+    memset(&addr, 0, sizeof(struct sockaddr_un));
+    addr.sun_family = AF_LOCAL;
+    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
+    if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
+        err(EXIT_FAILURE, "Could not connect to i3");
 
-        ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t*)payload);
+    ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t*)payload);
 
-        if (quiet)
-                return 0;
+    if (quiet)
+        return 0;
 
-        uint32_t reply_length;
-        uint8_t *reply;
-        ipc_recv_message(sockfd, message_type, &reply_length, &reply);
-        printf("%.*s", reply_length, reply);
-        free(reply);
+    uint32_t reply_length;
+    uint8_t *reply;
+    ipc_recv_message(sockfd, message_type, &reply_length, &reply);
+    printf("%.*s", reply_length, reply);
+    free(reply);
 
-        close(sockfd);
+    close(sockfd);
 
-        return 0;
+    return 0;
 }
diff --git a/i3-nagbar/Makefile b/i3-nagbar/Makefile
new file mode 100644 (file)
index 0000000..933ae76
--- /dev/null
@@ -0,0 +1,30 @@
+# Default value so one can compile i3-nagbar standalone
+TOPDIR=..
+
+include $(TOPDIR)/common.mk
+
+# Depend on the object files of all source-files in src/*.c and on all header files
+FILES=$(patsubst %.c,%.o,$(wildcard *.c))
+HEADERS=$(wildcard *.h)
+
+# Depend on the specific file (.c for each .o) and on all headers
+%.o: %.c ${HEADERS}
+       echo "CC $<"
+       $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+
+all: i3-nagbar
+
+i3-nagbar: ${FILES}
+       echo "LINK i3-nagbar"
+       $(CC) $(LDFLAGS) -o $@ ${FILES} $(LIBS)
+
+install: all
+       echo "INSTALL"
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
+       $(INSTALL) -m 0755 i3-nagbar $(DESTDIR)$(PREFIX)/bin/
+
+clean:
+       rm -f *.o
+
+distclean: clean
+       rm -f i3-nagbar
diff --git a/i3-nagbar/atoms.xmacro b/i3-nagbar/atoms.xmacro
new file mode 100644 (file)
index 0000000..333ba2d
--- /dev/null
@@ -0,0 +1,6 @@
+xmacro(_NET_WM_WINDOW_TYPE)
+xmacro(_NET_WM_WINDOW_TYPE_DOCK)
+xmacro(_NET_WM_STRUT_PARTIAL)
+xmacro(I3_SOCKET_PATH)
+xmacro(ATOM)
+xmacro(CARDINAL)
diff --git a/i3-nagbar/i3-nagbar.h b/i3-nagbar/i3-nagbar.h
new file mode 100644 (file)
index 0000000..2fbe3cb
--- /dev/null
@@ -0,0 +1,26 @@
+#ifndef _I3_NAGBAR
+#define _I3_NAGBAR
+
+#include <err.h>
+
+#define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
+#define FREE(pointer) do { \
+        if (pointer != NULL) { \
+                free(pointer); \
+                pointer = NULL; \
+        } \
+} \
+while (0)
+
+#define xmacro(atom) xcb_atom_t A_ ## atom;
+#include "atoms.xmacro"
+#undef xmacro
+
+extern xcb_window_t root;
+
+uint32_t get_colorpixel(xcb_connection_t *conn, char *hex);
+xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height);
+int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height);
+void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value);
+
+#endif
diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c
new file mode 100644 (file)
index 0000000..d0d7e77
--- /dev/null
@@ -0,0 +1,395 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ *
+ * © 2009-2011 Michael Stapelberg and contributors
+ *
+ * See file LICENSE for license information.
+ *
+ * i3-nagbar is a utility which displays a nag message.
+ *
+ */
+#include <ev.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <err.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <limits.h>
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_aux.h>
+#include <xcb/xcb_event.h>
+
+#include "i3-nagbar.h"
+
+typedef struct {
+    char *label;
+    char *action;
+    int16_t x;
+    uint16_t width;
+} button_t;
+
+static xcb_window_t win;
+static xcb_pixmap_t pixmap;
+static xcb_gcontext_t pixmap_gc;
+static xcb_rectangle_t rect = { 0, 0, 600, 20 };
+static int font_height;
+static char *prompt = "Please do not run this program.";
+static button_t *buttons;
+static int buttoncnt;
+xcb_window_t root;
+
+/*
+ * Starts the given application by passing it through a shell. We use double fork
+ * to avoid zombie processes. As the started application’s parent exits (immediately),
+ * the application is reparented to init (process-id 1), which correctly handles
+ * childs, so we don’t have to do it :-).
+ *
+ * The shell is determined by looking for the SHELL environment variable. If it
+ * does not exist, /bin/sh is used.
+ *
+ */
+static void start_application(const char *command) {
+    printf("executing: %s\n", command);
+    if (fork() == 0) {
+        /* Child process */
+        setsid();
+        if (fork() == 0) {
+            /* Stores the path of the shell */
+            static const char *shell = NULL;
+
+            if (shell == NULL)
+                if ((shell = getenv("SHELL")) == NULL)
+                    shell = "/bin/sh";
+
+            /* This is the child */
+            execl(shell, shell, "-c", command, (void*)NULL);
+            /* not reached */
+        }
+        exit(0);
+    }
+    wait(0);
+}
+
+static button_t *get_button_at(int16_t x, int16_t y) {
+    for (int c = 0; c < buttoncnt; c++)
+        if (x >= (buttons[c].x) && x <= (buttons[c].x + buttons[c].width))
+            return &buttons[c];
+
+    return NULL;
+}
+
+static void handle_button_press(xcb_connection_t *conn, xcb_button_press_event_t *event) {
+    printf("button pressed on x = %d, y = %d\n",
+            event->event_x, event->event_y);
+    /* TODO: set a flag for the button, re-render */
+}
+
+/*
+ * Called when the user releases the mouse button. Checks whether the
+ * coordinates are over a button and executes the appropriate action.
+ *
+ */
+static void handle_button_release(xcb_connection_t *conn, xcb_button_release_event_t *event) {
+    printf("button released on x = %d, y = %d\n",
+            event->event_x, event->event_y);
+    /* If the user hits the close button, we exit(0) */
+    if (event->event_x >= (rect.width - 32))
+        exit(0);
+    button_t *button = get_button_at(event->event_x, event->event_y);
+    if (!button)
+        return;
+    start_application(button->action);
+
+    /* TODO: unset flag, re-render */
+}
+
+/*
+ * Handles expose events (redraws of the window) and rendering in general. Will
+ * be called from the code with event == NULL or from X with event != NULL.
+ *
+ */
+static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
+    printf("expose!\n");
+
+    /* re-draw the background */
+    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#900000"));
+    xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect);
+
+    /* restore font color */
+    uint32_t values[3];
+    values[0] = get_colorpixel(conn, "#FFFFFF");
+    values[1] = get_colorpixel(conn, "#900000");
+    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);
+
+    /* render close button */
+    int line_width = 4;
+    int w = 20;
+    int y = rect.width;
+    values[0] = get_colorpixel(conn, "#680a0a");
+    values[1] = line_width;
+    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
+
+    xcb_rectangle_t close = { y - w - (2 * line_width), 0, w + (2 * line_width), rect.height };
+    xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close);
+
+    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#d92424"));
+    xcb_point_t points[] = {
+        { y - w - (2 * line_width), line_width / 2 },
+        { y - (line_width / 2), line_width / 2 },
+        { y - (line_width / 2), (rect.height - (line_width / 2)) - 2 },
+        { y - w - (2 * line_width), (rect.height - (line_width / 2)) - 2 },
+        { y - w - (2 * line_width), line_width / 2 }
+    };
+    xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points);
+
+    values[0] = get_colorpixel(conn, "#ffffff");
+    values[1] = get_colorpixel(conn, "#680a0a");
+    values[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");
+    y -= w;
+
+    y -= 20;
+
+    /* render custom buttons */
+    line_width = 1;
+    for (int c = 0; c < buttoncnt; c++) {
+        /* TODO: make w = text extents of the label */
+        w = 90;
+        y -= 30;
+        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#680a0a"));
+        close = (xcb_rectangle_t){ y - w - (2 * line_width), 2, w + (2 * line_width), rect.height - 6 };
+        xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close);
+
+        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#d92424"));
+        buttons[c].x = y - w - (2 * line_width);
+        buttons[c].width = w;
+        xcb_point_t points2[] = {
+            { y - w - (2 * line_width), (line_width / 2) + 2 },
+            { y - (line_width / 2), (line_width / 2) + 2 },
+            { y - (line_width / 2), (rect.height - 4 - (line_width / 2)) },
+            { y - w - (2 * line_width), (rect.height - 4 - (line_width / 2)) },
+            { y - w - (2 * line_width), (line_width / 2) + 2 }
+        };
+        xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points2);
+
+        values[0] = get_colorpixel(conn, "#ffffff");
+        values[1] = get_colorpixel(conn, "#680a0a");
+        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);
+
+        y -= w;
+    }
+
+    /* border line at the bottom */
+    line_width = 2;
+    values[0] = get_colorpixel(conn, "#470909");
+    values[1] = line_width;
+    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
+    xcb_point_t bottom[] = {
+        { 0, rect.height - 0 },
+        { rect.width, rect.height - 0 }
+    };
+    xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 2, bottom);
+
+
+    /* Copy the contents of the pixmap to the real window */
+    xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, rect.width, rect.height);
+    xcb_flush(conn);
+
+    return 1;
+}
+
+int main(int argc, char *argv[]) {
+    char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
+    int o, option_index = 0;
+
+    static struct option long_options[] = {
+        {"version", no_argument, 0, 'v'},
+        {"font", required_argument, 0, 'f'},
+        {"button", required_argument, 0, 'b'},
+        {"help", no_argument, 0, 'h'},
+        {"message", no_argument, 0, 'm'},
+        {0, 0, 0, 0}
+    };
+
+    char *options_string = "b:f:m:vh";
+
+    while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
+        switch (o) {
+            case 'v':
+                printf("i3-nagbar " I3_VERSION);
+                return 0;
+            case 'f':
+                FREE(pattern);
+                pattern = strdup(optarg);
+                break;
+            case 'm':
+                prompt = strdup(optarg);
+                break;
+            case 'h':
+                printf("i3-nagbar " I3_VERSION "\n");
+                printf("i3-nagbar [-m <message>] [-b <button> <action>] [-f <font>] [-v]\n");
+                return 0;
+            case 'b':
+                buttons = realloc(buttons, sizeof(button_t) * (buttoncnt + 1));
+                buttons[buttoncnt].label = optarg;
+                buttons[buttoncnt].action = argv[optind];
+                printf("button with label *%s* and action *%s*\n",
+                        buttons[buttoncnt].label,
+                        buttons[buttoncnt].action);
+                buttoncnt++;
+                printf("now %d buttons\n", buttoncnt);
+                if (optind < argc)
+                    optind++;
+                break;
+        }
+    }
+
+    int screens;
+    xcb_connection_t *conn;
+    if ((conn = xcb_connect(NULL, &screens)) == NULL ||
+        xcb_connection_has_error(conn))
+        die("Cannot open display\n");
+
+    /* Place requests for the atoms we need as soon as possible */
+    #define xmacro(atom) \
+        xcb_intern_atom_cookie_t atom ## _cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom);
+    #include "atoms.xmacro"
+    #undef xmacro
+
+    xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
+    root = root_screen->root;
+
+    uint32_t font_id = get_font_id(conn, pattern, &font_height);
+
+    /* Open an input window */
+    win = open_input_window(conn, 500, font_height + 8 + 8 /* 8px padding */);
+
+    /* Setup NetWM atoms */
+    #define xmacro(name) \
+        do { \
+            xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, name ## _cookie, NULL); \
+            if (!reply) \
+                die("Could not get atom " # name "\n"); \
+            \
+            A_ ## name = reply->atom; \
+            free(reply); \
+        } while (0);
+    #include "atoms.xmacro"
+    #undef xmacro
+
+    /* Set dock mode */
+    xcb_change_property(conn,
+        XCB_PROP_MODE_REPLACE,
+        win,
+        A__NET_WM_WINDOW_TYPE,
+        A_ATOM,
+        32,
+        1,
+        (unsigned char*) &A__NET_WM_WINDOW_TYPE_DOCK);
+
+    /* Reserve some space at the top of the screen */
+    struct {
+        uint32_t left;
+        uint32_t right;
+        uint32_t top;
+        uint32_t bottom;
+        uint32_t left_start_y;
+        uint32_t left_end_y;
+        uint32_t right_start_y;
+        uint32_t right_end_y;
+        uint32_t top_start_x;
+        uint32_t top_end_x;
+        uint32_t bottom_start_x;
+        uint32_t bottom_end_x;
+    } __attribute__((__packed__)) strut_partial = {0,};
+
+    strut_partial.top = font_height + 6;
+    strut_partial.top_start_x = 0;
+    strut_partial.top_end_x = 800;
+
+    xcb_change_property(conn,
+        XCB_PROP_MODE_REPLACE,
+        win,
+        A__NET_WM_STRUT_PARTIAL,
+        A_CARDINAL,
+        32,
+        12,
+        &strut_partial);
+
+    /* Create pixmap */
+    pixmap = xcb_generate_id(conn);
+    pixmap_gc = xcb_generate_id(conn);
+    xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font_height + 8);
+    xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
+
+    /* Create graphics context */
+    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
+
+    /* Grab the keyboard to get all input */
+    xcb_flush(conn);
+
+    xcb_generic_event_t *event;
+    while ((event = xcb_wait_for_event(conn)) != NULL) {
+        if (event->response_type == 0) {
+            fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence);
+            continue;
+        }
+
+        /* Strip off the highest bit (set if the event is generated) */
+        int type = (event->response_type & 0x7F);
+
+        switch (type) {
+            case XCB_EXPOSE:
+                handle_expose(conn, (xcb_expose_event_t*)event);
+                break;
+
+            case XCB_BUTTON_PRESS:
+                handle_button_press(conn, (xcb_button_press_event_t*)event);
+                break;
+
+            case XCB_BUTTON_RELEASE:
+                handle_button_release(conn, (xcb_button_release_event_t*)event);
+                break;
+
+            case XCB_CONFIGURE_NOTIFY: {
+                xcb_configure_notify_event_t *configure_notify = (xcb_configure_notify_event_t*)event;
+                rect = (xcb_rectangle_t){
+                    configure_notify->x,
+                    configure_notify->y,
+                    configure_notify->width,
+                    configure_notify->height
+                };
+
+                /* Recreate the pixmap / gc */
+                xcb_free_pixmap(conn, pixmap);
+                xcb_free_gc(conn, pixmap_gc);
+
+                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_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
+                break;
+            }
+        }
+
+        free(event);
+    }
+
+    return 0;
+}
diff --git a/i3-nagbar/xcb.c b/i3-nagbar/xcb.c
new file mode 100644 (file)
index 0000000..ed1bfd8
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * vim:ts=8:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ *
+ * © 2009 Michael Stapelberg and contributors
+ *
+ * See file LICENSE for license information.
+ *
+ */
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_keysyms.h>
+
+#include <X11/keysym.h>
+
+#include "i3-nagbar.h"
+
+/*
+ * Convenience-wrapper around xcb_change_gc which saves us declaring a variable
+ *
+ */
+void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value) {
+        xcb_change_gc(conn, gc, mask, &value);
+}
+
+/*
+ * Returns the colorpixel to use for the given hex color (think of HTML).
+ *
+ * The hex_color has to start with #, for example #FF00FF.
+ *
+ * NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
+ * This has to be done by the caller.
+ *
+ */
+uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
+        char strgroups[3][3] = {{hex[1], hex[2], '\0'},
+                                {hex[3], hex[4], '\0'},
+                                {hex[5], hex[6], '\0'}};
+        uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
+                             (strtol(strgroups[1], NULL, 16)),
+                             (strtol(strgroups[2], NULL, 16))};
+
+        return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
+}
+
+/*
+ * Opens the window we use for input/output and maps it
+ *
+ */
+xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height) {
+        xcb_window_t win = xcb_generate_id(conn);
+        //xcb_cursor_t cursor_id = xcb_generate_id(conn);
+
+#if 0
+        /* Use the default cursor (left pointer) */
+        if (cursor > -1) {
+                i3Font *cursor_font = load_font(conn, "cursor");
+                xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id,
+                                XCB_CURSOR_LEFT_PTR, XCB_CURSOR_LEFT_PTR + 1,
+                                0, 0, 0, 65535, 65535, 65535);
+        }
+#endif
+
+        uint32_t mask = 0;
+        uint32_t values[3];
+
+        mask |= XCB_CW_BACK_PIXEL;
+        values[0] = 0;
+
+       mask |= XCB_CW_EVENT_MASK;
+       values[1] = XCB_EVENT_MASK_EXPOSURE |
+                XCB_EVENT_MASK_STRUCTURE_NOTIFY |
+                XCB_EVENT_MASK_BUTTON_PRESS |
+                XCB_EVENT_MASK_BUTTON_RELEASE;
+
+        xcb_create_window(conn,
+                          XCB_COPY_FROM_PARENT,
+                          win, /* the window id */
+                          root, /* parent == root */
+                          50, 50, width, height, /* dimensions */
+                          0, /* border = 0, we draw our own */
+                          XCB_WINDOW_CLASS_INPUT_OUTPUT,
+                          XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
+                          mask,
+                          values);
+
+#if 0
+        if (cursor > -1)
+                xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
+#endif
+
+        /* Map the window (= make it visible) */
+        xcb_map_window(conn, win);
+
+       return win;
+}
+
+/*
+ * Returns the ID of the font matching the given pattern and stores the height
+ * of the font (in pixels) in *font_height. die()s if no font matches.
+ *
+ */
+int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height) {
+        xcb_void_cookie_t font_cookie;
+        xcb_list_fonts_with_info_cookie_t info_cookie;
+
+        /* Send all our requests first */
+        int result;
+        result = xcb_generate_id(conn);
+        font_cookie = xcb_open_font_checked(conn, result, strlen(pattern), pattern);
+        info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
+
+        xcb_generic_error_t *error = xcb_request_check(conn, font_cookie);
+        if (error != NULL) {
+                fprintf(stderr, "ERROR: Could not open font: %d\n", error->error_code);
+                exit(1);
+        }
+
+        /* Get information (height/name) for this font */
+        xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL);
+        if (reply == NULL)
+                die("Could not load font \"%s\"\n", pattern);
+
+        *font_height = reply->font_ascent + reply->font_descent;
+
+        return result;
+}
index 11bd9066510372814b6614dfaefdcde8ec2c14f2..92654f0b180fb17b0496e5bd363d0f75e9a4d98b 100644 (file)
--- a/i3.config
+++ b/i3.config
-# This configuration uses Mod1 and Mod3. Make sure they are mapped properly using xev(1)
-# and xmodmap(1). Usually, Mod1 is Alt (Alt_L) and Mod3 is Windows (Super_L)
-
-# ISO 10646 = Unicode
+# i3 config file (v4)
+#
+# Please see http://i3wm.org/docs/userguide.html for a complete reference!
+#
+# This config file uses keycodes (bindsym) and was written for the QWERTY
+# layout.
+#
+# To get a config file with the same key positions, but for your current
+# layout, use the i3-config-wizard
+#
+
+# font for window titles. ISO 10646 = Unicode
 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
 
-# Use Mouse+Mod1 to drag floating windows to their wanted position
+# use Mouse+Mod1 to drag floating windows to their wanted position
 floating_modifier Mod1
 
-# Fullscreen (Mod1+f)
-bind Mod1+41 f
-
-# Stacking (Mod1+h)
-bind Mod1+43 s
-
-# Tabbed (Mod1+w)
-bind Mod1+25 T
-
-# Default (Mod1+e)
-bind Mod1+26 d
-
-# Toggle tiling/floating of the current window (Mod1+Shift+Space)
-bind Mod1+Shift+65 t
-
-# Go into the tiling layer / floating layer, depending on whether
-# the current window is tiling / floating (Mod1+t)
-bind Mod1+28 focus ft
-
-# Focus (Mod1+j/k/l/;)
-bind Mod1+44 h
-bind Mod1+45 j
-bind Mod1+46 k
-bind Mod1+47 l
-# (alternatively, you can use the cursor keys:)
-bindsym Mod1+Left h
-bindsym Mod1+Down j
-bindsym Mod1+Up k
-bindsym Mod1+Right l
-
-# Focus Container (Mod3+j/k/l/;)
-bind Mod3+44 wch
-bind Mod3+45 wcj
-bind Mod3+46 wck
-bind Mod3+47 wcl
-# (alternatively, you can use the cursor keys:)
-bindsym Mod3+Left wch
-bindsym Mod3+Down wcj
-bindsym Mod3+Up wck
-bindsym Mod3+Right wcl
-
-# Snap (Mod1+Control+j/k/l/;)
-bind Mod1+Control+44 sh
-bind Mod1+Control+45 sj
-bind Mod1+Control+46 sk
-bind Mod1+Control+47 sl
-# (alternatively, you can use the cursor keys:)
-bindsym Mod1+Control+Left sh
-bindsym Mod1+Control+Down sj
-bindsym Mod1+Control+Up sk
-bindsym Mod1+Control+Right sl
-
-# Move (Mod1+Shift+j/k/l/;)
-bind Mod1+Shift+44 mh
-bind Mod1+Shift+45 mj
-bind Mod1+Shift+46 mk
-bind Mod1+Shift+47 ml
-# (alternatively, you can use the cursor keys:)
-bindsym Mod1+Shift+Left mh
-bindsym Mod1+Shift+Down mj
-bindsym Mod1+Shift+Up mk
-bindsym Mod1+Shift+Right ml
-
-# Move Container (Mod3+Shift+j/k/l/;)
-bind Mod3+Shift+44 wcmh
-bind Mod3+Shift+45 wcmj
-bind Mod3+Shift+46 wcmk
-bind Mod3+Shift+47 wcml
-
-# Workspaces (Mod1+1/2/…)
-bind Mod1+10 1
-bind Mod1+11 2
-bind Mod1+12 3
-bind Mod1+13 4
-bind Mod1+14 5
-bind Mod1+15 6
-bind Mod1+16 7
-bind Mod1+17 8
-bind Mod1+18 9
-bind Mod1+19 10
-
-# Move to Workspaces
-bind Mod1+Shift+10 m1
-bind Mod1+Shift+11 m2
-bind Mod1+Shift+12 m3
-bind Mod1+Shift+13 m4
-bind Mod1+Shift+14 m5
-bind Mod1+Shift+15 m6
-bind Mod1+Shift+16 m7
-bind Mod1+Shift+17 m8
-bind Mod1+Shift+18 m9
-bind Mod1+Shift+19 m10
-
-# Mod1+Enter starts a new terminal
-bind Mod1+36 exec /usr/bin/urxvt
-
-# Mod1+Shift+q kills the current client
-bind Mod1+Shift+24 kill
-
-# Mod1+v starts dmenu and launches the selected application
-# for now, we don’t have a launcher of our own.
-bind Mod1+55 exec /usr/bin/dmenu_run
-
-# Mod1+Shift+e exits i3
-bind Mod1+Shift+26 exit
-
-# Mod1+Shift+r restarts i3 inplace
-bind Mod1+Shift+27 restart
-
-# The IPC interface allows programs like an external workspace bar
-# (i3-wsbar) or i3-msg (can be used to "remote-control" i3) to work.
-ipc-socket ~/.i3/ipc.sock
-
-#############################################################
-# DELETE THE FOLLOWING LINES TO DISABLE THE WELCOME MESSAGE #
-#############################################################
-exec xmessage -file /etc/i3/welcome
+# start a terminal
+bindsym Mod1+Return exec /usr/bin/urxvt
+
+# kill focused window
+bindsym Mod1+Shift+q kill
+
+# start dmenu (a program launcher)
+bindsym Mod1+d exec /usr/bin/dmenu_run
+
+# change focus
+bindsym Mod1+j focus left
+bindsym Mod1+k focus down
+bindsym Mod1+l focus up
+bindsym Mod1+semicolon focus right
+
+# alternatively, you can use the cursor keys:
+bindsym Mod1+Left focus left
+bindsym Mod1+Down focus down
+bindsym Mod1+Up focus up
+bindsym Mod1+Right focus right
+
+# move focused window
+bindsym Mod1+Shift+j move left
+bindsym Mod1+Shift+k move down
+bindsym Mod1+Shift+l move up
+bindsym Mod1+Shift+semicolon move right
+
+# alternatively, you can use the cursor keys:
+bindsym Mod1+Shift+Left move left
+bindsym Mod1+Shift+Down move down
+bindsym Mod1+Shift+Up move up
+bindsym Mod1+Shift+Right move right
+
+# split in horizontal orientation
+bindsym Mod1+h split h
+
+# split in vertical orientation
+bindsym Mod1+v split v
+
+# enter fullscreen mode for the focused container
+bindsym Mod1+f fullscreen
+
+# change container layout (stacked, tabbed, default)
+bindsym Mod1+s layout stacking
+bindsym Mod1+w layout tabbed
+bindsym Mod1+e layout default
+
+# toggle tiling / floating
+bindsym Mod1+Shift+space floating toggle
+
+# change focus between tiling / floating windows
+bindsym Mod1+space focus mode_toggle
+
+# focus the parent container
+bindsym Mod1+a focus parent
+
+# focus the child container
+#bindsym Mod1+d focus child
+
+# switch to workspace
+bindsym Mod1+1 workspace 1
+bindsym Mod1+2 workspace 2
+bindsym Mod1+3 workspace 3
+bindsym Mod1+4 workspace 4
+bindsym Mod1+5 workspace 5
+bindsym Mod1+6 workspace 6
+bindsym Mod1+7 workspace 7
+bindsym Mod1+8 workspace 8
+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
+
+# reload the configuration file
+bindsym Mod1+Shift+c reload
+# restart i3 inplace (preserves your layout/session, can be used to upgrade i3)
+bindsym Mod1+Shift+r restart
+# exit i3 (logs you out of your X session)
+bindsym Mod1+Shift+e exit
+
+# resize window (you can also use the mouse for that)
+mode "resize" {
+        # These bindings trigger as soon as you enter the resize mode
+
+        # They resize the border in the direction you pressed, e.g.
+        # when pressing left, the window is resized so that it has
+        # more space on its left
+
+        bindsym j               resize shrink left 10 px or 10 ppt
+        bindsym Shift+j         resize grow   left 10 px or 10 ppt
+
+        bindsym k               resize shrink down 10 px or 10 ppt
+        bindsym Shift+k         resize grow   down 10 px or 10 ppt
+
+        bindsym l               resize shrink up 10 px or 10 ppt
+        bindsym Shift+l         resize grow   up 10 px or 10 ppt
+
+        bindsym semicolon       resize shrink right 10 px or 10 ppt
+        bindsym Shift+semicolon resize grow   right 10 px or 10 ppt
+
+        # same bindings, but for the arrow keys
+        bindsym Left        resize shrink left 10 px or 10 ppt
+        bindsym Shift+Left  resize grow   left 10 px or 10 ppt
+
+        bindsym Down        resize shrink down 10 px or 10 ppt
+        bindsym Shift+Down  resize grow   down 10 px or 10 ppt
+
+        bindsym Up          resize shrink up 10 px or 10 ppt
+        bindsym Shift+Up    resize grow   up 10 px or 10 ppt
+
+        bindsym Right       resize shrink right 10 px or 10 ppt
+        bindsym Shift+Right resize grow   right 10 px or 10 ppt
+
+        # back to normal: Enter or Escape
+        bindsym Return mode "default"
+        bindsym Escape mode "default"
+}
+
+bindsym Mod1+r mode "resize"
+
+# Start i3bar to display a workspace bar (plus the system information i3status
+# finds out, if available)
+exec i3status | i3bar -d
+
+#######################################################################
+# automatically start i3-config-wizard to offer the user to create a
+# keysym-based config which used his favorite modifier (alt or windows)
+#
+# i3-config-wizard will not launch if there already is a config file
+# in ~/.i3/config.
+#
+# Please remove the following exec line:
+#######################################################################
+exec i3-config-wizard
diff --git a/i3.config.keycodes b/i3.config.keycodes
new file mode 100644 (file)
index 0000000..f017045
--- /dev/null
@@ -0,0 +1,147 @@
+# WARNING
+# WARNING: This configuration file is a template for the i3-config-wizard to
+# WARNING: generate a config which uses keysyms in your current layout. It does
+# WARNING: not get loaded by i3. Please do not change it.
+# WARNING
+
+# i3 config file (v4)
+#
+# Please see http://i3wm.org/docs/userguide.html for a complete reference!
+
+set $mod Mod1
+
+# font for window titles. ISO 10646 = Unicode
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+# Use Mouse+$mod to drag floating windows to their wanted position
+floating_modifier $mod
+
+# start a terminal
+bindcode $mod+36 exec /usr/bin/urxvt
+
+# kill focused window
+bindcode $mod+Shift+24 kill
+
+# start dmenu (a program launcher)
+bindcode $mod+40 exec /usr/bin/dmenu_run
+
+# change focus
+bindcode $mod+44 focus left
+bindcode $mod+45 focus down
+bindcode $mod+46 focus up
+bindcode $mod+47 focus right
+
+# alternatively, you can use the cursor keys:
+bindcode $mod+113 focus left
+bindcode $mod+116 focus down
+bindcode $mod+111 focus up
+bindcode $mod+114 focus right
+
+# move focused window
+bindcode $mod+Shift+44 move left
+bindcode $mod+Shift+45 move down
+bindcode $mod+Shift+46 move up
+bindcode $mod+Shift+47 move right
+
+# alternatively, you can use the cursor keys:
+bindcode $mod+Shift+113 move left
+bindcode $mod+Shift+116 move down
+bindcode $mod+Shift+111 move up
+bindcode $mod+Shift+114 move right
+
+# split in horizontal orientation
+bindcode $mod+43 split h
+
+# split in vertical orientation
+bindcode $mod+55 split v
+
+# enter fullscreen mode for the focused container
+bindcode $mod+41 fullscreen
+
+# change container layout (stacked, tabbed, default)
+bindcode $mod+39 layout stacking
+bindcode $mod+25 layout tabbed
+bindcode $mod+26 layout default
+
+# toggle tiling / floating
+bindcode $mod+Shift+65 floating toggle
+
+# change focus between tiling / floating windows
+bindcode $mod+65 focus mode_toggle
+
+# focus the parent container
+bindcode $mod+38 focus parent
+
+# focus the child container
+#bindcode $mod+d focus child
+
+# switch to workspace
+bindcode $mod+10 workspace 1
+bindcode $mod+11 workspace 2
+bindcode $mod+12 workspace 3
+bindcode $mod+13 workspace 4
+bindcode $mod+14 workspace 5
+bindcode $mod+15 workspace 6
+bindcode $mod+16 workspace 7
+bindcode $mod+17 workspace 8
+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
+
+# reload the configuration file
+bindcode $mod+Shift+54 reload
+# restart i3 inplace (preserves your layout/session, can be used to upgrade i3)
+bindcode $mod+Shift+27 restart
+# exit i3 (logs you out of your X session)
+bindcode $mod+Shift+26 exit
+
+# resize window (you can also use the mouse for that)
+mode "resize" {
+        # These bindings trigger as soon as you enter the resize mode
+
+        # They resize the border in the direction you pressed, e.g.
+        # when pressing left, the window is resized so that it has
+        # more space on its left
+
+        bindcode 44 resize shrink left 10 px or 10 ppt
+        bindcode Shift+44 resize grow   left 10 px or 10 ppt
+
+        bindcode 45 resize shrink down 10 px or 10 ppt
+        bindcode Shift+45 resize grow   down 10 px or 10 ppt
+
+        bindcode 46 resize shrink up 10 px or 10 ppt
+        bindcode Shift+46 resize grow   up 10 px or 10 ppt
+
+        bindcode 47 resize shrink right 10 px or 10 ppt
+        bindcode Shift+47 resize grow   right 10 px or 10 ppt
+
+        # same bindings, but for the arrow keys
+        bindcode 113 resize shrink left 10 px or 10 ppt
+        bindcode Shift+113 resize grow   left 10 px or 10 ppt
+
+        bindcode 116 resize shrink down 10 px or 10 ppt
+        bindcode Shift+116 resize grow   down 10 px or 10 ppt
+
+        bindcode 111 resize shrink up 10 px or 10 ppt
+        bindcode Shift+111 resize grow   up 10 px or 10 ppt
+
+        bindcode 114 resize shrink right 10 px or 10 ppt
+        bindcode Shift+114 resize grow   right 10 px or 10 ppt
+
+        # back to normal: Enter or Escape
+        bindcode 36 mode "default"
+        bindcode 9 mode "default"
+}
+
+bindcode $mod+27 mode "resize"
diff --git a/include/all.h b/include/all.h
new file mode 100644 (file)
index 0000000..ba582a8
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * This header file includes all relevant files of i3 and the most often used
+ * system header files. This reduces boilerplate (the amount of code duplicated
+ * at the beginning of each source file) and is not significantly slower at
+ * compile-time.
+ *
+ */
+#ifndef _ALL_H
+#define _ALL_H
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <locale.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <glob.h>
+#include <errno.h>
+#include <err.h>
+#include <stdint.h>
+#include <math.h>
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_aux.h>
+#include <xcb/xcb_keysyms.h>
+#include <xcb/xcb_icccm.h>
+
+/* Contains compatibility definitions for old libxcb versions */
+#ifdef XCB_COMPAT
+#include "xcb_compat.h"
+#endif
+
+#include "data.h"
+#include "util.h"
+#include "ipc.h"
+#include "tree.h"
+#include "log.h"
+#include "xcb.h"
+#include "manage.h"
+#include "workspace.h"
+#include "i3.h"
+#include "x.h"
+#include "click.h"
+#include "floating.h"
+#include "config.h"
+#include "handlers.h"
+#include "randr.h"
+#include "xinerama.h"
+#include "con.h"
+#include "load_layout.h"
+#include "render.h"
+#include "window.h"
+#include "match.h"
+#include "cmdparse.h"
+#include "xcursor.h"
+#include "resize.h"
+#include "sighandler.h"
+#include "move.h"
+#include "output.h"
+#include "ewmh.h"
+#include "assignments.h"
+
+#endif
diff --git a/include/assignments.h b/include/assignments.h
new file mode 100644 (file)
index 0000000..f72dd2e
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ */
+#ifndef _ASSIGNMENTS_H
+#define _ASSIGNMENTS_H
+
+/**
+ * Checks the list of assignments for the given window and runs all matching
+ * ones (unless they have already been run for this specific window).
+ *
+ */
+void run_assignments(i3Window *window);
+
+/**
+ * Returns the first matching assignment for the given window.
+ *
+ */
+Assignment *assignment_for(i3Window *window, int type);
+
+#endif
diff --git a/include/atoms.xmacro b/include/atoms.xmacro
new file mode 100644 (file)
index 0000000..5d2ffb1
--- /dev/null
@@ -0,0 +1,33 @@
+xmacro(_NET_SUPPORTED)
+xmacro(_NET_SUPPORTING_WM_CHECK)
+xmacro(_NET_WM_NAME)
+xmacro(_NET_WM_STATE_FULLSCREEN)
+xmacro(_NET_WM_STATE)
+xmacro(_NET_WM_WINDOW_TYPE)
+xmacro(_NET_WM_WINDOW_TYPE_DOCK)
+xmacro(_NET_WM_WINDOW_TYPE_DIALOG)
+xmacro(_NET_WM_WINDOW_TYPE_UTILITY)
+xmacro(_NET_WM_WINDOW_TYPE_TOOLBAR)
+xmacro(_NET_WM_WINDOW_TYPE_SPLASH)
+xmacro(_NET_WM_DESKTOP)
+xmacro(_NET_WM_STRUT_PARTIAL)
+xmacro(_NET_CURRENT_DESKTOP)
+xmacro(_NET_ACTIVE_WINDOW)
+xmacro(_NET_WORKAREA)
+xmacro(WM_PROTOCOLS)
+xmacro(WM_DELETE_WINDOW)
+xmacro(UTF8_STRING)
+xmacro(WM_STATE)
+xmacro(WM_CLIENT_LEADER)
+xmacro(WM_TAKE_FOCUS)
+xmacro(WM_HINTS)
+xmacro(WM_NORMAL_HINTS)
+xmacro(WM_TRANSIENT_FOR)
+xmacro(ATOM)
+xmacro(WINDOW)
+xmacro(WM_NAME)
+xmacro(WM_CLASS)
+xmacro(STRING)
+xmacro(CARDINAL)
+xmacro(I3_SOCKET_PATH)
+xmacro(I3_CONFIG_PATH)
index de977ea4873aaabd12c2a4ca36d0d418e62312af..2de32d045308059495b0e136aa7d72fcffbb5d84 100644 (file)
@@ -1,9 +1,9 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
  *
- * © 2009 Michael Stapelberg and contributors
+ * © 2009-2011 Michael Stapelberg and contributors
  *
  * See file LICENSE for license information.
  *
 #ifndef _CLICK_H
 #define _CLICK_H
 
-int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event);
+/**
+ * The button press X callback. This function determines whether the floating
+ * modifier is pressed and where the user clicked (decoration, border, inside
+ * the window).
+ *
+ * Then, route_click is called on the appropriate con.
+ *
+ */
+int handle_button_press(xcb_button_press_event_t *event);
 
 #endif
diff --git a/include/client.h b/include/client.h
deleted file mode 100644 (file)
index 45b8f4a..0000000
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- */
-#include <xcb/xcb.h>
-
-#include "data.h"
-
-#ifndef _CLIENT_H
-#define _CLIENT_H
-
-/**
- * Removes the given client from the container, either because it will be
- * inserted into another one or because it was unmapped
- *
- */
-void client_remove_from_container(xcb_connection_t *conn, Client *client,
-                                  Container *container,
-                                  bool remove_from_focusstack);
-
-/**
- * Warps the pointer into the given client (in the middle of it, to be
- * specific), therefore selecting it
- *
- */
-void client_warp_pointer_into(xcb_connection_t *conn, Client *client);
-
-/**
- * Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
- *
- */
-void client_kill(xcb_connection_t *conn, Client *window);
-
-/**
- * Checks if the given window class and title match the given client Window
- * title is passed as "normal" string and as UCS-2 converted string for
- * matching _NET_WM_NAME capable clients as well as those using legacy hints.
- *
- */
-bool client_matches_class_name(Client *client, char *to_class, char *to_title,
-                               char *to_title_ucs, int to_title_ucs_len);
-
-/**
- * Enters fullscreen mode for the given client. This is called by toggle_fullscreen
- * and when moving a fullscreen client to another screen.
- *
- */
-void client_enter_fullscreen(xcb_connection_t *conn, Client *client, bool global);
-
-/**
- * Leaves fullscreen mode for the given client. This is called by toggle_fullscreen.
- *
- */
-void client_leave_fullscreen(xcb_connection_t *conn, Client *client);
-
-/**
- * Leaves fullscreen mode for the current client. This is called by toggle_fullscreen.
- *
- */
-void client_leave_fullscreen(xcb_connection_t *conn, Client *client);
-
-/**
- * Toggles fullscreen mode for the given client. It updates the data
- * structures and reconfigures (= resizes/moves) the client and its frame to
- * the full size of the screen. When leaving fullscreen, re-rendering the
- * layout is forced.
- *
- */
-void client_toggle_fullscreen(xcb_connection_t *conn, Client *client);
-
-/**
- * Like client_toggle_fullscreen(), but putting it in global fullscreen-mode.
- *
- */
-void client_toggle_fullscreen_global(xcb_connection_t *conn, Client *client);
-
-/**
- * Sets the position of the given client in the X stack to the highest (tiling
- * layer is always on the same position, so this doesn’t matter) below the
- * first floating client, so that floating windows are always on top.
- *
- */
-void client_set_below_floating(xcb_connection_t *conn, Client *client);
-
-/**
- * Returns true if the client is floating. Makes the code more beatiful, as
- * floating is not simply a boolean, but also saves whether the user selected
- * the current state or whether it was automatically set.
- *
- */
-bool client_is_floating(Client *client);
-
-/**
- * Change the border type for the given client to normal (n), 1px border (p) or
- * completely borderless (b).
- *
- */
-void client_change_border(xcb_connection_t *conn, Client *client, char border_type);
-
-/**
- * Change the border type for the given client to normal (n), 1px border (p) or
- * completely borderless (b) without actually re-rendering the layout. Useful
- * for calling it when initializing a new client.
- *
- */
-bool client_init_border(xcb_connection_t *conn, Client *client, char border_type);
-
-/**
- * Unmap the client, correctly setting any state which is needed.
- *
- */
-void client_unmap(xcb_connection_t *conn, Client *client);
-
-/**
- * Map the client, correctly restoring any state needed.
- *
- */
-void client_map(xcb_connection_t *conn, Client *client);
-
-/**
- * Set the given mark for this client. Used for jumping to the client
- * afterwards (like m<mark> and '<mark> in vim).
- *
- */
-void client_mark(xcb_connection_t *conn, Client *client, const char *mark);
-
-/**
- * Returns the minimum height of a specific window. The height is calculated
- * by using 2 pixels (for the client window itself), possibly padding this to
- * comply with the client’s base_height and then adding the decoration height.
- *
- */
-uint32_t client_min_height(Client *client);
-
-/**
- * See client_min_height.
- *
- */
-uint32_t client_min_width(Client *client);
-
-/**
- * Pretty-prints the client’s information into the logfile.
- *
- */
-#define CLIENT_LOG(client) do { \
-                DLOG("Window: frame 0x%08x, child 0x%08x\n", client->frame, client->child); \
-        } while (0)
-
-#endif
diff --git a/include/cmdparse.h b/include/cmdparse.h
new file mode 100644 (file)
index 0000000..09d56ba
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef _CMDPARSE_H
+#define _CMDPARSE_H
+
+char *parse_cmd(const char *new);
+
+#endif
diff --git a/include/commands.h b/include/commands.h
deleted file mode 100644 (file)
index fbad973..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * (c) 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- */
-#ifndef _COMMANDS_H
-#define _COMMANDS_H
-
-#include <xcb/xcb.h>
-
-bool focus_window_in_container(xcb_connection_t *conn, Container *container,
-                               direction_t direction);
-
-/** Parses a command, see file CMDMODE for more information */
-void parse_command(xcb_connection_t *conn, const char *command);
-
-#endif
diff --git a/include/con.h b/include/con.h
new file mode 100644 (file)
index 0000000..6ce7bf8
--- /dev/null
@@ -0,0 +1,215 @@
+#ifndef _CON_H
+#define _CON_H
+
+/**
+ * Create a new container (and attach it to the given parent, if not NULL).
+ * This function initializes the data structures and creates the appropriate
+ * X11 IDs using x_con_init().
+ *
+ */
+Con *con_new(Con *parent, i3Window *window);
+
+/**
+ * Sets input focus to the given container. Will be updated in X11 in the next
+ * run of x_push_changes().
+ *
+ */
+void con_focus(Con *con);
+
+/**
+ * Returns true when this node is a leaf node (has no children)
+ *
+ */
+bool con_is_leaf(Con *con);
+
+/**
+ * Returns true if this node accepts a window (if the node swallows windows,
+ * it might already have swallowed enough and cannot hold any more).
+ *
+ */
+bool con_accepts_window(Con *con);
+
+/**
+ * Gets the output container (first container with CT_OUTPUT in hierarchy) this
+ * node is on.
+ *
+ */
+Con *con_get_output(Con *con);
+
+/**
+ * Gets the workspace container this node is on.
+ *
+ */
+Con *con_get_workspace(Con *con);
+
+/**
+ * Searches parenst of the given 'con' until it reaches one with the specified
+ * 'orientation'. Aborts when it comes across a floating_con.
+ *
+ */
+Con *con_parent_with_orientation(Con *con, orientation_t orientation);
+
+/**
+ * Returns the first fullscreen node below this node.
+ *
+ */
+Con *con_get_fullscreen_con(Con *con, int fullscreen_mode);
+
+/**
+ * Returns true if the node is floating.
+ *
+ */
+bool con_is_floating(Con *con);
+
+/**
+ * Checks if the given container is either floating or inside some floating
+ * container. It returns the FLOATING_CON container.
+ *
+ */
+Con *con_inside_floating(Con *con);
+
+/**
+ * Returns the container with the given client window ID or NULL if no such
+ * container exists.
+ *
+ */
+Con *con_by_window_id(xcb_window_t window);
+
+/**
+ * Returns the container with the given frame ID or NULL if no such container
+ * exists.
+ *
+ */
+Con *con_by_frame_id(xcb_window_t frame);
+
+/**
+ * Returns the first container below 'con' which wants to swallow this window
+ * TODO: priority
+ *
+ */
+Con *con_for_window(Con *con, i3Window *window, Match **store_match);
+
+/**
+ * Returns the number of children of this container.
+ *
+ */
+int con_num_children(Con *con);
+
+/**
+ * Attaches the given container to the given parent. This happens when moving
+ * a container or when inserting a new container at a specific place in the
+ * tree.
+ *
+ * ignore_focus is to just insert the Con at the end (useful when creating a
+ * new split container *around* some containers, that is, detaching and
+ * attaching them in order without wanting to mess with the focus in between).
+ *
+ */
+void con_attach(Con *con, Con *parent, bool ignore_focus);
+
+/**
+ * Detaches the given container from its current parent
+ *
+ */
+void con_detach(Con *con);
+
+/**
+ * Updates the percent attribute of the children of the given container. This
+ * function needs to be called when a window is added or removed from a
+ * container.
+ *
+ */
+void con_fix_percent(Con *con);
+
+/**
+ * Toggles fullscreen mode for the given container. Fullscreen mode will not be
+ * entered when there already is a fullscreen container on this workspace.
+ *
+ */
+void con_toggle_fullscreen(Con *con, int fullscreen_mode);
+
+/**
+ * Moves the given container to the currently focused container on the given
+ * workspace.
+ * TODO: is there a better place for this function?
+ *
+ */
+void con_move_to_workspace(Con *con, Con *workspace);
+
+/**
+ * Returns the orientation of the given container (for stacked containers,
+ * vertical orientation is used regardless of the actual orientation of the
+ * container).
+ *
+ */
+int con_orientation(Con *con);
+
+/**
+ * Returns the container which will be focused next when the given container
+ * is not available anymore. Called in tree_close and con_move_to_workspace
+ * to properly restore focus.
+ *
+ */
+Con *con_next_focused(Con *con);
+
+/**
+ * Get the next/previous container in the specified orientation. This may
+ * travel up until it finds a container with suitable orientation.
+ *
+ */
+Con *con_get_next(Con *con, char way, orientation_t orientation);
+
+/**
+ * Returns the focused con inside this client, descending the tree as far as
+ * possible. This comes in handy when attaching a con to a workspace at the
+ * currently focused position, for example.
+ *
+ */
+Con *con_descend_focused(Con *con);
+
+/**
+ * Returns the focused con inside this client, descending the tree as far as
+ * possible. This comes in handy when attaching a con to a workspace at the
+ * currently focused position, for example.
+ *
+ * Works like con_descend_focused but considers only tiling cons.
+ *
+ */
+Con *con_descend_tiling_focused(Con *con);
+
+/**
+ * Returns a "relative" Rect which contains the amount of pixels that need to
+ * be added to the original Rect to get the final position (obviously the
+ * amount of pixels for normal, 1pixel and borderless are different).
+ *
+ */
+Rect con_border_style_rect(Con *con);
+
+/**
+ * Use this function to get a container’s border style. This is important
+ * because when inside a stack, the border style is always BS_NORMAL.
+ * For tabbed mode, the same applies, with one exception: when the container is
+ * borderless and the only element in the tabbed container, the border is not
+ * rendered.
+ *
+ * For children of a CT_DOCKAREA, the border style is always none.
+ *
+ */
+int con_border_style(Con *con);
+
+/**
+ * This function changes the layout of a given container. Use it to handle
+ * special cases like changing a whole workspace to stacked/tabbed (creates a
+ * new split container before).
+ *
+ */
+void con_set_layout(Con *con, int layout);
+
+/**
+ * Determines the minimum size of the given con by looking at its children (for
+ * split/stacked/tabbed cons). Will be called when resizing floating cons
+ *
+ */
+Rect con_minimum_size(Con *con);
+
+#endif
index b19589bda2a4048c1851b0694eceb75b6625b684..1021a612e7a98917a211a61d037b688e255c6861 100644 (file)
@@ -22,6 +22,7 @@
 #include "i3.h"
 
 typedef struct Config Config;
+extern char *current_configpath;
 extern Config config;
 extern SLIST_HEAD(modes_head, Mode) modes;
 
@@ -31,10 +32,14 @@ extern SLIST_HEAD(modes_head, Mode) modes;
  *
  */
 struct context {
+        bool has_errors;
+
         int line_number;
         char *line_copy;
         const char *filename;
 
+        char *compact_error;
+
         /* These are the same as in YYLTYPE */
         int first_column;
         int last_column;
@@ -84,14 +89,18 @@ struct Mode {
  */
 struct Config {
         const char *terminal;
-        const char *font;
+        i3Font font;
 
-        const char *ipc_socket_path;
+        char *ipc_socket_path;
+        const char *restart_state_path;
 
-        int container_mode;
+        int default_layout;
         int container_stack_limit;
         int container_stack_limit_value;
 
+        /** Default orientation for new containers */
+        int default_orientation;
+
         /** By default, focus follows mouse. If the user explicitly wants to
          * turn this off (and instead rely only on the keyboard for changing
          * focus), we allow him to do this with this relatively special option.
@@ -104,7 +113,18 @@ struct Config {
          * comes with i3. Thus, you can turn it off entirely. */
         bool disable_workspace_bar;
 
-        const char *default_border;
+        /** Think of the following layout: Horizontal workspace with a tabbed
+         * con on the left of the screen and a terminal on the right of the
+         * screen. You are in the second container in the tabbed container and
+         * focus to the right. By default, i3 will set focus to the terminal on
+         * the right. If you are in the first container in the tabbed container
+         * however, focusing to the left will wrap. This option forces i3 to
+         * always wrap, which will result in you having to use "focus parent"
+         * more often. */
+        bool force_focus_wrapping;
+
+        /** The default border style for new windows. */
+        border_style_t default_border;
 
         /** The modifier which needs to be pressed in combination with your mouse
          * buttons to do things with floating windows (move, resize) */
@@ -123,21 +143,13 @@ struct Config {
                 struct Colortriple unfocused;
                 struct Colortriple urgent;
         } bar;
-};
 
-/**
- * This function resolves ~ in pathnames.
- * It may resolve wildcards in the first part of the path, but if no match
- * or multiple matches are found, it just returns a copy of path as given.
- *
- */
-char *resolve_tilde(const char *path);
-
-/**
- * Checks if the given path exists by calling stat().
- *
- */
-bool path_exists(const char *path);
+        /** What should happen when a new popup is opened during fullscreen mode */
+        enum {
+                PDF_LEAVE_FULLSCREEN = 0,
+                PDF_IGNORE = 1
+        } popup_during_fullscreen;
+};
 
 /**
  * Reads the configuration from ~/.i3/config or /etc/i3/config if not found.
@@ -171,7 +183,7 @@ void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch);
  * Switches the key bindings to the given mode, if the mode exists
  *
  */
-void switch_mode(xcb_connection_t *conn, const char *new_mode);
+void switch_mode(const char *new_mode);
 
 /**
  * Returns a pointer to the Binding with the specified modifiers and keycode
@@ -180,6 +192,17 @@ void switch_mode(xcb_connection_t *conn, const char *new_mode);
  */
 Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode);
 
+/**
+ * Kills the configerror i3-nagbar process, if any.
+ *
+ * Called when reloading/restarting.
+ *
+ * If wait_for_it is set (restarting), this function will waitpid(), otherwise,
+ * ev is assumed to handle it (reloading).
+ *
+ */
+void kill_configerror_nagbar(bool wait_for_it);
+
 /* prototype for src/cfgparse.y */
 void parse_file(const char *f);
 
diff --git a/include/container.h b/include/container.h
deleted file mode 100644 (file)
index 7893850..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- */
-#include "data.h"
-
-#ifndef _CONTAINER_H
-#define _CONTAINER_H
-
-/**
- * Returns the mode of the given container (or MODE_DEFAULT if a NULL pointer
- * was passed in order to save a few explicit checks in other places). If
- * for_frame was set to true, the special case of having exactly one client
- * in a container is handled so that MODE_DEFAULT is returned. For some parts
- * of the rendering, this is interesting, other parts need the real mode.
- *
- */
-int container_mode(Container *con, bool for_frame);
-
-#endif
index a8b31d3b3e81a66b54f17c2aea6f70ed2fd6962d..4dc379c2cd727452a6a8b0d7c936d9e0592ff60c 100644 (file)
@@ -1,16 +1,12 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * include/data.h: This file defines all data structures used by i3
  *
  */
-#include <xcb/xcb.h>
 #include <xcb/randr.h>
 #include <xcb/xcb_atom.h>
 #include <stdbool.h>
  *
  * Let’s start from the biggest to the smallest:
  *
- * - An Output is a physical output on your graphics driver. Outputs which
- *   are currently in use have (output->active == true). Each output has a
- *   position and a mode. An output usually corresponds to one connected
- *   screen (except if you are running multiple screens in clone mode).
- *
- * - Each Output contains Workspaces. The concept is known from various
- *   other window managers.  Basically, a workspace is a specific set of
- *   windows, usually grouped thematically (irc, www, work, …). You can switch
- *   between these.
- *
- * - Each Workspace has a table, which is our layout abstraction. You manage
- *   your windows by moving them around in your table. It grows as necessary.
- *
- * - Each cell of the table has a container, which can be in default or
- *   stacking mode. In default mode, each client is given equally much space
- *   in the container. In stacking mode, only one client is shown at a time,
- *   but all the titlebars are rendered at the top.
- *
- * - Inside the container are clients, which is X11-speak for a window.
+ * TODO
  *
  */
 
 /* Forward definitions */
-typedef struct Cell Cell;
 typedef struct Font i3Font;
-typedef struct Container Container;
-typedef struct Client Client;
 typedef struct Binding Binding;
-typedef struct Workspace Workspace;
 typedef struct Rect Rect;
 typedef struct xoutput Output;
+typedef struct Con Con;
+typedef struct Match Match;
+typedef struct Assignment Assignment;
+typedef struct Window i3Window;
+
 
 /******************************************************************************
  * Helper types
  *****************************************************************************/
 typedef enum { D_LEFT, D_RIGHT, D_UP, D_DOWN } direction_t;
+typedef enum { NO_ORIENTATION = 0, HORIZ, VERT } orientation_t;
+typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_1PIXEL = 2 } border_style_t;
+
+/** parameter to specify whether tree_close() and x_window_kill() should kill
+ * only this specific window or the whole X11 client */
+typedef enum { DONT_KILL_WINDOW = 0, KILL_WINDOW = 1, KILL_CLIENT = 2 } kill_window_t;
 
 enum {
-        BIND_NONE = 0,
-        BIND_SHIFT = XCB_MOD_MASK_SHIFT,        /* (1 << 0) */
-        BIND_CONTROL = XCB_MOD_MASK_CONTROL,    /* (1 << 2) */
-        BIND_MOD1 = XCB_MOD_MASK_1,             /* (1 << 3) */
-        BIND_MOD2 = XCB_MOD_MASK_2,             /* (1 << 4) */
-        BIND_MOD3 = XCB_MOD_MASK_3,             /* (1 << 5) */
-        BIND_MOD4 = XCB_MOD_MASK_4,             /* (1 << 6) */
-        BIND_MOD5 = XCB_MOD_MASK_5,             /* (1 << 7) */
-        BIND_MODE_SWITCH = (1 << 8)
+    BIND_NONE = 0,
+    BIND_SHIFT = XCB_MOD_MASK_SHIFT,        /* (1 << 0) */
+    BIND_CONTROL = XCB_MOD_MASK_CONTROL,    /* (1 << 2) */
+    BIND_MOD1 = XCB_MOD_MASK_1,             /* (1 << 3) */
+    BIND_MOD2 = XCB_MOD_MASK_2,             /* (1 << 4) */
+    BIND_MOD3 = XCB_MOD_MASK_3,             /* (1 << 5) */
+    BIND_MOD4 = XCB_MOD_MASK_4,             /* (1 << 6) */
+    BIND_MOD5 = XCB_MOD_MASK_5,             /* (1 << 7) */
+    BIND_MODE_SWITCH = (1 << 8)
 };
 
 /**
@@ -86,234 +71,120 @@ enum {
  * _NET_WM_WORKAREA hint). Not declaring x/y as int32_t saves us a lot of
  * typecasts.
  *
- * Note that x and y can contain signed values in some cases (for example when
- * used for the coordinates of a window, which can be set outside of the
- * visible area, but not when specifying the position of a workspace for the
- * _NET_WM_WORKAREA hint). Not declaring x/y as int32_t saves us a lot of
- * typecasts.
- *
  */
 struct Rect {
-        uint32_t x;
-        uint32_t y;
-        uint32_t width;
-        uint32_t height;
+    uint32_t x;
+    uint32_t y;
+    uint32_t width;
+    uint32_t height;
 } __attribute__((packed));
 
 /**
- * Defines a position in the table
+ * Stores the reserved pixels on each screen edge read from a
+ * _NET_WM_STRUT_PARTIAL.
  *
  */
-struct Cell {
-        int row;
-        int column;
+struct reservedpx {
+    uint32_t left;
+    uint32_t right;
+    uint32_t top;
+    uint32_t bottom;
 };
 
 /**
- * Used for the cache of colorpixels.
+ * Stores a width/height pair, used as part of deco_render_params to check
+ * whether the rects width/height have changed.
  *
  */
-struct Colorpixel {
-        uint32_t pixel;
-        char *hex;
-        SLIST_ENTRY(Colorpixel) colorpixels;
+struct width_height {
+    uint32_t w;
+    uint32_t h;
 };
 
-struct Cached_Pixmap {
-        xcb_pixmap_t id;
-
-        /* We’re going to paint on it, so a graphics context will be needed */
-        xcb_gcontext_t gc;
-
-        /* The rect with which the pixmap was created */
-        Rect rect;
-
-        /* The rect of the object to which this pixmap belongs. Necessary to
-         * find out when we need to re-create the pixmap. */
-        Rect *referred_rect;
-
-        xcb_drawable_t referred_drawable;
+/**
+ * Stores the parameters for rendering a window decoration. This structure is
+ * cached in every Con and no re-rendering will be done if the parameters have
+ * not changed (only the pixmaps will be copied).
+ *
+ */
+struct deco_render_params {
+    struct Colortriple *color;
+    int border_style;
+    struct width_height con_rect;
+    struct width_height con_window_rect;
+    Rect con_deco_rect;
+    uint32_t background;
+    bool con_is_leaf;
+    xcb_font_t font;
 };
 
 /**
- * Contains data for the windows needed to draw the titlebars on in stacking
- * mode
+ * Stores which workspace (by name) goes to which output.
  *
  */
-struct Stack_Window {
-        xcb_window_t window;
-        struct Cached_Pixmap pixmap;
-        Rect rect;
+struct Workspace_Assignment {
+    char *name;
+    char *output;
 
-        /** Backpointer to the container this stack window is in */
-        Container *container;
-
-        SLIST_ENTRY(Stack_Window) stack_windows;
+    TAILQ_ENTRY(Workspace_Assignment) ws_assignments;
 };
 
 struct Ignore_Event {
-        int sequence;
-        time_t added;
+    int sequence;
+    int response_type;
+    time_t added;
 
-        SLIST_ENTRY(Ignore_Event) ignore_events;
-};
-
-/**
- * Emulates the behaviour of tables of libxcb-wm, which in libxcb 0.3.4
- * suddenly vanished.
- *
- */
-struct keyvalue_element {
-        uint32_t key;
-        void *value;
-        TAILQ_ENTRY(keyvalue_element) elements;
+    SLIST_ENTRY(Ignore_Event) ignore_events;
 };
 
 /******************************************************************************
  * Major types
  *****************************************************************************/
 
-/**
- * The concept of Workspaces is known from various other window
- * managers. Basically, a workspace is a specific set of windows, usually
- * grouped thematically (irc, www, work, …). You can switch between these.
- *
- */
-struct Workspace {
-        /** Number of this workspace, starting from 0 */
-        int num;
-
-        /** Name of the workspace (in UTF-8) */
-        char *utf8_name;
-
-        /** Name of the workspace (in UCS-2) */
-        char *name;
-
-        /** Length of the workspace’s name (in glyphs) */
-        int name_len;
-
-        /** Width of the workspace’s name (in pixels) rendered in config.font */
-        int text_width;
-
-        /** x, y, width, height */
-        Rect rect;
-
-        /** table dimensions */
-        int cols;
-        /** table dimensions */
-        int rows;
-
-        /** These are stored here only while this workspace is _not_ shown
-         * (see show_workspace()) */
-        int current_row;
-        /** These are stored here only while this workspace is _not_ shown
-         * (see show_workspace()) */
-        int current_col;
-
-        /** Should clients on this workspace be automatically floating? */
-        bool auto_float;
-        /** Are the floating clients on this workspace currently hidden? */
-        bool floating_hidden;
-
-        /** The name of the RandR output this screen should be on */
-        char *preferred_output;
-
-        /** True if any client on this workspace has its urgent flag set */
-        bool urgent;
-
-        /** the client who is started in fullscreen mode on this workspace,
-         * NULL if there is none */
-        Client *fullscreen_client;
-
-        /** The focus stack contains the clients in the correct order of focus
-           so that the focus can be reverted correctly when a client is
-           closed */
-        SLIST_HEAD(focus_stack_head, Client) focus_stack;
-
-        /** This tail queue contains the floating clients in order of when
-         * they were first set to floating (new floating clients are just
-         * appended) */
-        TAILQ_HEAD(floating_clients_head, Client) floating_clients;
-
-        /** Backpointer to the output this workspace is on */
-        Output *output;
-
-        /** This is a two-dimensional dynamic array of
-         * Container-pointers. I’ve always wanted to be a three-star
-         * programmer :) */
-        Container ***table;
-
-        /** width_factor and height_factor contain the amount of space
-         * (percentage) a column/row has of all the space which is available
-         * for resized windows. This ensures that non-resized windows (newly
-         * opened, for example) have the same size as always */
-        float *width_factor;
-        float *height_factor;
-
-        TAILQ_ENTRY(Workspace) workspaces;
-};
-
 /**
  * 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)
  *
  */
 struct Binding {
-        /** Symbol the user specified in configfile, if any. This needs to be
-         * stored with the binding to be able to re-convert it into a keycode
-         * if the keyboard mapping changes (using Xmodmap for example) */
-        char *symbol;
+    /** Symbol the user specified in configfile, if any. This needs to be
+     * stored with the binding to be able to re-convert it into a keycode
+     * if the keyboard mapping changes (using Xmodmap for example) */
+    char *symbol;
 
-        /** Only in use if symbol != NULL. Gets set to the value to which the
-         * symbol got translated when binding. Useful for unbinding and
-         * checking which binding was used when a key press event comes in.
-         *
-         * This is an array of number_keycodes size. */
-        xcb_keycode_t *translated_to;
+    /** Only in use if symbol != NULL. Gets set to the value to which the
+     * symbol got translated when binding. Useful for unbinding and
+     * checking which binding was used when a key press event comes in.
+     *
+     * This is an array of number_keycodes size. */
+    xcb_keycode_t *translated_to;
 
-        uint32_t number_keycodes;
+    uint32_t number_keycodes;
 
-        /** Keycode to bind */
-        uint32_t keycode;
+    /** Keycode to bind */
+    uint32_t keycode;
 
-        /** Bitmask consisting of BIND_MOD_1, BIND_MODE_SWITCH, … */
-        uint32_t mods;
+    /** Bitmask consisting of BIND_MOD_1, BIND_MODE_SWITCH, … */
+    uint32_t mods;
 
-        /** Command, like in command mode */
-        char *command;
+    /** Command, like in command mode */
+    char *command;
 
-        TAILQ_ENTRY(Binding) bindings;
+    TAILQ_ENTRY(Binding) bindings;
 };
 
 /**
- * Holds a command specified by an exec-line in the config (see src/config.c)
+ * Holds a command specified by either an:
+ * - exec-line
+ * - exec_always-line
+ * in the config (see src/config.c)
  *
  */
 struct Autostart {
-        /** Command, like in command mode */
-        char *command;
-        TAILQ_ENTRY(Autostart) autostarts;
-};
-
-/**
- * Holds an assignment for a given window class/title to a specific workspace
- * (see src/config.c)
- *
- */
-struct Assignment {
-        char *windowclass_title;
-        /** floating is true if this was an assignment to the special
-         * workspace "~".  Matching clients will be put into floating mode
-         * automatically. */
-        enum {
-                ASSIGN_FLOATING_NO,   /* don’t float, but put on a workspace */
-                ASSIGN_FLOATING_ONLY, /* float, but don’t assign on a workspace */
-                ASSIGN_FLOATING       /* float and put on a workspace */
-        } floating;
-
-        /** The number of the workspace to assign to. */
-        int workspace;
-        TAILQ_ENTRY(Assignment) assignments;
+    /** Command, like in command mode */
+    char *command;
+    TAILQ_ENTRY(Autostart) autostarts;
+    TAILQ_ENTRY(Autostart) autostarts_always;
 };
 
 /**
@@ -323,184 +194,12 @@ struct Assignment {
  *
  */
 struct Font {
-        /** The name of the font, that is what the pattern resolves to */
-        char *name;
-        /** A copy of the pattern to build a cache */
-        char *pattern;
-        /** The height of the font, built from font_ascent + font_descent */
-        int height;
-        /** The xcb-id for the font */
-        xcb_font_t id;
-
-        TAILQ_ENTRY(Font) fonts;
+    /** The height of the font, built from font_ascent + font_descent */
+    int height;
+    /** The xcb-id for the font */
+    xcb_font_t id;
 };
 
-/**
- * A client is X11-speak for a window.
- *
- */
-struct Client {
-        /** initialized will be set to true if the client was fully
-         * initialized by manage_window() and all functions can be used
-         * normally */
-        bool initialized;
-
-        /** if you set a client to floating and set it back to managed, it
-         * does remember its old position and *tries* to get back there */
-        Cell old_position;
-
-        /** Backpointer. A client is inside a container */
-        Container *container;
-        /** Because dock clients don’t have a container, we have this
-         * workspace-backpointer */
-        Workspace *workspace;
-
-        /** x, y, width, height of the frame */
-        Rect rect;
-        /** Position in floating mode and in tiling mode are saved
-         * separately */
-        Rect floating_rect;
-        /** x, y, width, height of the child (relative to its frame) */
-        Rect child_rect;
-
-        /** contains the size calculated from the hints set by the window or 0
-         * if the client did not send any hints */
-        int proportional_height;
-        int proportional_width;
-
-        int base_height;
-        int base_width;
-
-        /** The amount of pixels which X will draw around the client. */
-        int border_width;
-
-        /** contains the minimum increment size as specified for the window
-         * (in pixels). */
-        int width_increment;
-        int height_increment;
-
-        /** Height which was determined by reading the _NET_WM_STRUT_PARTIAL
-         * top/bottom of the screen reservation */
-        int desired_height;
-
-        /** Name (= window title) */
-        char *name;
-        /** name_len stores the real string length (glyphs) of the window
-         * title if the client uses _NET_WM_NAME. Otherwise, it is set to -1
-         * to indicate that name should be just passed to X as 8-bit string
-         * and therefore will not be rendered correctly. This behaviour is to
-         * support legacy applications which do not set _NET_WM_NAME */
-        int name_len;
-        /** This will be set to true as soon as the first _NET_WM_NAME comes
-         * in. If set to true, legacy window names are ignored. */
-        bool uses_net_wm_name;
-
-        /** Holds the WM_CLASS (which consists of two strings, the instance
-         * and the class), useful for matching the client in commands */
-        char *window_class_instance;
-        char *window_class_class;
-
-        /** Holds the client’s mark, for vim-like jumping */
-        char *mark;
-
-        /** Holds the xcb_window_t (just an ID) for the leader window (logical
-         * parent for toolwindows and similar floating windows) */
-        xcb_window_t leader;
-
-        /** fullscreen is pretty obvious */
-        bool fullscreen;
-
-        /** floating? (= not in tiling layout) This cannot be simply a bool
-         * because we want to keep track of whether the status was set by the
-         * application (by setting WM_CLASS to tools for example) or by the
-         * user. The user’s choice overwrites automatic mode, of course. The
-         * order of the values is important because we check with >=
-         * FLOATING_AUTO_ON if a client is floating. */
-        enum { FLOATING_AUTO_OFF = 0, FLOATING_USER_OFF = 1, FLOATING_AUTO_ON = 2, FLOATING_USER_ON = 3 } floating;
-
-        /** Ensure TITLEBAR_TOP maps to 0 because we use calloc for
-         * initialization later */
-        enum { TITLEBAR_TOP = 0, TITLEBAR_LEFT, TITLEBAR_RIGHT, TITLEBAR_BOTTOM, TITLEBAR_OFF } titlebar_position;
-
-        /** Contains a bool specifying whether this window should not be drawn
-         * with the usual decorations */
-        bool borderless;
-
-        /** If a client is set as a dock, it is placed at the very bottom of
-         * the screen and its requested size is used */
-        bool dock;
-
-        /** True if the client set the urgency flag in its WM_HINTS property */
-        bool urgent;
-
-        /* After leaving fullscreen mode, a client needs to be reconfigured
-         * (configuration = setting X, Y, width and height). By setting the
-         * force_reconfigure flag, render_layout() will reconfigure the
-         * client. */
-        bool force_reconfigure;
-
-        /* When reparenting a window, an unmap-notify is sent. As we delete
-         * windows when they’re unmapped, we need to ignore that
-         * one. Therefore, this flag is set when reparenting. */
-        bool awaiting_useless_unmap;
-
-        /* XCB contexts */
-        xcb_window_t frame;             /**< Our window: The frame around the
-                                         * client */
-        xcb_gcontext_t titlegc;         /**< The titlebar’s graphic context
-                                         * inside the frame */
-        xcb_window_t child;             /**< The client’s window */
-
-        /** The following entry provides the necessary list pointers to use
-         * Client with LIST_* macros */
-        CIRCLEQ_ENTRY(Client) clients;
-        SLIST_ENTRY(Client) dock_clients;
-        SLIST_ENTRY(Client) focus_clients;
-        TAILQ_ENTRY(Client) floating_clients;
-};
-
-/**
- * A container is either in default, stacking or tabbed mode. There is one for
- * each cell of the table.
- *
- */
-struct Container {
-        /* Those are speaking for themselves: */
-        Client *currently_focused;
-        int colspan;
-        int rowspan;
-
-        /* Position of the container inside our table */
-        int row;
-        int col;
-        /* Xinerama: X/Y of the container */
-        int x;
-        int y;
-        /* Width/Height of the container. Changeable by the user */
-        int width;
-        int height;
-
-        /* When in stacking mode, we draw the titlebars of each client onto a
-         * separate window */
-        struct Stack_Window stack_win;
-
-        /* Backpointer to the workspace this container is in */
-        Workspace *workspace;
-
-        /* Ensure MODE_DEFAULT maps to 0 because we use calloc for
-         * initialization later */
-        enum { MODE_DEFAULT = 0, MODE_STACK, MODE_TABBED } mode;
-
-        /* When in stacking, one can either have unlimited windows inside the
-         * container or set a limit for the rows or columns the stack window
-         * should display to use the screen more efficiently. */
-        enum { STACK_LIMIT_NONE = 0, STACK_LIMIT_COLS, STACK_LIMIT_ROWS } stack_limit;
-
-        /* The number of columns or rows to limit to, see stack_limit */
-        int stack_limit_value;
-
-        CIRCLEQ_HEAD(client_head, Client) clients;
-};
 
 /**
  * An Output is a physical output on your graphics driver. Outputs which
@@ -510,35 +209,259 @@ struct Container {
  *
  */
 struct xoutput {
-        /** Output id, so that we can requery the output directly later */
-        xcb_randr_output_t id;
-        /** Name of the output */
-        char *name;
+    /** Output id, so that we can requery the output directly later */
+    xcb_randr_output_t id;
+    /** Name of the output */
+    char *name;
+
+    /** Pointer to the Con which represents this output */
+    Con *con;
+
+    /** Whether the output is currently active (has a CRTC attached with a
+     * valid mode) */
+    bool active;
+
+    /** Internal flags, necessary for querying RandR screens (happens in
+     * two stages) */
+    bool changed;
+    bool to_be_disabled;
+    bool primary;
+
+    /** x, y, width, height */
+    Rect rect;
+
+#if 0
+    /** The bar window */
+    xcb_window_t bar;
+    xcb_gcontext_t bargc;
+
+    /** Contains all clients with _NET_WM_WINDOW_TYPE ==
+     * _NET_WM_WINDOW_TYPE_DOCK */
+    SLIST_HEAD(dock_clients_head, Client) dock_clients;
+#endif
+
+    TAILQ_ENTRY(xoutput) outputs;
+};
 
-        /** Whether the output is currently active (has a CRTC attached with a
-         * valid mode) */
-        bool active;
+struct Window {
+    xcb_window_t id;
 
-        /** Internal flags, necessary for querying RandR screens (happens in
-         * two stages) */
-        bool changed;
-        bool to_be_disabled;
+    /** Holds the xcb_window_t (just an ID) for the leader window (logical
+     * parent for toolwindows and similar floating windows) */
+    xcb_window_t leader;
+    xcb_window_t transient_for;
 
-        /** Current workspace selected on this virtual screen */
-        Workspace *current_workspace;
+    char *class_class;
+    char *class_instance;
 
-        /** x, y, width, height */
-        Rect rect;
+    /** The name of the window as it will be passed to X11 (in UCS2 if the
+     * application supports _NET_WM_NAME, in COMPOUND_TEXT otherwise). */
+    char *name_x;
 
-        /** The bar window */
-        xcb_window_t bar;
-        xcb_gcontext_t bargc;
+    /** Flag to force re-rendering the decoration upon changes */
+    bool name_x_changed;
 
-        /** Contains all clients with _NET_WM_WINDOW_TYPE ==
-         * _NET_WM_WINDOW_TYPE_DOCK */
-        SLIST_HEAD(dock_clients_head, Client) dock_clients;
+    /** The name of the window as used in JSON (in UTF-8 if the application
+     * supports _NET_WM_NAME, in COMPOUND_TEXT otherwise) */
+    char *name_json;
+
+    /** The length of the name in glyphs (not bytes) */
+    int name_len;
+
+    /** Whether the application used _NET_WM_NAME */
+    bool uses_net_wm_name;
+
+    /** Whether the application needs to receive WM_TAKE_FOCUS */
+    bool needs_take_focus;
+
+    /** Whether the window says it is a dock window */
+    enum { W_NODOCK = 0, W_DOCK_TOP = 1, W_DOCK_BOTTOM = 2 } dock;
+
+    /** Pixels the window reserves. left/right/top/bottom */
+    struct reservedpx reserved;
+
+    /** Pointers to the Assignments which were already ran for this Window
+     * (assignments run only once) */
+    uint32_t nr_assignments;
+    Assignment **ran_assignments;
+};
+
+struct Match {
+    char *title;
+    int title_len;
+    char *application;
+    char *class;
+    char *instance;
+    char *mark;
+    enum {
+        M_DONTCHECK = -1,
+        M_NODOCK = 0,
+        M_DOCK_ANY = 1,
+        M_DOCK_TOP = 2,
+        M_DOCK_BOTTOM = 3
+    } dock;
+    xcb_window_t id;
+    Con *con_id;
+    enum { M_ANY = 0, M_TILING, M_FLOATING } floating;
+
+    /* Where the window looking for a match should be inserted:
+     *
+     * M_HERE   = the matched container will be replaced by the window
+     *            (layout saving)
+     * M_ASSIGN_WS = the matched container will be inserted in the target_ws.
+     * M_BELOW  = the window will be inserted as a child of the matched container
+     *            (dockareas)
+     *
+     */
+    enum { M_HERE = 0, M_ASSIGN_WS, M_BELOW } insert_where;
+
+    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()).
+ *
+ */
+struct Assignment {
+    /** type of this assignment:
+     *
+     * A_COMMAND = run the specified command for the matching window
+     * A_TO_WORKSPACE = assign the matching window to the specified workspace
+     * A_TO_OUTPUT = assign the matching window to the specified output
+     *
+     * While the type is a bitmask, only one value can be set at a time. It is
+     * a bitmask to allow filtering for multiple types, for example in the
+     * assignment_for() function.
+     *
+     */
+    enum {
+        A_ANY          = 0,
+        A_COMMAND      = (1 << 0),
+        A_TO_WORKSPACE = (1 << 1),
+        A_TO_OUTPUT    = (1 << 2)
+    } type;
+
+    /** the criteria to check if a window matches */
+    Match match;
+
+    /** destination workspace/output/command, depending on the type */
+    union {
+        char *command;
+        char *workspace;
+        char *output;
+    } dest;
+
+    TAILQ_ENTRY(Assignment) assignments;
+};
 
-        TAILQ_ENTRY(xoutput) outputs;
+struct Con {
+    bool mapped;
+    enum {
+        CT_ROOT = 0,
+        CT_OUTPUT = 1,
+        CT_CON = 2,
+        CT_FLOATING_CON = 3,
+        CT_WORKSPACE = 4,
+        CT_DOCKAREA = 5
+    } type;
+    orientation_t orientation;
+    struct Con *parent;
+
+    struct Rect rect;
+    struct Rect window_rect;
+    struct Rect deco_rect;
+    /** the geometry this window requested when getting mapped */
+    struct Rect geometry;
+
+    char *name;
+
+    /** the workspace number, if this Con is of type CT_WORKSPACE and the
+     * workspace is not a named workspace (for named workspaces, num == -1) */
+    int num;
+
+    /* a sticky-group is an identifier which bundles several containers to a
+     * group. The contents are shared between all of them, that is they are
+     * displayed on whichever of the containers is currently visible */
+    char *sticky_group;
+
+    /* user-definable mark to jump to this container later */
+    char *mark;
+
+    double percent;
+
+    /* proportional width/height, calculated from WM_NORMAL_HINTS, used to
+     * apply an aspect ratio to windows (think of MPlayer) */
+    int proportional_width;
+    int proportional_height;
+    /* the wanted size of the window, used in combination with size
+     * increments (see below). */
+    int base_width;
+    int base_height;
+
+    /* the x11 border pixel attribute */
+    int border_width;
+
+    /* minimum increment size specified for the window (in pixels) */
+    int width_increment;
+    int height_increment;
+
+    struct Window *window;
+
+    /* Should this container be marked urgent? This gets set when the window
+     * inside this container (if any) sets the urgency hint, for example. */
+    bool urgent;
+
+    /* ids/pixmap/graphics context for the frame window */
+    xcb_window_t frame;
+    xcb_pixmap_t pixmap;
+    xcb_gcontext_t pm_gc;
+    bool pixmap_recreated;
+
+    /** Cache for the decoration rendering */
+    struct deco_render_params *deco_render_params;
+
+    /* Only workspace-containers can have floating clients */
+    TAILQ_HEAD(floating_head, Con) floating_head;
+
+    TAILQ_HEAD(nodes_head, Con) nodes_head;
+    TAILQ_HEAD(focus_head, Con) focus_head;
+
+    TAILQ_HEAD(swallow_head, Match) swallow_head;
+
+    enum { CF_NONE = 0, CF_OUTPUT = 1, CF_GLOBAL = 2 } fullscreen_mode;
+    enum { L_DEFAULT = 0, L_STACKED = 1, L_TABBED = 2, L_DOCKAREA = 3, L_OUTPUT = 4 } layout;
+    border_style_t border_style;
+    /** floating? (= not in tiling layout) This cannot be simply a bool
+     * because we want to keep track of whether the status was set by the
+     * application (by setting _NET_WM_WINDOW_TYPE appropriately) or by the
+     * user. The user’s choice overwrites automatic mode, of course. The
+     * order of the values is important because we check with >=
+     * FLOATING_AUTO_ON if a client is floating. */
+    enum {
+        FLOATING_AUTO_OFF = 0,
+        FLOATING_USER_OFF = 1,
+        FLOATING_AUTO_ON = 2,
+        FLOATING_USER_ON = 3
+    } floating;
+
+    /** This counter contains the number of UnmapNotify events for this
+     * container (or, more precisely, for its ->frame) which should be ignored.
+     * UnmapNotify events need to be ignored when they are caused by i3 itself,
+     * for example when reparenting or when unmapping the window on a workspace
+     * change. */
+    uint8_t ignore_unmap;
+
+    TAILQ_ENTRY(Con) nodes;
+    TAILQ_ENTRY(Con) focused;
+    TAILQ_ENTRY(Con) all_cons;
+    TAILQ_ENTRY(Con) floating_windows;
+
+    /** callbacks */
+    void(*on_remove_child)(Con *);
 };
 
 #endif
index c73c4a45cb36addaa7afeb8832b48e9ad5c03715..2f2bf431dacdc98961fd35e2c273dc970431518c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
  *
index aa9d9d5f62a1c76a45db7dd75e1293274ff12fed..6ab4cf2e1621597e191d57f2a8ecff4091ade4f6 100644 (file)
 #ifndef _FLOATING_H
 #define _FLOATING_H
 
+#include "tree.h"
+
 /** Callback for dragging */
-typedef void(*callback_t)(xcb_connection_t*, Client*, Rect*, uint32_t, uint32_t, void*);
+typedef void(*callback_t)(Con*, Rect*, uint32_t, uint32_t, void*);
 
 /** Macro to create a callback function for dragging */
 #define DRAGGING_CB(name) \
-        static void name(xcb_connection_t *conn, Client *client, \
-                         Rect *old_rect, uint32_t new_x, uint32_t new_y, \
-                         void *extra)
+        static void name(Con *con, Rect *old_rect, uint32_t new_x, \
+                         uint32_t new_y, void *extra)
 
 /** On which border was the dragging initiated? */
 typedef enum { BORDER_LEFT   = (1 << 0),
@@ -27,18 +28,45 @@ typedef enum { BORDER_LEFT   = (1 << 0),
                BORDER_BOTTOM = (1 << 3)} border_t;
 
 /**
- * Enters floating mode for the given client.  Correctly takes care of the
- * position/size (separately stored for tiling/floating mode) and
- * repositions/resizes/redecorates the client.
+ * Enables floating mode for the given container by detaching it from its
+ * parent, creating a new container around it and storing this container in the
+ * floating_windows list of the workspace.
+ *
+ */
+void floating_enable(Con *con, bool automatic);
+
+/**
+ * Disables floating mode for the given container by re-attaching the container
+ * to its old parent.
+ *
+ */
+void floating_disable(Con *con, bool automatic);
+
+/**
+ * Calls floating_enable() for tiling containers and floating_disable() for
+ * floating containers.
  *
  * If the automatic flag is set to true, this was an automatic update by a
  * change of the window class from the application which can be overwritten by
  * the user.
  *
  */
-void toggle_floating_mode(xcb_connection_t *conn, Client *client,
-                          bool automatic);
+void toggle_floating_mode(Con *con, bool automatic);
+
+/**
+ * Raises the given container in the list of floating containers
+ *
+ */
+void floating_raise_con(Con *con);
+
+/**
+ * Checks if con’s coordinates are within its workspace and re-assigns it to
+ * the actual workspace if not.
+ *
+ */
+bool floating_maybe_reassign_ws(Con *con);
 
+#if 0
 /**
  * Removes the floating client from its workspace and attaches it to the new
  * workspace. This is centralized here because it may happen if you move it
@@ -56,13 +84,13 @@ void floating_assign_to_workspace(Client *client, Workspace *new_workspace);
 int floating_border_click(xcb_connection_t *conn, Client *client,
                           xcb_button_press_event_t *event);
 
+#endif
 /**
  * Called when the user clicked on the titlebar of a floating window.
  * Calls the drag_pointer function with the drag_window callback
  *
  */
-void floating_drag_window(xcb_connection_t *conn, Client *client,
-                          xcb_button_press_event_t *event);
+void floating_drag_window(Con *con, xcb_button_press_event_t *event);
 
 /**
  * Called when the user clicked on a floating window while holding the
@@ -70,9 +98,9 @@ void floating_drag_window(xcb_connection_t *conn, Client *client,
  * Calls the drag_pointer function with the resize_window callback
  *
  */
-void floating_resize_window(xcb_connection_t *conn, Client *client,
-                            bool proportional, xcb_button_press_event_t *event);
+void floating_resize_window(Con *con, bool proportional, xcb_button_press_event_t *event);
 
+#if 0
 /**
  * Changes focus in the given direction for floating clients.
  *
@@ -97,6 +125,7 @@ void floating_move(xcb_connection_t *conn, Client *currently_focused,
  */
 void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace);
 
+#endif
 /**
  * This function grabs your pointer and lets you drag stuff around (borders).
  * Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received
@@ -105,7 +134,7 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace);
  * the event and the new coordinates (x, y).
  *
  */
-void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event,
+void drag_pointer(Con *con, xcb_button_press_event_t *event,
                   xcb_window_t confine_to, border_t border, callback_t callback,
                   void *extra);
 
index c7cbb3226a44b960c0caf5e9022db34dfc7df99b..0aaaf158a67c33730958e5ba3ede0a087ae2dbb9 100644 (file)
 
 #include <xcb/randr.h>
 
-/**
- * There was a key press. We compare this key code with our bindings table and
- * pass the bound action to parse_command().
- *
- */
-int handle_key_press(void *ignored, xcb_connection_t *conn,
-                     xcb_key_press_event_t *event);
+extern int randr_base;
 
 /**
- * When the user moves the mouse pointer onto a window, this callback gets
- * called.
+ * Adds the given sequence to the list of events which are ignored.
+ * If this ignore should only affect a specific response_type, pass
+ * response_type, otherwise, pass -1.
  *
- */
-int handle_enter_notify(void *ignored, xcb_connection_t *conn,
-                        xcb_enter_notify_event_t *event);
-
-/**
- * When the user moves the mouse but does not change the active window
- * (e.g. when having no windows opened but moving mouse on the root screen
- * and crossing virtual screen boundaries), this callback gets called.
+ * Every ignored sequence number gets garbage collected after 5 seconds.
  *
  */
-int handle_motion_notify(void *ignored, xcb_connection_t *conn,
-                         xcb_motion_notify_event_t *event);
+void add_ignore_event(const int sequence, const int response_type);
 
 /**
- * Called when the keyboard mapping changes (for example by using Xmodmap),
- * we need to update our key bindings then (re-translate symbols).
+ * Checks if the given sequence is ignored and returns true if so.
  *
  */
-int handle_mapping_notify(void *ignored, xcb_connection_t *conn,
-                          xcb_mapping_notify_event_t *event);
+bool event_is_ignored(const int sequence, const int response_type);
 
 /**
- * Checks if the button press was on a stack window, handles focus setting and
- * returns true if so, or false otherwise.
+ * Takes an xcb_generic_event_t and calls the appropriate handler, based on the
+ * event type.
  *
  */
-int handle_button_press(void *ignored, xcb_connection_t *conn,
-                        xcb_button_press_event_t *event);
+void handle_event(int type, xcb_generic_event_t *event);
 
 /**
- * A new window appeared on the screen (=was mapped), so let’s manage it.
+ * Sets the appropriate atoms for the property handlers after the atoms were
+ * received from X11
  *
  */
-int handle_map_request(void *prophs, xcb_connection_t *conn,
-                       xcb_map_request_event_t *event);
+void property_handlers_init();
 
+#if 0
 /**
  * Configuration notifies are only handled because we need to set up ignore
  * for the following enter notify events
  *
  */
 int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event);
+#endif
 
-/**
- * Gets triggered upon a RandR screen change event, that is when the user
- * changes the screen configuration in any way (mode, position, …)
- *
- */
-int handle_screen_change(void *prophs, xcb_connection_t *conn,
-                         xcb_generic_event_t *e);
-
-/**
- * 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.
- *
- */
-int handle_configure_request(void *prophs, xcb_connection_t *conn,
-                             xcb_configure_request_event_t *event);
-
-/**
- * Our window decorations were unmapped. That means, the window will be killed
- * now, so we better clean up before.
- *
- */
-int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event);
-
-/**
- * A destroy notify event is sent when the window is not unmapped, but
- * immediately destroyed (for example when starting a window and immediately
- * killing the program which started it).
- *
- * We just pass on the event to the unmap notify handler (by copying the
- * important fields in the event data structure).
- *
- */
-int handle_destroy_notify_event(void *data, xcb_connection_t *conn,
-                                xcb_destroy_notify_event_t *event);
-
-/**
- * Called when a window changes its title
- *
- */
-int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
-                             xcb_window_t window, xcb_atom_t atom,
-                             xcb_get_property_reply_t *prop);
-
-/**
- * We handle legacy window names (titles) which are in COMPOUND_TEXT
- * encoding. However, we just pass them along, so when containing non-ASCII
- * characters, those will be rendering incorrectly. In order to correctly
- * render unicode window titles in i3, an application has to set _NET_WM_NAME,
- * which is in UTF-8 encoding.
- *
- * On every update, a message is put out to the user, so he may improve the
- * situation and update applications which display filenames in their title to
- * correctly use _NET_WM_NAME and therefore support unicode.
- *
- */
-int handle_windowname_change_legacy(void *data, xcb_connection_t *conn,
-                                    uint8_t state, xcb_window_t window,
-                                    xcb_atom_t atom, xcb_get_property_reply_t
-                                    *prop);
-
-/**
- * Store the window classes for jumping to them later.
- *
- */
-int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state,
-                              xcb_window_t window, xcb_atom_t atom,
-                              xcb_get_property_reply_t *prop);
-
-
-/**
- * Expose event means we should redraw our windows (= title bar)
- *
- */
-int handle_expose_event(void *data, xcb_connection_t *conn,
-                        xcb_expose_event_t *event);
-
-/**
- * Handle client messages (EWMH)
- *
- */
-int handle_client_message(void *data, xcb_connection_t *conn,
-                          xcb_client_message_event_t *event);
-
+#if 0
 /**
  * Handles _NET_WM_WINDOW_TYPE changes
  *
@@ -161,44 +62,6 @@ int handle_client_message(void *data, xcb_connection_t *conn,
 int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state,
                        xcb_window_t window, xcb_atom_t atom,
                        xcb_get_property_reply_t *property);
-
-/**
- * Handles the size hints set by a window, but currently only the part
- * necessary for displaying clients proportionally inside their frames
- * (mplayer for example)
- *
- * See ICCCM 4.1.2.3 for more details
- *
- */
-int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state,
-                        xcb_window_t window, xcb_atom_t name,
-                        xcb_get_property_reply_t *reply);
-
-/**
- * Handles the WM_HINTS property for extracting the urgency state of the window.
- *
- */
-int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
-                  xcb_atom_t name, xcb_get_property_reply_t *reply);
-
-/**
- * Handles the transient for hints set by a window, signalizing that this
- * window is a popup window for some other window.
- *
- * See ICCCM 4.1.2.6 for more details
- *
- */
-int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state,
-                         xcb_window_t window, xcb_atom_t name,
-                         xcb_get_property_reply_t *reply);
-
-/**
- * Handles changes of the WM_CLIENT_LEADER atom which specifies if this is a
- * toolwindow (or similar) and to which window it belongs (logical parent).
- *
- */
-int handle_clientleader_change(void *data, xcb_connection_t *conn,
-                               uint8_t state, xcb_window_t window,
-                               xcb_atom_t name, xcb_get_property_reply_t *prop);
+#endif
 
 #endif
index bf9d4b814e6960e9c949cf72fbd0d3bc0497dedf..22dcd476b9605349f2ef53fbd02039a3659498b1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
  *
@@ -8,34 +8,32 @@
  * See file LICENSE for license information.
  *
  */
-#include <xcb/xcb.h>
-#include <xcb/xcb_property.h>
-#include <xcb/xcb_event.h>
 #include <xcb/xcb_keysyms.h>
 
 #include <X11/XKBlib.h>
 
 #include "queue.h"
 #include "data.h"
+#include "xcb.h"
 
 #ifndef _I3_H
 #define _I3_H
 
-#define NUM_ATOMS 21
-
-extern xcb_connection_t *global_conn;
+extern xcb_connection_t *conn;
 extern xcb_key_symbols_t *keysyms;
 extern char **start_argv;
-extern Display *xkbdpy;
+extern Display *xlibdpy, *xkbdpy;
 extern int xkb_current_group;
 extern TAILQ_HEAD(bindings_head, Binding) *bindings;
 extern TAILQ_HEAD(autostarts_head, Autostart) autostarts;
+extern TAILQ_HEAD(autostarts_always_head, Autostart) autostarts_always;
+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_event_handlers_t evenths;
+extern xcb_screen_t *root_screen;
 extern uint8_t root_depth;
-extern bool xkb_supported;
-extern xcb_atom_t atoms[NUM_ATOMS];
+extern bool xcursor_supported, xkb_supported;
 extern xcb_window_t root;
+extern struct ev_loop *main_loop;
 
 #endif
index 1ea3918284a9ed999e4e13d284f2b30f22ccd66c..e81f9a155ad5e7254a631c1fb04d7a6a172e6a48 100644 (file)
 /** Requests the current outputs from i3 */
 #define I3_IPC_MESSAGE_TYPE_GET_OUTPUTS         3
 
+/** Requests the tree layout from i3 */
+#define I3_IPC_MESSAGE_TYPE_GET_TREE            4
+
+
 /*
  * Messages from i3 to clients
  *
 /** Outputs reply type */
 #define I3_IPC_REPLY_TYPE_OUTPUTS               3
 
+/** Tree reply type */
+#define I3_IPC_REPLY_TYPE_TREE                  4
+
+
 /*
  * Events from i3 to clients. Events have the first bit set high.
  *
index 63d59141cee191c5325e50d79f3bc2364b5eb1d8..a5de487aeeb177def2834824f384417a74f2b414 100644 (file)
 #define _IPC_H
 
 #include <ev.h>
+#include <stdbool.h>
+#include <yajl/yajl_gen.h>
+#include <yajl/yajl_parse.h>
+
+#include "data.h"
+#include "tree.h"
 
 #include "i3/ipc.h"
 
+extern char *current_socketpath;
+
 typedef struct ipc_client {
         int fd;
 
@@ -74,4 +82,6 @@ void ipc_send_event(const char *event, uint32_t message_type, const char *payloa
  */
 void ipc_shutdown();
 
+void dump_node(yajl_gen gen, Con *con, bool inplace_restart);
+
 #endif
diff --git a/include/layout.h b/include/layout.h
deleted file mode 100644 (file)
index 1cbb783..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * (c) 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- */
-#include <xcb/xcb.h>
-
-#ifndef _LAYOUT_H
-#define _LAYOUT_H
-
-/**
- * Gets the unoccupied space (= space which is available for windows which
- * were resized by the user) This is necessary to render both, customly
- * resized windows and never touched windows correctly, meaning that the
- * aspect ratio will be maintained when opening new windows.
- *
- */
-int get_unoccupied_x(Workspace *workspace);
-
-/** See get_unoccupied_x */
-int get_unoccupied_y(Workspace *workspace);
-
-/**
- * (Re-)draws window decorations for a given Client onto the given
- * drawable/graphic context.  When in stacking mode, the window decorations
- * are drawn onto an own window.
- *
- */
-void decorate_window(xcb_connection_t *conn, Client *client,
-                     xcb_drawable_t drawable, xcb_gcontext_t gc,
-                     int offset_x, int offset_y);
-
-/**
- * Redecorates the given client correctly by checking if it’s in a stacking
- * container and re-rendering the stack window or just calling decorate_window
- * if it’s not in a stacking container.
- *
- */
-void redecorate_window(xcb_connection_t *conn, Client *client);
-
-/**
- * Pushes the client’s x and y coordinates to X11
- *
- */
-void reposition_client(xcb_connection_t *conn, Client *client);
-
-/**
- * Pushes the client’s width/height to X11 and resizes the child window. This
- * function also updates the client’s position, so if you work on tiling clients
- * only, you can use this function instead of separate calls to reposition_client
- * and resize_client to reduce flickering.
- *
- */
-void resize_client(xcb_connection_t *conn, Client *client);
-
-/**
- * Renders the given container. Is called by render_layout() or individually
- * (for example when focus changes in a stacking container)
- *
- */
-void render_container(xcb_connection_t *conn, Container *container);
-
-/**
- * Modifies the event mask of all clients on the given workspace to either
- * ignore or to handle enter notifies. It is handy to ignore notifies because
- * they will be sent when a window is mapped under the cursor, thus when the
- * user didn’t enter the window actively at all.
- *
- */
-void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace,
-                                bool ignore_enter_notify);
-
-/**
- * Renders the given workspace on the given screen
- *
- */
-void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws);
-
-/**
- * Renders the whole layout, that is: Go through each screen, each workspace,
- * each container and render each client. This also renders the bars.
- *
- * If you don’t need to render *everything*, you should call render_container
- * on the container you want to refresh.
- *
- */
-void render_layout(xcb_connection_t *conn);
-
-#endif
diff --git a/include/load_layout.h b/include/load_layout.h
new file mode 100644 (file)
index 0000000..f3a60a0
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef _LOAD_LAYOUT_H
+#define _LOAD_LAYOUT_H
+
+void tree_append_json(const char *filename);
+
+#endif
index 6d529a00f27133b524e3c9a6171898634b027c2c..c1e10b0639b321727d95efaf09783423ba8d1884 100644 (file)
@@ -1,9 +1,9 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
  *
- * © 2009-2010 Michael Stapelberg and contributors
+ * © 2009-2011 Michael Stapelberg and contributors
  *
  * See file LICENSE for license information.
  *
 #define DLOG(fmt, ...) debuglog(LOGLEVEL, "%s:%s:%d - " fmt, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__)
 
 extern char *loglevels[];
+extern char *errorfilename;
+
+/**
+ * Initializes logging by creating an error logfile in /tmp (or
+ * XDG_RUNTIME_DIR, see get_process_filename()).
+ *
+ */
+void init_logging();
 
 /**
  * Enables the given loglevel.
@@ -41,7 +49,7 @@ void set_verbosity(bool _verbose);
  * but only if the corresponding debug loglevel was activated.
  *
  */
-void debuglog(int lev, char *fmt, ...);
+void debuglog(uint64_t lev, char *fmt, ...);
 
 /**
  * Logs the given message to stdout while prefixing the current time to it.
index 9c87a08ef64fda93ce66d46668026f6dda4d4023..e23eccf36a9960f19712061e4471911754ff9f7b 100644 (file)
@@ -8,7 +8,6 @@
  * See file LICENSE for license information.
  *
  */
-#include <xcb/xcb.h>
 
 #include "data.h"
 
@@ -20,8 +19,7 @@
  * manage them
  *
  */
-void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t
-                             *prophs, xcb_window_t root);
+void manage_existing_windows(xcb_window_t root);
 
 /**
  * Restores the geometry of each window by reparenting it to the root window
@@ -31,17 +29,17 @@ void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t
  * side-effects which are to be expected when continuing to run i3.
  *
  */
-void restore_geometry(xcb_connection_t *conn);
+void restore_geometry();
 
 /**
  * Do some sanity checks and then reparent the window.
  *
  */
-void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn,
-                   xcb_window_t window,
+void manage_window(xcb_window_t window,
                    xcb_get_window_attributes_cookie_t cookie,
                    bool needs_to_be_mapped);
 
+#if 0
 /**
  * reparent_window() gets called when a new window was opened and becomes a
  * child of the root window, or it gets called by us when we manage the
@@ -56,3 +54,4 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
                      uint32_t border_width);
 
 #endif
+#endif
diff --git a/include/match.h b/include/match.h
new file mode 100644 (file)
index 0000000..2786c66
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef _MATCH_H
+#define _MATCH_H
+
+/*
+ * Initializes the Match data structure. This function is necessary because the
+ * members representing boolean values (like dock) need to be initialized with
+ * -1 instead of 0.
+ *
+ */
+void match_init(Match *match);
+
+/**
+ * Check if a match is empty. This is necessary while parsing commands to see
+ * whether the user specified a match at all.
+ *
+ */
+bool match_is_empty(Match *match);
+
+/**
+ * Copies the data of a match from src to dest.
+ *
+ */
+void match_copy(Match *dest, Match *src);
+
+/**
+ * Check if a match data structure matches the given window.
+ *
+ */
+bool match_matches_window(Match *match, i3Window *window);
+
+#endif
diff --git a/include/move.h b/include/move.h
new file mode 100644 (file)
index 0000000..d0c9701
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ */
+
+#ifndef _MOVE_H
+#define _MOVE_H
+
+/**
+ * Moves the current container in the given direction (TOK_LEFT, TOK_RIGHT,
+ * TOK_UP, TOK_DOWN from cmdparse.l)
+ *
+ */
+void tree_move(int direction);
+
+#endif
diff --git a/include/output.h b/include/output.h
new file mode 100644 (file)
index 0000000..67652fa
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ */
+
+#ifndef _OUTPUT_H
+#define _OUTPUT_H
+
+/**
+ * Returns the output container below the given output container.
+ *
+ */
+Con *output_get_content(Con *output);
+
+#endif
index 75bb957aec793b66a6da6763a8d205f7e3e4fe42..0c6852150e5bf60b77333254e17ddd7d5440369b 100644 (file)
@@ -407,6 +407,19 @@ struct {                                                           \
        _Q_INVALIDATE((elm)->field.tqe_next);                           \
 } while (0)
 
+/* Swaps two consecutive elements. 'second' *MUST* follow 'first' */
+#define TAILQ_SWAP(first, second, head, field) do {                    \
+       *((first)->field.tqe_prev) = (second);                          \
+       (second)->field.tqe_prev = (first)->field.tqe_prev;             \
+       (first)->field.tqe_prev = &((second)->field.tqe_next);          \
+       (first)->field.tqe_next = (second)->field.tqe_next;             \
+       if ((second)->field.tqe_next)                                   \
+               (second)->field.tqe_next->field.tqe_prev = &((first)->field.tqe_next); \
+       (second)->field.tqe_next = first;                               \
+       if ((head)->tqh_last == &((second)->field.tqe_next))            \
+               (head)->tqh_last = &((first)->field.tqe_next);          \
+} while (0)
+
 /*
  * Circular queue definitions.
  */
index 4832efe50d9d1b06099b49dbba869ea18c259af9..d14d04788aeb7ea283b66ff085f9af0b1a2ff6dd 100644 (file)
@@ -22,7 +22,7 @@ extern struct outputs_head outputs;
  * XRandR information to setup workspaces for each screen.
  *
  */
-void initialize_randr(xcb_connection_t *conn, int *event_base);
+void randr_init(int *event_base);
 
 /**
  * Disables RandR support by creating exactly one output with the size of the
@@ -31,17 +31,36 @@ void initialize_randr(xcb_connection_t *conn, int *event_base);
  */
 void disable_randr(xcb_connection_t *conn);
 
+/**
+ * Initializes a CT_OUTPUT Con (searches existing ones from inplace restart
+ * before) to use for the given Output.
+ *
+ */
+void output_init_con(Output *output);
+
+/**
+ * Initializes at least one workspace for this output, trying the following
+ * steps until there is at least one workspace:
+ *
+ * • Move existing workspaces, which are assigned to be on the given output, to
+ *   the output.
+ * • Create the first assigned workspace for this output.
+ * • Create the first unused workspace.
+ *
+ */
+void init_ws_for_output(Output *output, Con *content);
+
 /**
  * Initializes the specified output, assigning the specified workspace to it.
  *
  */
-void initialize_output(xcb_connection_t *conn, Output *output, Workspace *workspace);
+//void initialize_output(xcb_connection_t *conn, Output *output, Workspace *workspace);
 
 /**
  * (Re-)queries the outputs via RandR and stores them in the list of outputs.
  *
  */
-void randr_query_outputs(xcb_connection_t *conn);
+void randr_query_outputs();
 
 /**
  * Returns the first output which is active.
diff --git a/include/render.h b/include/render.h
new file mode 100644 (file)
index 0000000..9408448
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ */
+
+#ifndef _RENDER_H
+#define _RENDER_H
+
+/**
+ * "Renders" the given container (and its children), meaning that all rects are
+ * updated correctly. Note that this function does not call any xcb_*
+ * functions, so the changes are completely done in memory only (and
+ * side-effect free). As soon as you call x_push_changes(), the changes will be
+ * updated in X11.
+ *
+ */
+void render_con(Con *con, bool render_fullscreen);
+
+#endif
index c187e837a8b6098ef98470a579dd6a5196107e24..5c8ea5d5d072ab92eb7f6f0a2089cbb23e8557b3 100644 (file)
@@ -1,36 +1,6 @@
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * (c) 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- */
-
 #ifndef _RESIZE_H
 #define _RESIZE_H
 
-#include <xcb/xcb.h>
-
-typedef enum { O_HORIZONTAL, O_VERTICAL } resize_orientation_t;
-
-/**
- * Renders the resize window between the first/second container and resizes
- * the table column/row.
- *
- */
-int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first,
-                             int second, resize_orientation_t orientation,
-                             xcb_button_press_event_t *event);
-/**
- * Resizes a column/row by the given amount of pixels. Called by
- * resize_graphical_handler (the user clicked) or parse_resize_command (the
- * user issued the command)
- *
- */
-void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int second,
-                      resize_orientation_t orientation, int pixels);
+int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, xcb_button_press_event_t *event);
 
 #endif
index 4931743877e3e6eed6cb73f740a30d8835a613a8..02a4f5dd6c37b99b2b03b00a479b09bf0fc48238 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
  *
diff --git a/include/table.h b/include/table.h
deleted file mode 100644 (file)
index 6236bef..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * (c) 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- */
-#include <stdbool.h>
-
-#include <xcb/xcb.h>
-
-#include "data.h"
-
-#ifndef _TABLE_H
-#define _TABLE_H
-
-#define CUR_TABLE (c_ws->table)
-#define CUR_CELL (CUR_TABLE[current_col][current_row])
-
-extern Workspace *c_ws;
-extern TAILQ_HEAD(workspaces_head, Workspace) *workspaces;
-//extern int num_workspaces;
-extern int current_col;
-extern int current_row;
-
-/** Initialize table */
-void init_table();
-
-/** Add one row to the table */
-void expand_table_rows(Workspace *workspace);
-
-/** Adds one row at the head of the table */
-void expand_table_rows_at_head(Workspace *workspace);
-
-/** Add one column to the table */
-void expand_table_cols(Workspace *workspace);
-
-/**
- * Inserts one column at the table’s head
- *
- */
-void expand_table_cols_at_head(Workspace *workspace);
-
-/**
- * Performs simple bounds checking for the given column/row
- *
- */
-bool cell_exists(Workspace *ws, int col, int row);
-
-/**
- * Shrinks the table by "compacting" it, that is, removing completely empty
- * rows/columns
- *
- */
-void cleanup_table(xcb_connection_t *conn, Workspace *workspace);
-
-/**
- * Fixes col/rowspan (makes sure there are no overlapping windows)
- *
- */
-void fix_colrowspan(xcb_connection_t *conn, Workspace *workspace);
-
-/**
- * Prints the table’s contents in human-readable form for debugging
- *
- */
-void dump_table(xcb_connection_t *conn, Workspace *workspace);
-
-#endif
diff --git a/include/tree.h b/include/tree.h
new file mode 100644 (file)
index 0000000..b9bf7f5
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ */
+
+#ifndef _TREE_H
+#define _TREE_H
+
+extern Con *croot;
+/* TODO: i am not sure yet how much access to the focused container should
+ * be permitted to source files */
+extern Con *focused;
+TAILQ_HEAD(all_cons_head, Con);
+extern struct all_cons_head all_cons;
+
+/**
+ * Initializes the tree by creating the root node, adding all RandR outputs
+ * to the tree (that means randr_init() has to be called before) and
+ * assigning a workspace to each RandR output.
+ *
+ */
+void tree_init(xcb_get_geometry_reply_t *geometry);
+
+/**
+ * Opens an empty container in the current container
+ *
+ */
+Con *tree_open_con(Con *con, i3Window *window);
+
+/**
+ * Splits (horizontally or vertically) the given container by creating a new
+ * container which contains the old one and the future ones.
+ *
+ */
+void tree_split(Con *con, orientation_t orientation);
+
+/**
+ * Moves focus one level up.
+ *
+ */
+void level_up();
+
+/**
+ * Moves focus one level down.
+ *
+ */
+void level_down();
+
+/**
+ * Renders the tree, that is rendering all outputs using render_con() and
+ * pushing the changes to X11 using x_push_changes().
+ *
+ */
+void tree_render();
+
+/**
+ * Closes the current container using tree_close().
+ *
+ */
+void tree_close_con(kill_window_t kill_window);
+
+/**
+ * Changes focus in the given way (next/previous) and given orientation
+ * (horizontal/vertical).
+ *
+ */
+void tree_next(char way, orientation_t orientation);
+
+/**
+ * Closes the given container including all children.
+ * Returns true if the container was killed or false if just WM_DELETE was sent
+ * and the window is expected to kill itself.
+ *
+ */
+bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent);
+
+/**
+ * Loads tree from ~/.i3/_restart.json (used for in-place restarts).
+ *
+ */
+bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry);
+
+/**
+ * tree_flatten() removes pairs of redundant split containers, e.g.:
+ *       [workspace, horizontal]
+ *   [v-split]           [child3]
+ *   [h-split]
+ * [child1] [child2]
+ * In this example, the v-split and h-split container are redundant.
+ * Such a situation can be created by moving containers in a direction which is
+ * not the orientation of their parent container. i3 needs to create a new
+ * split container then and if you move containers this way multiple times,
+ * redundant chains of split-containers can be the result.
+ *
+ */
+void tree_flatten(Con *child);
+
+#endif
index d1384962b1fa3bfee0647a61faa20448eae42551..0e48843cfb36b3d9f8f280749dddc464f0b51917 100644 (file)
@@ -8,7 +8,6 @@
  * See file LICENSE for license information.
  *
  */
-#include <xcb/xcb.h>
 #include <err.h>
 
 #include "data.h"
 #define FOR_TABLE(workspace) \
                         for (int cols = 0; cols < (workspace)->cols; cols++) \
                                 for (int rows = 0; rows < (workspace)->rows; rows++)
+
+#define NODES_FOREACH(head) \
+    for (Con *child = (Con*)-1; (child == (Con*)-1) && ((child = 0), true);) \
+        TAILQ_FOREACH(child, &((head)->nodes_head), nodes)
+
+/* greps the ->nodes of the given head and returns the first node that matches the given condition */
+#define GREP_FIRST(dest, head, condition) \
+    NODES_FOREACH(head) { \
+        if (!(condition)) \
+            continue; \
+        \
+        (dest) = child; \
+        break; \
+    }
+
 #define FREE(pointer) do { \
         if (pointer != NULL) { \
                 free(pointer); \
 } \
 while (0)
 
-TAILQ_HEAD(keyvalue_table_head, keyvalue_element);
-extern struct keyvalue_table_head by_parent;
-extern struct keyvalue_table_head by_child;
+#define CALL(obj, member, ...) obj->member(obj, ## __VA_ARGS__)
 
 int min(int a, int b);
 int max(int a, int b);
+bool rect_contains(Rect rect, uint32_t x, uint32_t y);
+Rect rect_add(Rect a, Rect b);
 
 /**
  * Updates *destination with new_value and returns true if it was changed or false
@@ -63,31 +77,18 @@ void *smalloc(size_t size);
 void *scalloc(size_t size);
 
 /**
- * Safe-wrapper around strdup which exits if malloc returns NULL (meaning that
- * there is no more memory available)
- *
- */
-char *sstrdup(const char *str);
-
-/**
- * Inserts an element into the given keyvalue-table using the given key.
+ * Safe-wrapper around realloc which exits if realloc returns NULL (meaning
+ * that there is no more memory available).
  *
  */
-bool table_put(struct keyvalue_table_head *head, uint32_t key, void *value);
+void *srealloc(void *ptr, size_t size);
 
 /**
- * Removes the element from the given keyvalue-table with the given key and
- * returns its value;
- *
- */
-void *table_remove(struct keyvalue_table_head *head, uint32_t key);
-
-/**
- * Returns the value of the element of the given keyvalue-table with the given
- * key.
+ * Safe-wrapper around strdup which exits if malloc returns NULL (meaning that
+ * there is no more memory available)
  *
  */
-void *table_get(struct keyvalue_table_head *head, uint32_t key);
+char *sstrdup(const char *str);
 
 /**
  * Starts the given application by passing it through a shell. We use double
@@ -101,6 +102,23 @@ void *table_get(struct keyvalue_table_head *head, uint32_t key);
  */
 void start_application(const char *command);
 
+/**
+ * exec()s an i3 utility, for example the config file migration script or
+ * i3-nagbar. This function first searches $PATH for the given utility named,
+ * then falls back to the dirname() of the i3 executable path and then falls
+ * back to the dirname() of the target of /proc/self/exe (on linux).
+ *
+ * This function should be called after fork()ing.
+ *
+ * The first argument of the given argv vector will be overwritten with the
+ * executable name, so pass NULL.
+ *
+ * If the utility cannot be found in any of these locations, it exits with
+ * return code 2.
+ *
+ */
+void exec_i3_utility(char *name, char *argv[]);
+
 /**
  * Checks a generic cookie for errors and quits with the given message if
  * there was an error.
@@ -119,54 +137,54 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie,
 char *convert_utf8_to_ucs2(char *input, int *real_strlen);
 
 /**
- * Returns the client which comes next in focus stack (= was selected before) for
- * the given container, optionally excluding the given client.
+ * This function resolves ~ in pathnames.
+ * It may resolve wildcards in the first part of the path, but if no match
+ * or multiple matches are found, it just returns a copy of path as given.
  *
  */
-Client *get_last_focused_client(xcb_connection_t *conn, Container *container,
-                                Client *exclude);
+char *resolve_tilde(const char *path);
 
 /**
- * Sets the given client as focused by updating the data structures correctly,
- * updating the X input focus and finally re-decorating both windows (to
- * signalize the user the new focus situation)
+ * Checks if the given path exists by calling stat().
  *
  */
-void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways);
+bool path_exists(const char *path);
+
 
 /**
- * Called when the user switches to another mode or when the container is
- * destroyed and thus needs to be cleaned up.
+ * Returns the name of a temporary file with the specified prefix.
  *
  */
-void leave_stack_mode(xcb_connection_t *conn, Container *container);
+char *get_process_filename(const char *prefix);
 
 /**
- * Switches the layout of the given container taking care of the necessary
- * house-keeping
+ * Restart i3 in-place
+ * appends -a to argument list to disable autostart
  *
  */
-void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode);
+void i3_restart(bool forget_layout);
 
-/**
- * Gets the first matching client for the given window class/window title.
- * If the paramater specific is set to a specific client, only this one
- * will be checked.
+#if defined(__OpenBSD__) || defined(__APPLE__)
+
+/*
+ * Taken from FreeBSD
+ * Find the first occurrence of the byte string s in byte string l.
  *
  */
-Client *get_matching_client(xcb_connection_t *conn,
-                            const char *window_classtitle, Client *specific);
+void *memmem(const void *l, size_t l_len, const void *s, size_t s_len);
+
+#endif
+
+#if defined(__APPLE__)
 
 /*
- * Restart i3 in-place
- * appends -a to argument list to disable autostart
+ * Taken from FreeBSD
+ * Returns a pointer to a new string which is a duplicate of the
+ * string, but only copies at most n characters.
  *
  */
-void i3_restart();
+char *strndup(const char *str, size_t n);
 
-#if defined(__OpenBSD__)
-/* OpenBSD does not provide memmem(), so we provide FreeBSD’s implementation */
-void *memmem(const void *l, size_t l_len, const void *s, size_t s_len);
 #endif
 
 #endif
diff --git a/include/window.h b/include/window.h
new file mode 100644 (file)
index 0000000..fe282aa
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef _WINDOW_H
+#define _WINDOW_H
+
+/**
+ * Updates the WM_CLASS (consisting of the class and instance) for the
+ * given window.
+ *
+ */
+void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
+
+/**
+ * Updates the name by using _NET_WM_NAME (encoded in UTF-8) for the given
+ * window. Further updates using window_update_name_legacy will be ignored.
+ *
+ */
+void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
+
+/**
+ * Updates the name by using WM_NAME (encoded in COMPOUND_TEXT). We do not
+ * touch what the client sends us but pass it to xcb_image_text_8. To get
+ * proper unicode rendering, the application has to use _NET_WM_NAME (see
+ * window_update_name()).
+ *
+ */
+void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
+
+/**
+ * Updates the CLIENT_LEADER (logical parent window).
+ *
+ */
+void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop);
+
+/**
+ * Updates the TRANSIENT_FOR (logical parent window).
+ *
+ */
+void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop);
+
+/**
+ * Updates the _NET_WM_STRUT_PARTIAL (reserved pixels at the screen edges)
+ *
+ */
+void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop);
+
+#endif
index dae245ce036ec2060a69d3ed5e006b7e920bba37..aebf13656d866e8b15e3de5a03678f3bb9f3baed 100644 (file)
@@ -8,9 +8,9 @@
  * See file LICENSE for license information.
  *
  */
-#include <xcb/xcb.h>
 
 #include "data.h"
+#include "tree.h"
 #include "randr.h"
 
 #ifndef _WORKSPACE_H
  * creating the workspace if necessary (by allocating the necessary amount of
  * memory and initializing the data structures correctly).
  *
+ * If created is not NULL, *created will be set to whether or not the
+ * workspace has just been created.
+ *
  */
-Workspace *workspace_get(int number);
+Con *workspace_get(const char *num, bool *created);
 
+#if 0
 /**
  * Sets the name (or just its number) for the given workspace. This has to
  * be called for every workspace as the rendering function
@@ -32,6 +36,7 @@ Workspace *workspace_get(int number);
  *
  */
 void workspace_set_name(Workspace *ws, const char *name);
+#endif
 
 /**
  * Returns true if the workspace is currently visible. Especially important for
@@ -39,11 +44,24 @@ void workspace_set_name(Workspace *ws, const char *name);
  * workspaces.
  *
  */
-bool workspace_is_visible(Workspace *ws);
+bool workspace_is_visible(Con *ws);
 
 /** Switches to the given workspace */
-void workspace_show(xcb_connection_t *conn, int workspace);
+void workspace_show(const char *num);
+
+/**
+ * Focuses the next workspace.
+ *
+ */
+void workspace_next();
+
+/**
+ * Focuses the previous workspace.
+ *
+ */
+void workspace_prev();
 
+#if 0
 /**
  * Assigns the given workspace to the given screen by correctly updating its
  * state and reconfiguring all the clients on this workspace.
@@ -86,25 +104,33 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws);
  *
  */
 void workspace_map_clients(xcb_connection_t *conn, Workspace *ws);
+#endif
 
 /**
  * Goes through all clients on the given workspace and updates the workspace’s
  * urgent flag accordingly.
  *
  */
-void workspace_update_urgent_flag(Workspace *ws);
+void workspace_update_urgent_flag(Con *ws);
 
-/*
- * Returns the width of the workspace.
+/**
+ * 'Forces' workspace orientation by moving all cons into a new split-con with
+ * the same orientation as the workspace and then changing the workspace
+ * orientation.
  *
  */
-int workspace_width(Workspace *ws);
+void ws_force_orientation(Con *ws, orientation_t orientation);
 
-/*
- * Returns the effective height of the workspace (without the internal bar and
- * without dock clients).
+/**
+ * Called when a new con (with a window, not an empty or split con) should be
+ * attached to the workspace (for example when managing a new window or when
+ * moving an existing window to the workspace level).
+ *
+ * Depending on the workspace_layout setting, this function either returns the
+ * workspace itself (default layout) or creates a new stacked/tabbed con and
+ * returns that.
  *
  */
-int workspace_height(Workspace *ws);
+Con *workspace_attach_to(Con *ws);
 
 #endif
diff --git a/include/x.h b/include/x.h
new file mode 100644 (file)
index 0000000..df4ee27
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ */
+
+#ifndef _X_H
+#define _X_H
+
+/** Stores the X11 window ID of the currently focused window */
+extern xcb_window_t focused_id;
+
+/**
+ * Initializes the X11 part for the given container. Called exactly once for
+ * every container from con_new().
+ *
+ */
+void x_con_init(Con *con);
+
+/**
+ * Moves a child window from Container src to Container dest.
+ *
+ */
+void x_move_win(Con *src, Con *dest);
+
+/**
+ * Reparents the child window of the given container (necessary for sticky
+ * containers). The reparenting happens in the next call of x_push_changes().
+ *
+ */
+void x_reparent_child(Con *con, Con *old);
+
+/**
+ * Re-initializes the associated X window state for this container. You have
+ * to call this when you assign a client to an empty container to ensure that
+ * its state gets updated correctly.
+ *
+ */
+void x_reinit(Con *con);
+
+/**
+ * Kills the window decoration associated with the given container.
+ *
+ */
+void x_con_kill(Con *con);
+
+/**
+ * Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
+ *
+ */
+bool window_supports_protocol(xcb_window_t window, xcb_atom_t atom);
+
+/**
+ * Kills the given X11 window using WM_DELETE_WINDOW (if supported).
+ *
+ */
+void x_window_kill(xcb_window_t window, kill_window_t kill_window);
+
+/**
+ * Draws the decoration of the given container onto its parent.
+ *
+ */
+void x_draw_decoration(Con *con);
+
+/**
+ * Recursively calls x_draw_decoration. This cannot be done in x_push_node
+ * because x_push_node uses focus order to recurse (see the comment above)
+ * while drawing the decoration needs to happen in the actual order.
+ *
+ */
+void x_deco_recurse(Con *con);
+
+/**
+ * This function pushes the properties of each node of the layout tree to
+ * X11 if they have changed (like the map state, position of the window, …).
+ * It recursively traverses all children of the given node.
+ *
+ */
+void x_push_node(Con *con);
+
+/**
+ * Pushes all changes (state of each node, see x_push_node() and the window
+ * stack) to X11.
+ *
+ */
+void x_push_changes(Con *con);
+
+/**
+ * Raises the specified container in the internal stack of X windows. The
+ * next call to x_push_changes() will make the change visible in X11.
+ *
+ */
+void x_raise_con(Con *con);
+
+/**
+ * Sets the WM_NAME property (so, no UTF8, but used only for debugging anyways)
+ * of the given name. Used for properly tagging the windows for easily spotting
+ * i3 windows in xwininfo -root -all.
+ *
+ */
+void x_set_name(Con *con, const char *name);
+
+/**
+ * Sets up i3 specific atoms (I3_SOCKET_PATH and I3_CONFIG_PATH)
+ *
+ */
+void x_set_i3_atoms();
+
+#endif
index 8a21fe4a902f02c2236d9f616fc2ae2277b34f2c..13fafb0b9c72777ce34dd8e941b304c6c07af995 100644 (file)
@@ -1,9 +1,9 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
  *
- * (c) 2009 Michael Stapelberg and contributors
+ * © 2009-2011 Michael Stapelberg and contributors
  *
  * See file LICENSE for license information.
  *
@@ -12,6 +12,7 @@
 #define _XCB_H
 
 #include "data.h"
+#include "xcursor.h"
 
 #define _NET_WM_STATE_REMOVE    0
 #define _NET_WM_STATE_ADD       1
 /** The XCB_CW_EVENT_MASK for the child (= real window) */
 #define CHILD_EVENT_MASK (XCB_EVENT_MASK_PROPERTY_CHANGE | \
                           XCB_EVENT_MASK_STRUCTURE_NOTIFY | \
-                          XCB_EVENT_MASK_ENTER_WINDOW)
+                          XCB_EVENT_MASK_FOCUS_CHANGE)
 
 /** The XCB_CW_EVENT_MASK for its frame */
 #define FRAME_EVENT_MASK (XCB_EVENT_MASK_BUTTON_PRESS |          /* …mouse is pressed/released */ \
                           XCB_EVENT_MASK_BUTTON_RELEASE | \
+                          XCB_EVENT_MASK_POINTER_MOTION |        /* …mouse is moved */ \
                           XCB_EVENT_MASK_EXPOSURE |              /* …our window needs to be redrawn */ \
                           XCB_EVENT_MASK_STRUCTURE_NOTIFY |      /* …the frame gets destroyed */ \
                           XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | /* …the application tries to resize itself */ \
                           XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |   /* …subwindows get notifies */ \
                           XCB_EVENT_MASK_ENTER_WINDOW)           /* …user moves cursor inside our window */
 
-
-enum { _NET_SUPPORTED = 0,
-        _NET_SUPPORTING_WM_CHECK,
-        _NET_WM_NAME,
-        _NET_WM_STATE_FULLSCREEN,
-        _NET_WM_STATE,
-        _NET_WM_WINDOW_TYPE,
-        _NET_WM_WINDOW_TYPE_DOCK,
-        _NET_WM_WINDOW_TYPE_DIALOG,
-        _NET_WM_WINDOW_TYPE_UTILITY,
-        _NET_WM_WINDOW_TYPE_TOOLBAR,
-        _NET_WM_WINDOW_TYPE_SPLASH,
-        _NET_WM_DESKTOP,
-        _NET_WM_STRUT_PARTIAL,
-        _NET_CURRENT_DESKTOP,
-        _NET_ACTIVE_WINDOW,
-        _NET_WORKAREA,
-        WM_PROTOCOLS,
-        WM_DELETE_WINDOW,
-        UTF8_STRING,
-        WM_STATE,
-        WM_CLIENT_LEADER
-};
+#define xmacro(atom) xcb_atom_t A_ ## atom;
+#include "atoms.xmacro"
+#undef xmacro
 
 extern unsigned int xcb_numlock_mask;
 
 /**
- * Loads a font for usage, getting its height. This function is used very
- * often, so it maintains a cache.
+ * Loads a font for usage, also getting its height. If fallback is true,
+ * i3 loads 'fixed' or '-misc-*' if the font cannot be found instead of
+ * exiting.
  *
  */
-i3Font *load_font(xcb_connection_t *conn, const char *pattern);
+i3Font load_font(const char *pattern, bool fallback);
 
 /**
  * Returns the colorpixel to use for the given hex color (think of HTML).
@@ -85,7 +68,7 @@ i3Font *load_font(xcb_connection_t *conn, const char *pattern);
  * validity.  This has to be done by the caller.
  *
  */
-uint32_t get_colorpixel(xcb_connection_t *conn, char *hex);
+uint32_t get_colorpixel(char *hex);
 
 /**
  * Convenience wrapper around xcb_create_window which takes care of depth,
@@ -93,7 +76,7 @@ uint32_t get_colorpixel(xcb_connection_t *conn, char *hex);
  *
  */
 xcb_window_t create_window(xcb_connection_t *conn, Rect r, uint16_t window_class,
-                           int cursor, bool map, uint32_t mask, uint32_t *values);
+        enum xcursor_cursor_t cursor, bool map, uint32_t mask, uint32_t *values);
 
 /**
  * Changes a single value in the graphic context (so one doesn’t have to
@@ -132,7 +115,13 @@ void fake_configure_notify(xcb_connection_t *conn, Rect r, xcb_window_t window);
  * the X root window, not to the client’s frame) for the given client.
  *
  */
-void fake_absolute_configure_notify(xcb_connection_t *conn, Client *client);
+void fake_absolute_configure_notify(Con *con);
+
+/**
+ * Sends the WM_TAKE_FOCUS ClientMessage to the given window
+ *
+ */
+void send_take_focus(xcb_window_t window);
 
 /**
  * Finds out which modifier mask is the one for numlock, as the user may
@@ -147,22 +136,12 @@ void xcb_get_numlock_mask(xcb_connection_t *conn);
  */
 void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window);
 
-/**
- *
- * Prepares the given Cached_Pixmap for usage (checks whether the size of the
- * object this pixmap is related to (e.g. a window) has changed and re-creates
- * the pixmap if so).
- *
- */
-void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap);
-
 /**
  * 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(xcb_connection_t *conn, const char *font_pattern, char *text,
-                       int length);
+int predict_text_width(char *text, int length);
 
 /**
  * Configures the given window to have the size/position specified by given rect
@@ -170,4 +149,7 @@ int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *t
  */
 void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r);
 
+
+bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom);
+
 #endif
diff --git a/include/xcb_compat.h b/include/xcb_compat.h
new file mode 100644 (file)
index 0000000..c00e266
--- /dev/null
@@ -0,0 +1,25 @@
+#ifndef _XCB_COMPAT_H
+#define _XCB_COMPAT_H
+
+#define xcb_icccm_get_wm_protocols_reply_t xcb_get_wm_protocols_reply_t
+#define xcb_icccm_get_wm_protocols xcb_get_wm_protocols
+#define xcb_icccm_get_wm_protocols_unchecked xcb_get_wm_protocols_unchecked
+#define xcb_icccm_get_wm_protocols_reply xcb_get_wm_protocols_reply
+#define xcb_icccm_get_wm_protocols_reply_wipe xcb_get_wm_protocols_reply_wipe
+#define XCB_ICCCM_WM_STATE_NORMAL XCB_WM_STATE_NORMAL
+#define XCB_ICCCM_WM_STATE_WITHDRAWN XCB_WM_STATE_WITHDRAWN
+#define xcb_icccm_get_wm_size_hints_from_reply xcb_get_wm_size_hints_from_reply
+#define xcb_icccm_get_wm_normal_hints_reply xcb_get_wm_normal_hints_reply
+#define xcb_icccm_get_wm_normal_hints_unchecked xcb_get_wm_normal_hints_unchecked
+#define XCB_ICCCM_SIZE_HINT_P_MIN_SIZE XCB_SIZE_HINT_P_MIN_SIZE
+#define XCB_ICCCM_SIZE_HINT_P_RESIZE_INC XCB_SIZE_HINT_P_RESIZE_INC
+#define XCB_ICCCM_SIZE_HINT_BASE_SIZE XCB_SIZE_HINT_BASE_SIZE
+#define XCB_ICCCM_SIZE_HINT_P_ASPECT XCB_SIZE_HINT_P_ASPECT
+#define xcb_icccm_wm_hints_t xcb_wm_hints_t
+#define xcb_icccm_get_wm_hints_from_reply xcb_get_wm_hints_from_reply
+#define xcb_icccm_get_wm_hints_reply xcb_get_wm_hints_reply
+#define xcb_icccm_get_wm_hints_unchecked xcb_get_wm_hints_unchecked
+#define xcb_icccm_wm_hints_get_urgency xcb_wm_hints_get_urgency
+#define xcb_icccm_get_wm_transient_for_from_reply xcb_get_wm_transient_for_from_reply
+
+#endif
diff --git a/include/xcursor.h b/include/xcursor.h
new file mode 100644 (file)
index 0000000..e129a36
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ */
+#ifndef _XCURSOR_CURSOR_H
+#define _XCURSOR_CURSOR_H
+
+#include <X11/Xlib.h>
+
+enum xcursor_cursor_t {
+    XCURSOR_CURSOR_POINTER = 0,
+    XCURSOR_CURSOR_RESIZE_HORIZONTAL,
+    XCURSOR_CURSOR_RESIZE_VERTICAL,
+    XCURSOR_CURSOR_MAX
+};
+
+void xcursor_load_cursors();
+Cursor xcursor_get_cursor(enum xcursor_cursor_t c);
+int xcursor_get_xcb_cursor(enum xcursor_cursor_t c);
+
+/**
+ * Sets the cursor of the root window to the 'pointer' cursor.
+ *
+ * This function is called when i3 is initialized, because with some login
+ * managers, the root window will not have a cursor otherwise.
+ *
+ * We have a separate xcursor function to use the same X11 connection as the
+ * xcursor_load_cursors() function. If we mix the Xlib and the XCB connection,
+ * races might occur (even though we flush the Xlib connection).
+ *
+ */
+void xcursor_set_root_cursor();
+
+#endif
index f11823498ec0a01772ec3e9b4cf45f2d2b9759f7..600b77f338d44068bcb775d36792d132a99e76eb 100644 (file)
@@ -18,6 +18,6 @@
  * Xinerama information to setup workspaces for each screen.
  *
  */
-void initialize_xinerama(xcb_connection_t *conn);
+void xinerama_init();
 
 #endif
index 4d7836ecf335e4d8f5e54da692bbdf81eb110a1e..ed72ebed47866167d417bbdd36e0841429918f3e 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-wsbar.1
+all: i3.1 i3-msg.1 i3-input.1 i3-nagbar.1 i3-wsbar.1
 
 %.1: %.man asciidoc.conf
        ${A2M} $<
@@ -9,7 +9,7 @@ i3-wsbar.1: ../i3-wsbar
        pod2man $^ > $@
 
 clean:
-       for file in "i3 i3-msg i3-input"; \
+       for file in $$(echo i3 i3-msg i3-input i3-nagbar i3-wsbar); \
        do \
                rm -f $${file}.1 $${file}.html $${file}.xml; \
        done
index e15695d26f6912e765fe06f363c6b27e3e923749..7776d63216a5e45c366ea8cccc54dbb0405f39d5 100644 (file)
@@ -7,7 +7,7 @@ template::[header-declarations]
 <refentrytitle>{mantitle}</refentrytitle>
 <manvolnum>{manvolnum}</manvolnum>
 <refmiscinfo class="source">i3</refmiscinfo>
-<refmiscinfo class="version">epsilon</refmiscinfo>
+<refmiscinfo class="version">4.0</refmiscinfo>
 <refmiscinfo class="manual">i3 Manual</refmiscinfo>
 </refmeta>
 <refnamediv>
index fee4d92e939f1955b19a5bc579d787ce4d91674a..cd85c92c622e62d0b82a50c43128e12cb57882ea 100644 (file)
@@ -23,6 +23,14 @@ mark/goto command.
 i3-input -p 'mark ' -l 1 -P 'Mark: '
 ------------------------------------------------
 
+== ENVIRONMENT
+
+=== I3SOCK
+
+If no ipc-socket is specified on the commandline, this variable is used
+to determine the path, at wich the unix domain socket is expected, on which
+to connect to i3.
+
 == SEE ALSO
 
 i3(1)
index c723bd1e840cdadbbb30455b23d3d2133739d922..116195b723445c24284f8a8edd467b943317a4f9 100644 (file)
@@ -24,6 +24,14 @@ future (staying backwards-compatible, of course).
 i3-msg "bp" # Use 1-px border for current client
 ------------------------------------------------
 
+== ENVIRONMENT
+
+=== I3SOCK
+
+If no ipc-socket is specified on the commandline, this variable is used
+to determine the path, at wich the unix domain socket is expected, on which
+to connect to i3.
+
 == SEE ALSO
 
 i3(1)
diff --git a/man/i3-nagbar.man b/man/i3-nagbar.man
new file mode 100644 (file)
index 0000000..3dd37bb
--- /dev/null
@@ -0,0 +1,34 @@
+i3-nagbar(1)
+============
+Michael Stapelberg <michael+i3@stapelberg.de>
+v4.0, July 2011
+
+== NAME
+
+i3-nagbar - displays an error bar on top of your screen
+
+== SYNOPSIS
+
+i3-nagbar -m 'message' -b 'label' 'action'
+
+== DESCRIPTION
+
+i3-nagbar is used by i3 to tell you about errors in your configuration file
+(for example). While these errors are logged to the logfile (if any), the past
+has proven that users are either not aware of their logfile or do not check it
+after modifying the configuration file.
+
+== EXAMPLE
+
+------------------------------------------------
+i3-nagbar -m 'You have an error in your i3 config file!' \
+-b 'edit config' 'xterm $EDITOR ~/.i3/config'
+------------------------------------------------
+
+== SEE ALSO
+
+i3(1)
+
+== AUTHOR
+
+Michael Stapelberg and contributors
index 00bad74b87d4d76299bd24b89310aafbc2cf37e3..bb705e8180e08d92271ba5f2f6b3f35a680bd7e6 100644 (file)
@@ -156,10 +156,10 @@ Exits i3.
 
 When starting, i3 looks for configuration files in the following order:
 
-1. ~/.i3/config
-2. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set)
-3. /etc/i3/config
-4. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set)
+1. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set)
+2. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set)
+3. ~/.i3/config
+4. /etc/i3/config
 
 You can specify a custom path using the -c option.
 
@@ -168,84 +168,84 @@ You can specify a custom path using the -c option.
 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
 
 # Start terminal (Mod1+Enter)
-bind Mod1+36 exec /usr/bin/urxvt
+bindcode Mod1+36 exec /usr/bin/urxvt
 
 # Start dmenu (Mod1+v)
-bind Mod1+55 exec /usr/bin/dmenu_run
+bindcode Mod1+55 exec /usr/bin/dmenu_run
 
 # Kill current client (Mod1+Shift+q)
-bind Mod1+Shift+24 kill
+bindcode Mod1+Shift+24 kill
 
 # Beamer on/off
-bind Mod1+73 exec /home/michael/toggle_beamer.sh
+bindcode Mod1+73 exec /home/michael/toggle_beamer.sh
 
 # Screen locking
-bind Mod1+68 exec /usr/bin/i3lock
+bindcode Mod1+68 exec /usr/bin/i3lock
 
 # Restart i3 inplace (Mod1+Shift+r)
-bind Mod1+Shift+27 restart
+bindcode Mod1+Shift+27 restart
 
 # Exit i3 (Mod1+Shift+e)
-bind Mod1+Shift+26 exit
+bindcode Mod1+Shift+26 exit
 
 # Brightness
-bind Mod1+97 exec sudo sh -c "echo up > /proc/acpi/ibm/brightness"
-bind Mod1+103 exec sudo sh -c "echo down > /proc/acpi/ibm/brightness"
+bindcode Mod1+97 exec sudo sh -c "echo up > /proc/acpi/ibm/brightness"
+bindcode Mod1+103 exec sudo sh -c "echo down > /proc/acpi/ibm/brightness"
 
 # Fullscreen (Mod1+f)
-bind Mod1+41 f
+bindcode Mod1+41 f
 
 # Stacking (Mod1+h)
-bind Mod1+43 s
+bindcode Mod1+43 s
 
 # Default (Mod1+e)
-bind Mod1+26 d
+bindcode Mod1+26 d
 
 # Toggle tiling/floating of the current window (Mod1+Shift+Space)
-bind Mod1+Shift+65 t
+bindcode Mod1+Shift+65 t
 
 # Go into the tiling layer / floating layer, depending on whether
 # the current window is tiling / floating (Mod1+t)
-bind Mod1+28 focus ft
+bindcode Mod1+28 focus ft
 
 # Focus (Mod1+j/k/l/;)
-bind Mod1+44 h
-bind Mod1+45 j
-bind Mod1+46 k
-bind Mod1+47 l
+bindcode Mod1+44 h
+bindcode Mod1+45 j
+bindcode Mod1+46 k
+bindcode Mod1+47 l
 
 # Focus Container (Mod3+j/k/l/;)
-bind Mod3+44 wch
-bind Mod3+45 wcj
-bind Mod3+46 wck
-bind Mod3+47 wcl
+bindcode Mod3+44 wch
+bindcode Mod3+45 wcj
+bindcode Mod3+46 wck
+bindcode Mod3+47 wcl
 
 # Snap (Mod1+Control+j/k/l/;)
-bind Mod1+Control+44 sh
-bind Mod1+Control+45 sj
-bind Mod1+Control+46 sk
-bind Mod1+Control+47 sl
+bindcode Mod1+Control+44 sh
+bindcode Mod1+Control+45 sj
+bindcode Mod1+Control+46 sk
+bindcode Mod1+Control+47 sl
 
 # Move (Mod1+Shift+j/k/l/;)
-bind Mod1+Shift+44 mh
-bind Mod1+Shift+45 mj
-bind Mod1+Shift+46 mk
-bind Mod1+Shift+47 ml
+bindcode Mod1+Shift+44 mh
+bindcode Mod1+Shift+45 mj
+bindcode Mod1+Shift+46 mk
+bindcode Mod1+Shift+47 ml
 
 # Move Container (Mod3+Shift+j/k/l/;)
-bind Mod3+Shift+44 wcmh
-bind Mod3+Shift+45 wcmj
-bind Mod3+Shift+46 wcmk
-bind Mod3+Shift+47 wcml
+bindcode Mod3+Shift+44 wcmh
+bindcode Mod3+Shift+45 wcmj
+bindcode Mod3+Shift+46 wcmk
+bindcode Mod3+Shift+47 wcml
 
 # Workspaces
-bind Mod1+10 1
-bind Mod1+11 2
+bindcode Mod1+10 1
+bindcode Mod1+11 2
 ...
 
 # Move to Workspace
-bind Mod1+Shift+10 1
-bind Mod1+Shift+11 2
+bindcode Mod1+Shift+10 1
+bindcode Mod1+Shift+11 2
 ...
 -------------------------------------------------------------
 
@@ -294,6 +294,14 @@ echo "Starting at $(date)" >> ~/.i3/logfile
 exec /usr/bin/i3 -V -d all >> ~/.i3/logfile
 -------------------------------------------------------------
 
+== ENVIRONMENT
+
+=== I3SOCK
+
+If no ipc-socket is specified in the configfile, this variable is used
+to determine the path, at wich the unix domain socket is created, on which
+i3 listenes to incoming connections.
+
 == TODO
 
 There is still lot of work to do. Please check our bugtracker for up-to-date
diff --git a/render-tree/Con.pm b/render-tree/Con.pm
new file mode 100644 (file)
index 0000000..1830caf
--- /dev/null
@@ -0,0 +1,21 @@
+# vim:ts=4:sw=4:expandtab
+package Con;
+
+use Moose;
+use MooseX::AttributeHelpers;
+use v5.10;
+
+has 'name' => (is => 'ro', isa => 'Str');
+has 'width' => (is => 'rw', isa => 'Int', default => 100);
+has '_nodes' => (is => 'ro', metaclass => 'Collection::Array', isa => 'ArrayRef[Con]',
+    default => sub { [] },
+    provides => {
+        'push' => 'add_node',
+        elements => 'nodes',
+    }
+);
+has 'parent' => (is => 'rw', isa => 'Con', predicate => 'has_parent');
+
+__PACKAGE__->meta->make_immutable;
+
+1
diff --git a/render-tree/render.pl b/render-tree/render.pl
new file mode 100755 (executable)
index 0000000..bea5811
--- /dev/null
@@ -0,0 +1,125 @@
+#!/usr/bin/env perl
+# vim:ts=4:sw=4:expandtab
+# © 2011 Michael Stapelberg, see LICENSE
+#
+# Needs SVG (libsvg-perl), IO::All (libio-all-perl), JSON::XS (libjson-xs-perl) and Moose (libmoose-perl)
+#
+# XXX: unfinished proof-of-concept. awaits a json dump in my.tree, renders to test.svg
+# XXX: needs more beautifying (in the SVG but also in the code)
+# XXX: has some rendering differences between firefox and chromium. maybe inkscape makes the file look the same in both browsers
+
+use strict;
+use warnings;
+use SVG;
+use Data::Dumper;
+use JSON::XS;
+use IO::All;
+use List::Util qw(sum);
+use lib qw(.);
+use Con;
+use v5.10;
+
+my $input = io('my.tree')->slurp;
+my $tree = decode_json($input);
+my $root = parse_tree($tree);
+render_tree($root);
+
+sub parse_tree {
+    my ($input, $parent) = @_;
+    my $con = Con->new(name => $input->{name});
+    $con->parent($parent) if defined($parent);
+    for my $node (@{$input->{nodes}}) {
+        $con->add_node(parse_tree($node, $con));
+    }
+
+    return $con;
+}
+
+sub render_tree {
+    my ($con) = @_;
+    say 'rendering con ' . $con->name;
+    my @nodes = $con->nodes;
+    for my $node (@nodes) {
+        render_tree($node);
+    }
+
+    # nothing to calculate when there are no children
+    return unless @nodes > 0;
+
+    $con->width((@nodes > 1 ? (@nodes - 1) * 20 : 0) + sum map { $_->width } @nodes);
+
+    say $con->name . ' has width ' . $con->width;
+}
+
+# TODO: figure out the height
+my $svg = SVG->new(id => "tree", width => $root->width + 5, height => '1052');
+
+my $l1 = $svg->group(id => 'layer1');
+
+# gaussian blur (for drop shadows)
+$svg->defs()->filter(id => 'dropshadow')->fe(-type => 'gaussianblur', stdDeviation => '2.19');
+
+my $idcnt = 0;
+my $y = 10;
+render_svg($root, 0, 0);
+
+sub render_svg {
+    my ($con, $level, $x) = @_;
+
+    my $indent = ' ' x $level;
+
+    say $indent . 'svg-rendering con ' . $con->name . ' on level ' . $level;
+    say $indent . 'width: ' . $con->width;
+
+    # render the dropshadow rect
+    $l1->rect(
+        id => 'outer_rect_shadow' . $idcnt,
+        style => 'opacity:1.0;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-opacity:1;stroke-miterlimit:4;filter:url(#dropshadow)',
+        width => "96",
+        height => '50',
+        #x => $x + ($con->has_parent ? ($con->parent->width - 100) / 2 : 0),
+        x => $x + ($con->width / 2) - (96 / 2) + 0,
+        y => 4 + $level * 70 + 0,
+    );
+    $idcnt++;
+
+    # render the main rect
+    $l1->rect(
+        id => 'outer_rect' . $idcnt,
+        style => 'opacity:1.0;fill:#c30000;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-opacity:1;stroke-miterlimit:4',
+        width => "96",
+        height => '50',
+        x => $x + ($con->width / 2) - (96 / 2),
+        y => 4 + $level * 70,
+    );
+
+    $idcnt++;
+
+    # render the text
+    $l1->text(
+        style => 'font-size:14px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:left;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Trebuchet MS;-inkscape-font-specification:Trebuchet MS',
+        x => $x + ($con->width / 2) - (100/2) + 5,
+        y => 4 + 15 + $level * 70,
+        id => 'title_'.$idcnt,
+    )->tspan(style => 'text-align:start;text-anchor:start')->cdata($con->name);
+    $idcnt++;
+
+    $y = $y + 50;
+    my @nodes = $con->nodes;
+    my $startx = $x + ($con->width / 2);
+
+    for my $node (@nodes) {
+        render_svg($node, $level + 1, $x);
+        my $mid = $x + ($node->width / 2);
+        $l1->path(
+            d => 'M ' . $startx . ',' . (4 + $level * 70 + 50) . ' ' . $mid . ',' . (4 + ($level+1) * 70),
+            id => 'path' . $idcnt,
+            style => 'fill:none;stroke:#000000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1'
+        );
+        $x += $node->width + 20;
+        $idcnt++;
+    }
+
+}
+
+$svg->render > io('test.svg');
diff --git a/src/assignments.c b/src/assignments.c
new file mode 100644 (file)
index 0000000..f171dc3
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include "all.h"
+
+/*
+ * Checks the list of assignments for the given window and runs all matching
+ * ones (unless they have already been run for this specific window).
+ *
+ */
+void run_assignments(i3Window *window) {
+    DLOG("Checking assignments...\n");
+
+    /* Check if any assignments match */
+    Assignment *current;
+    TAILQ_FOREACH(current, &assignments, assignments) {
+        if (!match_matches_window(&(current->match), window))
+            continue;
+
+        bool skip = false;
+        for (int c = 0; c < window->nr_assignments; c++) {
+            if (window->ran_assignments[c] != current)
+                continue;
+
+            DLOG("This assignment already ran for the given window, not executing it again.\n");
+            skip = true;
+            break;
+        }
+
+        if (skip)
+            continue;
+
+        DLOG("matching assignment, would do:\n");
+        if (current->type == A_COMMAND) {
+            DLOG("execute command %s\n", current->dest.command);
+            char *full_command;
+            asprintf(&full_command, "[id=\"%d\"] %s", window->id, current->dest.command);
+            parse_cmd(full_command);
+        }
+
+        /* Store that we ran this assignment to not execute it again */
+        window->nr_assignments++;
+        window->ran_assignments = srealloc(window->ran_assignments, sizeof(Assignment*) * window->nr_assignments);
+        window->ran_assignments[window->nr_assignments-1] = current;
+    }
+}
+
+/*
+ * Returns the first matching assignment for the given window.
+ *
+ */
+Assignment *assignment_for(i3Window *window, int type) {
+    Assignment *assignment;
+
+    TAILQ_FOREACH(assignment, &assignments, assignments) {
+        if ((type != A_ANY && (assignment->type & type) == 0) ||
+            !match_matches_window(&(assignment->match), window))
+            continue;
+        DLOG("got a matching assignment (to %s)\n", assignment->dest.workspace);
+        return assignment;
+    }
+
+    return NULL;
+}
index 2d31913428f696519f9ea187b80159c9aebd9aff..2e1d240af58bf21a76e5f5e3e75f2b720c4ee422 100644 (file)
@@ -1,17 +1,16 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ */
 %option nounput
 %option noinput
 %option noyy_top_state
 %option stack
 
 %{
-/*
- * vim:ts=8:expandtab
- *
- */
 #include <stdio.h>
 #include <string.h>
-#include <stdint.h> /* Defines uint32_t, required by cfgparse.tab.h */
-#include "cfgparse.tab.h"
+#include <stdint.h>
 #include <xcb/xcb.h>
 
 #include "data.h"
 #include "log.h"
 #include "util.h"
 
+#include "cfgparse.tab.h"
+
 int yycolumn = 1;
 
 #define YY_DECL int yylex (struct context *context)
 
 #define YY_USER_ACTION { \
-        context->first_column = yycolumn; \
-        context->last_column = yycolumn+yyleng-1; \
-        yycolumn += yyleng; \
+    context->first_column = yycolumn; \
+    context->last_column = yycolumn+yyleng-1; \
+    yycolumn += yyleng; \
 }
 
+/* macro to first eat whitespace, then expect a string */
+#define WS_STRING do { \
+    yy_push_state(WANT_STRING); \
+    yy_push_state(EAT_WHITESPACE); \
+} while (0)
+
 %}
 
-EOL    (\r?\n)
+EOL     (\r?\n)
 
-%s BIND_COND
+%s WANT_STRING
+%s WANT_QSTRING
 %s BINDSYM_COND
-%s BIND_AWS_COND
-%s BINDSYM_AWS_COND
-%s BIND_A2WS_COND
 %s ASSIGN_COND
+%s ASSIGN_TARGET_COND
 %s COLOR_COND
 %s OUTPUT_COND
-%s OUTPUT_AWS_COND
+%s FOR_WINDOW_COND
+%s EAT_WHITESPACE
 %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);
-               }
-       }
+    {
+        /* This is called when a new line is lexed. We only want the
+         * first line to match to go into state BUFFER_LINE */
+        if (context->line_number == 0) {
+            context->line_number = 1;
+            BEGIN(INITIAL);
+            yy_push_state(BUFFER_LINE);
+        }
+    }
 
 <BUFFER_LINE>^[^\r\n]*/{EOL}? {
-       /* save whole line */
-       context->line_copy = strdup(yytext);
+    /* save whole line */
+    context->line_copy = sstrdup(yytext);
 
-       yyless(0);
-       yy_pop_state();
-       yy_set_bol(true);
-       yycolumn = 1;
+    yyless(0);
+    yy_pop_state();
+    yy_set_bol(true);
+    yycolumn = 1;
 }
 
 
-<BIND_A2WS_COND>[^\n]+          { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR; }
-<OUTPUT_AWS_COND>[a-zA-Z0-9_-]+ { yylval.string = strdup(yytext); return OUTPUT; }
+<FOR_WINDOW_COND>"]"            { yy_pop_state(); return ']'; }
+<EAT_WHITESPACE>[ \t]*          { yy_pop_state(); }
+<WANT_QSTRING>\"[^\"]+\"        {
+                                  yy_pop_state();
+                                  /* strip quotes */
+                                  char *copy = sstrdup(yytext+1);
+                                  copy[strlen(copy)-1] = '\0';
+                                  yylval.string = copy;
+                                  return STR;
+                                }
+<WANT_STRING>[^\n]+             { BEGIN(INITIAL); yylval.string = sstrdup(yytext); return STR; }
+<OUTPUT_COND>[a-zA-Z0-9_-]+     { yylval.string = sstrdup(yytext); return OUTPUT; }
 ^[ \t]*#[^\n]*                  { return TOKCOMMENT; }
-<COLOR_COND>[0-9a-fA-F]+        { yylval.string = strdup(yytext); return HEX; }
+<COLOR_COND>[0-9a-fA-F]+        { yylval.string = sstrdup(yytext); return HEX; }
+<ASSIGN_TARGET_COND>[ \t]*→[ \t]*     { BEGIN(WANT_STRING); }
+<ASSIGN_TARGET_COND>[ \t]+      { BEGIN(WANT_STRING); }
 [0-9]+                          { yylval.number = atoi(yytext); return NUMBER; }
 mode                            { return TOKMODE; }
-bind                            { BEGIN(BIND_COND); return TOKBIND; }
-bindsym                         { BEGIN(BINDSYM_COND); return TOKBINDSYM; }
+bind                            { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; }
+bindcode                        { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; }
+bindsym                         { yy_push_state(BINDSYM_COND); yy_push_state(EAT_WHITESPACE); return TOKBINDSYM; }
 floating_modifier               { BEGIN(INITIAL); return TOKFLOATING_MODIFIER; }
 workspace                       { BEGIN(INITIAL); return TOKWORKSPACE; }
-output                          { BEGIN(OUTPUT_COND); return TOKOUTPUT; }
+output                          { yy_push_state(OUTPUT_COND); yy_push_state(EAT_WHITESPACE); return TOKOUTPUT; }
 screen                          {
                                   /* for compatibility until v3.φ */
                                   ELOG("Assignments to screens are DEPRECATED and will not work. " \
                                        "Please replace them with assignments to outputs.\n");
-                                  BEGIN(OUTPUT_COND);
+                                  yy_push_state(OUTPUT_COND); yy_push_state(EAT_WHITESPACE);
                                   return TOKOUTPUT;
                                 }
-terminal                        { BEGIN(BIND_AWS_COND); return TOKTERMINAL; }
-font                            { BEGIN(BIND_AWS_COND); return TOKFONT; }
-assign                          { BEGIN(ASSIGN_COND); return TOKASSIGN; }
+terminal                        { WS_STRING; return TOKTERMINAL; }
+font                            { WS_STRING; return TOKFONT; }
+assign                          { yy_push_state(ASSIGN_TARGET_COND); yy_push_state(ASSIGN_COND); return TOKASSIGN; }
 set[^\n]*                       { return TOKCOMMENT; }
-ipc-socket                      { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; }
-ipc_socket                      { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; }
-new_container                   { return TOKNEWCONTAINER; }
+ipc-socket                      { WS_STRING; return TOKIPCSOCKET; }
+ipc_socket                      { WS_STRING; return TOKIPCSOCKET; }
+restart_state                   { WS_STRING; return TOKRESTARTSTATE; }
+default_orientation             { return TOK_ORIENTATION; }
+horizontal                      { return TOK_HORIZ; }
+vertical                        { return TOK_VERT; }
+auto                            { return TOK_AUTO; }
+workspace_layout                { return TOK_WORKSPACE_LAYOUT; }
 new_window                      { return TOKNEWWINDOW; }
+normal                          { return TOK_NORMAL; }
+none                            { return TOK_NONE; }
+1pixel                          { return TOK_1PIXEL; }
 focus_follows_mouse             { return TOKFOCUSFOLLOWSMOUSE; }
+force_focus_wrapping            { return TOK_FORCE_FOCUS_WRAPPING; }
 workspace_bar                   { return TOKWORKSPACEBAR; }
-default                         { yylval.number = MODE_DEFAULT; return TOKCONTAINERMODE; }
-stacking                        { yylval.number = MODE_STACK; return TOKCONTAINERMODE; }
-tabbed                          { yylval.number = MODE_TABBED; return TOKCONTAINERMODE; }
+popup_during_fullscreen         { return TOK_POPUP_DURING_FULLSCREEN; }
+ignore                          { return TOK_IGNORE; }
+leave_fullscreen                { return TOK_LEAVE_FULLSCREEN; }
+for_window                      {
+                                  /* Example: for_window [class="urxvt"] border none
+                                   *
+                                   * First, we wait for the ']' that finishes a match (FOR_WINDOW_COND)
+                                   * Then, we require a whitespace (EAT_WHITESPACE)
+                                   * And the rest of the line is parsed as a string
+                                   */
+                                  yy_push_state(WANT_STRING);
+                                  yy_push_state(EAT_WHITESPACE);
+                                  yy_push_state(FOR_WINDOW_COND);
+                                  return TOK_FOR_WINDOW;
+                                }
+default                         { /* yylval.number = MODE_DEFAULT; */return TOK_DEFAULT; }
+stacking                        { /* yylval.number = MODE_STACK; */return TOK_STACKING; }
+stacked                         { return TOK_STACKING; }
+tabbed                          { /* yylval.number = MODE_TABBED; */return TOK_TABBED; }
 stack-limit                     { return TOKSTACKLIMIT; }
-cols                            { yylval.number = STACK_LIMIT_COLS; return TOKSTACKLIMIT; }
-rows                            { yylval.number = STACK_LIMIT_ROWS; return TOKSTACKLIMIT; }
-exec                            { BEGIN(BIND_AWS_COND); return TOKEXEC; }
+cols                            { /* yylval.number = STACK_LIMIT_COLS; */return TOKSTACKLIMIT; }
+rows                            { /* yylval.number = STACK_LIMIT_ROWS; */return TOKSTACKLIMIT; }
+exec                            { WS_STRING; return TOKEXEC; }
+exec_always                     { WS_STRING; return TOKEXEC_ALWAYS; }
 client.background               { BEGIN(COLOR_COND); yylval.single_color = &config.client.background; return TOKSINGLECOLOR; }
 client.focused                  { BEGIN(COLOR_COND); yylval.color = &config.client.focused; return TOKCOLOR; }
 client.focused_inactive         { BEGIN(COLOR_COND); yylval.color = &config.client.focused_inactive; return TOKCOLOR; }
@@ -119,38 +165,42 @@ Mode_switch                     { yylval.number = BIND_MODE_SWITCH; return MODIF
 control                         { return TOKCONTROL; }
 ctrl                            { return TOKCONTROL; }
 shift                           { return TOKSHIFT; }
-→                               { return TOKARROW; }
+
+class                           { yy_push_state(WANT_QSTRING); return TOK_CLASS; }
+id                              { yy_push_state(WANT_QSTRING); return TOK_ID; }
+con_id                          { yy_push_state(WANT_QSTRING); return TOK_CON_ID; }
+con_mark                        { yy_push_state(WANT_QSTRING); return TOK_MARK; }
+title                           { yy_push_state(WANT_QSTRING); return TOK_TITLE; }
+
 {EOL}                           {
                                   FREE(context->line_copy);
                                   context->line_number++;
                                   BEGIN(INITIAL);
                                   yy_push_state(BUFFER_LINE);
                                 }
-<BIND_COND>[ \t]+               { BEGIN(BIND_AWS_COND); return WHITESPACE; }
-<BINDSYM_COND>[ \t]+            { BEGIN(BINDSYM_AWS_COND); return WHITESPACE; }
-<BIND_AWS_COND>[ \t]+           { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
-<BINDSYM_AWS_COND>[ \t]+        { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
-<OUTPUT_COND>[ \t]+             { BEGIN(OUTPUT_AWS_COND); return WHITESPACE; }
-<OUTPUT_AWS_COND>[ \t]+         { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
-[ \t]+                          { return WHITESPACE; }
+<BINDSYM_COND>[ \t]+            { BEGIN(WANT_STRING); }
+<OUTPUT_COND>[ \t]+             { BEGIN(WANT_STRING); }
+[ \t]+                          { /* ignore whitespace */ ; }
 \"[^\"]+\"                      {
                                   /* if ASSIGN_COND then */
-                                  BEGIN(INITIAL);
+                                  if (yy_start_stack_ptr > 0)
+                                      yy_pop_state();
+                                  else BEGIN(INITIAL);
                                   /* yylval will be the string, but without quotes */
-                                  char *copy = strdup(yytext+1);
+                                  char *copy = sstrdup(yytext+1);
                                   copy[strlen(copy)-1] = '\0';
                                   yylval.string = copy;
                                   return QUOTEDSTRING;
                                 }
-<ASSIGN_COND>[^ \t]+            { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR_NG; }
-<BINDSYM_AWS_COND>[a-zA-Z0-9_]+ { yylval.string = strdup(yytext); return WORD; }
-[a-zA-Z]+                       { yylval.string = strdup(yytext); return WORD; }
+<ASSIGN_COND>[^ \t\"]+          { BEGIN(ASSIGN_TARGET_COND); yylval.string = sstrdup(yytext); return STR_NG; }
+<BINDSYM_COND>[a-zA-Z0-9_]+     { yylval.string = sstrdup(yytext); return WORD; }
+[a-zA-Z]+                       { yylval.string = sstrdup(yytext); return WORD; }
 .                               { return (int)yytext[0]; }
 
 <<EOF>> {
-        while (yy_start_stack_ptr > 0)
-                yy_pop_state();
-        yyterminate();
+    while (yy_start_stack_ptr > 0)
+        yy_pop_state();
+    yyterminate();
 }
 
 %%
index 666445ccfae6c85558fbdacaa18f429c3b41493c..ecfa665859c51520d8bbe557633192e561a03266 100644 (file)
@@ -1,31 +1,25 @@
 %{
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  */
-#include <stdio.h>
-#include <string.h>
-#include <xcb/xcb.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/wait.h>
 #include <unistd.h>
 #include <fcntl.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#include "data.h"
-#include "config.h"
-#include "i3.h"
-#include "util.h"
-#include "queue.h"
-#include "table.h"
-#include "workspace.h"
-#include "xcb.h"
-#include "log.h"
+#include <limits.h>
+
+#include "all.h"
+
+static pid_t configerror_pid = -1;
+
+static Match current_match;
 
 typedef struct yy_buffer_state *YY_BUFFER_STATE;
 extern int yylex(struct context *context);
 extern int yyparse(void);
+extern int yylex_destroy(void);
 extern FILE *yyin;
 YY_BUFFER_STATE yy_scan_string(const char *);
 
@@ -38,595 +32,1093 @@ static struct context *context;
 //int yydebug = 1;
 
 void yyerror(const char *error_message) {
-        ELOG("\n");
-        ELOG("CONFIG: %s\n", error_message);
-        ELOG("CONFIG: in file \"%s\", line %d:\n",
-                context->filename, context->line_number);
-        ELOG("CONFIG:   %s\n", context->line_copy);
-        ELOG("CONFIG:   ");
-        for (int c = 1; c <= context->last_column; c++)
-                if (c >= context->first_column)
-                        printf("^");
-                else printf(" ");
-        printf("\n");
-        ELOG("\n");
+    context->has_errors = true;
+
+    ELOG("\n");
+    ELOG("CONFIG: %s\n", error_message);
+    ELOG("CONFIG: in file \"%s\", line %d:\n",
+        context->filename, context->line_number);
+    ELOG("CONFIG:   %s\n", context->line_copy);
+    char buffer[context->last_column+1];
+    buffer[context->last_column] = '\0';
+    for (int c = 1; c <= context->last_column; c++)
+        buffer[c-1] = (c >= context->first_column ? '^' : ' ');
+    ELOG("CONFIG:   %s\n", buffer);
+    ELOG("\n");
 }
 
 int yywrap() {
-        return 1;
+    return 1;
 }
 
-void parse_file(const char *f) {
-        SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
-        int fd, ret, read_bytes = 0;
-        struct stat stbuf;
-        char *buf;
-        FILE *fstr;
-        char buffer[1026], key[512], value[512];
-
-        if ((fd = open(f, O_RDONLY)) == -1)
-                die("Could not open configuration file: %s\n", strerror(errno));
-
-        if (fstat(fd, &stbuf) == -1)
-                die("Could not fstat file: %s\n", strerror(errno));
-
-        buf = scalloc((stbuf.st_size + 1) * sizeof(char));
-        while (read_bytes < stbuf.st_size) {
-                if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0)
-                        die("Could not read(): %s\n", strerror(errno));
-                read_bytes += ret;
+/*
+ * Goes through each line of buf (separated by \n) and checks for statements /
+ * commands which only occur in i3 v4 configuration files. If it finds any, it
+ * returns version 4, otherwise it returns version 3.
+ *
+ */
+static int detect_version(char *buf) {
+    char *walk = buf;
+    char *line = buf;
+    while (*walk != '\0') {
+        if (*walk != '\n') {
+            walk++;
+            continue;
         }
 
-        if (lseek(fd, 0, SEEK_SET) == (off_t)-1)
-                die("Could not lseek: %s\n", strerror(errno));
+        /* check for some v4-only statements */
+        if (strncasecmp(line, "bindcode", strlen("bindcode")) == 0 ||
+            strncasecmp(line, "force_focus_wrapping", strlen("force_focus_wrapping")) == 0 ||
+            strncasecmp(line, "# i3 config file (v4)", strlen("# i3 config file (v4)")) == 0 ||
+            strncasecmp(line, "workspace_layout", strlen("workspace_layout")) == 0) {
+            printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line);
+            return 4;
+        }
 
-        if ((fstr = fdopen(fd, "r")) == NULL)
-                die("Could not fdopen: %s\n", strerror(errno));
+        /* if this is a bind statement, we can check the command */
+        if (strncasecmp(line, "bind", strlen("bind")) == 0) {
+            char *bind = strchr(line, ' ');
+            if (bind == NULL)
+                goto next;
+            while ((*bind == ' ' || *bind == '\t') && *bind != '\0')
+                bind++;
+            if (*bind == '\0')
+                goto next;
+            if ((bind = strchr(bind, ' ')) == NULL)
+                goto next;
+            while ((*bind == ' ' || *bind == '\t') && *bind != '\0')
+                bind++;
+            if (*bind == '\0')
+                goto next;
+            if (strncasecmp(bind, "layout", strlen("layout")) == 0 ||
+                strncasecmp(bind, "floating", strlen("floating")) == 0 ||
+                strncasecmp(bind, "workspace", strlen("workspace")) == 0 ||
+                strncasecmp(bind, "focus left", strlen("focus left")) == 0 ||
+                strncasecmp(bind, "focus right", strlen("focus right")) == 0 ||
+                strncasecmp(bind, "focus up", strlen("focus up")) == 0 ||
+                strncasecmp(bind, "focus down", strlen("focus down")) == 0 ||
+                strncasecmp(bind, "border normal", strlen("border normal")) == 0 ||
+                strncasecmp(bind, "border 1pixel", strlen("border 1pixel")) == 0 ||
+                strncasecmp(bind, "border borderless", strlen("border borderless")) == 0) {
+                printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line);
+                return 4;
+            }
+        }
 
-        while (!feof(fstr)) {
-                if (fgets(buffer, 1024, fstr) == NULL) {
-                        if (feof(fstr))
-                                break;
-                        die("Could not read configuration file\n");
-                }
+next:
+        /* advance to the next line */
+        walk++;
+        line = walk;
+    }
 
-                /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */
-                if (sscanf(buffer, "%s %[^\n]", key, value) < 1 ||
-                    key[0] == '#' || strlen(key) < 3)
-                        continue;
+    return 3;
+}
+
+/*
+ * Calls i3-migrate-config-to-v4.pl to migrate a configuration file (input
+ * buffer).
+ *
+ * Returns the converted config file or NULL if there was an error (for
+ * example the script could not be found in $PATH or the i3 executable’s
+ * directory).
+ *
+ */
+static char *migrate_config(char *input, off_t size) {
+    int writepipe[2];
+    int readpipe[2];
+
+    if (pipe(writepipe) != 0 ||
+        pipe(readpipe) != 0) {
+        warn("migrate_config: Could not create pipes");
+        return NULL;
+    }
+
+    pid_t pid = fork();
+    if (pid == -1) {
+        warn("Could not fork()");
+        return NULL;
+    }
+
+    /* child */
+    if (pid == 0) {
+        /* close writing end of writepipe, connect reading side to stdin */
+        close(writepipe[1]);
+        dup2(writepipe[0], 0);
+
+        /* close reading end of readpipe, connect writing side to stdout */
+        close(readpipe[0]);
+        dup2(readpipe[1], 1);
+
+        static char *argv[] = {
+            NULL, /* will be replaced by the executable path */
+            NULL
+        };
+        exec_i3_utility("i3-migrate-config-to-v4.pl", argv);
+    }
+
+    /* parent */
+
+    /* close reading end of the writepipe (connected to the script’s stdin) */
+    close(writepipe[0]);
+
+    /* write the whole config file to the pipe, the script will read everything
+     * immediately */
+    int written = 0;
+    int ret;
+    while (written < size) {
+        if ((ret = write(writepipe[1], input + written, size - written)) < 0) {
+            warn("Could not write to pipe");
+            return NULL;
+        }
+        written += ret;
+    }
+    close(writepipe[1]);
+
+    /* close writing end of the readpipe (connected to the script’s stdout) */
+    close(readpipe[1]);
+
+    /* read the script’s output */
+    int conv_size = 65535;
+    char *converted = malloc(conv_size);
+    int read_bytes = 0;
+    do {
+        if (read_bytes == conv_size) {
+            conv_size += 65535;
+            converted = realloc(converted, conv_size);
+        }
+        ret = read(readpipe[0], converted + read_bytes, conv_size - read_bytes);
+        if (ret == -1) {
+            warn("Cannot read from pipe");
+            return NULL;
+        }
+        read_bytes += ret;
+    } while (ret > 0);
+
+    /* get the returncode */
+    int status;
+    wait(&status);
+    if (!WIFEXITED(status)) {
+        fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n");
+        return NULL;
+    }
+
+    int returncode = WEXITSTATUS(status);
+    if (returncode != 0) {
+        fprintf(stderr, "Migration process exit code was != 0\n");
+        if (returncode == 2) {
+            fprintf(stderr, "could not start the migration script\n");
+            /* TODO: script was not found. tell the user to fix his system or create a v4 config */
+        } else if (returncode == 1) {
+            fprintf(stderr, "This already was a v4 config. Please add the following line to your config file:\n");
+            fprintf(stderr, "# i3 config file (v4)\n");
+            /* TODO: nag the user with a message to include a hint for i3 in his config file */
+        }
+        return NULL;
+    }
 
-                if (strcasecmp(key, "set") == 0) {
-                        if (value[0] != '$')
-                                die("Malformed variable assignment, name has to start with $\n");
+    return converted;
+}
 
-                        /* get key/value for this variable */
-                        char *v_key = value, *v_value;
-                        if ((v_value = strstr(value, " ")) == NULL)
-                                die("Malformed variable assignment, need a value\n");
+/*
+ * 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");
+    }
+
+    configerror_pid = -1;
+}
 
-                        *(v_value++) = '\0';
+/*
+ * Starts an i3-nagbar process which alerts the user that his configuration
+ * file contains one or more errors. Also offers two buttons: One to launch an
+ * $EDITOR on the config file and another one to launch a $PAGER on the error
+ * logfile.
+ *
+ */
+static void start_configerror_nagbar(const char *config_path) {
+    fprintf(stderr, "Would start i3-nagscreen now\n");
+    configerror_pid = fork();
+    if (configerror_pid == -1) {
+        warn("Could not fork()");
+        return;
+    }
+
+    /* child */
+    if (configerror_pid == 0) {
+        char *editaction,
+             *pageraction;
+        if (asprintf(&editaction, TERM_EMU " -e sh -c \"${EDITOR:-vi} \"%s\" && i3-msg reload\"", config_path) == -1)
+            exit(1);
+        if (asprintf(&pageraction, TERM_EMU " -e sh -c \"${PAGER:-less} \"%s\"\"", errorfilename) == -1)
+            exit(1);
+        char *argv[] = {
+            NULL, /* will be replaced by the executable path */
+            "-m",
+            "You have an error in your i3 config file!",
+            "-b",
+            "edit config",
+            editaction,
+            (errorfilename ? "-b" : NULL),
+            "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, configerror_pid, 0);
+    ev_child_start(main_loop, child);
+}
 
-                        struct Variable *new = scalloc(sizeof(struct Variable));
-                        new->key = sstrdup(v_key);
-                        new->value = sstrdup(v_value);
-                        SLIST_INSERT_HEAD(&variables, new, variables);
-                        DLOG("Got new variable %s = %s\n", v_key, v_value);
-                        continue;
-                }
-        }
+/*
+ * Kills the configerror i3-nagbar process, if any.
+ *
+ * Called when reloading/restarting.
+ *
+ * If wait_for_it is set (restarting), this function will waitpid(), otherwise,
+ * ev is assumed to handle it (reloading).
+ *
+ */
+void kill_configerror_nagbar(bool wait_for_it) {
+    if (configerror_pid == -1)
+        return;
 
-        /* For every custom variable, see how often it occurs in the file and
-         * how much extra bytes it requires when replaced. */
-        struct Variable *current, *nearest;
-        int extra_bytes = 0;
-        /* We need to copy the buffer because we need to invalidate the
-         * variables (otherwise we will count them twice, which is bad when
-         * 'extra' is negative) */
-        char *bufcopy = sstrdup(buf);
-        SLIST_FOREACH(current, &variables, variables) {
-                int extra = (strlen(current->value) - strlen(current->key));
-                char *next;
-                for (next = bufcopy;
-                     (next = strcasestr(bufcopy + (next - bufcopy), current->key)) != NULL;
-                     next += strlen(current->key)) {
-                        *next = '_';
-                        extra_bytes += extra;
-                }
-        }
-        FREE(bufcopy);
-
-        /* Then, allocate a new buffer and copy the file over to the new one,
-         * but replace occurences of our variables */
-        char *walk = buf, *destwalk;
-        char *new = smalloc((stbuf.st_size + extra_bytes + 1) * sizeof(char));
-        destwalk = new;
-        while (walk < (buf + stbuf.st_size)) {
-                /* Find the next variable */
-                SLIST_FOREACH(current, &variables, variables)
-                        current->next_match = strcasestr(walk, current->key);
-                nearest = NULL;
-                int distance = stbuf.st_size;
-                SLIST_FOREACH(current, &variables, variables) {
-                        if (current->next_match == NULL)
-                                continue;
-                        if ((current->next_match - walk) < distance) {
-                                distance = (current->next_match - walk);
-                                nearest = current;
-                        }
-                }
-                if (nearest == NULL) {
-                        /* If there are no more variables, we just copy the rest */
-                        strncpy(destwalk, walk, (buf + stbuf.st_size) - walk);
-                        destwalk += (buf + stbuf.st_size) - walk;
-                        *destwalk = '\0';
-                        break;
-                } else {
-                        /* Copy until the next variable, then copy its value */
-                        strncpy(destwalk, walk, distance);
-                        strncpy(destwalk + distance, nearest->value, strlen(nearest->value));
-                        walk += distance + strlen(nearest->key);
-                        destwalk += distance + strlen(nearest->value);
-                }
-        }
+    if (kill(configerror_pid, SIGTERM) == -1)
+        warn("kill(configerror_nagbar) failed");
 
-        yy_scan_string(new);
+    if (!wait_for_it)
+        return;
 
-        context = scalloc(sizeof(struct context));
-        context->filename = f;
+    /* When restarting, we don’t enter the ev main loop anymore and after the
+     * exec(), our old pid is no longer watched. So, ev won’t handle SIGCHLD
+     * for us and we would end up with a <defunct> process. Therefore we
+     * waitpid() here. */
+    waitpid(configerror_pid, NULL, 0);
+}
 
-        if (yyparse() != 0) {
-                fprintf(stderr, "Could not parse configfile\n");
-                exit(1);
+void parse_file(const char *f) {
+    SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
+    int fd, ret, read_bytes = 0;
+    struct stat stbuf;
+    char *buf;
+    FILE *fstr;
+    char buffer[1026], key[512], value[512];
+
+    if ((fd = open(f, O_RDONLY)) == -1)
+        die("Could not open configuration file: %s\n", strerror(errno));
+
+    if (fstat(fd, &stbuf) == -1)
+        die("Could not fstat file: %s\n", strerror(errno));
+
+    buf = scalloc((stbuf.st_size + 1) * sizeof(char));
+    while (read_bytes < stbuf.st_size) {
+        if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0)
+            die("Could not read(): %s\n", strerror(errno));
+        read_bytes += ret;
+    }
+
+    if (lseek(fd, 0, SEEK_SET) == (off_t)-1)
+        die("Could not lseek: %s\n", strerror(errno));
+
+    if ((fstr = fdopen(fd, "r")) == NULL)
+        die("Could not fdopen: %s\n", strerror(errno));
+
+    while (!feof(fstr)) {
+        if (fgets(buffer, 1024, fstr) == NULL) {
+            if (feof(fstr))
+                break;
+            die("Could not read configuration file\n");
         }
 
-        FREE(context->line_copy);
-        free(context);
-        free(new);
-        free(buf);
-
-        while (!SLIST_EMPTY(&variables)) {
-                current = SLIST_FIRST(&variables);
-                FREE(current->key);
-                FREE(current->value);
-                SLIST_REMOVE_HEAD(&variables, variables);
-                FREE(current);
+        /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */
+        if (sscanf(buffer, "%s %[^\n]", key, value) < 1 ||
+            key[0] == '#' || strlen(key) < 3)
+            continue;
+
+        if (strcasecmp(key, "set") == 0) {
+            if (value[0] != '$')
+                die("Malformed variable assignment, name has to start with $\n");
+
+            /* get key/value for this variable */
+            char *v_key = value, *v_value;
+            if ((v_value = strstr(value, " ")) == NULL)
+                die("Malformed variable assignment, need a value\n");
+
+            *(v_value++) = '\0';
+
+            struct Variable *new = scalloc(sizeof(struct Variable));
+            new->key = sstrdup(v_key);
+            new->value = sstrdup(v_value);
+            SLIST_INSERT_HEAD(&variables, new, variables);
+            DLOG("Got new variable %s = %s\n", v_key, v_value);
+            continue;
+        }
+    }
+    fclose(fstr);
+
+    /* For every custom variable, see how often it occurs in the file and
+     * how much extra bytes it requires when replaced. */
+    struct Variable *current, *nearest;
+    int extra_bytes = 0;
+    /* We need to copy the buffer because we need to invalidate the
+     * variables (otherwise we will count them twice, which is bad when
+     * 'extra' is negative) */
+    char *bufcopy = sstrdup(buf);
+    SLIST_FOREACH(current, &variables, variables) {
+        int extra = (strlen(current->value) - strlen(current->key));
+        char *next;
+        for (next = bufcopy;
+             (next = strcasestr(bufcopy + (next - bufcopy), current->key)) != NULL;
+             next += strlen(current->key)) {
+            *next = '_';
+            extra_bytes += extra;
+        }
+    }
+    FREE(bufcopy);
+
+    /* Then, allocate a new buffer and copy the file over to the new one,
+     * but replace occurences of our variables */
+    char *walk = buf, *destwalk;
+    char *new = smalloc((stbuf.st_size + extra_bytes + 1) * sizeof(char));
+    destwalk = new;
+    while (walk < (buf + stbuf.st_size)) {
+        /* Find the next variable */
+        SLIST_FOREACH(current, &variables, variables)
+            current->next_match = strcasestr(walk, current->key);
+        nearest = NULL;
+        int distance = stbuf.st_size;
+        SLIST_FOREACH(current, &variables, variables) {
+            if (current->next_match == NULL)
+                continue;
+            if ((current->next_match - walk) < distance) {
+                distance = (current->next_match - walk);
+                nearest = current;
+            }
+        }
+        if (nearest == NULL) {
+            /* If there are no more variables, we just copy the rest */
+            strncpy(destwalk, walk, (buf + stbuf.st_size) - walk);
+            destwalk += (buf + stbuf.st_size) - walk;
+            *destwalk = '\0';
+            break;
+        } else {
+            /* Copy until the next variable, then copy its value */
+            strncpy(destwalk, walk, distance);
+            strncpy(destwalk + distance, nearest->value, strlen(nearest->value));
+            walk += distance + strlen(nearest->key);
+            destwalk += distance + strlen(nearest->value);
+        }
+    }
+
+    /* analyze the string to find out whether this is an old config file (3.x)
+     * or a new config file (4.x). If it’s old, we run the converter script. */
+    int version = detect_version(buf);
+    if (version == 3) {
+        /* We need to convert this v3 configuration */
+        char *converted = migrate_config(new, stbuf.st_size);
+        if (converted != NULL) {
+            printf("\n");
+            printf("****************************************************************\n");
+            printf("NOTE: Automatically converted configuration file from v3 to v4.\n");
+            printf("\n");
+            printf("Please convert your config file to v4. You can use this command:\n");
+            printf("    mv %s %s.O\n", f, f);
+            printf("    i3-migrate-config-to-v4.pl %s.O > %s\n", f, f);
+            printf("****************************************************************\n");
+            printf("\n");
+            free(new);
+            new = converted;
+        } else {
+            printf("\n");
+            printf("**********************************************************************\n");
+            printf("ERROR: Could not convert config file. Maybe i3-migrate-config-to-v4.pl\n");
+            printf("was not correctly installed on your system?\n");
+            printf("**********************************************************************\n");
+            printf("\n");
         }
-        fclose(fstr);
-        close(fd);
+    }
+
+    /* now lex/parse it */
+    yy_scan_string(new);
+
+    context = scalloc(sizeof(struct context));
+    context->filename = f;
+
+    if (yyparse() != 0) {
+        fprintf(stderr, "Could not parse configfile\n");
+        exit(1);
+    }
+
+    if (context->has_errors) {
+        start_configerror_nagbar(f);
+    }
+
+    yylex_destroy();
+    FREE(context->line_copy);
+    free(context);
+    free(new);
+    free(buf);
+
+    while (!SLIST_EMPTY(&variables)) {
+        current = SLIST_FIRST(&variables);
+        FREE(current->key);
+        FREE(current->value);
+        SLIST_REMOVE_HEAD(&variables, variables);
+        FREE(current);
+    }
 }
 
 %}
 
-%expect 1
 %error-verbose
 %lex-param { struct context *context }
 
 %union {
-        int number;
-        char *string;
-        uint32_t *single_color;
-        struct Colortriple *color;
-        struct Assignment *assignment;
-        struct Binding *binding;
+    int number;
+    char *string;
+    uint32_t *single_color;
+    struct Colortriple *color;
+    Match *match;
+    struct Binding *binding;
 }
 
-%token <number>NUMBER "<number>"
-%token <string>WORD "<word>"
-%token <string>STR "<string>"
-%token <string>STR_NG "<string (non-greedy)>"
-%token <string>HEX "<hex>"
-%token <string>OUTPUT "<RandR output>"
-%token TOKBIND
-%token TOKTERMINAL
-%token TOKCOMMENT "<comment>"
-%token TOKFONT "font"
-%token TOKBINDSYM "bindsym"
-%token MODIFIER "<modifier>"
-%token TOKCONTROL "control"
-%token TOKSHIFT "shift"
-%token WHITESPACE "<whitespace>"
-%token TOKFLOATING_MODIFIER "floating_modifier"
-%token QUOTEDSTRING "<quoted string>"
-%token TOKWORKSPACE "workspace"
-%token TOKOUTPUT "output"
-%token TOKASSIGN "assign"
-%token TOKSET
-%token TOKIPCSOCKET "ipc_socket"
-%token TOKEXEC "exec"
-%token TOKSINGLECOLOR
-%token TOKCOLOR
-%token TOKARROW "→"
-%token TOKMODE "mode"
-%token TOKNEWCONTAINER "new_container"
-%token TOKNEWWINDOW "new_window"
-%token TOKFOCUSFOLLOWSMOUSE "focus_follows_mouse"
-%token TOKWORKSPACEBAR "workspace_bar"
-%token TOKCONTAINERMODE "default/stacking/tabbed"
-%token TOKSTACKLIMIT "stack-limit"
+%token  <number>        NUMBER                      "<number>"
+%token  <string>        WORD                        "<word>"
+%token  <string>        STR                         "<string>"
+%token  <string>        STR_NG                      "<string (non-greedy)>"
+%token  <string>        HEX                         "<hex>"
+%token  <string>        OUTPUT                      "<RandR output>"
+%token                  TOKBINDCODE
+%token                  TOKTERMINAL
+%token                  TOKCOMMENT                  "<comment>"
+%token                  TOKFONT                     "font"
+%token                  TOKBINDSYM                  "bindsym"
+%token  <number>        MODIFIER                    "<modifier>"
+%token                  TOKCONTROL                  "control"
+%token                  TOKSHIFT                    "shift"
+%token                  TOKFLOATING_MODIFIER        "floating_modifier"
+%token  <string>        QUOTEDSTRING                "<quoted string>"
+%token                  TOKWORKSPACE                "workspace"
+%token                  TOKOUTPUT                   "output"
+%token                  TOKASSIGN                   "assign"
+%token                  TOKSET
+%token                  TOKIPCSOCKET                "ipc_socket"
+%token                  TOKRESTARTSTATE             "restart_state"
+%token                  TOKEXEC                     "exec"
+%token                  TOKEXEC_ALWAYS              "exec_always"
+%token  <single_color>  TOKSINGLECOLOR
+%token  <color>         TOKCOLOR
+%token                  TOKARROW                    "→"
+%token                  TOKMODE                     "mode"
+%token                  TOK_ORIENTATION             "default_orientation"
+%token                  TOK_HORIZ                   "horizontal"
+%token                  TOK_VERT                    "vertical"
+%token                  TOK_AUTO                    "auto"
+%token                  TOK_WORKSPACE_LAYOUT        "workspace_layout"
+%token                  TOKNEWWINDOW                "new_window"
+%token                  TOK_NORMAL                  "normal"
+%token                  TOK_NONE                    "none"
+%token                  TOK_1PIXEL                  "1pixel"
+%token                  TOKFOCUSFOLLOWSMOUSE        "focus_follows_mouse"
+%token                  TOK_FORCE_FOCUS_WRAPPING    "force_focus_wrapping"
+%token                  TOKWORKSPACEBAR             "workspace_bar"
+%token                  TOK_DEFAULT                 "default"
+%token                  TOK_STACKING                "stacking"
+%token                  TOK_TABBED                  "tabbed"
+%token  <number>        TOKSTACKLIMIT               "stack-limit"
+%token                  TOK_POPUP_DURING_FULLSCREEN "popup_during_fullscreen"
+%token                  TOK_IGNORE                  "ignore"
+%token                  TOK_LEAVE_FULLSCREEN        "leave_fullscreen"
+%token                  TOK_FOR_WINDOW              "for_window"
+
+%token              TOK_MARK            "mark"
+%token              TOK_CLASS           "class"
+%token              TOK_ID              "id"
+%token              TOK_CON_ID          "con_id"
+%token              TOK_TITLE           "title"
+
+%type   <binding>       binding
+%type   <binding>       bindcode
+%type   <binding>       bindsym
+%type   <number>        binding_modifiers
+%type   <number>        binding_modifier
+%type   <number>        direction
+%type   <number>        layout_mode
+%type   <number>        border_style
+%type   <number>        new_window
+%type   <number>        colorpixel
+%type   <number>        bool
+%type   <number>        popup_setting
+%type   <string>        command
+%type   <string>        word_or_number
+%type   <string>        optional_workspace_name
+%type   <string>        workspace_name
+%type   <string>        window_class
 
 %%
 
 lines: /* empty */
-        | lines WHITESPACE line
-        | lines error
-        | lines line
-        ;
+    | lines error
+    | lines line
+    ;
 
 line:
-        bindline
-        | mode
-        | floating_modifier
-        | new_container
-        | new_window
-        | focus_follows_mouse
-        | workspace_bar
-        | workspace
-        | assign
-        | ipcsocket
-        | exec
-        | single_color
-        | color
-        | terminal
-        | font
-        | comment
-        ;
+    bindline
+    | for_window
+    | mode
+    | floating_modifier
+    | orientation
+    | workspace_layout
+    | new_window
+    | focus_follows_mouse
+    | force_focus_wrapping
+    | workspace_bar
+    | workspace
+    | assign
+    | ipcsocket
+    | restart_state
+    | exec
+    | exec_always
+    | single_color
+    | color
+    | terminal
+    | font
+    | comment
+    | popup_during_fullscreen
+    ;
 
 comment:
-        TOKCOMMENT
-        ;
+    TOKCOMMENT
+    ;
 
 command:
-        STR
-        ;
+    STR
+    ;
 
 bindline:
-        binding
-        {
-                TAILQ_INSERT_TAIL(bindings, $<binding>1, bindings);
-        }
-        ;
+    binding
+    {
+        TAILQ_INSERT_TAIL(bindings, $1, bindings);
+    }
+    ;
 
 binding:
-        TOKBIND WHITESPACE bind                 { $<binding>$ = $<binding>3; }
-        | TOKBINDSYM WHITESPACE bindsym         { $<binding>$ = $<binding>3; }
-        ;
+    TOKBINDCODE bindcode         { $$ = $2; }
+    | TOKBINDSYM bindsym         { $$ = $2; }
+    ;
 
-bind:
-        binding_modifiers NUMBER WHITESPACE command
-        {
-                printf("\tFound binding mod%d with key %d and command %s\n", $<number>1, $2, $<string>4);
-                Binding *new = scalloc(sizeof(Binding));
+bindcode:
+    binding_modifiers NUMBER command
+    {
+        printf("\tFound keycode binding mod%d with key %d and command %s\n", $1, $2, $3);
+        Binding *new = scalloc(sizeof(Binding));
 
-                new->keycode = $<number>2;
-                new->mods = $<number>1;
-                new->command = $<string>4;
+        new->keycode = $2;
+        new->mods = $1;
+        new->command = $3;
 
-                $<binding>$ = new;
-        }
-        ;
+        $$ = new;
+    }
+    ;
 
 bindsym:
-        binding_modifiers word_or_number WHITESPACE command
-        {
-                printf("\tFound symbolic mod%d with key %s and command %s\n", $<number>1, $<string>2, $<string>4);
-                Binding *new = scalloc(sizeof(Binding));
+    binding_modifiers word_or_number command
+    {
+        printf("\tFound keysym binding mod%d with key %s and command %s\n", $1, $2, $3);
+        Binding *new = scalloc(sizeof(Binding));
+
+        new->symbol = $2;
+        new->mods = $1;
+        new->command = $3;
+
+        $$ = new;
+    }
+    ;
+
+for_window:
+    TOK_FOR_WINDOW match command
+    {
+        printf("\t should execute command %s for the criteria mentioned above\n", $3);
+        Assignment *assignment = scalloc(sizeof(Assignment));
+        assignment->type = A_COMMAND;
+        assignment->match = current_match;
+        assignment->dest.command = $3;
+        TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
+    }
+    ;
+
+match:
+    | matchstart criteria matchend
+    {
+        printf("match parsed\n");
+    }
+    ;
+
+matchstart:
+    '['
+    {
+        printf("start\n");
+        match_init(&current_match);
+    }
+    ;
+
+matchend:
+    ']'
+    {
+        printf("match specification finished\n");
+    }
+    ;
+
+criteria:
+    TOK_CLASS '=' STR
+    {
+        printf("criteria: class = %s\n", $3);
+        current_match.class = $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 = $3;
+    }
+    | TOK_TITLE '=' STR
+    {
+        printf("criteria: title = %s\n", $3);
+        current_match.title = $3;
+    }
+    ;
 
-                new->symbol = $<string>2;
-                new->mods = $<number>1;
-                new->command = $<string>4;
 
-                $<binding>$ = new;
-        }
-        ;
 
 word_or_number:
-        WORD
-        | NUMBER
-        {
-                asprintf(&$<string>$, "%d", $1);
-        }
-        ;
+    WORD
+    | NUMBER
+    {
+        asprintf(&$$, "%d", $1);
+    }
+    ;
 
 mode:
-        TOKMODE WHITESPACE QUOTEDSTRING WHITESPACE '{' modelines '}'
-        {
-                if (strcasecmp($<string>3, "default") == 0) {
-                        printf("You cannot use the name \"default\" for your mode\n");
-                        exit(1);
-                }
-                printf("\t now in mode %s\n", $<string>3);
-                printf("\t current bindings = %p\n", current_bindings);
-                Binding *binding;
-                TAILQ_FOREACH(binding, current_bindings, bindings) {
-                        printf("got binding on mods %d, keycode %d, symbol %s, command %s\n",
-                                        binding->mods, binding->keycode, binding->symbol, binding->command);
-                }
-
-                struct Mode *mode = scalloc(sizeof(struct Mode));
-                mode->name = $<string>3;
-                mode->bindings = current_bindings;
-                current_bindings = NULL;
-                SLIST_INSERT_HEAD(&modes, mode, modes);
+    TOKMODE QUOTEDSTRING '{' modelines '}'
+    {
+        if (strcasecmp($2, "default") == 0) {
+            printf("You cannot use the name \"default\" for your mode\n");
+            exit(1);
+        }
+        printf("\t now in mode %s\n", $2);
+        printf("\t current bindings = %p\n", current_bindings);
+        Binding *binding;
+        TAILQ_FOREACH(binding, current_bindings, bindings) {
+            printf("got binding on mods %d, keycode %d, symbol %s, command %s\n",
+                            binding->mods, binding->keycode, binding->symbol, binding->command);
         }
-        ;
+
+        struct Mode *mode = scalloc(sizeof(struct Mode));
+        mode->name = $2;
+        mode->bindings = current_bindings;
+        current_bindings = NULL;
+        SLIST_INSERT_HEAD(&modes, mode, modes);
+    }
+    ;
 
 
 modelines:
-        /* empty */
-        | modelines modeline
-        ;
+    /* empty */
+    | modelines modeline
+    ;
 
 modeline:
-        WHITESPACE
-        | comment
-        | binding
-        {
-                if (current_bindings == NULL) {
-                        current_bindings = scalloc(sizeof(struct bindings_head));
-                        TAILQ_INIT(current_bindings);
-                }
-
-                TAILQ_INSERT_TAIL(current_bindings, $<binding>1, bindings);
+    comment
+    | binding
+    {
+        if (current_bindings == NULL) {
+            current_bindings = scalloc(sizeof(struct bindings_head));
+            TAILQ_INIT(current_bindings);
         }
-        ;
+
+        TAILQ_INSERT_TAIL(current_bindings, $1, bindings);
+    }
+    ;
 
 floating_modifier:
-        TOKFLOATING_MODIFIER WHITESPACE binding_modifiers
-        {
-                DLOG("floating modifier = %d\n", $<number>3);
-                config.floating_modifier = $<number>3;
-        }
-        ;
-
-new_container:
-        TOKNEWCONTAINER WHITESPACE TOKCONTAINERMODE
-        {
-                DLOG("new containers will be in mode %d\n", $<number>3);
-                config.container_mode = $<number>3;
-
-                /* We also need to change the layout of the already existing
-                 * workspaces here. Workspaces may exist at this point because
-                 * of the other directives which are modifying workspaces
-                 * (setting the preferred screen or name). While the workspace
-                 * objects are already created, they have never been used.
-                 * Thus, the user very likely awaits the default container mode
-                 * to trigger in this case, regardless of where it is inside
-                 * his configuration file. */
-                Workspace *ws;
-                TAILQ_FOREACH(ws, workspaces, workspaces) {
-                        if (ws->table == NULL)
-                                continue;
-                        switch_layout_mode(global_conn,
-                                           ws->table[0][0],
-                                           config.container_mode);
-                }
+    TOKFLOATING_MODIFIER binding_modifiers
+    {
+        DLOG("floating modifier = %d\n", $2);
+        config.floating_modifier = $2;
+    }
+    ;
+
+orientation:
+    TOK_ORIENTATION direction
+    {
+        DLOG("New containers should start with split direction %d\n", $2);
+        config.default_orientation = $2;
+    }
+    ;
+
+direction:
+    TOK_HORIZ       { $$ = HORIZ; }
+    | TOK_VERT      { $$ = VERT; }
+    | TOK_AUTO      { $$ = NO_ORIENTATION; }
+    ;
+
+workspace_layout:
+    TOK_WORKSPACE_LAYOUT layout_mode
+    {
+        DLOG("new containers will be in mode %d\n", $2);
+        config.default_layout = $2;
+
+#if 0
+        /* We also need to change the layout of the already existing
+         * workspaces here. Workspaces may exist at this point because
+         * of the other directives which are modifying workspaces
+         * (setting the preferred screen or name). While the workspace
+         * objects are already created, they have never been used.
+         * Thus, the user very likely awaits the default container mode
+         * to trigger in this case, regardless of where it is inside
+         * his configuration file. */
+        Workspace *ws;
+        TAILQ_FOREACH(ws, workspaces, workspaces) {
+                if (ws->table == NULL)
+                        continue;
+                switch_layout_mode(global_conn,
+                                   ws->table[0][0],
+                                   config.container_mode);
         }
-        | TOKNEWCONTAINER WHITESPACE TOKSTACKLIMIT WHITESPACE TOKSTACKLIMIT WHITESPACE NUMBER
-        {
-                if ($<number>7 <= 0) {
-                        ELOG("Invalid stack-limit, need a limit which is > 0\n");
-                } else {
-                        DLOG("stack-limit %d with val %d\n", $<number>5, $<number>7);
-                        config.container_stack_limit = $<number>5;
-                        config.container_stack_limit_value = $<number>7;
-
-                        /* See the comment above */
-                        Workspace *ws;
-                        TAILQ_FOREACH(ws, workspaces, workspaces) {
-                                if (ws->table == NULL)
-                                        continue;
-                                Container *con = ws->table[0][0];
-                                con->stack_limit = config.container_stack_limit;
-                                con->stack_limit_value = config.container_stack_limit_value;
-                        }
-                }
+#endif
+    }
+    | TOK_WORKSPACE_LAYOUT TOKSTACKLIMIT TOKSTACKLIMIT NUMBER
+    {
+        DLOG("stack-limit %d with val %d\n", $3, $4);
+        config.container_stack_limit = $3;
+        config.container_stack_limit_value = $4;
+
+#if 0
+        /* See the comment above */
+        Workspace *ws;
+        TAILQ_FOREACH(ws, workspaces, workspaces) {
+                if (ws->table == NULL)
+                        continue;
+                Container *con = ws->table[0][0];
+                con->stack_limit = config.container_stack_limit;
+                con->stack_limit_value = config.container_stack_limit_value;
         }
-        ;
+#endif
+    }
+    ;
+
+layout_mode:
+    TOK_DEFAULT       { $$ = L_DEFAULT; }
+    | TOK_STACKING    { $$ = L_STACKED; }
+    | TOK_TABBED      { $$ = L_TABBED; }
+    ;
 
 new_window:
-        TOKNEWWINDOW WHITESPACE WORD
-        {
-                DLOG("new windows should start in mode %s\n", $<string>3);
-                config.default_border = sstrdup($<string>3);
-        }
-        ;
+    TOKNEWWINDOW border_style
+    {
+        DLOG("new windows should start with border style %d\n", $2);
+        config.default_border = $2;
+    }
+    ;
+
+border_style:
+    TOK_NORMAL      { $$ = BS_NORMAL; }
+    | TOK_NONE      { $$ = BS_NONE; }
+    | TOK_1PIXEL    { $$ = BS_1PIXEL; }
+    ;
 
 bool:
-        NUMBER
-        {
-                $<number>$ = ($<number>1 == 1);
-        }
-        | WORD
-        {
-                DLOG("checking word \"%s\"\n", $<string>1);
-                $<number>$ = (strcasecmp($<string>1, "yes") == 0 ||
-                              strcasecmp($<string>1, "true") == 0 ||
-                              strcasecmp($<string>1, "on") == 0 ||
-                              strcasecmp($<string>1, "enable") == 0 ||
-                              strcasecmp($<string>1, "active") == 0);
-        }
-        ;
+    NUMBER
+    {
+        $$ = ($1 == 1);
+    }
+    | WORD
+    {
+        DLOG("checking word \"%s\"\n", $1);
+        $$ = (strcasecmp($1, "yes") == 0 ||
+              strcasecmp($1, "true") == 0 ||
+              strcasecmp($1, "on") == 0 ||
+              strcasecmp($1, "enable") == 0 ||
+              strcasecmp($1, "active") == 0);
+    }
+    ;
 
 focus_follows_mouse:
-        TOKFOCUSFOLLOWSMOUSE WHITESPACE bool
-        {
-                DLOG("focus follows mouse = %d\n", $<number>3);
-                config.disable_focus_follows_mouse = !($<number>3);
-        }
-        ;
+    TOKFOCUSFOLLOWSMOUSE bool
+    {
+        DLOG("focus follows mouse = %d\n", $2);
+        config.disable_focus_follows_mouse = !($2);
+    }
+    ;
+
+force_focus_wrapping:
+    TOK_FORCE_FOCUS_WRAPPING bool
+    {
+        DLOG("force focus wrapping = %d\n", $2);
+        config.force_focus_wrapping = $2;
+    }
+    ;
 
 workspace_bar:
-        TOKWORKSPACEBAR WHITESPACE bool
-        {
-                DLOG("workspace bar = %d\n", $<number>3);
-                config.disable_workspace_bar = !($<number>3);
-        }
-        ;
+    TOKWORKSPACEBAR bool
+    {
+        DLOG("workspace bar = %d\n", $2);
+        config.disable_workspace_bar = !($2);
+    }
+    ;
 
 workspace:
-        TOKWORKSPACE WHITESPACE NUMBER WHITESPACE TOKOUTPUT WHITESPACE OUTPUT optional_workspace_name
-        {
-                int ws_num = $<number>3;
-                if (ws_num < 1) {
-                        ELOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
-                } else {
-                        Workspace *ws = workspace_get(ws_num - 1);
-                        ws->preferred_output = $<string>7;
-                        if ($<string>8 != NULL) {
-                                workspace_set_name(ws, $<string>8);
-                                free($<string>8);
-                        }
-                }
+    TOKWORKSPACE NUMBER TOKOUTPUT OUTPUT optional_workspace_name
+    {
+        int ws_num = $2;
+        if (ws_num < 1) {
+            DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
+        } else {
+            char *ws_name = NULL;
+            if ($5 == NULL) {
+                asprintf(&ws_name, "%d", ws_num);
+            } else {
+                ws_name = $5;
+            }
+
+            DLOG("Should assign workspace %s to output %s\n", ws_name, $4);
+            struct Workspace_Assignment *assignment = scalloc(sizeof(struct Workspace_Assignment));
+            assignment->name = ws_name;
+            assignment->output = $4;
+            TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
         }
-        | TOKWORKSPACE WHITESPACE NUMBER WHITESPACE workspace_name
-        {
-                int ws_num = $<number>3;
-                if (ws_num < 1) {
-                        ELOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
-                } else {
-                        DLOG("workspace name to: %s\n", $<string>5);
-                        if ($<string>5 != NULL) {
-                                workspace_set_name(workspace_get(ws_num - 1), $<string>5);
-                                free($<string>5);
-                        }
-                }
+    }
+    | TOKWORKSPACE NUMBER workspace_name
+    {
+        int ws_num = $2;
+        if (ws_num < 1) {
+            DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
+        } else {
+            DLOG("workspace name to: %s\n", $3);
+#if 0
+            if ($<string>3 != NULL) {
+                    workspace_set_name(workspace_get(ws_num - 1), $<string>3);
+                    free($<string>3);
+            }
+#endif
         }
-        ;
+    }
+    ;
 
 optional_workspace_name:
-        /* empty */                     { $<string>$ = NULL; }
-        | WHITESPACE workspace_name     { $<string>$ = $<string>2; }
-        ;
+    /* empty */          { $$ = NULL; }
+    | workspace_name     { $$ = $1; }
+    ;
 
 workspace_name:
-        QUOTEDSTRING         { $<string>$ = $<string>1; }
-        | STR                { $<string>$ = $<string>1; }
-        | WORD               { $<string>$ = $<string>1; }
-        ;
+    QUOTEDSTRING         { $$ = $1; }
+    | STR                { $$ = $1; }
+    | WORD               { $$ = $1; }
+    ;
 
 assign:
-        TOKASSIGN WHITESPACE window_class WHITESPACE optional_arrow assign_target
-        {
-                DLOG("assignment of %s\n", $<string>3);
-
-                struct Assignment *new = $<assignment>6;
-                if (new->floating != ASSIGN_FLOATING_ONLY && new->workspace < 1) {
-                        ELOG("Invalid client assignment, workspace number %d out of range\n", new->workspace);
-                        free(new);
-                } else {
-                        DLOG("  to %d\n", new->workspace);
-                        DLOG("  floating = %d\n", new->floating);
-                        new->windowclass_title = $<string>3;
-                        TAILQ_INSERT_TAIL(&assignments, new, assignments);
-                }
-        }
-        ;
-
-assign_target:
-        NUMBER
-        {
-                struct Assignment *new = scalloc(sizeof(struct Assignment));
-                new->workspace = $<number>1;
-                new->floating = ASSIGN_FLOATING_NO;
-                $<assignment>$ = new;
-        }
-        | '~'
-        {
-                struct Assignment *new = scalloc(sizeof(struct Assignment));
-                new->floating = ASSIGN_FLOATING_ONLY;
-                $<assignment>$ = new;
+    TOKASSIGN window_class STR
+    {
+        printf("assignment of %s to *%s*\n", $2, $3);
+        char *workspace = $3;
+        char *criteria = $2;
+
+        Assignment *assignment = scalloc(sizeof(Assignment));
+        Match *match = &(assignment->match);
+        match_init(match);
+
+        char *separator = NULL;
+        if ((separator = strchr(criteria, '/')) != NULL) {
+            *(separator++) = '\0';
+            match->title = sstrdup(separator);
         }
-        | '~' NUMBER
-        {
-                struct Assignment *new = scalloc(sizeof(struct Assignment));
-                new->workspace = $<number>2;
-                new->floating = ASSIGN_FLOATING;
-                $<assignment>$ = new;
+        if (*criteria != '\0')
+            match->class = sstrdup(criteria);
+        free(criteria);
+
+        printf("  class = %s\n", match->class);
+        printf("  title = %s\n", match->title);
+
+        /* Compatibility with older versions: If the assignment target starts
+         * with ~, we create the equivalent of:
+         *
+         * for_window [class="foo"] mode floating
+         */
+        if (*workspace == '~') {
+            workspace++;
+            if (*workspace == '\0') {
+                /* This assignment was *only* for floating */
+                assignment->type = A_COMMAND;
+                assignment->dest.command = sstrdup("floating enable");
+                TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
+                break;
+            } else {
+                /* Create a new assignment and continue afterwards */
+                Assignment *floating = scalloc(sizeof(Assignment));
+                match_copy(&(floating->match), match);
+                floating->type = A_COMMAND;
+                floating->dest.command = sstrdup("floating enable");
+                TAILQ_INSERT_TAIL(&assignments, floating, assignments);
+            }
         }
-        ;
 
-window_class:
-        QUOTEDSTRING
-        | STR_NG
-        ;
+        assignment->type = A_TO_WORKSPACE;
+        assignment->dest.workspace = workspace;
+        TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
+    }
+    ;
 
-optional_arrow:
-        /* NULL */
-        | TOKARROW WHITESPACE
-        ;
+window_class:
+    QUOTEDSTRING
+    | STR_NG
+    ;
 
 ipcsocket:
-        TOKIPCSOCKET WHITESPACE STR
-        {
-                config.ipc_socket_path = $<string>3;
-        }
-        ;
+    TOKIPCSOCKET STR
+    {
+        config.ipc_socket_path = $2;
+    }
+    ;
+
+restart_state:
+    TOKRESTARTSTATE STR
+    {
+        config.restart_state_path = $2;
+    }
+    ;
 
 exec:
-        TOKEXEC WHITESPACE STR
-        {
-                struct Autostart *new = smalloc(sizeof(struct Autostart));
-                new->command = $<string>3;
-                TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
-        }
-        ;
+    TOKEXEC STR
+    {
+        struct Autostart *new = smalloc(sizeof(struct Autostart));
+        new->command = $2;
+        TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
+    }
+    ;
+
+exec_always:
+    TOKEXEC_ALWAYS STR
+    {
+        struct Autostart *new = smalloc(sizeof(struct Autostart));
+        new->command = $2;
+        TAILQ_INSERT_TAIL(&autostarts_always, new, autostarts_always);
+    }
+    ;
 
 terminal:
-        TOKTERMINAL WHITESPACE STR
-        {
-                ELOG("The terminal option is DEPRECATED and has no effect. "
-                    "Please remove it from your configuration file.\n");
-        }
-        ;
+    TOKTERMINAL STR
+    {
+        ELOG("The terminal option is DEPRECATED and has no effect. "
+            "Please remove it from your configuration file.\n");
+    }
+    ;
 
 font:
-        TOKFONT WHITESPACE STR
-        {
-                config.font = $<string>3;
-                printf("font %s\n", config.font);
-        }
-        ;
+    TOKFONT STR
+    {
+        config.font = load_font($2, true);
+        printf("font %s\n", $2);
+        free($2);
+    }
+    ;
 
 single_color:
-        TOKSINGLECOLOR WHITESPACE colorpixel
-        {
-                uint32_t *dest = $<single_color>1;
-                *dest = $<number>3;
-        }
-        ;
+    TOKSINGLECOLOR colorpixel
+    {
+        uint32_t *dest = $1;
+        *dest = $2;
+    }
+    ;
 
 color:
-        TOKCOLOR WHITESPACE colorpixel WHITESPACE colorpixel WHITESPACE colorpixel
-        {
-                struct Colortriple *dest = $<color>1;
+    TOKCOLOR colorpixel colorpixel colorpixel
+    {
+        struct Colortriple *dest = $1;
 
-                dest->border = $<number>3;
-                dest->background = $<number>5;
-                dest->text = $<number>7;
-        }
-        ;
+        dest->border = $2;
+        dest->background = $3;
+        dest->text = $4;
+    }
+    ;
 
 colorpixel:
-        '#' HEX
-        {
-                char *hex;
-                if (asprintf(&hex, "#%s", $<string>2) == -1)
-                        die("asprintf()");
-                $<number>$ = get_colorpixel(global_conn, hex);
-                free(hex);
-        }
-        ;
+    '#' HEX
+    {
+        char *hex;
+        if (asprintf(&hex, "#%s", $2) == -1)
+            die("asprintf()");
+        $$ = get_colorpixel(hex);
+        free(hex);
+    }
+    ;
 
 
 binding_modifiers:
-        /* NULL */                               { $<number>$ = 0; }
-        | binding_modifier
-        | binding_modifiers '+' binding_modifier { $<number>$ = $<number>1 | $<number>3; }
-        | binding_modifiers '+'                  { $<number>$ = $<number>1; }
-        ;
+    /* NULL */                               { $$ = 0; }
+    | binding_modifier
+    | binding_modifiers '+' binding_modifier { $$ = $1 | $3; }
+    | binding_modifiers '+'                  { $$ = $1; }
+    ;
 
 binding_modifier:
-        MODIFIER        { $<number>$ = $<number>1; }
-        | TOKCONTROL    { $<number>$ = BIND_CONTROL; }
-        | TOKSHIFT      { $<number>$ = BIND_SHIFT; }
-        ;
+    MODIFIER        { $$ = $1; }
+    | TOKCONTROL    { $$ = BIND_CONTROL; }
+    | TOKSHIFT      { $$ = BIND_SHIFT; }
+    ;
+
+popup_during_fullscreen:
+    TOK_POPUP_DURING_FULLSCREEN popup_setting
+    {
+        DLOG("popup_during_fullscreen setting: %d\n", $2);
+        config.popup_during_fullscreen = $2;
+    }
+    ;
+
+popup_setting:
+    TOK_IGNORE              { $$ = PDF_IGNORE; }
+    | TOK_LEAVE_FULLSCREEN  { $$ = PDF_LEAVE_FULLSCREEN; }
+    ;
index d8183269f87e0949d979d58201f3b2a9689ef317..acfcad893f4ddb1c2da297e3a6f99223cf8dd558 100644 (file)
@@ -1,9 +1,9 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
  *
- * © 2009-2010 Michael Stapelberg and contributors
+ * © 2009-2011 Michael Stapelberg and contributors
  *
  * See file LICENSE for license information.
  *
  *              because they are quite large.
  *
  */
-#include <stdio.h>
-#include <assert.h>
-#include <string.h>
-#include <stdlib.h>
 #include <time.h>
-#include <stdbool.h>
 #include <math.h>
 
-#include <xcb/xcb.h>
 #include <xcb/xcb_atom.h>
 #include <xcb/xcb_icccm.h>
 
 #include <X11/XKBlib.h>
 
-#include "i3.h"
-#include "queue.h"
-#include "table.h"
-#include "config.h"
-#include "util.h"
-#include "xcb.h"
-#include "client.h"
-#include "workspace.h"
-#include "commands.h"
-#include "floating.h"
-#include "resize.h"
-#include "log.h"
-#include "randr.h"
-
-static struct Stack_Window *get_stack_window(xcb_window_t window_id) {
-        struct Stack_Window *current;
-
-        SLIST_FOREACH(current, &stack_wins, stack_windows) {
-                if (current->window != window_id)
-                        continue;
-
-                return current;
-        }
-
-        return NULL;
-}
-
-/*
- * Checks if the button press was on a stack window, handles focus setting and returns true
- * if so, or false otherwise.
- *
- */
-static bool button_press_stackwin(xcb_connection_t *conn, xcb_button_press_event_t *event) {
-        struct Stack_Window *stack_win;
-
-        /* If we find a corresponding stack window, we can handle the event */
-        if ((stack_win = get_stack_window(event->event)) == NULL)
-                return false;
-
-        /* A stack window was clicked, we check if it was button4 or button5
-           which are scroll up / scroll down. */
-        if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) {
-                direction_t direction = (event->detail == XCB_BUTTON_INDEX_4 ? D_UP : D_DOWN);
-                focus_window_in_container(conn, CUR_CELL, direction);
-                return true;
-        }
-
-        /* It was no scrolling, so we calculate the destination client by
-           dividing the Y position of the event through the height of a window
-           decoration and then set the focus to this client. */
-        i3Font *font = load_font(conn, config.font);
-        int decoration_height = (font->height + 2 + 2);
-        int destination = (event->event_y / decoration_height),
-            c = 0,
-            num_clients = 0;
-        Client *client;
-        Container *container = stack_win->container;
-
-        CIRCLEQ_FOREACH(client, &(container->clients), clients)
-                num_clients++;
-
-        /* If we don’t have any clients in this container, we cannot do
-         * anything useful anyways. */
-        if (num_clients == 0)
-                return true;
-
-        if (container->mode == MODE_TABBED)
-                destination = (event->event_x / (container->width / num_clients));
-        else if (container->mode == MODE_STACK &&
-                 container->stack_limit != STACK_LIMIT_NONE) {
-                if (container->stack_limit == STACK_LIMIT_COLS) {
-                        int wrap = ceil((float)num_clients / container->stack_limit_value);
-                        int clicked_column = (event->event_x / (stack_win->rect.width / container->stack_limit_value));
-                        int clicked_row = (event->event_y / decoration_height);
-                        DLOG("clicked on column %d, row %d\n", clicked_column, clicked_row);
-                        destination = (wrap * clicked_column) + clicked_row;
-                } else {
-                        int width = (stack_win->rect.width / ceil((float)num_clients / container->stack_limit_value));
-                        int clicked_column = (event->event_x / width);
-                        int clicked_row = (event->event_y / decoration_height);
-                        DLOG("clicked on column %d, row %d\n", clicked_column, clicked_row);
-                        destination = (container->stack_limit_value * clicked_column) + clicked_row;
-                }
-        }
+#include "all.h"
 
-        DLOG("Click on stack_win for client %d\n", destination);
-        CIRCLEQ_FOREACH(client, &(stack_win->container->clients), clients)
-                if (c++ == destination) {
-                        set_focus(conn, client, true);
-                        return true;
-                }
 
-        return true;
-}
+typedef enum { CLICK_BORDER = 0, CLICK_DECORATION = 1, CLICK_INSIDE = 2 } click_destination_t;
 
 /*
- * Checks if the button press was on a bar, switches to the workspace and returns true
- * if so, or false otherwise.
+ * Finds the correct pair of first/second cons between the resize will take
+ * place according to the passed border position (top, left, right, bottom),
+ * then calls resize_graphical_handler().
  *
  */
-static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *event) {
-        Output *output;
-        TAILQ_FOREACH(output, &outputs, outputs) {
-                if (output->bar != event->event)
-                        continue;
-
-                DLOG("Click on a bar\n");
-
-                /* Check if the button was one of button4 or button5 (scroll up / scroll down) */
-                if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) {
-                        Workspace *ws = c_ws;
-                        if (event->detail == XCB_BUTTON_INDEX_5) {
-                                while ((ws = TAILQ_NEXT(ws, workspaces)) != TAILQ_END(workspaces_head)) {
-                                        if (ws->output == output) {
-                                                workspace_show(conn, ws->num + 1);
-                                                return true;
-                                        }
-                                }
-                        } else {
-                                while ((ws = TAILQ_PREV(ws, workspaces_head, workspaces)) != TAILQ_END(workspaces)) {
-                                        if (ws->output == output) {
-                                                workspace_show(conn, ws->num + 1);
-                                                return true;
-                                        }
-                                }
-                        }
-                        return true;
-                }
-                int drawn = 0;
-                /* Because workspaces can be on different outputs, we need to loop
-                   through all of them and decide to count it based on its ->output */
-                Workspace *ws;
-                TAILQ_FOREACH(ws, workspaces, workspaces) {
-                        if (ws->output != output)
-                                continue;
-                        DLOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n",
-                                        ws->num, drawn, ws->text_width);
-                        if (event->event_x > (drawn + 1) &&
-                            event->event_x <= (drawn + 1 + ws->text_width + 5 + 5)) {
-                                workspace_show(conn, ws->num + 1);
-                                return true;
-                        }
-
-                        drawn += ws->text_width + 5 + 5 + 2;
-                }
-                return true;
+static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press_event_t *event) {
+    DLOG("border = %d\n", border);
+    char way = (border == BORDER_TOP || border == BORDER_LEFT ? 'p' : 'n');
+    orientation_t orientation = (border == BORDER_TOP || border == BORDER_BOTTOM ? VERT : HORIZ);
+
+    /* look for a parent container with the right orientation */
+    Con *first = NULL, *second = NULL;
+    Con *resize_con = con;
+    while (resize_con->type != CT_WORKSPACE &&
+           resize_con->type != CT_FLOATING_CON &&
+           resize_con->parent->orientation != orientation)
+        resize_con = resize_con->parent;
+
+    if (resize_con->type != CT_WORKSPACE &&
+        resize_con->type != CT_FLOATING_CON &&
+        resize_con->parent->orientation == orientation) {
+        first = resize_con;
+        second = (way == 'n') ? TAILQ_NEXT(first, nodes) : TAILQ_PREV(first, nodes_head, nodes);
+        if (second == TAILQ_END(&(first->nodes_head))) {
+            second = NULL;
+        }
+        else if (way == 'p') {
+            Con *tmp = first;
+            first = second;
+            second = tmp;
         }
+    }
 
+    if (first == NULL || second == NULL) {
+        DLOG("Resize not possible\n");
         return false;
+    }
+    else {
+        assert(first != second);
+        assert(first->parent == second->parent);
+        resize_graphical_handler(first, second, orientation, event);
+    }
+
+    DLOG("After resize handler, rendering\n");
+    tree_render();
+    return true;
 }
 
 /*
@@ -184,231 +81,202 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e
  * to the client).
  *
  */
-static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client,
-                                         xcb_button_press_event_t *event) {
-        /* Only the right mouse button is interesting for us at the moment */
-        if (event->detail != 3)
-                return false;
-
-        /* The client is in tiling layout. We can still
-         * initiate a resize with the right mouse button,
-         * by chosing the border which is the most near one
-         * to the position of the mouse pointer */
-        int to_right = client->rect.width - event->event_x,
-            to_left = event->event_x,
-            to_top = event->event_y,
-            to_bottom = client->rect.height - event->event_y;
-        resize_orientation_t orientation = O_VERTICAL;
-        Container *con = client->container;
-        Workspace *ws = con->workspace;
-        int first = 0, second = 0;
-
-        DLOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n",
-                        to_right, to_left, to_top, to_bottom);
-
-        if (to_right < to_left &&
-            to_right < to_top &&
-            to_right < to_bottom) {
-                /* …right border */
-                first = con->col + (con->colspan - 1);
-                DLOG("column %d\n", first);
-
-                if (!cell_exists(ws, first, con->row) ||
-                    (first == (ws->cols-1)))
-                        return false;
-
-                second = first + 1;
-        } else if (to_left < to_right &&
-                   to_left < to_top &&
-                   to_left < to_bottom) {
-                /* …left border */
-                if (con->col == 0)
-                        return false;
-
-                first = con->col - 1;
-                second = con->col;
-        } else if (to_top < to_right &&
-                   to_top < to_left &&
-                   to_top < to_bottom) {
-                /* This was a press on the top border */
-                if (con->row == 0)
-                        return false;
-                first = con->row - 1;
-                second = con->row;
-                orientation = O_HORIZONTAL;
-        } else if (to_bottom < to_right &&
-                   to_bottom < to_left &&
-                   to_bottom < to_top) {
-                /* …bottom border */
-                first = con->row + (con->rowspan - 1);
-                if (!cell_exists(ws, con->col, first) ||
-                    (first == (ws->rows-1)))
-                        return false;
-
-                second = first + 1;
-                orientation = O_HORIZONTAL;
-        }
-
-       return resize_graphical_handler(conn, ws, first, second, orientation, event);
+static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *event) {
+    /* The client is in tiling layout. We can still initiate a resize with the
+     * right mouse button, by chosing the border which is the most near one to
+     * the position of the mouse pointer */
+    int to_right = con->rect.width - event->event_x,
+        to_left = event->event_x,
+        to_top = event->event_y,
+        to_bottom = con->rect.height - event->event_y;
+
+    DLOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n",
+                    to_right, to_left, to_top, to_bottom);
+
+    if (to_right < to_left &&
+        to_right < to_top &&
+        to_right < to_bottom)
+        return tiling_resize_for_border(con, BORDER_RIGHT, event);
+
+    if (to_left < to_right &&
+        to_left < to_top &&
+        to_left < to_bottom)
+        return tiling_resize_for_border(con, BORDER_LEFT, event);
+
+    if (to_top < to_right &&
+        to_top < to_left &&
+        to_top < to_bottom)
+        return tiling_resize_for_border(con, BORDER_TOP, event);
+
+    if (to_bottom < to_right &&
+        to_bottom < to_left &&
+        to_bottom < to_top)
+        return tiling_resize_for_border(con, BORDER_BOTTOM, event);
+
+    return false;
 }
 
-int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) {
-        DLOG("Button %d pressed\n", event->state);
-        /* This was either a focus for a client’s parent (= titlebar)… */
-        Client *client = table_get(&by_child, event->event);
-        bool border_click = false;
-        if (client == NULL) {
-                client = table_get(&by_parent, event->event);
-                border_click = true;
-        }
-        /* See if this was a click with the configured modifier. If so, we need
-         * to move around the client if it was floating. if not, we just process
-         * as usual. */
-        if (config.floating_modifier != 0 &&
-            (event->state & config.floating_modifier) == config.floating_modifier) {
-                if (client == NULL) {
-                        DLOG("Not handling, floating_modifier was pressed and no client found\n");
-                        xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
-                        xcb_flush(conn);
-                        return 1;
-                }
-                if (client->fullscreen) {
-                        DLOG("Not handling, client is in fullscreen mode\n");
-                        xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
-                        xcb_flush(conn);
-                        return 1;
-                }
-                if (client_is_floating(client)) {
-                        DLOG("button %d pressed\n", event->detail);
-                        if (event->detail == 1) {
-                                DLOG("left mouse button, dragging\n");
-                                floating_drag_window(conn, client, event);
-                        } else if (event->detail == 3) {
-                                bool proportional = (event->state & BIND_SHIFT);
-                                DLOG("right mouse button\n");
-                                floating_resize_window(conn, client, proportional, event);
-                        }
-                        return 1;
-                }
-
-                if (!floating_mod_on_tiled_client(conn, client, event)) {
-                        xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
-                        xcb_flush(conn);
-                }
-
-                return 1;
-        }
-
-        if (client == NULL) {
-                /* The client was neither on a client’s titlebar nor on a client itself, maybe on a stack_window? */
-                if (button_press_stackwin(conn, event))
-                        return 1;
-
-                /* Or on a bar? */
-                if (button_press_bar(conn, event))
-                        return 1;
+/*
+ * Finds out which border was clicked on and calls tiling_resize_for_border().
+ *
+ */
+static bool tiling_resize(Con *con, xcb_button_press_event_t *event, click_destination_t dest) {
+    /* check if this was a click on the window border (and on which one) */
+    Rect bsr = con_border_style_rect(con);
+    DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x\n",
+            event->event_x, event->event_y, con, event->event);
+    DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width);
+    if (dest == CLICK_DECORATION)
+        return tiling_resize_for_border(con, BORDER_TOP, event);
+
+    if (event->event_x >= 0 && event->event_x <= bsr.x &&
+        event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height)
+        return tiling_resize_for_border(con, BORDER_LEFT, event);
+
+    if (event->event_x >= (con->window_rect.x + con->window_rect.width) &&
+        event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height)
+        return tiling_resize_for_border(con, BORDER_RIGHT, event);
+
+    if (event->event_y >= (con->window_rect.y + con->window_rect.height))
+        return tiling_resize_for_border(con, BORDER_BOTTOM, event);
+
+    return false;
+}
 
-                DLOG("Could not handle this button press\n");
-                xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
-                xcb_flush(conn);
-                return 1;
+/*
+ * Being called by handle_button_press, this function calls the appropriate
+ * functions for resizing/dragging.
+ *
+ */
+static int route_click(Con *con, xcb_button_press_event_t *event, bool mod_pressed, click_destination_t dest) {
+    DLOG("--> click properties: mod = %d, destination = %d\n", mod_pressed, dest);
+    DLOG("--> OUTCOME = %p\n", con);
+    DLOG("type = %d, name = %s\n", con->type, con->name);
+
+    /* don’t handle dockarea cons, they must not be focused */
+    if (con->parent->type == CT_DOCKAREA)
+        goto done;
+
+    /* get the floating con */
+    Con *floatingcon = con_inside_floating(con);
+    const bool proportional = (event->state & BIND_SHIFT);
+    const bool in_stacked = (con->parent->layout == L_STACKED || con->parent->layout == L_TABBED);
+
+    /* 1: see if the user scrolled on the decoration of a stacked/tabbed con */
+    if (in_stacked &&
+        dest == CLICK_DECORATION &&
+        (event->detail == XCB_BUTTON_INDEX_4 ||
+         event->detail == XCB_BUTTON_INDEX_5)) {
+        DLOG("Scrolling on a window decoration\n");
+        orientation_t orientation = (con->parent->layout == L_STACKED ? VERT : HORIZ);
+        if (event->detail == XCB_BUTTON_INDEX_4)
+            tree_next('p', orientation);
+        else tree_next('n', orientation);
+        goto done;
+    }
+
+    /* 2: focus this con */
+    con_focus(con);
+
+    /* 3: for floating containers, we also want to raise them on click */
+    if (floatingcon != NULL) {
+        floating_raise_con(floatingcon);
+
+        /* 4: floating_modifier plus left mouse button drags */
+        if (mod_pressed && event->detail == 1) {
+            floating_drag_window(floatingcon, event);
+            return 1;
         }
 
-        /* Set focus in any case */
-        set_focus(conn, client, false);
-
-        /* Let’s see if this was on the borders (= resize). If not, we’re done */
-        DLOG("press button on x=%d, y=%d\n", event->event_x, event->event_y);
-        resize_orientation_t orientation = O_VERTICAL;
-        Container *con = client->container;
-        int first, second;
-
-        if (client->dock) {
-                DLOG("dock. done.\n");
-                xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
-                xcb_flush(conn);
-                return 1;
+        /* 5: resize (floating) if this was a click on the left/right/bottom
+         * border. also try resizing (tiling) if it was a click on the top
+         * border, but continue if that does not work */
+        if (mod_pressed && event->detail == 3) {
+            DLOG("floating resize due to floatingmodifier\n");
+            floating_resize_window(floatingcon, proportional, event);
+            return 1;
         }
 
-        DLOG("event->event_x = %d, client->rect.width = %d\n", event->event_x, client->rect.width);
-
-        /* Some clients (xfontsel for example) seem to pass clicks on their
-         * window to the parent window, thus we receive an event here which in
-         * reality is a border_click. Check for the position and fix state. */
-        if (border_click &&
-            event->event_x >= client->child_rect.x &&
-            event->event_x <= (client->child_rect.x + client->child_rect.width) &&
-            event->event_y >= client->child_rect.y &&
-            event->event_y <= (client->child_rect.y + client->child_rect.height)) {
-                DLOG("Fixing border_click = false because of click in child\n");
-                border_click = false;
+        if (!in_stacked && dest == CLICK_DECORATION) {
+            /* try tiling resize, but continue if it doesn’t work */
+            DLOG("tiling resize with fallback\n");
+            if (tiling_resize(con, event, dest))
+                goto done;
         }
 
-        if (!border_click) {
-                DLOG("client. done.\n");
-                xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
-                /* Floating clients should be raised on click */
-                if (client_is_floating(client))
-                        xcb_raise_window(conn, client->frame);
-                xcb_flush(conn);
-                return 1;
+        if (dest == CLICK_BORDER) {
+            DLOG("floating resize due to border click\n");
+            floating_resize_window(floatingcon, proportional, event);
+            return 1;
         }
 
-        /* Don’t handle events inside the titlebar, only borders are interesting */
-        i3Font *font = load_font(conn, config.font);
-        if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) {
-                DLOG("click on titlebar\n");
-
-                /* Floating clients can be dragged by grabbing their titlebar */
-                if (client_is_floating(client)) {
-                        /* Firstly, we raise it. Maybe the user just wanted to raise it without grabbing */
-                        xcb_raise_window(conn, client->frame);
-                        xcb_flush(conn);
-
-                        floating_drag_window(conn, client, event);
-                }
-                return 1;
+        /* 6: dragging, if this was a click on a decoration (which did not lead
+         * to a resize) */
+        if (!in_stacked && dest == CLICK_DECORATION) {
+            floating_drag_window(floatingcon, event);
+            return 1;
         }
 
-        if (client_is_floating(client))
-                return floating_border_click(conn, client, event);
-
-        Workspace *ws = con->workspace;
-
-        if (event->event_y < 2) {
-                /* This was a press on the top border */
-                if (con->row == 0)
-                        return 1;
-                first = con->row - 1;
-                second = con->row;
-                orientation = O_HORIZONTAL;
-        } else if (event->event_y >= (client->rect.height - 2)) {
-                /* …bottom border */
-                first = con->row + (con->rowspan - 1);
-                if (!cell_exists(ws, con->col, first) ||
-                    (first == (ws->rows-1)))
-                        return 1;
-
-                second = first + 1;
-                orientation = O_HORIZONTAL;
-        } else if (event->event_x <= 2) {
-                /* …left border */
-                if (con->col == 0)
-                        return 1;
-
-                first = con->col - 1;
-                second = con->col;
-        } else if (event->event_x > 2) {
-                /* …right border */
-                first = con->col + (con->colspan - 1);
-                DLOG("column %d\n", first);
-
-                if (!cell_exists(ws, first, con->row) ||
-                    (first == (ws->cols-1)))
-                        return 1;
-
-                second = first + 1;
-        }
+        goto done;
+    }
+
+    if (in_stacked) {
+        /* for stacked/tabbed cons, the resizing applies to the parent
+         * container */
+        con = con->parent;
+    }
+
+    /* 7: floating modifier pressed, initiate a resize */
+    if (mod_pressed && event->detail == 3) {
+        if (floating_mod_on_tiled_client(con, event))
+            return 1;
+    }
+    /* 8: otherwise, check for border/decoration clicks and resize */
+    else if (dest == CLICK_BORDER || dest == CLICK_DECORATION) {
+        DLOG("Trying to resize (tiling)\n");
+        tiling_resize(con, event, dest);
+    }
+
+done:
+    xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
+    xcb_flush(conn);
+    tree_render();
+    return 0;
+}
 
-        return resize_graphical_handler(conn, ws, first, second, orientation, event);
+/*
+ * The button press X callback. This function determines whether the floating
+ * modifier is pressed and where the user clicked (decoration, border, inside
+ * the window).
+ *
+ * Then, route_click is called on the appropriate con.
+ *
+ */
+int handle_button_press(xcb_button_press_event_t *event) {
+    Con *con;
+    DLOG("Button %d pressed on window 0x%08x\n", event->state, event->event);
+
+    const uint32_t mod = config.floating_modifier;
+    bool mod_pressed = (mod != 0 && (event->state & mod) == mod);
+    DLOG("floating_mod = %d, detail = %d\n", mod_pressed, event->detail);
+    if ((con = con_by_window_id(event->event)))
+        return route_click(con, event, mod_pressed, CLICK_INSIDE);
+
+    if (!(con = con_by_frame_id(event->event))) {
+        ELOG("Clicked into unknown window?!\n");
+        xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
+        xcb_flush(conn);
+        return 0;
+    }
+
+    /* Check if the click was on the decoration of a child */
+    Con *child;
+    TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+        if (!rect_contains(child->deco_rect, event->event_x, event->event_y))
+            continue;
+
+        return route_click(child, event, mod_pressed, CLICK_DECORATION);
+    }
+
+    return route_click(con, event, mod_pressed, CLICK_BORDER);
 }
diff --git a/src/client.c b/src/client.c
deleted file mode 100644 (file)
index e2868fa..0000000
+++ /dev/null
@@ -1,484 +0,0 @@
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * client.c: holds all client-specific functions
- *
- */
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <limits.h>
-
-#include <xcb/xcb.h>
-#include <xcb/xcb_icccm.h>
-
-#include "data.h"
-#include "i3.h"
-#include "xcb.h"
-#include "util.h"
-#include "queue.h"
-#include "layout.h"
-#include "client.h"
-#include "table.h"
-#include "workspace.h"
-#include "config.h"
-#include "log.h"
-
-/*
- * Removes the given client from the container, either because it will be inserted into another
- * one or because it was unmapped
- *
- */
-void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container, bool remove_from_focusstack) {
-        CIRCLEQ_REMOVE(&(container->clients), client, clients);
-
-        if (remove_from_focusstack)
-                SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients);
-
-        /* If the container will be empty now and is in stacking mode, we need to
-           unmap the stack_win */
-        if (CIRCLEQ_EMPTY(&(container->clients)) &&
-            (container->mode == MODE_STACK ||
-             container->mode == MODE_TABBED)) {
-                DLOG("Unmapping stack window\n");
-                struct Stack_Window *stack_win = &(container->stack_win);
-                stack_win->rect.height = 0;
-                xcb_unmap_window(conn, stack_win->window);
-                xcb_flush(conn);
-        }
-}
-
-/*
- * Warps the pointer into the given client (in the middle of it, to be specific), therefore
- * selecting it
- *
- */
-void client_warp_pointer_into(xcb_connection_t *conn, Client *client) {
-        int mid_x = client->rect.width / 2,
-            mid_y = client->rect.height / 2;
-        xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y);
-}
-
-/*
- * Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
- *
- */
-static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) {
-        xcb_get_property_cookie_t cookie;
-        xcb_get_wm_protocols_reply_t protocols;
-        bool result = false;
-
-        cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]);
-        if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1)
-                return false;
-
-        /* Check if the client’s protocols have the requested atom set */
-        for (uint32_t i = 0; i < protocols.atoms_len; i++)
-                if (protocols.atoms[i] == atom)
-                        result = true;
-
-        xcb_get_wm_protocols_reply_wipe(&protocols);
-
-        return result;
-}
-
-/*
- * Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
- *
- */
-void client_kill(xcb_connection_t *conn, Client *window) {
-        /* If the client does not support WM_DELETE_WINDOW, we kill it the hard way */
-        if (!client_supports_protocol(conn, window, atoms[WM_DELETE_WINDOW])) {
-                LOG("Killing window the hard way\n");
-                xcb_kill_client(conn, window->child);
-                return;
-        }
-
-        xcb_client_message_event_t ev;
-
-        memset(&ev, 0, sizeof(xcb_client_message_event_t));
-
-        ev.response_type = XCB_CLIENT_MESSAGE;
-        ev.window = window->child;
-        ev.type = atoms[WM_PROTOCOLS];
-        ev.format = 32;
-        ev.data.data32[0] = atoms[WM_DELETE_WINDOW];
-        ev.data.data32[1] = XCB_CURRENT_TIME;
-
-        LOG("Sending WM_DELETE to the client\n");
-        xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev);
-        xcb_flush(conn);
-}
-
-/*
- * Checks if the given window class and title match the given client
- * Window title is passed as "normal" string and as UCS-2 converted string for
- * matching _NET_WM_NAME capable clients as well as those using legacy hints.
- *
- */
-bool client_matches_class_name(Client *client, char *to_class, char *to_title,
-                               char *to_title_ucs, int to_title_ucs_len) {
-        /* Check if the given class is part of the window class */
-        if ((client->window_class_instance == NULL ||
-             strcasestr(client->window_class_instance, to_class) == NULL) &&
-            (client->window_class_class == NULL ||
-             strcasestr(client->window_class_class, to_class) == NULL))
-                return false;
-
-        /* If no title was given, we’re done */
-        if (to_title == NULL)
-                return true;
-
-        if (client->name_len > -1) {
-                /* UCS-2 converted window titles */
-                if (client->name == NULL || memmem(client->name, (client->name_len * 2), to_title_ucs, (to_title_ucs_len * 2)) == NULL)
-                        return false;
-        } else {
-                /* Legacy hints */
-                if (client->name == NULL || strcasestr(client->name, to_title) == NULL)
-                        return false;
-        }
-
-        return true;
-}
-
-/*
- * Enters fullscreen mode for the given client. This is called by toggle_fullscreen
- * and when moving a fullscreen client to another screen.
- *
- */
-void client_enter_fullscreen(xcb_connection_t *conn, Client *client, bool global) {
-        Workspace *workspace;
-        Output *output;
-        Rect r;
-
-        if (global) {
-                TAILQ_FOREACH(output, &outputs, outputs) {
-                        if (!output->active)
-                                continue;
-
-                        if (output->current_workspace->fullscreen_client == NULL)
-                                continue;
-
-                        LOG("Not entering global fullscreen mode, there already "
-                            "is a fullscreen client on output %s.\n", output->name);
-                        return;
-                }
-
-                r = (Rect) { UINT_MAX, UINT_MAX, 0,0 };
-                Output *output;
-
-                /* Set fullscreen_client for each active workspace.
-                 * Expand the rectangle to contain all outputs. */
-                TAILQ_FOREACH(output, &outputs, outputs) {
-                        if (!output->active)
-                                continue;
-
-                        output->current_workspace->fullscreen_client = client;
-
-                        /* Temporarily abuse width/heigth as coordinates of the lower right corner */
-                        if (r.x > output->rect.x)
-                                r.x = output->rect.x;
-                        if (r.y > output->rect.y)
-                                r.y = output->rect.y;
-                        if (r.x + r.width < output->rect.x + output->rect.width)
-                                r.width = output->rect.x + output->rect.width;
-                        if (r.y + r.height < output->rect.y + output->rect.height)
-                                r.height = output->rect.y + output->rect.height;
-                }
-
-                /* Putting them back to their original meaning */
-                r.height -= r.x;
-                r.width -= r.y;
-
-                LOG("Entering global fullscreen mode...\n");
-        } else {
-                workspace = client->workspace;
-                if (workspace->fullscreen_client != NULL && workspace->fullscreen_client != client) {
-                        LOG("Not entering fullscreen mode, there already is a fullscreen client.\n");
-                        return;
-                }
-
-                workspace->fullscreen_client = client;
-                r = workspace->rect;
-
-                LOG("Entering fullscreen mode...\n");
-        }
-
-        client->fullscreen = true;
-
-        /* We just entered fullscreen mode, let’s configure the window */
-        DLOG("child itself will be at %dx%d with size %dx%d\n",
-                        r.x, r.y, r.width, r.height);
-
-        xcb_set_window_rect(conn, client->frame, r);
-
-        /* Child’s coordinates are relative to the parent (=frame) */
-        r.x = 0;
-        r.y = 0;
-        xcb_set_window_rect(conn, client->child, r);
-
-        /* Raise the window */
-        uint32_t values[] = { XCB_STACK_MODE_ABOVE };
-        xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
-
-        /* Update _NET_WM_STATE */
-        values[0] = atoms[_NET_WM_STATE_FULLSCREEN];
-        xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[_NET_WM_STATE], ATOM, 32, 1, values);
-
-        fake_configure_notify(conn, r, client->child);
-
-        xcb_flush(conn);
-}
-
-/*
- * Leaves fullscreen mode for the current client. This is called by toggle_fullscreen.
- *
- */
-void client_leave_fullscreen(xcb_connection_t *conn, Client *client) {
-        LOG("leaving fullscreen mode\n");
-        client->fullscreen = false;
-        Workspace *ws;
-        TAILQ_FOREACH(ws, workspaces, workspaces)
-                if (ws->fullscreen_client == client)
-                        ws->fullscreen_client = NULL;
-
-        if (client_is_floating(client)) {
-                /* For floating clients it’s enough if we just reconfigure that window (in fact,
-                 * re-rendering the layout will not update the client.) */
-                reposition_client(conn, client);
-                resize_client(conn, client);
-                /* redecorate_window flushes */
-                redecorate_window(conn, client);
-        } else {
-                client_set_below_floating(conn, client);
-
-                /* Because the coordinates of the window haven’t changed, it would not be
-                   re-configured if we don’t set the following flag */
-                client->force_reconfigure = true;
-                /* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */
-                render_layout(conn);
-        }
-
-        /* Update _NET_WM_STATE */
-        xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[_NET_WM_STATE], ATOM, 32, 0, NULL);
-
-        xcb_flush(conn);
-}
-
-/*
- * Toggles fullscreen mode for the given client. It updates the data structures and
- * reconfigures (= resizes/moves) the client and its frame to the full size of the
- * screen. When leaving fullscreen, re-rendering the layout is forced.
- *
- */
-void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) {
-        /* dock clients cannot enter fullscreen mode */
-        assert(!client->dock);
-
-        if (!client->fullscreen) {
-                client_enter_fullscreen(conn, client, false);
-        } else {
-                client_leave_fullscreen(conn, client);
-        }
-}
-
-/*
- * Like client_toggle_fullscreen(), but putting it in global fullscreen-mode.
- *
- */
-void client_toggle_fullscreen_global(xcb_connection_t *conn, Client *client) {
-        /* dock clients cannot enter fullscreen mode */
-        assert(!client->dock);
-
-        if (!client->fullscreen) {
-                client_enter_fullscreen(conn, client, true);
-        } else {
-                client_leave_fullscreen(conn, client);
-        }
-}
-
-/*
- * Sets the position of the given client in the X stack to the highest (tiling layer is always
- * on the same position, so this doesn’t matter) below the first floating client, so that
- * floating windows are always on top.
- *
- */
-void client_set_below_floating(xcb_connection_t *conn, Client *client) {
-        /* Ensure that it is below all floating clients */
-        Workspace *ws = client->workspace;
-        Client *first_floating = TAILQ_FIRST(&(ws->floating_clients));
-        if (first_floating == TAILQ_END(&(ws->floating_clients)))
-                return;
-
-        DLOG("Setting below floating\n");
-        uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW };
-        xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
-
-        if (client->workspace->fullscreen_client == NULL)
-                return;
-
-        DLOG("(and below fullscreen)\n");
-        /* Ensure that the window is still below the fullscreen window */
-        values[0] = client->workspace->fullscreen_client->frame;
-        xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
-}
-
-/*
- * Returns true if the client is floating. Makes the code more beatiful, as floating
- * is not simply a boolean, but also saves whether the user selected the current state
- * or whether it was automatically set.
- *
- */
-bool client_is_floating(Client *client) {
-        return (client->floating >= FLOATING_AUTO_ON);
-}
-
-/*
- * Change the border type for the given client to normal (n), 1px border (p) or
- * completely borderless (b) without actually re-rendering the layout. Useful
- * for calling it when initializing a new client.
- *
- */
-bool client_init_border(xcb_connection_t *conn, Client *client, char border_type) {
-        switch (border_type) {
-                case 'n':
-                        LOG("Changing to normal border\n");
-                        client->titlebar_position = TITLEBAR_TOP;
-                        client->borderless = false;
-                        return true;
-                case 'p':
-                        LOG("Changing to 1px border\n");
-                        client->titlebar_position = TITLEBAR_OFF;
-                        client->borderless = false;
-                        return true;
-                case 'b':
-                        LOG("Changing to borderless\n");
-                        client->titlebar_position = TITLEBAR_OFF;
-                        client->borderless = true;
-                        return true;
-                default:
-                        LOG("Unknown border mode\n");
-                        return false;
-        }
-}
-
-/*
- * Change the border type for the given client to normal (n), 1px border (p) or
- * completely borderless (b).
- *
- */
-void client_change_border(xcb_connection_t *conn, Client *client, char border_type) {
-        if (!client_init_border(conn, client, border_type))
-                return;
-
-        /* Ensure that the child’s position inside our window gets updated */
-        client->force_reconfigure = true;
-
-        /* For clients inside a container, we can simply render the container */
-        if (client->container != NULL)
-                render_container(conn, client->container);
-        else {
-                /* If the client is floating, directly push its size */
-                if (client_is_floating(client))
-                        resize_client(conn, client);
-                /* Otherwise, it may be a dock client, thus render the whole layout */
-                else render_layout(conn);
-        }
-
-        redecorate_window(conn, client);
-}
-
-/*
- * Unmap the client, correctly setting any state which is needed.
- *
- */
-void client_unmap(xcb_connection_t *conn, Client *client) {
-        /* Set WM_STATE_WITHDRAWN, it seems like Java apps need it */
-        long data[] = { XCB_WM_STATE_WITHDRAWN, XCB_NONE };
-        xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
-
-        xcb_unmap_window(conn, client->frame);
-}
-
-/*
- * Map the client, correctly restoring any state needed.
- *
- */
-void client_map(xcb_connection_t *conn, Client *client) {
-        /* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t.
-         * Also, xprop(1) needs that to work. */
-        long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
-        xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
-
-        xcb_map_window(conn, client->frame);
-}
-
-/*
- * Set the given mark for this client. Used for jumping to the client
- * afterwards (like m<mark> and '<mark> in vim).
- *
- */
-void client_mark(xcb_connection_t *conn, Client *client, const char *mark) {
-        if (client->mark != NULL)
-                free(client->mark);
-        client->mark = sstrdup(mark);
-
-        /* Make sure no other client has this mark set */
-        Client *current;
-        Workspace *ws;
-        TAILQ_FOREACH(ws, workspaces, workspaces)
-                SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) {
-                        if (current == client ||
-                            current->mark == NULL ||
-                            strcmp(current->mark, mark) != 0)
-                                continue;
-
-                        free(current->mark);
-                        current->mark = NULL;
-                        /* We can break here since there can only be one other
-                         * client with this mark. */
-                        break;
-                }
-}
-
-/*
- * Returns the minimum height of a specific window. The height is calculated
- * by using 2 pixels (for the client window itself), possibly padding this to
- * comply with the client’s base_height and then adding the decoration height.
- *
- */
-uint32_t client_min_height(Client *client) {
-        uint32_t height = max(2, client->base_height);
-        i3Font *font = load_font(global_conn, config.font);
-
-        if (client->titlebar_position == TITLEBAR_OFF && client->borderless)
-                return height;
-
-        if (client->titlebar_position == TITLEBAR_OFF && !client->borderless)
-                return height + 2;
-
-        return height + font->height + 2 + 2;
-}
-
-/*
- * See client_min_height.
- *
- */
-uint32_t client_min_width(Client *client) {
-        uint32_t width = max(2, client->base_width);
-
-        if (client->titlebar_position == TITLEBAR_OFF && client->borderless)
-                return width;
-
-        if (client->titlebar_position == TITLEBAR_OFF && !client->borderless)
-                return width + 2;
-
-        return width + 2 + 2;
-}
diff --git a/src/cmdparse.l b/src/cmdparse.l
new file mode 100644 (file)
index 0000000..898416c
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ * 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"
+
+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 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 tokens are here to recognize them *before* handling
+     * strings ('workspace' command) */
+next                            { return TOK_NEXT; }
+prev                            { return TOK_PREV; }
+
+<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                            { WS_STRING; 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; }
+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; }
+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
new file mode 100644 (file)
index 0000000..d84023c
--- /dev/null
@@ -0,0 +1,859 @@
+%{
+/*
+ * 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 <limits.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) {
+    LOG("COMMAND: *%s*\n", new);
+    cmdyy_scan_string(new);
+
+    match_init(&current_match);
+    context = scalloc(sizeof(struct context));
+    context->filename = "cmd";
+    FREE(json_output);
+    if (cmdyyparse() != 0) {
+        fprintf(stderr, "Could not parse command\n");
+        asprintf(&json_output, "{\"success\":false, \"error\":\"%s at position %d\"}",
+                 context->compact_error, context->first_column);
+        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;
+}
+
+%}
+
+%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_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_CLASS           "class"
+%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
+
+%%
+
+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 &&
+                    strcasecmp(current_match.mark, current->con->mark) == 0) {
+                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 = $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 = $3;
+    }
+    | TOK_TITLE '=' STR
+    {
+        printf("criteria: title = %s\n", $3);
+        current_match.title = $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 STR
+    {
+        printf("should execute %s\n", $2);
+        start_application($2);
+        free($2);
+    }
+    ;
+
+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
+    {
+        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");
+
+            asprintf(&json_output, "{\"success\":false, \"error\":\"You have to "
+                     "specify which window/container should be focused\"}");
+            break;
+        }
+
+        int count = 0;
+        TAILQ_FOREACH(current, &owindows, owindows) {
+            Con *ws = con_get_workspace(current->con);
+            workspace_show(ws->name);
+            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
+    {
+        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
+    {
+        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->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 ($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);
+            }
+        }
+
+        tree_render();
+    }
+    ;
+
+optional_kill_mode:
+    /* empty */             { $$ = KILL_WINDOW; }
+    | TOK_WINDOW { $$ = KILL_WINDOW; }
+    | TOK_CLIENT { $$ = KILL_CLIENT; }
+    ;
+
+workspace:
+    TOK_WORKSPACE TOK_NEXT
+    {
+        workspace_next();
+        tree_render();
+    }
+    | TOK_WORKSPACE TOK_PREV
+    {
+        workspace_prev();
+        tree_render();
+    }
+    | TOK_WORKSPACE STR
+    {
+        printf("should switch to workspace %s\n", $2);
+        workspace_show($2);
+        free($2);
+
+        tree_render();
+    }
+    ;
+
+open:
+    TOK_OPEN
+    {
+        printf("opening new container\n");
+        Con *con = tree_open_con(NULL, NULL);
+        con_focus(con);
+        asprintf(&json_output, "{\"success\":true, \"id\":%ld}", (long int)con);
+
+        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);
+            if ($2 == TOK_TOGGLE) {
+                current->con->border_style++;
+                current->con->border_style %= 3;
+            } else current->con->border_style = $2;
+        }
+
+        tree_render();
+    }
+    ;
+
+border_style:
+    TOK_NORMAL      { $$ = BS_NORMAL; }
+    | TOK_NONE      { $$ = BS_NONE; }
+    | TOK_1PIXEL    { $$ = BS_1PIXEL; }
+    | TOK_TOGGLE    { $$ = TOK_TOGGLE; }
+    ;
+
+move:
+    TOK_MOVE direction
+    {
+        printf("moving in direction %d\n", $2);
+        tree_move($2);
+
+        tree_render();
+    }
+    | TOK_MOVE TOK_WORKSPACE STR
+    {
+        owindow *current;
+
+        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);
+        }
+
+        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("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 = sstrdup($2);
+        }
+
+        free($<string>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;
+        }
+
+        if (con_is_floating(focused)) {
+            printf("floating resize\n");
+            if (direction == TOK_UP) {
+                focused->parent->rect.y -= px;
+                focused->parent->rect.height += px;
+            } else if (direction == TOK_DOWN) {
+                focused->rect.height += px;
+            } else if (direction == TOK_LEFT) {
+                focused->rect.x -= px;
+                focused->rect.width += px;
+            } else {
+                focused->rect.width += px;
+            }
+        } else {
+            LOG("tiling resize\n");
+            /* get the default percentage */
+            int children = con_num_children(focused->parent);
+            Con *other;
+            LOG("ins. %d children\n", children);
+            double percentage = 1.0 / children;
+            LOG("default percentage = %f\n", percentage);
+
+            if (direction == TOK_UP || direction == TOK_LEFT) {
+                other = TAILQ_PREV(focused, nodes_head, nodes);
+            } else {
+                other = TAILQ_NEXT(focused, nodes);
+            }
+            if (other == TAILQ_END(workspaces)) {
+                LOG("No other container in this direction found, cannot resize.\n");
+                return 0;
+            }
+            LOG("other->percent = %f\n", other->percent);
+            LOG("focused->percent before = %f\n", focused->percent);
+            if (focused->percent == 0.0)
+                focused->percent = percentage;
+            if (other->percent == 0.0)
+                other->percent = percentage;
+            focused->percent += ((double)ppt / 100.0);
+            other->percent -= ((double)ppt / 100.0);
+            LOG("focused->percent after = %f\n", focused->percent);
+            LOG("other->percent after = %f\n", other->percent);
+        }
+
+        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
deleted file mode 100644 (file)
index 18181a6..0000000
+++ /dev/null
@@ -1,1383 +0,0 @@
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- */
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <unistd.h>
-#include <string.h>
-
-#include <xcb/xcb.h>
-
-#include "util.h"
-#include "data.h"
-#include "table.h"
-#include "layout.h"
-#include "i3.h"
-#include "randr.h"
-#include "client.h"
-#include "floating.h"
-#include "xcb.h"
-#include "config.h"
-#include "workspace.h"
-#include "commands.h"
-#include "resize.h"
-#include "log.h"
-#include "sighandler.h"
-#include "manage.h"
-#include "ipc.h"
-
-bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) {
-        /* If this container is empty, we’re done */
-        if (container->currently_focused == NULL)
-                return false;
-
-        /* Get the previous/next client or wrap around */
-        Client *candidate = NULL;
-        if (direction == D_UP) {
-                if ((candidate = CIRCLEQ_PREV_OR_NULL(&(container->clients), container->currently_focused, clients)) == NULL)
-                        candidate = CIRCLEQ_LAST(&(container->clients));
-        }
-        else if (direction == D_DOWN) {
-                if ((candidate = CIRCLEQ_NEXT_OR_NULL(&(container->clients), container->currently_focused, clients)) == NULL)
-                        candidate = CIRCLEQ_FIRST(&(container->clients));
-        } else ELOG("Direction not implemented!\n");
-
-        /* If we could not switch, the container contains exactly one client. We return false */
-        if (candidate == container->currently_focused)
-                return false;
-
-        /* Set focus */
-        set_focus(conn, candidate, true);
-
-        return true;
-}
-
-typedef enum { THING_WINDOW, THING_CONTAINER, THING_SCREEN } thing_t;
-
-static void jump_to_mark(xcb_connection_t *conn, const char *mark) {
-        Client *current;
-        LOG("Jumping to \"%s\"\n", mark);
-
-        Workspace *ws;
-        TAILQ_FOREACH(ws, workspaces, workspaces)
-                SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) {
-                        if (current->mark == NULL || strcmp(current->mark, mark) != 0)
-                                continue;
-
-                        set_focus(conn, current, true);
-                        workspace_show(conn, current->workspace->num + 1);
-                        return;
-                }
-
-        ELOG("No window with this mark found\n");
-}
-
-static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t thing) {
-        DLOG("focusing direction %d\n", direction);
-
-        int new_row = current_row,
-            new_col = current_col;
-        Container *container = CUR_CELL;
-        Workspace *t_ws = c_ws;
-
-        /* Makes sure new_col and new_row are within bounds of the new workspace */
-#define CHECK_COLROW_BOUNDARIES \
-        do { \
-                if (new_col >= t_ws->cols) \
-                        new_col = (t_ws->cols - 1); \
-                if (new_row >= t_ws->rows) \
-                        new_row = (t_ws->rows - 1); \
-        } while (0)
-
-        /* There always is a container. If not, current_col or current_row is wrong */
-        assert(container != NULL);
-
-        if (container->workspace->fullscreen_client != NULL) {
-                LOG("You're in fullscreen mode. Forcing focus to operate on whole screens\n");
-                thing = THING_SCREEN;
-        }
-
-        /* For focusing screens, situation is different: we get the rect
-         * of the current screen, then get the screen which is on its
-         * right/left/bottom/top and just switch to the workspace on
-         * the target screen. */
-        if (thing == THING_SCREEN) {
-                Output *cs = c_ws->output;
-                assert(cs != NULL);
-                Rect bounds = cs->rect;
-
-                if (direction == D_RIGHT)
-                        bounds.x += bounds.width;
-                else if (direction == D_LEFT)
-                        bounds.x -= bounds.width;
-                else if (direction == D_UP)
-                        bounds.y -= bounds.height;
-                else bounds.y += bounds.height;
-
-                Output *target = get_output_containing(bounds.x, bounds.y);
-                if (target == NULL) {
-                        DLOG("Target output NULL\n");
-                        /* Wrap around if the target screen is out of bounds */
-                        if (direction == D_RIGHT)
-                                target = get_output_most(D_LEFT, cs);
-                        else if (direction == D_LEFT)
-                                target = get_output_most(D_RIGHT, cs);
-                        else if (direction == D_UP)
-                                target = get_output_most(D_DOWN, cs);
-                        else target = get_output_most(D_UP, cs);
-                }
-
-                DLOG("Switching to ws %d\n", target->current_workspace + 1);
-                workspace_show(conn, target->current_workspace->num + 1);
-                return;
-        }
-
-        /* TODO: for horizontal default layout, this has to be expanded to LEFT/RIGHT */
-        if (direction == D_UP || direction == D_DOWN) {
-                if (thing == THING_WINDOW)
-                        /* Let’s see if we can perform up/down focus in the current container */
-                        if (focus_window_in_container(conn, container, direction))
-                                return;
-
-                if (direction == D_DOWN && cell_exists(t_ws, current_col, current_row+1))
-                        new_row = current_row + t_ws->table[current_col][current_row]->rowspan;
-                else if (direction == D_UP && cell_exists(c_ws, current_col, current_row-1)) {
-                        /* Set new_row as a sane default, but it may get overwritten in a second */
-                        new_row--;
-
-                        /* Search from the top to correctly handle rowspanned containers */
-                        for (int rows = 0; rows < current_row; rows += t_ws->table[current_col][rows]->rowspan) {
-                                if (new_row > (rows + (t_ws->table[current_col][rows]->rowspan - 1)))
-                                        continue;
-
-                                new_row = rows;
-                                break;
-                        }
-                } else {
-                        /* Let’s see if there is a screen down/up there to which we can switch */
-                        DLOG("container is at %d with height %d\n", container->y, container->height);
-                        Output *output;
-                        int destination_y = (direction == D_UP ? (container->y - 1) : (container->y + container->height + 1));
-                        if ((output = get_output_containing(container->x, destination_y)) == NULL) {
-                                DLOG("Wrapping screen around vertically\n");
-                                /* No screen found? Then wrap */
-                                output = get_output_most((direction == D_UP ? D_DOWN : D_UP), container->workspace->output);
-                        }
-                        t_ws = output->current_workspace;
-                        new_row = (direction == D_UP ? (t_ws->rows - 1) : 0);
-                }
-
-                CHECK_COLROW_BOUNDARIES;
-
-                DLOG("new_col = %d, new_row = %d\n", new_col, new_row);
-                if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
-                        DLOG("Cell empty, checking for colspanned client above...\n");
-                        for (int cols = 0; cols < new_col; cols += t_ws->table[cols][new_row]->colspan) {
-                                if (new_col > (cols + (t_ws->table[cols][new_row]->colspan - 1)))
-                                        continue;
-
-                                new_col = cols;
-                                DLOG("Fixed it to new col %d\n", new_col);
-                                break;
-                        }
-                }
-
-                if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
-                        DLOG("Cell still empty, checking for full cols above spanned width...\n");
-                        DLOG("new_col = %d\n", new_col);
-                        DLOG("colspan = %d\n", container->colspan);
-                        for (int cols = new_col;
-                             cols < container->col + container->colspan;
-                             cols += t_ws->table[cols][new_row]->colspan) {
-                                DLOG("candidate: new_row = %d, cols = %d\n", new_row, cols);
-                                if (t_ws->table[cols][new_row]->currently_focused == NULL)
-                                        continue;
-
-                                new_col = cols;
-                                DLOG("Fixed it to new col %d\n", new_col);
-                                break;
-                        }
-                }
-        } else if (direction == D_LEFT || direction == D_RIGHT) {
-                if (direction == D_RIGHT && cell_exists(t_ws, current_col+1, current_row))
-                        new_col = current_col + t_ws->table[current_col][current_row]->colspan;
-                else if (direction == D_LEFT && cell_exists(t_ws, current_col-1, current_row)) {
-                        /* Set new_col as a sane default, but it may get overwritten in a second */
-                        new_col--;
-
-                        /* Search from the left to correctly handle colspanned containers */
-                        for (int cols = 0; cols < current_col; cols += t_ws->table[cols][current_row]->colspan) {
-                                if (new_col > (cols + (t_ws->table[cols][current_row]->colspan - 1)))
-                                        continue;
-
-                                new_col = cols;
-                                break;
-                        }
-                } else {
-                        /* Let’s see if there is a screen left/right here to which we can switch */
-                        DLOG("container is at %d with width %d\n", container->x, container->width);
-                        Output *output;
-                        int destination_x = (direction == D_LEFT ? (container->x - 1) : (container->x + container->width + 1));
-                        if ((output = get_output_containing(destination_x, container->y)) == NULL) {
-                                DLOG("Wrapping screen around horizontally\n");
-                                output = get_output_most((direction == D_LEFT ? D_RIGHT : D_LEFT), container->workspace->output);
-                        }
-                        t_ws = output->current_workspace;
-                        new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0);
-                }
-
-                CHECK_COLROW_BOUNDARIES;
-
-                DLOG("new_col = %d, new_row = %d\n", new_col, new_row);
-                if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
-                        DLOG("Cell empty, checking for rowspanned client above...\n");
-                        for (int rows = 0; rows < new_row; rows += t_ws->table[new_col][rows]->rowspan) {
-                                if (new_row > (rows + (t_ws->table[new_col][rows]->rowspan - 1)))
-                                        continue;
-
-                                new_row = rows;
-                                DLOG("Fixed it to new row %d\n", new_row);
-                                break;
-                        }
-                }
-
-                if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
-                        DLOG("Cell still empty, checking for full cols near full spanned height...\n");
-                        DLOG("new_row = %d\n", new_row);
-                        DLOG("rowspan = %d\n", container->rowspan);
-                        for (int rows = new_row;
-                             rows < container->row + container->rowspan;
-                             rows += t_ws->table[new_col][rows]->rowspan) {
-                                DLOG("candidate: new_col = %d, rows = %d\n", new_col, rows);
-                                if (t_ws->table[new_col][rows]->currently_focused == NULL)
-                                        continue;
-
-                                new_row = rows;
-                                DLOG("Fixed it to new col %d\n", new_row);
-                                break;
-                        }
-                }
-
-        } else {
-                ELOG("direction unhandled\n");
-                return;
-        }
-
-        CHECK_COLROW_BOUNDARIES;
-
-        if (t_ws->table[new_col][new_row]->currently_focused != NULL)
-                set_focus(conn, t_ws->table[new_col][new_row]->currently_focused, true);
-}
-
-/*
- * Tries to move the window inside its current container.
- *
- * Returns true if the window could be moved, false otherwise.
- *
- */
-static bool move_current_window_in_container(xcb_connection_t *conn, Client *client,
-                direction_t direction) {
-        assert(client->container != NULL);
-
-        Client *other = (direction == D_UP ? CIRCLEQ_PREV(client, clients) :
-                                             CIRCLEQ_NEXT(client, clients));
-
-        if (other == CIRCLEQ_END(&(client->container->clients)))
-                return false;
-
-        DLOG("i can do that\n");
-        /* We can move the client inside its current container */
-        CIRCLEQ_REMOVE(&(client->container->clients), client, clients);
-        if (direction == D_UP)
-                CIRCLEQ_INSERT_BEFORE(&(client->container->clients), other, client, clients);
-        else CIRCLEQ_INSERT_AFTER(&(client->container->clients), other, client, clients);
-        render_layout(conn);
-        return true;
-}
-
-/*
- * Moves the current window or whole container to the given direction, creating a column/row if
- * necessary.
- *
- */
-static void move_current_window(xcb_connection_t *conn, direction_t direction) {
-        LOG("moving window to direction %s\n", (direction == D_UP ? "up" : (direction == D_DOWN ? "down" :
-                                            (direction == D_LEFT ? "left" : "right"))));
-        /* Get current window */
-        Container *container = CUR_CELL,
-                  *new = NULL;
-
-        /* There has to be a container, see focus_window() */
-        assert(container != NULL);
-
-        /* If there is no window or the dock window is focused, we’re done */
-        if (container->currently_focused == NULL ||
-            container->currently_focused->dock)
-                return;
-
-        /* As soon as the client is moved away, the last focused client in the old
-         * container needs to get focus, if any. Therefore, we save it here. */
-        Client *current_client = container->currently_focused;
-        Client *to_focus = get_last_focused_client(conn, container, current_client);
-
-        if (to_focus == NULL) {
-                to_focus = CIRCLEQ_NEXT_OR_NULL(&(container->clients), current_client, clients);
-                if (to_focus == NULL)
-                        to_focus = CIRCLEQ_PREV_OR_NULL(&(container->clients), current_client, clients);
-        }
-
-        switch (direction) {
-                case D_LEFT:
-                        /* If we’re at the left-most position, move the rest of the table right */
-                        if (current_col == 0) {
-                                expand_table_cols_at_head(c_ws);
-                                new = CUR_CELL;
-                        } else
-                                new = CUR_TABLE[--current_col][current_row];
-                        break;
-                case D_RIGHT:
-                        if (current_col == (c_ws->cols-1))
-                                expand_table_cols(c_ws);
-
-                        new = CUR_TABLE[++current_col][current_row];
-                        break;
-                case D_UP:
-                        if (move_current_window_in_container(conn, current_client, D_UP))
-                                return;
-
-                        /* if we’re at the up-most position, move the rest of the table down */
-                        if (current_row == 0) {
-                                expand_table_rows_at_head(c_ws);
-                                new = CUR_CELL;
-                        } else
-                                new = CUR_TABLE[current_col][--current_row];
-                        break;
-                case D_DOWN:
-                        if (move_current_window_in_container(conn, current_client, D_DOWN))
-                                return;
-
-                        if (current_row == (c_ws->rows-1))
-                                expand_table_rows(c_ws);
-
-                        new = CUR_TABLE[current_col][++current_row];
-                        break;
-                /* To make static analyzers happy: */
-                default:
-                        return;
-        }
-
-        /* Remove it from the old container and put it into the new one */
-        client_remove_from_container(conn, current_client, container, true);
-
-        if (new->currently_focused != NULL)
-                CIRCLEQ_INSERT_AFTER(&(new->clients), new->currently_focused, current_client, clients);
-        else CIRCLEQ_INSERT_TAIL(&(new->clients), current_client, clients);
-        SLIST_INSERT_HEAD(&(new->workspace->focus_stack), current_client, focus_clients);
-
-        /* Update data structures */
-        current_client->container = new;
-        current_client->workspace = new->workspace;
-        container->currently_focused = to_focus;
-        new->currently_focused = current_client;
-
-        Workspace *workspace = container->workspace;
-
-        /* delete all empty columns/rows */
-        cleanup_table(conn, workspace);
-
-        /* Fix colspan/rowspan if it’d overlap */
-        fix_colrowspan(conn, workspace);
-
-        render_workspace(conn, workspace->output, workspace);
-        xcb_flush(conn);
-
-        set_focus(conn, current_client, true);
-}
-
-static void move_current_container(xcb_connection_t *conn, direction_t direction) {
-        LOG("moving container to direction %s\n", (direction == D_UP ? "up" : (direction == D_DOWN ? "down" :
-                                            (direction == D_LEFT ? "left" : "right"))));
-        /* Get current window */
-        Container *container = CUR_CELL,
-                  *new = NULL;
-
-        Container **old = &CUR_CELL;
-
-        /* There has to be a container, see focus_window() */
-        assert(container != NULL);
-
-        switch (direction) {
-                case D_LEFT:
-                        /* If we’re at the left-most position, move the rest of the table right */
-                        if (current_col == 0) {
-                                expand_table_cols_at_head(c_ws);
-                                new = CUR_CELL;
-                                old = &CUR_TABLE[current_col+1][current_row];
-                        } else
-                                new = CUR_TABLE[--current_col][current_row];
-                        break;
-                case D_RIGHT:
-                        if (current_col == (c_ws->cols-1))
-                                expand_table_cols(c_ws);
-
-                        new = CUR_TABLE[++current_col][current_row];
-                        break;
-                case D_UP:
-                        /* if we’re at the up-most position, move the rest of the table down */
-                        if (current_row == 0) {
-                                expand_table_rows_at_head(c_ws);
-                                new = CUR_CELL;
-                                old = &CUR_TABLE[current_col][current_row+1];
-                        } else
-                                new = CUR_TABLE[current_col][--current_row];
-                        break;
-                case D_DOWN:
-                        if (current_row == (c_ws->rows-1))
-                                expand_table_rows(c_ws);
-
-                        new = CUR_TABLE[current_col][++current_row];
-                        break;
-                /* To make static analyzers happy: */
-                default:
-                        return;
-        }
-
-        DLOG("old = %d,%d and new = %d,%d\n", container->col, container->row, new->col, new->row);
-
-        /* Swap the containers */
-        int col = new->col;
-        int row = new->row;
-
-        *old = new;
-        new->col = container->col;
-        new->row = container->row;
-
-        CUR_CELL = container;
-        container->col = col;
-        container->row = row;
-
-        Workspace *workspace = container->workspace;
-
-        /* delete all empty columns/rows */
-        cleanup_table(conn, workspace);
-
-        /* Fix colspan/rowspan if it’d overlap */
-        fix_colrowspan(conn, workspace);
-
-        render_layout(conn);
-}
-
-/*
- * "Snaps" the current container (not possible for windows, because it works at table base)
- * to the given direction, that is, adjusts cellspan/rowspan
- *
- */
-static void snap_current_container(xcb_connection_t *conn, direction_t direction) {
-        LOG("snapping container to direction %d\n", direction);
-
-        Container *container = CUR_CELL;
-
-        assert(container != NULL);
-
-        switch (direction) {
-                case D_LEFT:
-                        /* Snap to the left is actually a move to the left and then a snap right */
-                        if (!cell_exists(container->workspace, container->col - 1, container->row) ||
-                            CUR_TABLE[container->col-1][container->row]->currently_focused != NULL) {
-                                ELOG("cannot snap to left - the cell is already used\n");
-                                return;
-                        }
-
-                        move_current_window(conn, D_LEFT);
-                        snap_current_container(conn, D_RIGHT);
-                        return;
-                case D_RIGHT: {
-                        /* Check if the cell is used */
-                        int new_col = container->col + container->colspan;
-                        for (int i = 0; i < container->rowspan; i++)
-                                if (!cell_exists(container->workspace, new_col, container->row + i) ||
-                                    CUR_TABLE[new_col][container->row + i]->currently_focused != NULL) {
-                                        ELOG("cannot snap to right - the cell is already used\n");
-                                        return;
-                                }
-
-                        /* Check if there are other cells with rowspan, which are in our way.
-                         * If so, reduce their rowspan. */
-                        for (int i = container->row-1; i >= 0; i--) {
-                                DLOG("we got cell %d, %d with rowspan %d\n",
-                                                new_col, i, CUR_TABLE[new_col][i]->rowspan);
-                                while ((CUR_TABLE[new_col][i]->rowspan-1) >= (container->row - i))
-                                        CUR_TABLE[new_col][i]->rowspan--;
-                                DLOG("new rowspan = %d\n", CUR_TABLE[new_col][i]->rowspan);
-                        }
-
-                        container->colspan++;
-                        break;
-                }
-                case D_UP:
-                        if (!cell_exists(container->workspace, container->col, container->row - 1) ||
-                            CUR_TABLE[container->col][container->row-1]->currently_focused != NULL) {
-                                ELOG("cannot snap to top - the cell is already used\n");
-                                return;
-                        }
-
-                        move_current_window(conn, D_UP);
-                        snap_current_container(conn, D_DOWN);
-                        return;
-                case D_DOWN: {
-                        DLOG("snapping down\n");
-                        int new_row = container->row + container->rowspan;
-                        for (int i = 0; i < container->colspan; i++)
-                                if (!cell_exists(container->workspace, container->col + i, new_row) ||
-                                    CUR_TABLE[container->col + i][new_row]->currently_focused != NULL) {
-                                        ELOG("cannot snap down - the cell is already used\n");
-                                        return;
-                                }
-
-                        for (int i = container->col-1; i >= 0; i--) {
-                                DLOG("we got cell %d, %d with colspan %d\n",
-                                                i, new_row, CUR_TABLE[i][new_row]->colspan);
-                                while ((CUR_TABLE[i][new_row]->colspan-1) >= (container->col - i))
-                                        CUR_TABLE[i][new_row]->colspan--;
-                                DLOG("new colspan = %d\n", CUR_TABLE[i][new_row]->colspan);
-
-                        }
-
-                        container->rowspan++;
-                        break;
-                }
-                /* To make static analyzers happy: */
-                default:
-                        return;
-        }
-
-        render_layout(conn);
-}
-
-static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *client, int workspace) {
-        /* t_ws (to workspace) is just a container pointer to the workspace we’re switching to */
-        Workspace *t_ws = workspace_get(workspace-1),
-                  *old_ws = client->workspace;
-
-        LOG("moving floating\n");
-
-        workspace_initialize(t_ws, c_ws->output, false);
-
-        /* Check if there is already a fullscreen client on the destination workspace and
-         * stop moving if so. */
-        if (client->fullscreen && (t_ws->fullscreen_client != NULL)) {
-                ELOG("Not moving: Fullscreen client already existing on destination workspace.\n");
-                return;
-        }
-
-        floating_assign_to_workspace(client, t_ws);
-
-        /* If we’re moving it to an invisible screen, we need to unmap it */
-        if (!workspace_is_visible(t_ws)) {
-                DLOG("This workspace is not visible, unmapping\n");
-                client_unmap(conn, client);
-        } else {
-                /* If this is not the case, we move the window to a workspace
-                 * which is on another screen, so we also need to adjust its
-                 * coordinates. */
-                DLOG("before x = %d, y = %d\n", client->rect.x, client->rect.y);
-                uint32_t relative_x = client->rect.x - old_ws->rect.x,
-                         relative_y = client->rect.y - old_ws->rect.y;
-                DLOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y);
-                if (client->fullscreen) {
-                        client_enter_fullscreen(conn, client, false);
-                        memcpy(&(client->rect), &(t_ws->rect), sizeof(Rect));
-                } else {
-                        client->rect.x = t_ws->rect.x + relative_x;
-                        client->rect.y = t_ws->rect.y + relative_y;
-                        DLOG("after x = %d, y = %d\n", client->rect.x, client->rect.y);
-                        reposition_client(conn, client);
-                        xcb_flush(conn);
-                }
-        }
-
-        /* Configure the window above all tiling windows (or below a fullscreen
-         * window, if any) */
-        if (t_ws->fullscreen_client != NULL) {
-                uint32_t values[] = { t_ws->fullscreen_client->frame, XCB_STACK_MODE_BELOW };
-                xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
-        } else {
-                Client *last_tiling;
-                SLIST_FOREACH(last_tiling, &(t_ws->focus_stack), focus_clients)
-                        if (!client_is_floating(last_tiling))
-                                break;
-                if (last_tiling != SLIST_END(&(t_ws->focus_stack))) {
-                        uint32_t values[] = { last_tiling->frame, XCB_STACK_MODE_ABOVE };
-                        xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
-                }
-        }
-
-        DLOG("done\n");
-
-        render_layout(conn);
-
-        if (workspace_is_visible(t_ws)) {
-                client_warp_pointer_into(conn, client);
-                set_focus(conn, client, true);
-        }
-}
-
-/*
- * Moves the currently selected window to the given workspace
- *
- */
-static void move_current_window_to_workspace(xcb_connection_t *conn, int workspace) {
-        LOG("Moving current window to workspace %d\n", workspace);
-
-        Container *container = CUR_CELL;
-
-        assert(container != NULL);
-
-        /* t_ws (to workspace) is just a container pointer to the workspace we’re switching to */
-        Workspace *t_ws = workspace_get(workspace-1);
-
-        Client *current_client = container->currently_focused;
-        if (current_client == NULL) {
-                ELOG("No currently focused client in current container.\n");
-                return;
-        }
-        Client *to_focus = CIRCLEQ_NEXT_OR_NULL(&(container->clients), current_client, clients);
-        if (to_focus == NULL)
-                to_focus = CIRCLEQ_PREV_OR_NULL(&(container->clients), current_client, clients);
-
-        workspace_initialize(t_ws, container->workspace->output, false);
-        /* Check if there is already a fullscreen client on the destination workspace and
-         * stop moving if so. */
-        if (current_client->fullscreen && (t_ws->fullscreen_client != NULL)) {
-                ELOG("Not moving: Fullscreen client already existing on destination workspace.\n");
-                return;
-        }
-
-        Container *to_container = t_ws->table[t_ws->current_col][t_ws->current_row];
-
-        assert(to_container != NULL);
-
-        client_remove_from_container(conn, current_client, container, true);
-        if (container->workspace->fullscreen_client == current_client)
-                container->workspace->fullscreen_client = NULL;
-
-        /* TODO: insert it to the correct position */
-        CIRCLEQ_INSERT_TAIL(&(to_container->clients), current_client, clients);
-
-        SLIST_INSERT_HEAD(&(to_container->workspace->focus_stack), current_client, focus_clients);
-        DLOG("Moved.\n");
-
-        current_client->container = to_container;
-        current_client->workspace = to_container->workspace;
-        container->currently_focused = to_focus;
-        to_container->currently_focused = current_client;
-
-        /* If we’re moving it to an invisible screen, we need to unmap it */
-        if (!workspace_is_visible(to_container->workspace)) {
-                DLOG("This workspace is not visible, unmapping\n");
-                client_unmap(conn, current_client);
-        } else {
-                if (current_client->fullscreen) {
-                        DLOG("Calling client_enter_fullscreen again\n");
-                        client_enter_fullscreen(conn, current_client, false);
-                }
-        }
-
-        /* delete all empty columns/rows */
-        cleanup_table(conn, container->workspace);
-
-        render_layout(conn);
-
-        if (workspace_is_visible(to_container->workspace)) {
-                client_warp_pointer_into(conn, current_client);
-                set_focus(conn, current_client, true);
-        }
-}
-
-/*
- * Brings the given window class / title to the current workspace.
- *
- */
-static void bring_window_here(xcb_connection_t *conn, const char *arguments) {
-       char *classtitle;
-       Client *client;
-
-       /* The first character is a quote, this was checked before */
-       classtitle = sstrdup(arguments+1);
-       /* The last character is a quote, we just set it to NULL */
-       classtitle[strlen(classtitle)-1] = '\0';
-
-       if ((client = get_matching_client(conn, classtitle, NULL)) == NULL) {
-               free(classtitle);
-               ELOG("No matching client found.\n");
-               return;
-       }
-
-       /* This is the workspace num that we are on */
-       int current_workspace_num = c_ws->output->current_workspace->num + 1;
-       /* This is the workspace num that the client is on */
-       int clients_workspace_num = client->workspace->num + 1;
-
-       free(classtitle);
-       workspace_show(conn, clients_workspace_num);
-       set_focus(conn, client, true);
-       if (client_is_floating(client))
-               move_floating_window_to_workspace(conn, client, current_workspace_num);
-       else move_current_window_to_workspace(conn, current_workspace_num);
-       workspace_show(conn, current_workspace_num);
-       set_focus(conn, client, true);
-
-}
-
-/*
- * Jumps to the given window class / title.
- * Title is matched using strstr, that is, matches if it appears anywhere
- * in the string. Regular expressions seem to be a bit overkill here. However,
- * if we need them for something else somewhen, we may introduce them here, too.
- *
- */
-static void jump_to_window(xcb_connection_t *conn, const char *arguments) {
-        char *classtitle;
-        Client *client;
-
-        /* The first character is a quote, this was checked before */
-        classtitle = sstrdup(arguments+1);
-        /* The last character is a quote, we just set it to NULL */
-        classtitle[strlen(classtitle)-1] = '\0';
-
-        if ((client = get_matching_client(conn, classtitle, NULL)) == NULL) {
-                free(classtitle);
-                ELOG("No matching client found.\n");
-                return;
-        }
-
-        free(classtitle);
-        workspace_show(conn, client->workspace->num + 1);
-        set_focus(conn, client, true);
-}
-
-/*
- * Jump directly to the specified workspace, row and col.
- * Great for reaching windows that you always keep in the same spot (hello irssi, I'm looking at you)
- *
- */
-static void jump_to_container(xcb_connection_t *conn, const char *arguments) {
-        int ws, row, col;
-        int result;
-
-        result = sscanf(arguments, "%d %d %d", &ws, &col, &row);
-        LOG("Jump called with %d parameters (\"%s\")\n", result, arguments);
-
-        /* No match? Either no arguments were specified, or no numbers */
-        if (result < 1) {
-                ELOG("At least one valid argument required\n");
-                return;
-        }
-
-        /* Move to the target workspace */
-        workspace_show(conn, ws);
-
-        if (result < 3)
-                return;
-
-        DLOG("Boundary-checking col %d, row %d... (max cols %d, max rows %d)\n", col, row, c_ws->cols, c_ws->rows);
-
-        /* Move to row/col */
-        if (row >= c_ws->rows)
-                row = c_ws->rows - 1;
-        if (col >= c_ws->cols)
-                col = c_ws->cols - 1;
-
-        DLOG("Jumping to col %d, row %d\n", col, row);
-        if (c_ws->table[col][row]->currently_focused != NULL)
-                set_focus(conn, c_ws->table[col][row]->currently_focused, true);
-}
-
-/*
- * Travels the focus stack by the given number of times (or once, if no argument
- * was specified). That is, selects the window you were in before you focused
- * the current window.
- *
- * The special values 'floating' (select the next floating window), 'tiling'
- * (select the next tiling window), 'ft' (if the current window is floating,
- * select the next tiling window and vice-versa) are also valid
- *
- */
-static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) {
-        /* Start count at -1 to always skip the first element */
-        int times, count = -1;
-        Client *current;
-        bool floating_criteria;
-
-        /* Either it’s one of the special values… */
-        if (strcasecmp(arguments, "floating") == 0) {
-                floating_criteria = true;
-        } else if (strcasecmp(arguments, "tiling") == 0) {
-                floating_criteria = false;
-        } else if (strcasecmp(arguments, "ft") == 0) {
-                Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
-                if (last_focused == SLIST_END(&(c_ws->focus_stack))) {
-                        ELOG("Cannot select the next floating/tiling client because there is no client at all\n");
-                        return;
-                }
-
-                floating_criteria = !client_is_floating(last_focused);
-        } else {
-                /* …or a number was specified */
-                if (sscanf(arguments, "%u", &times) != 1) {
-                        ELOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments);
-                        times = 1;
-                }
-
-                SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) {
-                        if (++count < times) {
-                                DLOG("Skipping\n");
-                                continue;
-                        }
-
-                        DLOG("Focussing\n");
-                        set_focus(conn, current, true);
-                        break;
-                }
-                return;
-        }
-
-        /* Select the next client matching the criteria parsed above */
-        SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients)
-                if (client_is_floating(current) == floating_criteria) {
-                        set_focus(conn, current, true);
-                        break;
-                }
-}
-
-/* 
- * Switch to next or previous existing workspace
- *
- */
-static void next_previous_workspace(xcb_connection_t *conn, int direction) {
-        Workspace *ws = c_ws;
-
-        if (direction == 'n') {
-                while (1) {
-                        ws = TAILQ_NEXT(ws, workspaces);
-
-                        if (ws == TAILQ_END(workspaces))
-                                ws = TAILQ_FIRST(workspaces);
-
-                        if (ws == c_ws)
-                                return;
-
-                        if (ws->output == NULL)
-                                continue;
-
-                        workspace_show(conn, ws->num + 1);
-                        return;
-                }
-        } else if (direction == 'p') {
-                while (1) {
-                        ws = TAILQ_PREV(ws, workspaces_head, workspaces);
-
-                        if (ws == TAILQ_END(workspaces))
-                                ws = TAILQ_LAST(workspaces, workspaces_head);
-
-                        if (ws == c_ws)
-                                return;
-
-                        if (ws->output == NULL)
-                                continue;
-
-                        workspace_show(conn, ws->num + 1);
-                        return;
-                }
-        }
-}
-
-static void parse_move_command(xcb_connection_t *conn, Client *last_focused, const char *command) {
-        if (!client_is_floating(last_focused)) {
-                LOG("You can only move floating clients with the \"move\" command\n");
-                return;
-        }
-
-        DLOG("Moving a floating client\n");
-        if (STARTS_WITH(command, "left")) {
-                command += strlen("left");
-                last_focused->rect.x -= atoi(command);
-        } else if (STARTS_WITH(command, "right")) {
-                command += strlen("right");
-                last_focused->rect.x += atoi(command);
-        } else if (STARTS_WITH(command, "top")) {
-                command += strlen("top");
-                last_focused->rect.y -= atoi(command);
-        } else if (STARTS_WITH(command, "bottom")) {
-                command += strlen("bottom");
-                last_focused->rect.y += atoi(command);
-        } else {
-                ELOG("Syntax: move <left|right|top|bottom> <pixels>\n");
-                return;
-        }
-        /* resize_client flushes */
-        resize_client(conn, last_focused);
-}
-
-static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, const char *command) {
-        int first, second;
-        resize_orientation_t orientation = O_VERTICAL;
-        Container *con = last_focused->container;
-        Workspace *ws = last_focused->workspace;
-
-        if (client_is_floating(last_focused)) {
-                DLOG("Resizing a floating client\n");
-                if (STARTS_WITH(command, "left")) {
-                        command += strlen("left");
-                        last_focused->rect.width -= atoi(command);
-                        last_focused->rect.x += atoi(command);
-                } else if (STARTS_WITH(command, "right")) {
-                        command += strlen("right");
-                        last_focused->rect.width += atoi(command);
-                } else if (STARTS_WITH(command, "top")) {
-                        command += strlen("top");
-                        last_focused->rect.height -= atoi(command);
-                        last_focused->rect.y += atoi(command);
-                } else if (STARTS_WITH(command, "bottom")) {
-                        command += strlen("bottom");
-                        last_focused->rect.height += atoi(command);
-                } else {
-                        ELOG("Syntax: resize <left|right|top|bottom> [+|-]<pixels>\n");
-                        return;
-                }
-
-                /* resize_client flushes */
-                resize_client(conn, last_focused);
-
-                return;
-        }
-
-        if (STARTS_WITH(command, "left")) {
-                if (con->col == 0)
-                        return;
-                first = con->col - 1;
-                second = con->col;
-                command += strlen("left");
-        } else if (STARTS_WITH(command, "right")) {
-                first = con->col + (con->colspan - 1);
-                DLOG("column %d\n", first);
-
-                if (!cell_exists(ws, first, con->row) ||
-                    (first == (ws->cols-1)))
-                        return;
-
-                second = first + 1;
-                command += strlen("right");
-        } else if (STARTS_WITH(command, "top")) {
-                if (con->row == 0)
-                        return;
-                first = con->row - 1;
-                second = con->row;
-                orientation = O_HORIZONTAL;
-                command += strlen("top");
-        } else if (STARTS_WITH(command, "bottom")) {
-                first = con->row + (con->rowspan - 1);
-                if (!cell_exists(ws, con->col, first) ||
-                    (first == (ws->rows-1)))
-                        return;
-
-                second = first + 1;
-                orientation = O_HORIZONTAL;
-                command += strlen("bottom");
-        } else {
-                ELOG("Syntax: resize <left|right|top|bottom> [+|-]<pixels>\n");
-                return;
-        }
-
-        int pixels = atoi(command);
-        if (pixels == 0)
-                return;
-
-        resize_container(conn, ws, first, second, orientation, pixels);
-}
-
-/*
- * Parses a command, see file CMDMODE for more information
- *
- */
-void parse_command(xcb_connection_t *conn, const char *command) {
-        LOG("--- parsing command \"%s\" ---\n", command);
-        /* Get the first client from focus stack because floating clients are not
-         * in any container, therefore CUR_CELL is not appropriate. */
-        Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
-        if (last_focused == SLIST_END(&(c_ws->focus_stack)))
-                last_focused = NULL;
-
-        /* Hmm, just to be sure */
-        if (command[0] == '\0')
-                return;
-
-        /* Is it an <exec>? Then execute the given command. */
-        if (STARTS_WITH(command, "exec ")) {
-                LOG("starting \"%s\"\n", command + strlen("exec "));
-                start_application(command+strlen("exec "));
-                return;
-        }
-
-        if (STARTS_WITH(command, "mark")) {
-                if (last_focused == NULL) {
-                        ELOG("There is no window to mark\n");
-                        return;
-                }
-                const char *rest = command + strlen("mark");
-                while (*rest == ' ')
-                        rest++;
-                if (*rest == '\0') {
-                        DLOG("interactive mark starting\n");
-                        start_application("i3-input -p 'mark ' -l 1 -P 'Mark: '");
-                } else {
-                        LOG("mark with \"%s\"\n", rest);
-                        client_mark(conn, last_focused, rest);
-                }
-                return;
-        }
-
-        if (STARTS_WITH(command, "goto")) {
-                const char *rest = command + strlen("goto");
-                while (*rest == ' ')
-                        rest++;
-                if (*rest == '\0') {
-                        DLOG("interactive go to mark starting\n");
-                        start_application("i3-input -p 'goto ' -l 1 -P 'Goto: '");
-                } else {
-                        LOG("go to \"%s\"\n", rest);
-                        jump_to_mark(conn, rest);
-                }
-                return;
-        }
-
-        if (STARTS_WITH(command, "stack-limit ")) {
-                if (last_focused == NULL || client_is_floating(last_focused)) {
-                        ELOG("No container focused\n");
-                        return;
-                }
-                const char *rest = command + strlen("stack-limit ");
-                if (strncmp(rest, "rows ", strlen("rows ")) == 0) {
-                        last_focused->container->stack_limit = STACK_LIMIT_ROWS;
-                        rest += strlen("rows ");
-                } else if (strncmp(rest, "cols ", strlen("cols ")) == 0) {
-                        last_focused->container->stack_limit = STACK_LIMIT_COLS;
-                        rest += strlen("cols ");
-                } else {
-                        ELOG("Syntax: stack-limit <cols|rows> <limit>\n");
-                        return;
-                }
-
-                last_focused->container->stack_limit_value = atoi(rest);
-                if (last_focused->container->stack_limit_value == 0)
-                        last_focused->container->stack_limit = STACK_LIMIT_NONE;
-
-                return;
-        }
-
-        if (STARTS_WITH(command, "resize ")) {
-                if (last_focused == NULL)
-                        return;
-                const char *rest = command + strlen("resize ");
-                parse_resize_command(conn, last_focused, rest);
-                return;
-        }
-
-        if (STARTS_WITH(command, "move ")) {
-                if (last_focused == NULL)
-                        return;
-                const char *rest = command + strlen("move ");
-                parse_move_command(conn, last_focused, rest);
-                return;
-        }
-
-        if (STARTS_WITH(command, "mode ")) {
-                const char *rest = command + strlen("mode ");
-                switch_mode(conn, rest);
-                return;
-        }
-
-        /* Is it an <exit>? */
-        if (STARTS_WITH(command, "exit")) {
-                LOG("User issued exit-command, exiting without error.\n");
-                restore_geometry(global_conn);
-                ipc_shutdown();
-                exit(EXIT_SUCCESS);
-        }
-
-        /* Is it a <reload>? */
-        if (STARTS_WITH(command, "reload")) {
-                load_configuration(conn, NULL, true);
-                render_layout(conn);
-                /* Send an IPC event just in case the ws names have changed */
-                ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}");
-                return;
-        }
-
-        /* Is it <restart>? Then restart in place. */
-        if (STARTS_WITH(command, "restart")) {
-                i3_restart();
-        }
-
-        if (STARTS_WITH(command, "kill")) {
-                if (last_focused == NULL) {
-                        ELOG("There is no window to kill\n");
-                        return;
-                }
-
-                LOG("Killing current window\n");
-                client_kill(conn, last_focused);
-                return;
-        }
-
-        if (STARTS_WITH(command, "bring ")) {
-                const char *arguments = command + strlen("bring ");
-                if (arguments[0] == '"')
-                        bring_window_here(conn, arguments);
-                return;
-        }
-
-        /* Is it a jump to a specified workspace, row, col? */
-        if (STARTS_WITH(command, "jump ")) {
-                const char *arguments = command + strlen("jump ");
-                if (arguments[0] == '"')
-                        jump_to_window(conn, arguments);
-                else jump_to_container(conn, arguments);
-                return;
-        }
-
-        /* Should we travel the focus stack? */
-        if (STARTS_WITH(command, "focus")) {
-                const char *arguments = command + strlen("focus ");
-                travel_focus_stack(conn, arguments);
-                return;
-        }
-
-        /* Is it 'f' for fullscreen, or 'fg' for fullscreen_global? */
-        if (command[0] == 'f') {
-                if (last_focused == NULL)
-                        return;
-                if (command[1] == 'g')
-                        client_toggle_fullscreen_global(conn, last_focused);
-                else
-                        client_toggle_fullscreen(conn, last_focused);
-                return;
-        }
-
-        /* Is it just 's' for stacking or 'd' for default? */
-        if ((command[0] == 's' || command[0] == 'd' || command[0] == 'T') && (command[1] == '\0')) {
-                if (last_focused != NULL && client_is_floating(last_focused)) {
-                        ELOG("not switching, this is a floating client\n");
-                        return;
-                }
-                LOG("Switching mode for current container\n");
-                int new_mode = MODE_DEFAULT;
-                if (command[0] == 's' && CUR_CELL->mode != MODE_STACK)
-                        new_mode = MODE_STACK;
-                if (command[0] == 'T' && CUR_CELL->mode != MODE_TABBED)
-                        new_mode = MODE_TABBED;
-                switch_layout_mode(conn, CUR_CELL, new_mode);
-                return;
-        }
-
-        /* Is it 'bn' (border normal), 'bp' (border 1pixel) or 'bb' (border borderless)? */
-        /* or even 'bt' (toggle border: 'bp' -> 'bb' -> 'bn' ) */
-        if (command[0] == 'b') {
-                if (last_focused == NULL) {
-                        ELOG("No window focused, cannot change border type\n");
-                        return;
-                }
-
-                char com = command[1];
-                if (command[1] == 't') {
-                        if (last_focused->titlebar_position == TITLEBAR_TOP &&
-                            !last_focused->borderless)
-                            com = 'p';
-                        else if (last_focused->titlebar_position == TITLEBAR_OFF &&
-                                 !last_focused->borderless)
-                            com = 'b';
-                        else com = 'n';
-                }
-
-                client_change_border(conn, last_focused, com);
-                return;
-        }
-
-        if (command[0] == 'H') {
-                LOG("Hiding all floating windows\n");
-                floating_toggle_hide(conn, c_ws);
-                return;
-        }
-
-        enum { WITH_WINDOW, WITH_CONTAINER, WITH_WORKSPACE, WITH_SCREEN } with = WITH_WINDOW;
-
-        /* Is it a <with>? */
-        if (command[0] == 'w') {
-                command++;
-                /* TODO: implement */
-                if (command[0] == 'c') {
-                        with = WITH_CONTAINER;
-                        command++;
-                } else if (command[0] == 'w') {
-                        with = WITH_WORKSPACE;
-                        command++;
-                } else if (command[0] == 's') {
-                        with = WITH_SCREEN;
-                        command++;
-                } else {
-                        ELOG("not yet implemented.\n");
-                        return;
-                }
-        }
-
-        /* Is it 't' for toggle tiling/floating? */
-        if (command[0] == 't') {
-                if (with == WITH_WORKSPACE) {
-                        c_ws->auto_float = !c_ws->auto_float;
-                        LOG("autofloat is now %d\n", c_ws->auto_float);
-                        return;
-                }
-                if (last_focused == NULL) {
-                        ELOG("Cannot toggle tiling/floating: workspace empty\n");
-                        return;
-                }
-
-                Workspace *ws = last_focused->workspace;
-
-                if (last_focused->fullscreen)
-                        client_leave_fullscreen(conn, last_focused);
-
-                toggle_floating_mode(conn, last_focused, false);
-                /* delete all empty columns/rows */
-                cleanup_table(conn, ws);
-
-                /* Fix colspan/rowspan if it’d overlap */
-                fix_colrowspan(conn, ws);
-
-                render_workspace(conn, ws->output, ws);
-
-                /* Re-focus the client because cleanup_table sets the focus to the last
-                 * focused client inside a container only. */
-                set_focus(conn, last_focused, true);
-
-                return;
-        }
-
-       /* Is it 'n' or 'p' for next/previous workspace? (nw) */
-        if ((command[0] == 'n' || command[0] == 'p') && command[1] == 'w') {
-               next_previous_workspace(conn, command[0]);
-               return;
-        }
-
-        /* It’s a normal <cmd> */
-        char *rest = NULL;
-        enum { ACTION_FOCUS, ACTION_MOVE, ACTION_SNAP } action = ACTION_FOCUS;
-        direction_t direction;
-        int times = strtol(command, &rest, 10);
-        if (rest == NULL) {
-                ELOG("Invalid command (\"%s\")\n", command);
-                return;
-        }
-
-        if (*rest == '\0') {
-                /* No rest? This was a workspace number, not a times specification */
-                workspace_show(conn, times);
-                return;
-        }
-
-        if (*rest == 'm' || *rest == 's') {
-                action = (*rest == 'm' ? ACTION_MOVE : ACTION_SNAP);
-                rest++;
-        }
-
-        int workspace = strtol(rest, &rest, 10);
-
-        if (rest == NULL) {
-                ELOG("Invalid command (\"%s\")\n", command);
-                return;
-        }
-
-        if (*rest == '\0') {
-                if (last_focused != NULL && client_is_floating(last_focused))
-                        move_floating_window_to_workspace(conn, last_focused, workspace);
-                else move_current_window_to_workspace(conn, workspace);
-                return;
-        }
-
-        if (last_focused == NULL) {
-                ELOG("Not performing (no window found)\n");
-                return;
-        }
-
-        if (client_is_floating(last_focused) &&
-            (action != ACTION_FOCUS && action != ACTION_MOVE)) {
-                ELOG("Not performing (floating)\n");
-                return;
-        }
-
-        /* Now perform action to <where> */
-        while (*rest != '\0') {
-                if (*rest == 'h')
-                        direction = D_LEFT;
-                else if (*rest == 'j')
-                        direction = D_DOWN;
-                else if (*rest == 'k')
-                        direction = D_UP;
-                else if (*rest == 'l')
-                        direction = D_RIGHT;
-                else {
-                        ELOG("unknown direction: %c\n", *rest);
-                        return;
-                }
-                rest++;
-
-                if (action == ACTION_FOCUS) {
-                        if (with == WITH_SCREEN) {
-                                focus_thing(conn, direction, THING_SCREEN);
-                                continue;
-                        }
-                        if (client_is_floating(last_focused)) {
-                                floating_focus_direction(conn, last_focused, direction);
-                                continue;
-                        }
-                        focus_thing(conn, direction, (with == WITH_WINDOW ? THING_WINDOW : THING_CONTAINER));
-                        continue;
-                }
-
-                if (action == ACTION_MOVE) {
-                        if (with == WITH_SCREEN) {
-                                /* TODO: this should swap the screen’s contents
-                                 * (e.g. all workspaces) with the next/previous/…
-                                 * screen */
-                                ELOG("Not yet implemented\n");
-                                continue;
-                        }
-                        if (client_is_floating(last_focused)) {
-                                floating_move(conn, last_focused, direction);
-                                continue;
-                        }
-                        if (with == WITH_WINDOW)
-                                move_current_window(conn, direction);
-                        else move_current_container(conn, direction);
-                        continue;
-                }
-
-                if (action == ACTION_SNAP) {
-                        if (with == WITH_SCREEN) {
-                                ELOG("You cannot snap a screen (it makes no sense).\n");
-                                continue;
-                        }
-                        snap_current_container(conn, direction);
-                        continue;
-                }
-        }
-}
diff --git a/src/con.c b/src/con.c
new file mode 100644 (file)
index 0000000..1cf1779
--- /dev/null
+++ b/src/con.c
@@ -0,0 +1,970 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * con.c contains all functions which deal with containers directly (creating
+ * containers, searching containers, getting specific properties from
+ * containers, …).
+ *
+ */
+#include "all.h"
+
+char *colors[] = {
+    "#ff0000",
+    "#00FF00",
+    "#0000FF",
+    "#ff00ff",
+    "#00ffff",
+    "#ffff00",
+    "#aa0000",
+    "#00aa00",
+    "#0000aa",
+    "#aa00aa"
+};
+
+static void con_on_remove_child(Con *con);
+
+/*
+ * Create a new container (and attach it to the given parent, if not NULL).
+ * This function initializes the data structures and creates the appropriate
+ * X11 IDs using x_con_init().
+ *
+ */
+Con *con_new(Con *parent, i3Window *window) {
+    Con *new = scalloc(sizeof(Con));
+    new->on_remove_child = con_on_remove_child;
+    TAILQ_INSERT_TAIL(&all_cons, new, all_cons);
+    new->type = CT_CON;
+    new->window = window;
+    new->border_style = config.default_border;
+    static int cnt = 0;
+    DLOG("opening window %d\n", cnt);
+
+    /* TODO: remove window coloring after test-phase */
+    DLOG("color %s\n", colors[cnt]);
+    new->name = strdup(colors[cnt]);
+    //uint32_t cp = get_colorpixel(colors[cnt]);
+    cnt++;
+    if ((cnt % (sizeof(colors) / sizeof(char*))) == 0)
+        cnt = 0;
+
+    x_con_init(new);
+
+    TAILQ_INIT(&(new->floating_head));
+    TAILQ_INIT(&(new->nodes_head));
+    TAILQ_INIT(&(new->focus_head));
+    TAILQ_INIT(&(new->swallow_head));
+
+    if (parent != NULL)
+        con_attach(new, parent, false);
+
+    return new;
+}
+
+/*
+ * Attaches the given container to the given parent. This happens when moving
+ * a container or when inserting a new container at a specific place in the
+ * tree.
+ *
+ * ignore_focus is to just insert the Con at the end (useful when creating a
+ * new split container *around* some containers, that is, detaching and
+ * attaching them in order without wanting to mess with the focus in between).
+ *
+ */
+void con_attach(Con *con, Con *parent, bool ignore_focus) {
+    con->parent = parent;
+    Con *loop;
+    Con *current = NULL;
+    struct nodes_head *nodes_head = &(parent->nodes_head);
+    struct focus_head *focus_head = &(parent->focus_head);
+
+    /* Workspaces are handled differently: they need to be inserted at the
+     * right position. */
+    if (con->type == CT_WORKSPACE) {
+        DLOG("it's a workspace. num = %d\n", con->num);
+        if (con->num == -1 || TAILQ_EMPTY(nodes_head)) {
+            TAILQ_INSERT_TAIL(nodes_head, con, nodes);
+        } else {
+            current = TAILQ_FIRST(nodes_head);
+            if (con->num < current->num) {
+                /* we need to insert the container at the beginning */
+                TAILQ_INSERT_HEAD(nodes_head, con, nodes);
+            } else {
+                while (current->num != -1 && con->num > current->num) {
+                    current = TAILQ_NEXT(current, nodes);
+                    if (current == TAILQ_END(nodes_head)) {
+                        current = NULL;
+                        break;
+                    }
+                }
+                /* we need to insert con after current, if current is not NULL */
+                if (current)
+                    TAILQ_INSERT_BEFORE(current, con, nodes);
+                else TAILQ_INSERT_TAIL(nodes_head, con, nodes);
+            }
+        }
+        goto add_to_focus_head;
+    }
+
+    if (con->type == CT_FLOATING_CON) {
+        DLOG("Inserting into floating containers\n");
+        TAILQ_INSERT_TAIL(&(parent->floating_head), con, floating_windows);
+    } else {
+        if (!ignore_focus) {
+            /* Get the first tiling container in focus stack */
+            TAILQ_FOREACH(loop, &(parent->focus_head), focused) {
+                if (loop->type == CT_FLOATING_CON)
+                    continue;
+                current = loop;
+                break;
+            }
+        }
+
+        /* When the container is not a split container (but contains a window)
+         * and is attached to a workspace, we check if the user configured a
+         * workspace_layout. This is done in workspace_attach_to, which will
+         * provide us with the container to which we should attach (either the
+         * workspace or a new split container with the configured
+         * workspace_layout).
+         */
+        if (con->window != NULL && parent->type == CT_WORKSPACE) {
+            DLOG("Parent is a workspace. Applying default layout...\n");
+            Con *target = workspace_attach_to(parent);
+
+            /* Attach the original con to this new split con instead */
+            nodes_head = &(target->nodes_head);
+            focus_head = &(target->focus_head);
+            con->parent = target;
+            current = NULL;
+
+            DLOG("done\n");
+        }
+
+        /* Insert the container after the tiling container, if found.
+         * When adding to a CT_OUTPUT, just append one after another. */
+        if (current && parent->type != CT_OUTPUT) {
+            DLOG("Inserting con = %p after last focused tiling con %p\n",
+                 con, current);
+            TAILQ_INSERT_AFTER(nodes_head, current, con, nodes);
+        } else TAILQ_INSERT_TAIL(nodes_head, con, nodes);
+    }
+
+add_to_focus_head:
+    /* We insert to the TAIL because con_focus() will correct this.
+     * This way, we have the option to insert Cons without having
+     * to focus them. */
+    TAILQ_INSERT_TAIL(focus_head, con, focused);
+}
+
+/*
+ * Detaches the given container from its current parent
+ *
+ */
+void con_detach(Con *con) {
+    if (con->type == CT_FLOATING_CON) {
+        TAILQ_REMOVE(&(con->parent->floating_head), con, floating_windows);
+        TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
+    } else {
+        TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes);
+        TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
+    }
+}
+
+/*
+ * Sets input focus to the given container. Will be updated in X11 in the next
+ * run of x_push_changes().
+ *
+ */
+void con_focus(Con *con) {
+    assert(con != NULL);
+    DLOG("con_focus = %p\n", con);
+
+    /* 1: set focused-pointer to the new con */
+    /* 2: exchange the position of the container in focus stack of the parent all the way up */
+    TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
+    TAILQ_INSERT_HEAD(&(con->parent->focus_head), con, focused);
+    if (con->parent->parent != NULL)
+        con_focus(con->parent);
+
+    focused = con;
+    if (con->urgent) {
+        con->urgent = false;
+        workspace_update_urgent_flag(con_get_workspace(con));
+    }
+    DLOG("con_focus done = %p\n", con);
+}
+
+/*
+ * Returns true when this node is a leaf node (has no children)
+ *
+ */
+bool con_is_leaf(Con *con) {
+    return TAILQ_EMPTY(&(con->nodes_head));
+}
+
+/*
+ * Returns true if this node accepts a window (if the node swallows windows,
+ * it might already have swallowed enough and cannot hold any more).
+ *
+ */
+bool con_accepts_window(Con *con) {
+    /* 1: workspaces never accept direct windows */
+    if (con->type == CT_WORKSPACE)
+        return false;
+
+    if (con->orientation != NO_ORIENTATION) {
+        DLOG("container %p does not accepts windows, orientation != NO_ORIENTATION\n", con);
+        return false;
+    }
+
+    /* TODO: if this is a swallowing container, we need to check its max_clients */
+    return (con->window == NULL);
+}
+
+/*
+ * Gets the output container (first container with CT_OUTPUT in hierarchy) this
+ * node is on.
+ *
+ */
+Con *con_get_output(Con *con) {
+    Con *result = con;
+    while (result != NULL && result->type != CT_OUTPUT)
+        result = result->parent;
+    /* We must be able to get an output because focus can never be set higher
+     * in the tree (root node cannot be focused). */
+    assert(result != NULL);
+    return result;
+}
+
+/*
+ * Gets the workspace container this node is on.
+ *
+ */
+Con *con_get_workspace(Con *con) {
+    Con *result = con;
+    while (result != NULL && result->type != CT_WORKSPACE)
+        result = result->parent;
+    return result;
+}
+
+/*
+ * Searches parenst of the given 'con' until it reaches one with the specified
+ * 'orientation'. Aborts when it comes across a floating_con.
+ *
+ */
+Con *con_parent_with_orientation(Con *con, orientation_t orientation) {
+    DLOG("Searching for parent of Con %p with orientation %d\n", con, orientation);
+    Con *parent = con->parent;
+    if (parent->type == CT_FLOATING_CON)
+        return NULL;
+    while (con_orientation(parent) != orientation) {
+        DLOG("Need to go one level further up\n");
+        parent = parent->parent;
+        /* Abort when we reach a floating con */
+        if (parent && parent->type == CT_FLOATING_CON)
+            parent = NULL;
+        if (parent == NULL)
+            break;
+    }
+    DLOG("Result: %p\n", parent);
+    return parent;
+}
+
+/*
+ * helper data structure for the breadth-first-search in
+ * con_get_fullscreen_con()
+ *
+ */
+struct bfs_entry {
+    Con *con;
+
+    TAILQ_ENTRY(bfs_entry) entries;
+};
+
+/*
+ * Returns the first fullscreen node below this node.
+ *
+ */
+Con *con_get_fullscreen_con(Con *con, int fullscreen_mode) {
+    Con *current, *child;
+
+    /* TODO: is breadth-first-search really appropriate? (check as soon as
+     * fullscreen levels and fullscreen for containers is implemented) */
+    TAILQ_HEAD(bfs_head, bfs_entry) bfs_head = TAILQ_HEAD_INITIALIZER(bfs_head);
+    struct bfs_entry *entry = smalloc(sizeof(struct bfs_entry));
+    entry->con = con;
+    TAILQ_INSERT_TAIL(&bfs_head, entry, entries);
+
+    while (!TAILQ_EMPTY(&bfs_head)) {
+        entry = TAILQ_FIRST(&bfs_head);
+        current = entry->con;
+        if (current != con && current->fullscreen_mode == fullscreen_mode) {
+            /* empty the queue */
+            while (!TAILQ_EMPTY(&bfs_head)) {
+                entry = TAILQ_FIRST(&bfs_head);
+                TAILQ_REMOVE(&bfs_head, entry, entries);
+                free(entry);
+            }
+            return current;
+        }
+
+        TAILQ_REMOVE(&bfs_head, entry, entries);
+        free(entry);
+
+        TAILQ_FOREACH(child, &(current->nodes_head), nodes) {
+            entry = smalloc(sizeof(struct bfs_entry));
+            entry->con = child;
+            TAILQ_INSERT_TAIL(&bfs_head, entry, entries);
+        }
+
+        TAILQ_FOREACH(child, &(current->floating_head), floating_windows) {
+            entry = smalloc(sizeof(struct bfs_entry));
+            entry->con = child;
+            TAILQ_INSERT_TAIL(&bfs_head, entry, entries);
+        }
+    }
+
+    return NULL;
+}
+
+/*
+ * Returns true if the node is floating.
+ *
+ */
+bool con_is_floating(Con *con) {
+    assert(con != NULL);
+    DLOG("checking if con %p is floating\n", con);
+    return (con->floating >= FLOATING_AUTO_ON);
+}
+
+/*
+ * Checks if the given container is either floating or inside some floating
+ * container. It returns the FLOATING_CON container.
+ *
+ */
+Con *con_inside_floating(Con *con) {
+    assert(con != NULL);
+    if (con->type == CT_FLOATING_CON)
+        return con;
+
+    if (con->floating >= FLOATING_AUTO_ON)
+        return con->parent;
+
+    if (con->type == CT_WORKSPACE || con->type == CT_OUTPUT)
+        return NULL;
+
+    return con_inside_floating(con->parent);
+}
+
+/*
+ * Returns the container with the given client window ID or NULL if no such
+ * container exists.
+ *
+ */
+Con *con_by_window_id(xcb_window_t window) {
+    Con *con;
+    TAILQ_FOREACH(con, &all_cons, all_cons)
+        if (con->window != NULL && con->window->id == window)
+            return con;
+    return NULL;
+}
+
+/*
+ * Returns the container with the given frame ID or NULL if no such container
+ * exists.
+ *
+ */
+Con *con_by_frame_id(xcb_window_t frame) {
+    Con *con;
+    TAILQ_FOREACH(con, &all_cons, all_cons)
+        if (con->frame == frame)
+            return con;
+    return NULL;
+}
+
+/*
+ * Returns the first container below 'con' which wants to swallow this window
+ * TODO: priority
+ *
+ */
+Con *con_for_window(Con *con, i3Window *window, Match **store_match) {
+    Con *child;
+    Match *match;
+    DLOG("searching con for window %p starting at con %p\n", window, con);
+    DLOG("class == %s\n", window->class_class);
+
+    TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+        TAILQ_FOREACH(match, &(child->swallow_head), matches) {
+            if (!match_matches_window(match, window))
+                continue;
+            if (store_match != NULL)
+                *store_match = match;
+            return child;
+        }
+        Con *result = con_for_window(child, window, store_match);
+        if (result != NULL)
+            return result;
+    }
+
+    TAILQ_FOREACH(child, &(con->floating_head), floating_windows) {
+        TAILQ_FOREACH(match, &(child->swallow_head), matches) {
+            if (!match_matches_window(match, window))
+                continue;
+            if (store_match != NULL)
+                *store_match = match;
+            return child;
+        }
+        Con *result = con_for_window(child, window, store_match);
+        if (result != NULL)
+            return result;
+    }
+
+    return NULL;
+}
+
+/*
+ * Returns the number of children of this container.
+ *
+ */
+int con_num_children(Con *con) {
+    Con *child;
+    int children = 0;
+
+    TAILQ_FOREACH(child, &(con->nodes_head), nodes)
+        children++;
+
+    return children;
+}
+
+/*
+ * Updates the percent attribute of the children of the given container. This
+ * function needs to be called when a window is added or removed from a
+ * container.
+ *
+ */
+void con_fix_percent(Con *con) {
+    Con *child;
+    int children = con_num_children(con);
+
+    // calculate how much we have distributed and how many containers
+    // with a percentage set we have
+    double total = 0.0;
+    int children_with_percent = 0;
+    TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+        if (child->percent > 0.0) {
+            total += child->percent;
+            ++children_with_percent;
+        }
+    }
+
+    // if there were children without a percentage set, set to a value that
+    // will make those children proportional to all others
+    if (children_with_percent != children) {
+        TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+            if (child->percent <= 0.0) {
+                if (children_with_percent == 0)
+                    total += (child->percent = 1.0);
+                else total += (child->percent = total / children_with_percent);
+            }
+        }
+    }
+
+    // if we got a zero, just distribute the space equally, otherwise
+    // distribute according to the proportions we got
+    if (total == 0.0) {
+        TAILQ_FOREACH(child, &(con->nodes_head), nodes)
+            child->percent = 1.0 / children;
+    } else if (total != 1.0) {
+        TAILQ_FOREACH(child, &(con->nodes_head), nodes)
+            child->percent /= total;
+    }
+}
+
+/*
+ * Toggles fullscreen mode for the given container. Fullscreen mode will not be
+ * entered when there already is a fullscreen container on this workspace.
+ *
+ */
+void con_toggle_fullscreen(Con *con, int fullscreen_mode) {
+    Con *workspace, *fullscreen;
+
+    if (con->type == CT_WORKSPACE) {
+        DLOG("You cannot make a workspace fullscreen.\n");
+        return;
+    }
+
+    DLOG("toggling fullscreen for %p / %s\n", con, con->name);
+    if (con->fullscreen_mode == CF_NONE) {
+        /* 1: check if there already is a fullscreen con */
+        if (fullscreen_mode == CF_GLOBAL)
+            fullscreen = con_get_fullscreen_con(croot, CF_GLOBAL);
+        else {
+            workspace = con_get_workspace(con);
+            fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
+        }
+        if (fullscreen != NULL) {
+            LOG("Not entering fullscreen mode, container (%p/%s) "
+                "already is in fullscreen mode\n",
+                fullscreen, fullscreen->name);
+            goto update_netwm_state;
+        }
+
+        /* 2: enable fullscreen */
+        con->fullscreen_mode = fullscreen_mode;
+    } else {
+        /* 1: disable fullscreen */
+        con->fullscreen_mode = CF_NONE;
+    }
+
+update_netwm_state:
+    DLOG("mode now: %d\n", con->fullscreen_mode);
+
+    /* update _NET_WM_STATE if this container has a window */
+    /* TODO: when a window is assigned to a container which is already
+     * fullscreened, this state needs to be pushed to the client, too */
+    if (con->window == NULL)
+        return;
+
+    uint32_t values[1];
+    unsigned int num = 0;
+
+    if (con->fullscreen_mode != CF_NONE)
+        values[num++] = A__NET_WM_STATE_FULLSCREEN;
+
+    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id,
+                        A__NET_WM_STATE, A_ATOM, 32, num, values);
+}
+
+/*
+ * Moves the given container to the currently focused container on the given
+ * workspace.
+ * TODO: is there a better place for this function?
+ *
+ */
+void con_move_to_workspace(Con *con, Con *workspace) {
+    if (con->type == CT_WORKSPACE) {
+        DLOG("Moving workspaces is not yet implemented.\n");
+        return;
+    }
+
+    if (con_is_floating(con)) {
+        DLOG("Using FLOATINGCON instead\n");
+        con = con->parent;
+    }
+
+    Con *source_output = con_get_output(con),
+        *dest_output = con_get_output(workspace);
+
+    /* 1: save the container which is going to be focused after the current
+     * container is moved away */
+    Con *focus_next = con_next_focused(con);
+
+    /* 2: get the focused container of this workspace */
+    Con *next = con_descend_focused(workspace);
+
+    /* 3: we go up one level, but only when next is a normal container */
+    if (next->type != CT_WORKSPACE) {
+        DLOG("next originally = %p / %s / type %d\n", next, next->name, next->type);
+        next = next->parent;
+    }
+
+    /* 4: if the target container is floating, we get the workspace instead.
+     * Only tiling windows need to get inserted next to the current container.
+     * */
+    Con *floatingcon = con_inside_floating(next);
+    if (floatingcon != NULL) {
+        DLOG("floatingcon, going up even further\n");
+        next = floatingcon->parent;
+    }
+
+    if (con->type == CT_FLOATING_CON) {
+        Con *ws = con_get_workspace(next);
+        DLOG("This is a floating window, using workspace %p / %s\n", ws, ws->name);
+        next = ws;
+    }
+
+    DLOG("Re-attaching container to %p / %s\n", next, next->name);
+    /* 5: re-attach the con to the parent of this focused container */
+    Con *parent = con->parent;
+    con_detach(con);
+    con_attach(con, next, false);
+
+    /* 6: fix the percentages */
+    con_fix_percent(parent);
+    con->percent = 0.0;
+    con_fix_percent(next);
+
+    /* 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) */
+    con_focus(con);
+
+    /* 8: when moving to a visible workspace on a different output, we keep the
+     * con focused. Otherwise, we leave the focus on the current workspace as we
+     * don’t want to focus invisible workspaces */
+    if (source_output != dest_output &&
+        workspace_is_visible(workspace)) {
+        DLOG("Moved to a different output, focusing target\n");
+    } else {
+        con_focus(focus_next);
+    }
+
+    CALL(parent, on_remove_child);
+}
+
+/*
+ * Returns the orientation of the given container (for stacked containers,
+ * vertical orientation is used regardless of the actual orientation of the
+ * container).
+ *
+ */
+int con_orientation(Con *con) {
+    /* stacking containers behave like they are in vertical orientation */
+    if (con->layout == L_STACKED)
+        return VERT;
+
+    if (con->layout == L_TABBED)
+        return HORIZ;
+
+    return con->orientation;
+}
+
+/*
+ * Returns the container which will be focused next when the given container
+ * is not available anymore. Called in tree_close and con_move_to_workspace
+ * to properly restore focus.
+ *
+ */
+Con *con_next_focused(Con *con) {
+    Con *next;
+    /* floating containers are attached to a workspace, so we focus either the
+     * next floating container (if any) or the workspace itself. */
+    if (con->type == CT_FLOATING_CON) {
+        DLOG("selecting next for CT_FLOATING_CON\n");
+        next = TAILQ_NEXT(con, floating_windows);
+        DLOG("next = %p\n", next);
+        if (!next) {
+            next = TAILQ_PREV(con, floating_head, floating_windows);
+            DLOG("using prev, next = %p\n", next);
+        }
+        if (!next) {
+            Con *ws = con_get_workspace(con);
+            next = ws;
+            DLOG("no more floating containers for next = %p, restoring workspace focus\n", next);
+            while (next != TAILQ_END(&(ws->focus_head)) && !TAILQ_EMPTY(&(next->focus_head))) {
+                next = TAILQ_FIRST(&(next->focus_head));
+                if (next == con) {
+                    DLOG("skipping container itself, we want the next client\n");
+                    next = TAILQ_NEXT(next, focused);
+                }
+            }
+            if (next == TAILQ_END(&(ws->focus_head))) {
+                DLOG("Focus list empty, returning ws\n");
+                next = ws;
+            }
+        } else {
+            /* Instead of returning the next CT_FLOATING_CON, we descend it to
+             * get an actual window to focus. */
+            next = con_descend_focused(next);
+        }
+        return next;
+    }
+
+    /* dock clients cannot be focused, so we focus the workspace instead */
+    if (con->parent->type == CT_DOCKAREA) {
+        DLOG("selecting workspace for dock client\n");
+        return con_descend_focused(output_get_content(con->parent->parent));
+    }
+
+    /* if 'con' is not the first entry in the focus stack, use the first one as
+     * it’s currently focused already */
+    Con *first = TAILQ_FIRST(&(con->parent->focus_head));
+    if (first != con) {
+        DLOG("Using first entry %p\n", first);
+        next = first;
+    } else {
+        /* try to focus the next container on the same level as this one or fall
+         * back to its parent */
+        if (!(next = TAILQ_NEXT(con, focused)))
+            next = con->parent;
+    }
+
+    /* now go down the focus stack as far as
+     * possible, excluding the current container */
+    while (!TAILQ_EMPTY(&(next->focus_head)) &&
+           TAILQ_FIRST(&(next->focus_head)) != con)
+        next = TAILQ_FIRST(&(next->focus_head));
+
+    return next;
+}
+
+/*
+ * Get the next/previous container in the specified orientation. This may
+ * travel up until it finds a container with suitable orientation.
+ *
+ */
+Con *con_get_next(Con *con, char way, orientation_t orientation) {
+    DLOG("con_get_next(way=%c, orientation=%d)\n", way, orientation);
+    /* 1: get the first parent with the same orientation */
+    Con *cur = con;
+    while (con_orientation(cur->parent) != orientation) {
+        DLOG("need to go one level further up\n");
+        if (cur->parent->type == CT_WORKSPACE) {
+            LOG("that's a workspace, we can't go further up\n");
+            return NULL;
+        }
+        cur = cur->parent;
+    }
+
+    /* 2: chose next (or previous) */
+    Con *next;
+    if (way == 'n') {
+        next = TAILQ_NEXT(cur, nodes);
+        /* if we are at the end of the list, we need to wrap */
+        if (next == TAILQ_END(&(parent->nodes_head)))
+            return NULL;
+    } else {
+        next = TAILQ_PREV(cur, nodes_head, nodes);
+        /* if we are at the end of the list, we need to wrap */
+        if (next == TAILQ_END(&(cur->nodes_head)))
+            return NULL;
+    }
+    DLOG("next = %p\n", next);
+
+    return next;
+}
+
+/*
+ * Returns the focused con inside this client, descending the tree as far as
+ * possible. This comes in handy when attaching a con to a workspace at the
+ * currently focused position, for example.
+ *
+ */
+Con *con_descend_focused(Con *con) {
+    Con *next = con;
+    while (!TAILQ_EMPTY(&(next->focus_head)))
+        next = TAILQ_FIRST(&(next->focus_head));
+    return next;
+}
+
+/*
+ * Returns the focused con inside this client, descending the tree as far as
+ * possible. This comes in handy when attaching a con to a workspace at the
+ * currently focused position, for example.
+ *
+ * Works like con_descend_focused but considers only tiling cons.
+ *
+ */
+Con *con_descend_tiling_focused(Con *con) {
+    Con *next = con;
+    Con *before;
+    Con *child;
+    do {
+        before = next;
+        TAILQ_FOREACH(child, &(next->focus_head), focused) {
+            if (child->type == CT_FLOATING_CON)
+                continue;
+
+            next = child;
+            break;
+        }
+    } while (before != next);
+    return next;
+}
+
+
+/*
+ * Returns a "relative" Rect which contains the amount of pixels that need to
+ * be added to the original Rect to get the final position (obviously the
+ * amount of pixels for normal, 1pixel and borderless are different).
+ *
+ */
+Rect con_border_style_rect(Con *con) {
+    switch (con_border_style(con)) {
+    case BS_NORMAL:
+        return (Rect){2, 0, -(2 * 2), -2};
+
+    case BS_1PIXEL:
+        return (Rect){1, 1, -2, -2};
+
+    case BS_NONE:
+        return (Rect){0, 0, 0, 0};
+
+    default:
+        assert(false);
+    }
+}
+
+/*
+ * Use this function to get a container’s border style. This is important
+ * because when inside a stack, the border style is always BS_NORMAL.
+ * For tabbed mode, the same applies, with one exception: when the container is
+ * borderless and the only element in the tabbed container, the border is not
+ * rendered.
+ *
+ * For children of a CT_DOCKAREA, the border style is always none.
+ *
+ */
+int con_border_style(Con *con) {
+    Con *fs = con_get_fullscreen_con(con->parent, CF_OUTPUT);
+    if (fs == con) {
+        DLOG("this one is fullscreen! overriding BS_NONE\n");
+        return BS_NONE;
+    }
+
+    if (con->parent->layout == L_STACKED)
+        return (con_num_children(con->parent) == 1 ? con->border_style : BS_NORMAL);
+
+    if (con->parent->layout == L_TABBED && con->border_style != BS_NORMAL)
+        return (con_num_children(con->parent) == 1 ? con->border_style : BS_NORMAL);
+
+    if (con->parent->type == CT_DOCKAREA)
+        return BS_NONE;
+
+    return con->border_style;
+}
+
+/*
+ * This function changes the layout of a given container. Use it to handle
+ * special cases like changing a whole workspace to stacked/tabbed (creates a
+ * new split container before).
+ *
+ */
+void con_set_layout(Con *con, int layout) {
+    /* When the container type is CT_WORKSPACE, the user wants to change the
+     * whole workspace into stacked/tabbed mode. To do this and still allow
+     * intuitive operations (like level-up and then opening a new window), we
+     * need to create a new split container. */
+    if (con->type == CT_WORKSPACE) {
+        DLOG("Creating new split container\n");
+        /* 1: create a new split container */
+        Con *new = con_new(NULL, NULL);
+        new->parent = con;
+
+        /* 2: set the requested layout on the split con */
+        new->layout = layout;
+
+        /* 3: While the layout is irrelevant in stacked/tabbed mode, it needs
+         * to be set. Otherwise, this con will not be interpreted as a split
+         * container. */
+        if (config.default_orientation == NO_ORIENTATION) {
+            new->orientation = (con->rect.height > con->rect.width) ? VERT : HORIZ;
+        } else {
+            new->orientation = config.default_orientation;
+        }
+
+        Con *old_focused = TAILQ_FIRST(&(con->focus_head));
+        if (old_focused == TAILQ_END(&(con->focus_head)))
+            old_focused = NULL;
+
+        /* 4: move the existing cons of this workspace below the new con */
+        DLOG("Moving cons\n");
+        Con *child;
+        while (!TAILQ_EMPTY(&(con->nodes_head))) {
+            child = TAILQ_FIRST(&(con->nodes_head));
+            con_detach(child);
+            con_attach(child, new, true);
+        }
+
+        /* 4: attach the new split container to the workspace */
+        DLOG("Attaching new split to ws\n");
+        con_attach(new, con, false);
+
+        if (old_focused)
+            con_focus(old_focused);
+
+        tree_flatten(croot);
+
+        return;
+    }
+
+    con->layout = layout;
+}
+
+/*
+ * Callback which will be called when removing a child from the given con.
+ * Kills the container if it is empty and replaces it with the child if there
+ * is exactly one child.
+ *
+ */
+static void con_on_remove_child(Con *con) {
+    DLOG("on_remove_child\n");
+
+    /* Every container 'above' (in the hierarchy) the workspace content should
+     * not be closed when the last child was removed */
+    if (con->type == CT_WORKSPACE ||
+        con->type == CT_OUTPUT ||
+        con->type == CT_ROOT ||
+        con->type == CT_DOCKAREA) {
+        DLOG("not handling, type = %d\n", con->type);
+        return;
+    }
+
+    /* TODO: check if this container would swallow any other client and
+     * don’t close it automatically. */
+    int children = con_num_children(con);
+    if (children == 0) {
+        DLOG("Container empty, closing\n");
+        tree_close(con, DONT_KILL_WINDOW, false);
+        return;
+    }
+}
+
+/*
+ * Determines the minimum size of the given con by looking at its children (for
+ * split/stacked/tabbed cons). Will be called when resizing floating cons
+ *
+ */
+Rect con_minimum_size(Con *con) {
+    DLOG("Determining minimum size for con %p\n", con);
+
+    if (con_is_leaf(con)) {
+        DLOG("leaf node, returning 75x50\n");
+        return (Rect){ 0, 0, 75, 50 };
+    }
+
+    if (con->type == CT_FLOATING_CON) {
+        DLOG("floating con\n");
+        Con *child = TAILQ_FIRST(&(con->nodes_head));
+        return con_minimum_size(child);
+    }
+
+    if (con->layout == L_STACKED || con->layout == L_TABBED) {
+        uint32_t max_width = 0, max_height = 0, deco_height = 0;
+        Con *child;
+        TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+            Rect min = con_minimum_size(child);
+            deco_height += child->deco_rect.height;
+            max_width = max(max_width, min.width);
+            max_height = max(max_height, min.height);
+        }
+        DLOG("stacked/tabbed now, returning %d x %d + deco_rect = %d\n",
+             max_width, max_height, deco_height);
+        return (Rect){ 0, 0, max_width, max_height + deco_height };
+    }
+
+    /* For horizontal/vertical split containers we sum up the width (h-split)
+     * or height (v-split) and use the maximum of the height (h-split) or width
+     * (v-split) as minimum size. */
+    if (con->orientation == HORIZ || con->orientation == VERT) {
+        uint32_t width = 0, height = 0;
+        Con *child;
+        TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+            Rect min = con_minimum_size(child);
+            if (con->orientation == HORIZ) {
+                width += min.width;
+                height = max(height, min.height);
+            } else {
+                height += min.height;
+                width = max(width, min.width);
+            }
+        }
+        DLOG("split container, returning width = %d x height = %d\n", width, height);
+        return (Rect){ 0, 0, width, height };
+    }
+
+    ELOG("Unhandled case, type = %d, layout = %d, orientation = %d\n",
+         con->type, con->layout, con->orientation);
+    assert(false);
+}
index b1456e04cbb66501c90a1d4e06b7df01d1993083..9088e4c64b789a5fa4cab30c697fd80e945f4b23 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
  *
  * mode).
  *
  */
-#include <stdio.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <stdlib.h>
-#include <glob.h>
-#include <unistd.h>
 
 /* We need Xlib for XStringToKeysym */
 #include <X11/Xlib.h>
 
-#include <xcb/xcb_keysyms.h>
-
-#include "i3.h"
-#include "util.h"
-#include "config.h"
-#include "xcb.h"
-#include "table.h"
-#include "workspace.h"
-#include "log.h"
+#include "all.h"
 
+char *current_configpath = NULL;
 Config config;
 struct modes_head modes;
 
-/*
- * This function resolves ~ in pathnames.
- * It may resolve wildcards in the first part of the path, but if no match
- * or multiple matches are found, it just returns a copy of path as given.
- *
- */
-char *resolve_tilde(const char *path) {
-        static glob_t globbuf;
-        char *head, *tail, *result;
-
-        tail = strchr(path, '/');
-        head = strndup(path, tail ? tail - path : strlen(path));
-
-        int res = glob(head, GLOB_TILDE, NULL, &globbuf);
-        free(head);
-        /* no match, or many wildcard matches are bad */
-        if (res == GLOB_NOMATCH || globbuf.gl_pathc != 1)
-                result = sstrdup(path);
-        else if (res != 0) {
-                die("glob() failed");
-        } else {
-                head = globbuf.gl_pathv[0];
-                result = scalloc(strlen(head) + (tail ? strlen(tail) : 0) + 1);
-                strncpy(result, head, strlen(head));
-                strncat(result, tail, strlen(tail));
-        }
-        globfree(&globbuf);
-
-        return result;
-}
-
-/*
- * Checks if the given path exists by calling stat().
- *
- */
-bool path_exists(const char *path) {
-        struct stat buf;
-        return (stat(path, &buf) == 0);
-}
-
 /**
  * Ungrabs all keys, to be called before re-grabbing the keys because of a
  * mapping_notify event or a configuration file reload
@@ -152,14 +98,6 @@ void translate_keysyms() {
                         continue;
                 }
 
-#ifdef OLD_XCB_KEYSYMS_API
-                bind->number_keycodes = 1;
-                xcb_keycode_t code = xcb_key_symbols_get_keycode(keysyms, keysym);
-                DLOG("Translated symbol \"%s\" to 1 keycode (%d)\n", bind->symbol, code);
-                grab_keycode_for_binding(global_conn, bind, code);
-                bind->translated_to = smalloc(sizeof(xcb_keycode_t));
-                memcpy(bind->translated_to, &code, sizeof(xcb_keycode_t));
-#else
                 uint32_t last_keycode = 0;
                 xcb_keycode_t *keycodes = xcb_key_symbols_get_keycode(keysyms, keysym);
                 if (keycodes == NULL) {
@@ -181,7 +119,6 @@ void translate_keysyms() {
                 bind->translated_to = smalloc(bind->number_keycodes * sizeof(xcb_keycode_t));
                 memcpy(bind->translated_to, keycodes, bind->number_keycodes * sizeof(xcb_keycode_t));
                 free(keycodes);
-#endif
         }
 }
 
@@ -212,7 +149,7 @@ void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) {
  * Switches the key bindings to the given mode, if the mode exists
  *
  */
-void switch_mode(xcb_connection_t *conn, const char *new_mode) {
+void switch_mode(const char *new_mode) {
         struct Mode *mode;
 
         LOG("Switching to mode %s\n", new_mode);
@@ -232,60 +169,72 @@ void switch_mode(xcb_connection_t *conn, const char *new_mode) {
 }
 
 /*
- * Get the path of the first configuration file found. Checks the home directory
- * first, then the system directory first, always taking into account the XDG
- * Base Directory Specification ($XDG_CONFIG_HOME, $XDG_CONFIG_DIRS)
+ * Get the path of the first configuration file found. If override_configpath
+ * is specified, that path is returned and saved for further calls. Otherwise,
+ * checks the home directory first, then the system directory first, always
+ * taking into account the XDG Base Directory Specification ($XDG_CONFIG_HOME,
+ * $XDG_CONFIG_DIRS)
  *
  */
-static char *get_config_path() {
-        char *xdg_config_home, *xdg_config_dirs, *config_path;
-
-        /* 1: check the traditional path under the home directory */
-        config_path = resolve_tilde("~/.i3/config");
-        if (path_exists(config_path))
-                return config_path;
-
-        /* 2: check for $XDG_CONFIG_HOME/i3/config */
-        if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL)
-                xdg_config_home = "~/.config";
-
-        xdg_config_home = resolve_tilde(xdg_config_home);
-        if (asprintf(&config_path, "%s/i3/config", xdg_config_home) == -1)
-                die("asprintf() failed");
-        free(xdg_config_home);
-
-        if (path_exists(config_path))
-                return config_path;
-        free(config_path);
-
-        /* 3: check the traditional path under /etc */
-        config_path = SYSCONFDIR "/i3/config";
-        if (path_exists(config_path))
-                return sstrdup(config_path);
-
-        /* 4: check for $XDG_CONFIG_DIRS/i3/config */
-        if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL)
-                xdg_config_dirs = "/etc/xdg";
-
-        char *buf = sstrdup(xdg_config_dirs);
-        char *tok = strtok(buf, ":");
-        while (tok != NULL) {
-                tok = resolve_tilde(tok);
-                if (asprintf(&config_path, "%s/i3/config", tok) == -1)
-                        die("asprintf() failed");
-                free(tok);
-                if (path_exists(config_path)) {
-                        free(buf);
-                        return config_path;
-                }
-                free(config_path);
-                tok = strtok(NULL, ":");
+static char *get_config_path(const char *override_configpath) {
+    char *xdg_config_home, *xdg_config_dirs, *config_path;
+
+    static const char *saved_configpath = NULL;
+
+    if (override_configpath != NULL) {
+        saved_configpath = override_configpath;
+        return sstrdup(saved_configpath);
+    }
+
+    if (saved_configpath != NULL)
+        return sstrdup(saved_configpath);
+
+    /* 1: check the traditional path under the home directory */
+    config_path = resolve_tilde("~/.i3/config");
+    if (path_exists(config_path))
+        return config_path;
+
+    /* 2: check for $XDG_CONFIG_HOME/i3/config */
+    if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL)
+        xdg_config_home = "~/.config";
+
+    xdg_config_home = resolve_tilde(xdg_config_home);
+    if (asprintf(&config_path, "%s/i3/config", xdg_config_home) == -1)
+        die("asprintf() failed");
+    free(xdg_config_home);
+
+    if (path_exists(config_path))
+        return config_path;
+    free(config_path);
+
+    /* 3: check the traditional path under /etc */
+    config_path = SYSCONFDIR "/i3/config";
+    if (path_exists(config_path))
+        return sstrdup(config_path);
+
+    /* 4: check for $XDG_CONFIG_DIRS/i3/config */
+    if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL)
+        xdg_config_dirs = "/etc/xdg";
+
+    char *buf = sstrdup(xdg_config_dirs);
+    char *tok = strtok(buf, ":");
+    while (tok != NULL) {
+        tok = resolve_tilde(tok);
+        if (asprintf(&config_path, "%s/i3/config", tok) == -1)
+            die("asprintf() failed");
+        free(tok);
+        if (path_exists(config_path)) {
+            free(buf);
+            return config_path;
         }
-        free(buf);
+        free(config_path);
+        tok = strtok(NULL, ":");
+    }
+    free(buf);
 
-        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)");
+    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)");
 }
 
 /*
@@ -295,15 +244,11 @@ static char *get_config_path() {
  *
  */
 static void parse_configuration(const char *override_configpath) {
-        if (override_configpath != NULL) {
-                parse_file(override_configpath);
-                return;
-        }
-
-        char *path = get_config_path();
-        DLOG("Parsing configfile %s\n", path);
-        parse_file(path);
-        free(path);
+    char *path = get_config_path(override_configpath);
+    DLOG("Parsing configfile %s\n", path);
+    FREE(current_configpath);
+    current_configpath = path;
+    parse_file(path);
 }
 
 /*
@@ -334,6 +279,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
                         SLIST_REMOVE(&modes, mode, Mode, modes);
                 }
 
+#if 0
                 struct Assignment *assign;
                 while (!TAILQ_EMPTY(&assignments)) {
                         assign = TAILQ_FIRST(&assignments);
@@ -341,11 +287,14 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
                         TAILQ_REMOVE(&assignments, assign, assignments);
                         FREE(assign);
                 }
+#endif
 
                 /* Clear workspace names */
+#if 0
                 Workspace *ws;
                 TAILQ_FOREACH(ws, workspaces, workspaces)
                         workspace_set_name(ws, NULL);
+#endif
         }
 
         SLIST_INIT(&modes);
@@ -368,12 +317,12 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
         /* Initialize default colors */
 #define INIT_COLOR(x, cborder, cbackground, ctext) \
         do { \
-                x.border = get_colorpixel(conn, cborder); \
-                x.background = get_colorpixel(conn, cbackground); \
-                x.text = get_colorpixel(conn, ctext); \
+                x.border = get_colorpixel(cborder); \
+                x.background = get_colorpixel(cbackground); \
+                x.text = get_colorpixel(ctext); \
         } while (0)
 
-        config.client.background = get_colorpixel(conn, "#000000");
+        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");
@@ -382,6 +331,10 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
         INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888");
         INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff");
 
+        config.default_border = BS_NORMAL;
+        /* Set default_orientation to NO_ORIENTATION for auto orientation. */
+        config.default_orientation = NO_ORIENTATION;
+
         parse_configuration(override_configpath);
 
         if (reload) {
@@ -389,8 +342,12 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
                 grab_all_keys(conn, false);
         }
 
-        REQUIRED_OPTION(font);
+        if (config.font.id == 0) {
+                ELOG("You did not specify required configuration option \"font\"\n");
+                config.font = load_font("fixed", true);
+        }
 
+#if 0
         /* Set an empty name for every workspace which got no name */
         Workspace *ws;
         TAILQ_FOREACH(ws, workspaces, workspaces) {
@@ -405,4 +362,5 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
 
                 workspace_set_name(ws, NULL);
         }
+#endif
 }
diff --git a/src/container.c b/src/container.c
deleted file mode 100644 (file)
index 8533fd4..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- */
-
-#include "data.h"
-#include "log.h"
-
-/*
- * Returns the mode of the given container (or MODE_DEFAULT if a NULL pointer
- * was passed in order to save a few explicit checks in other places). If
- * for_frame was set to true, the special case of having exactly one client
- * in a container is handled so that MODE_DEFAULT is returned. For some parts
- * of the rendering, this is interesting, other parts need the real mode.
- *
- */
-int container_mode(Container *con, bool for_frame) {
-        int num_clients = 0;
-        Client *client;
-
-        if (con == NULL || con->mode == MODE_DEFAULT)
-                return MODE_DEFAULT;
-
-        if (!for_frame)
-                return con->mode;
-
-        CIRCLEQ_FOREACH(client, &(con->clients), clients)
-                num_clients++;
-
-        /* If the container contains only one client, mode is irrelevant */
-        if (num_clients == 1) {
-                DLOG("mode to default\n");
-                return MODE_DEFAULT;
-        }
-
-        return con->mode;
-}
index de47fca257299b32cfe95e00be7e148a4c7e209d..1e0f828df76bc027d63736de610bad320662401c 100644 (file)
@@ -244,7 +244,3 @@ int format_event(xcb_generic_event_t *e) {
     fflush(stdout);
     return 1;
 }
-
-int handle_event(void *ignored, xcb_connection_t *c, xcb_generic_event_t *e) {
-        return format_event(e);
-}
index 6bfa3096ef25c777bf5f8b6d22961d8768022037..7b2cc3e458fe03d754acae891d0ca48a61a72a20 100644 (file)
@@ -1,25 +1,17 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
  *
- * © 2009 Michael Stapelberg and contributors
+ * © 2009-2011 Michael Stapelberg and contributors
  *
  * See file LICENSE for license information.
  *
  * ewmh.c: Functions to get/set certain EWMH properties easily.
  *
  */
-#include <stdint.h>
-#include <string.h>
-#include <stdlib.h>
 
-#include "data.h"
-#include "table.h"
-#include "i3.h"
-#include "xcb.h"
-#include "util.h"
-#include "log.h"
+#include "all.h"
 
 /*
  * Updates _NET_CURRENT_DESKTOP with the current desktop number.
  *
  */
 void ewmh_update_current_desktop() {
-        uint32_t current_desktop = c_ws->num;
-        xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root,
-                            atoms[_NET_CURRENT_DESKTOP], CARDINAL, 32, 1,
-                            &current_desktop);
+    Con *focused_ws = con_get_workspace(focused);
+    Con *output;
+    uint32_t idx = 0;
+    /* We count to get the index of this workspace because named workspaces
+     * don’t have the ->num property */
+    TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
+        Con *ws;
+        TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
+            if (ws == focused_ws) {
+                xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
+                        A__NET_CURRENT_DESKTOP, A_CARDINAL, 32, 1, &idx);
+                return;
+            }
+            ++idx;
+        }
+    }
 }
 
 /*
@@ -43,8 +47,8 @@ void ewmh_update_current_desktop() {
  *
  */
 void ewmh_update_active_window(xcb_window_t window) {
-        xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root,
-                            atoms[_NET_ACTIVE_WINDOW], WINDOW, 32, 1, &window);
+    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
+            A__NET_ACTIVE_WINDOW, A_WINDOW, 32, 1, &window);
 }
 
 /*
@@ -56,48 +60,53 @@ void ewmh_update_active_window(xcb_window_t window) {
  *
  */
 void ewmh_update_workarea() {
-        Workspace *ws;
-        int num_workspaces = 0, count = 0;
-        Rect last_rect = {0, 0, 0, 0};
+    int num_workspaces = 0, count = 0;
+    Rect last_rect = {0, 0, 0, 0};
+    Con *output;
 
-        /* Get the number of workspaces */
-        TAILQ_FOREACH(ws, workspaces, workspaces) {
-                /* Check if we need to initialize last_rect. The case that the
-                 * first workspace is all-zero may happen when the user
-                 * assigned workspace 2 for his first screen, for example. Thus
-                 * we need an initialized last_rect in the very first run of
-                 * the following loop. */
-                if (last_rect.width == 0 && last_rect.height == 0 &&
+    TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
+        Con *ws;
+        TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
+            /* Check if we need to initialize last_rect. The case that the
+             * first workspace is all-zero may happen when the user
+             * assigned workspace 2 for his first screen, for example. Thus
+             * we need an initialized last_rect in the very first run of
+             * the following loop. */
+            if (last_rect.width == 0 && last_rect.height == 0 &&
                     ws->rect.width != 0 && ws->rect.height != 0) {
-                        memcpy(&last_rect, &(ws->rect), sizeof(Rect));
-                }
-                num_workspaces++;
+                memcpy(&last_rect, &(ws->rect), sizeof(Rect));
+            }
+            num_workspaces++;
         }
+    }
 
-        DLOG("Got %d workspaces\n", num_workspaces);
-        uint8_t *workarea = smalloc(sizeof(Rect) * num_workspaces);
-        TAILQ_FOREACH(ws, workspaces, workspaces) {
-                DLOG("storing %d: %dx%d with %d x %d\n", count, ws->rect.x,
-                     ws->rect.y, ws->rect.width, ws->rect.height);
-                /* If a workspace is not yet initialized and thus its
-                 * dimensions are zero, we will instead put the dimensions
-                 * of the last workspace in the list. For example firefox
-                 * intersects all workspaces and does not cope so well with
-                 * an all-zero workspace. */
-                if (ws->rect.width == 0 || ws->rect.height == 0) {
-                        DLOG("re-using last_rect (%dx%d, %d, %d)\n",
-                             last_rect.x, last_rect.y, last_rect.width,
-                             last_rect.height);
-                        memcpy(workarea + (sizeof(Rect) * count++), &last_rect, sizeof(Rect));
-                        continue;
-                }
-                memcpy(workarea + (sizeof(Rect) * count++), &(ws->rect), sizeof(Rect));
-                memcpy(&last_rect, &(ws->rect), sizeof(Rect));
+    DLOG("Got %d workspaces\n", num_workspaces);
+    uint8_t *workarea = smalloc(sizeof(Rect) * num_workspaces);
+    TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
+        Con *ws;
+        TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
+            DLOG("storing %d: %dx%d with %d x %d\n", count, ws->rect.x,
+                 ws->rect.y, ws->rect.width, ws->rect.height);
+            /* If a workspace is not yet initialized and thus its
+             * dimensions are zero, we will instead put the dimensions
+             * of the last workspace in the list. For example firefox
+             * intersects all workspaces and does not cope so well with
+             * an all-zero workspace. */
+            if (ws->rect.width == 0 || ws->rect.height == 0) {
+                DLOG("re-using last_rect (%dx%d, %d, %d)\n",
+                     last_rect.x, last_rect.y, last_rect.width,
+                     last_rect.height);
+                memcpy(workarea + (sizeof(Rect) * count++), &last_rect, sizeof(Rect));
+                continue;
+            }
+            memcpy(workarea + (sizeof(Rect) * count++), &(ws->rect), sizeof(Rect));
+            memcpy(&last_rect, &(ws->rect), sizeof(Rect));
         }
-        xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root,
-                            atoms[_NET_WORKAREA], CARDINAL, 32,
-                            num_workspaces * (sizeof(Rect) / sizeof(uint32_t)),
-                            workarea);
-        free(workarea);
-        xcb_flush(global_conn);
+    }
+    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
+            A__NET_WORKAREA, A_CARDINAL, 32,
+            num_workspaces * (sizeof(Rect) / sizeof(uint32_t)),
+            workarea);
+    free(workarea);
+    xcb_flush(conn);
 }
index 93a5b11c47ab5dbf58e46b2012d3872befcb32cb..f13476dbb88da7f67a276457776149166d1fc2eb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
  *
  * src/floating.c: contains all functions for handling floating clients
  *
  */
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-
-#include <xcb/xcb.h>
-#include <xcb/xcb_event.h>
-
-#include "i3.h"
-#include "config.h"
-#include "data.h"
-#include "util.h"
-#include "xcb.h"
-#include "debug.h"
-#include "layout.h"
-#include "client.h"
-#include "floating.h"
-#include "workspace.h"
-#include "log.h"
 
-/*
- * Toggles floating mode for the given client.
- * Correctly takes care of the position/size (separately stored for tiling/floating mode)
- * and repositions/resizes/redecorates the client.
- *
- * If the automatic flag is set to true, this was an automatic update by a change of the
- * window class from the application which can be overwritten by the user.
- *
- */
-void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic) {
-        Container *con = client->container;
-        i3Font *font = load_font(conn, config.font);
 
-        if (client->dock) {
-                DLOG("Not putting dock client into floating mode\n");
-                return;
-        }
+#include "all.h"
 
-        if (con == NULL) {
-                DLOG("This client is already in floating (container == NULL), re-inserting\n");
-                Client *next_tiling;
-                Workspace *ws = client->workspace;
-                SLIST_FOREACH(next_tiling, &(ws->focus_stack), focus_clients)
-                        if (!client_is_floating(next_tiling))
-                                break;
-                /* If there are no tiling clients on this workspace, there can only be one
-                 * container: the first one */
-                if (next_tiling == TAILQ_END(&(ws->focus_stack)))
-                        con = ws->table[0][0];
-                else con = next_tiling->container;
+extern xcb_connection_t *conn;
 
-                /* Remove the client from the list of floating clients */
-                TAILQ_REMOVE(&(ws->floating_clients), client, floating_clients);
+void floating_enable(Con *con, bool automatic) {
+    bool set_focus = true;
 
-                DLOG("destination container = %p\n", con);
-                Client *old_focused = con->currently_focused;
-                /* Preserve position/size */
-                memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect));
+    if (con_is_floating(con)) {
+        LOG("Container is already in floating mode, not doing anything.\n");
+        return;
+    }
 
-                client->floating = FLOATING_USER_OFF;
-                client->container = con;
+    /* 1: If the container is a workspace container, we need to create a new
+     * split-container with the same orientation and make that one floating. We
+     * cannot touch the workspace container itself because floating containers
+     * are children of the workspace. */
+    if (con->type == CT_WORKSPACE) {
+        LOG("This is a workspace, creating new container around content\n");
+        if (con_num_children(con) == 0) {
+            LOG("Workspace is empty, aborting\n");
+            return;
+        }
+        /* TODO: refactor this with src/con.c:con_set_layout */
+        Con *new = con_new(NULL, NULL);
+        new->parent = con;
+        new->orientation = con->orientation;
+
+        /* since the new container will be set into floating mode directly
+         * afterwards, we need to copy the workspace rect. */
+        memcpy(&(new->rect), &(con->rect), sizeof(Rect));
+
+        Con *old_focused = TAILQ_FIRST(&(con->focus_head));
+        if (old_focused == TAILQ_END(&(con->focus_head)))
+            old_focused = NULL;
+
+        /* 4: move the existing cons of this workspace below the new con */
+        DLOG("Moving cons\n");
+        Con *child;
+        while (!TAILQ_EMPTY(&(con->nodes_head))) {
+            child = TAILQ_FIRST(&(con->nodes_head));
+            con_detach(child);
+            con_attach(child, new, true);
+        }
 
-                if (old_focused != NULL && !old_focused->dock)
-                        CIRCLEQ_INSERT_AFTER(&(con->clients), old_focused, client, clients);
-                else CIRCLEQ_INSERT_TAIL(&(con->clients), client, clients);
+        /* 4: attach the new split container to the workspace */
+        DLOG("Attaching new split to ws\n");
+        con_attach(new, con, false);
+
+        if (old_focused)
+            con_focus(old_focused);
+
+        con = new;
+        set_focus = false;
+    }
+
+    /* 1: detach the container from its parent */
+    /* TODO: refactor this with tree_close() */
+    TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes);
+    TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
+
+    con_fix_percent(con->parent);
+
+    /* 2: create a new container to render the decoration on, add
+     * it as a floating window to the workspace */
+    Con *nc = con_new(NULL, NULL);
+    /* we need to set the parent afterwards instead of passing it as an
+     * argument to con_new() because nc would be inserted into the tiling layer
+     * otherwise. */
+    nc->parent = con_get_workspace(con);
+
+    /* check if the parent container is empty and close it if so */
+    if ((con->parent->type == CT_CON || con->parent->type == CT_FLOATING_CON) && con_num_children(con->parent) == 0) {
+        DLOG("Old container empty after setting this child to floating, closing\n");
+        tree_close(con->parent, DONT_KILL_WINDOW, false);
+    }
+
+    char *name;
+    asprintf(&name, "[i3 con] floatingcon around %p", con);
+    x_set_name(nc, name);
+    free(name);
+
+    /* find the height for the decorations */
+    int deco_height = config.font.height + 5;
+
+    DLOG("Original rect: (%d, %d) with %d x %d\n", con->rect.x, con->rect.y, con->rect.width, con->rect.height);
+    Rect zero = { 0, 0, 0, 0 };
+    nc->rect = con->geometry;
+    /* If the geometry was not set (split containers), we need to determine a
+     * sensible one by combining the geometry of all children */
+    if (memcmp(&(nc->rect), &zero, sizeof(Rect)) == 0) {
+        DLOG("Geometry not set, combining children\n");
+        Con *child;
+        TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+            DLOG("child geometry: %d x %d\n", child->geometry.width, child->geometry.height);
+            nc->rect.width += child->geometry.width;
+            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);
+    /* add pixels for the decoration */
+    /* TODO: don’t add them when the user automatically puts new windows into
+     * 1pixel/borderless mode */
+    nc->rect.height += deco_height + 4;
+    nc->rect.width += 4;
+    DLOG("Floating rect: (%d, %d) with %d x %d\n", nc->rect.x, nc->rect.y, nc->rect.width, nc->rect.height);
+    nc->orientation = NO_ORIENTATION;
+    nc->type = CT_FLOATING_CON;
+    TAILQ_INSERT_TAIL(&(nc->parent->floating_head), nc, floating_windows);
+    TAILQ_INSERT_TAIL(&(nc->parent->focus_head), nc, focused);
+
+    /* 3: attach the child to the new parent container */
+    con->parent = nc;
+    con->percent = 1.0;
+    con->floating = FLOATING_USER_ON;
+
+    /* Some clients (like GIMP’s color picker window) get mapped
+     * to (0, 0), so we push them to a reasonable position
+     * (centered over their leader) */
+    if (nc->rect.x == 0 && nc->rect.y == 0) {
+        Con *leader;
+        if (con->window && con->window->leader != XCB_NONE &&
+            (leader = con_by_window_id(con->window->leader)) != NULL) {
+            DLOG("Centering above leader\n");
+            nc->rect.x = leader->rect.x + (leader->rect.width / 2) - (nc->rect.width / 2);
+            nc->rect.y = leader->rect.y + (leader->rect.height / 2) - (nc->rect.height / 2);
+        } else {
+            /* center the window on workspace as fallback */
+            Con *ws = nc->parent;
+            nc->rect.x = ws->rect.x + (ws->rect.width / 2) - (nc->rect.width / 2);
+            nc->rect.y = ws->rect.y + (ws->rect.height / 2) - (nc->rect.height / 2);
+        }
+    }
 
-                DLOG("Re-inserted the window.\n");
-                con->currently_focused = client;
+    TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes);
+    TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused);
 
-                client_set_below_floating(conn, client);
+    /* render the cons to get initial window_rect correct */
+    render_con(nc, false);
+    render_con(con, false);
 
-                render_container(conn, con);
-                xcb_flush(conn);
+    // TODO: don’t influence focus handling when Con was not focused before.
+    if (set_focus)
+        con_focus(con);
 
-                return;
-        }
+    /* Check if we need to re-assign it to a different workspace because of its
+     * coordinates and exit if that was done successfully. */
+    if (floating_maybe_reassign_ws(nc))
+        return;
 
-        DLOG("Entering floating for client %08x\n", client->child);
+    /* Sanitize coordinates: Check if they are on any output */
+    if (get_output_containing(nc->rect.x, nc->rect.y) != NULL)
+        return;
 
-        /* Remove the client of its container */
-        client_remove_from_container(conn, client, con, false);
-        client->container = NULL;
+    ELOG("No output found at destination coordinates, centering floating window on current ws\n");
+    Con *ws = nc->parent;
+    nc->rect.x = ws->rect.x + (ws->rect.width / 2) - (nc->rect.width / 2);
+    nc->rect.y = ws->rect.y + (ws->rect.height / 2) - (nc->rect.height / 2);
+}
 
-        /* Add the client to the list of floating clients for its workspace */
-        TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients);
+void floating_disable(Con *con, bool automatic) {
+    if (!con_is_floating(con)) {
+        LOG("Container isn't floating, not doing anything.\n");
+        return;
+    }
 
-        if (con->currently_focused == client) {
-                DLOG("Need to re-adjust currently_focused\n");
-                /* Get the next client in the focus stack for this particular container */
-                con->currently_focused = get_last_focused_client(conn, con, NULL);
-        }
+    Con *ws = con_get_workspace(con);
 
-        if (automatic)
-                client->floating = FLOATING_AUTO_ON;
-        else client->floating = FLOATING_USER_ON;
+    /* 1: detach from parent container */
+    TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes);
+    TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
 
-        /* Initialize the floating position from the position in tiling mode, if this
-         * client never was floating (x == -1) */
-        if (client->floating_rect.x == -1) {
-                /* Copy over the position */
-                client->floating_rect.x = client->rect.x;
-                client->floating_rect.y = client->rect.y;
+    /* 2: kill parent container */
+    TAILQ_REMOVE(&(con->parent->parent->floating_head), con->parent, floating_windows);
+    TAILQ_REMOVE(&(con->parent->parent->focus_head), con->parent, focused);
+    tree_close(con->parent, DONT_KILL_WINDOW, false);
 
-                /* Copy size the other direction */
-                client->child_rect.width = client->floating_rect.width;
-                client->child_rect.height = client->floating_rect.height;
+    /* 3: re-attach to the parent of the currently focused con on the workspace
+     * this floating con was on */
+    Con *focused = con_descend_tiling_focused(ws);
 
-                client->rect.width = client->child_rect.width + 2 + 2;
-                client->rect.height = client->child_rect.height + (font->height + 2 + 2) + 2;
+    /* if there is no other container on this workspace, focused will be the
+     * workspace itself */
+    if (focused->type == CT_WORKSPACE)
+        con->parent = focused;
+    else con->parent = focused->parent;
 
-                DLOG("copying size from tiling (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
-                                client->floating_rect.width, client->floating_rect.height);
-        } else {
-                /* If the client was already in floating before we restore the old position / size */
-                DLOG("using: (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
-                        client->floating_rect.width, client->floating_rect.height);
-                memcpy(&(client->rect), &(client->floating_rect), sizeof(Rect));
-        }
+    /* con_fix_percent will adjust the percent value */
+    con->percent = 0.0;
 
-        /* Raise the client */
-        xcb_raise_window(conn, client->frame);
-        reposition_client(conn, client);
-        resize_client(conn, client);
-        /* redecorate_window flushes */
-        redecorate_window(conn, client);
+    TAILQ_INSERT_TAIL(&(con->parent->nodes_head), con, nodes);
+    TAILQ_INSERT_TAIL(&(con->parent->focus_head), con, focused);
 
-        /* Re-render the tiling layout of this container */
-        render_container(conn, con);
-        xcb_flush(conn);
+    con->floating = FLOATING_USER_OFF;
+
+    con_fix_percent(con->parent);
+    // TODO: don’t influence focus handling when Con was not focused before.
+    con_focus(con);
 }
 
 /*
- * Removes the floating client from its workspace and attaches it to the new workspace.
- * This is centralized here because it may happen if you move it via keyboard and
- * if you move it using your mouse.
+ * Toggles floating mode for the given container.
+ *
+ * If the automatic flag is set to true, this was an automatic update by a change of the
+ * window class from the application which can be overwritten by the user.
  *
  */
-void floating_assign_to_workspace(Client *client, Workspace *new_workspace) {
-        /* Remove from focus stack and list of floating clients */
-        SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
-        TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
-
-        if (client->workspace->fullscreen_client == client)
-                client->workspace->fullscreen_client = NULL;
-
-        /* Insert into destination focus stack and list of floating clients */
-        client->workspace = new_workspace;
-        SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients);
-        TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients);
-        if (client->fullscreen)
-                client->workspace->fullscreen_client = client;
+void toggle_floating_mode(Con *con, bool automatic) {
+    /* see if the client is already floating */
+    if (con_is_floating(con)) {
+        LOG("already floating, re-setting to tiling\n");
+
+        floating_disable(con, automatic);
+        return;
+    }
+
+    floating_enable(con, automatic);
 }
 
 /*
- * This is an ugly data structure which we need because there is no standard
- * way of having nested functions (only available as a gcc extension at the
- * moment, clang doesn’t support it) or blocks (only available as a clang
- * extension and only on Mac OS X systems at the moment).
+ * Raises the given container in the list of floating containers
  *
  */
-struct resize_callback_params {
-        border_t border;
-        xcb_button_press_event_t *event;
-};
-
-DRAGGING_CB(resize_callback) {
-        struct resize_callback_params *params = extra;
-        xcb_button_press_event_t *event = params->event;
-        switch (params->border) {
-                case BORDER_RIGHT: {
-                        int new_width = old_rect->width + (new_x - event->root_x);
-                        if ((new_width < 0) ||
-                            (new_width < client_min_width(client) && client->rect.width >= new_width))
-                                return;
-                        client->rect.width = new_width;
-                        break;
-                }
-
-                case BORDER_BOTTOM: {
-                        int new_height = old_rect->height + (new_y - event->root_y);
-                        if ((new_height < 0) ||
-                            (new_height < client_min_height(client) && client->rect.height >= new_height))
-                                return;
-                        client->rect.height = old_rect->height + (new_y - event->root_y);
-                        break;
-                }
-
-                case BORDER_TOP: {
-                        int new_height = old_rect->height + (event->root_y - new_y);
-                        if ((new_height < 0) ||
-                            (new_height < client_min_height(client) && client->rect.height >= new_height))
-                                return;
-
-                        client->rect.y = old_rect->y + (new_y - event->root_y);
-                        client->rect.height = new_height;
-                        break;
-                }
-
-                case BORDER_LEFT: {
-                        int new_width = old_rect->width + (event->root_x - new_x);
-                        if ((new_width < 0) ||
-                            (new_width < client_min_width(client) && client->rect.width >= new_width))
-                                return;
-                        client->rect.x = old_rect->x + (new_x - event->root_x);
-                        client->rect.width = new_width;
-                        break;
-                }
-        }
-
-        /* Push the new position/size to X11 */
-        reposition_client(conn, client);
-        resize_client(conn, client);
-        xcb_flush(conn);
+void floating_raise_con(Con *con) {
+    DLOG("Raising floating con %p / %s\n", con, con->name);
+    TAILQ_REMOVE(&(con->parent->floating_head), con, floating_windows);
+    TAILQ_INSERT_TAIL(&(con->parent->floating_head), con, floating_windows);
 }
 
-
 /*
- * Called whenever the user clicks on a border (not the titlebar!) of a floating window.
- * Determines on which border the user clicked and launches the drag_pointer function
- * with the resize_callback.
+ * Checks if con’s coordinates are within its workspace and re-assigns it to
+ * the actual workspace if not.
  *
  */
-int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
-        DLOG("floating border click\n");
-
-        border_t border;
-
-        if (event->event_y < 2)
-                border = BORDER_TOP;
-        else if (event->event_y >= (client->rect.height - 2))
-                border = BORDER_BOTTOM;
-        else if (event->event_x <= 2)
-                border = BORDER_LEFT;
-        else if (event->event_x >= (client->rect.width - 2))
-                border = BORDER_RIGHT;
-        else {
-                DLOG("Not on any border, not doing anything.\n");
-                return 1;
-        }
-
-        DLOG("border = %d\n", border);
-
-        struct resize_callback_params params = { border, event };
-
-        drag_pointer(conn, client, event, XCB_NONE, border, resize_callback, &params);
-
-        return 1;
+bool floating_maybe_reassign_ws(Con *con) {
+    Output *output = get_output_containing(
+        con->rect.x + (con->rect.width / 2),
+        con->rect.y + (con->rect.height / 2));
+
+    if (!output) {
+        ELOG("No output found at destination coordinates?\n");
+        return false;
+    }
+
+    if (con_get_output(con) == output->con) {
+        DLOG("still the same ws\n");
+        return false;
+    }
+
+    DLOG("Need to re-assign!\n");
+
+    Con *content = output_get_content(output->con);
+    Con *ws = TAILQ_FIRST(&(content->focus_head));
+    DLOG("Moving con %p / %s to workspace %p / %s\n", con, con->name, ws, ws->name);
+    con_move_to_workspace(con, ws);
+    con_focus(con_descend_focused(con));
+    return true;
 }
 
 DRAGGING_CB(drag_window_callback) {
-        struct xcb_button_press_event_t *event = extra;
+    struct xcb_button_press_event_t *event = extra;
 
-        /* Reposition the client correctly while moving */
-        client->rect.x = old_rect->x + (new_x - event->root_x);
-        client->rect.y = old_rect->y + (new_y - event->root_y);
-        reposition_client(conn, client);
-        /* Because reposition_client does not send a faked configure event (only resize does),
-         * we need to initiate that on our own */
-        fake_absolute_configure_notify(conn, client);
-        /* fake_absolute_configure_notify flushes */
+    /* Reposition the client correctly while moving */
+    con->rect.x = old_rect->x + (new_x - event->root_x);
+    con->rect.y = old_rect->y + (new_y - event->root_y);
+
+    render_con(con, false);
+    x_push_node(con);
+    xcb_flush(conn);
+
+    /* Check if we cross workspace boundaries while moving */
+    if (!floating_maybe_reassign_ws(con))
+        return;
+    tree_render();
 }
 
 /*
@@ -276,10 +294,11 @@ DRAGGING_CB(drag_window_callback) {
  * Calls the drag_pointer function with the drag_window callback
  *
  */
-void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
-        DLOG("floating_drag_window\n");
+void floating_drag_window(Con *con, xcb_button_press_event_t *event) {
+    DLOG("floating_drag_window\n");
 
-        drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback, event);
+    drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback, event);
+    tree_render();
 }
 
 /*
@@ -290,55 +309,58 @@ void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_pre
  *
  */
 struct resize_window_callback_params {
-        border_t corner;
-        bool proportional;
-        xcb_button_press_event_t *event;
+    border_t corner;
+    bool proportional;
+    xcb_button_press_event_t *event;
 };
 
 DRAGGING_CB(resize_window_callback) {
-        struct resize_window_callback_params *params = extra;
-        xcb_button_press_event_t *event = params->event;
-        border_t corner = params->corner;
-
-        int32_t dest_x = client->rect.x;
-        int32_t dest_y = client->rect.y;
-        uint32_t dest_width;
-        uint32_t dest_height;
-
-        double ratio = (double) old_rect->width / old_rect->height;
-
-        /* First guess: We resize by exactly the amount the mouse moved,
-         * taking into account in which corner the client was grabbed */
-        if (corner & BORDER_LEFT)
-                dest_width = old_rect->width - (new_x - event->root_x);
-        else dest_width = old_rect->width + (new_x - event->root_x);
-
-        if (corner & BORDER_TOP)
-                dest_height = old_rect->height - (new_y - event->root_y);
-        else dest_height = old_rect->height + (new_y - event->root_y);
-
-        /* Obey minimum window size */
-        dest_width = max(dest_width, client_min_width(client));
-        dest_height = max(dest_height, client_min_height(client));
-
-        /* User wants to keep proportions, so we may have to adjust our values */
-        if (params->proportional) {
-                dest_width = max(dest_width, (int) (dest_height * ratio));
-                dest_height = max(dest_height, (int) (dest_width / ratio));
-        }
-
-        /* If not the lower right corner is grabbed, we must also reposition
-         * the client by exactly the amount we resized it */
-        if (corner & BORDER_LEFT)
-                dest_x = old_rect->x + (old_rect->width - dest_width);
-
-        if (corner & BORDER_TOP)
-                dest_y = old_rect->y + (old_rect->height - dest_height);
-
-        client->rect = (Rect) { dest_x, dest_y, dest_width, dest_height };
-
-        /* resize_client flushes */
-        resize_client(conn, client);
+    struct resize_window_callback_params *params = extra;
+    xcb_button_press_event_t *event = params->event;
+    border_t corner = params->corner;
+
+    int32_t dest_x = con->rect.x;
+    int32_t dest_y = con->rect.y;
+    uint32_t dest_width;
+    uint32_t dest_height;
+
+    double ratio = (double) old_rect->width / old_rect->height;
+
+    /* First guess: We resize by exactly the amount the mouse moved,
+     * taking into account in which corner the client was grabbed */
+    if (corner & BORDER_LEFT)
+        dest_width = old_rect->width - (new_x - event->root_x);
+    else dest_width = old_rect->width + (new_x - event->root_x);
+
+    if (corner & BORDER_TOP)
+        dest_height = old_rect->height - (new_y - event->root_y);
+    else dest_height = old_rect->height + (new_y - event->root_y);
+
+    /* Obey minimum window size */
+    Rect minimum = con_minimum_size(con);
+    dest_width = max(dest_width, minimum.width);
+    dest_height = max(dest_height, minimum.height);
+
+    /* User wants to keep proportions, so we may have to adjust our values */
+    if (params->proportional) {
+        dest_width = max(dest_width, (int) (dest_height * ratio));
+        dest_height = max(dest_height, (int) (dest_width / ratio));
+    }
+
+    /* If not the lower right corner is grabbed, we must also reposition
+     * the client by exactly the amount we resized it */
+    if (corner & BORDER_LEFT)
+        dest_x = old_rect->x + (old_rect->width - dest_width);
+
+    if (corner & BORDER_TOP)
+        dest_y = old_rect->y + (old_rect->height - dest_height);
+
+    con->rect = (Rect) { dest_x, dest_y, dest_width, dest_height };
+
+    /* TODO: don’t re-render the whole tree just because we change
+     * coordinates of a floating window */
+    tree_render();
+    x_push_changes(croot);
 }
 
 /*
@@ -347,28 +369,27 @@ DRAGGING_CB(resize_window_callback) {
  * Calls the drag_pointer function with the resize_window callback
  *
  */
-void floating_resize_window(xcb_connection_t *conn, Client *client,
-                            bool proportional, xcb_button_press_event_t *event) {
-        DLOG("floating_resize_window\n");
+void floating_resize_window(Con *con, bool proportional,
+                            xcb_button_press_event_t *event) {
+    DLOG("floating_resize_window\n");
 
-        /* corner saves the nearest corner to the original click. It contains
-         * a bitmask of the nearest borders (BORDER_LEFT, BORDER_RIGHT, …) */
-        border_t corner = 0;
+    /* corner saves the nearest corner to the original click. It contains
+     * a bitmask of the nearest borders (BORDER_LEFT, BORDER_RIGHT, …) */
+    border_t corner = 0;
 
-        if (event->event_x <= (client->rect.width / 2))
-                corner |= BORDER_LEFT;
-        else corner |= BORDER_RIGHT;
+    if (event->event_x <= (con->rect.width / 2))
+        corner |= BORDER_LEFT;
+    else corner |= BORDER_RIGHT;
 
-        if (event->event_y <= (client->rect.height / 2))
-                corner |= BORDER_TOP;
-        else corner |= BORDER_RIGHT;
+    if (event->event_y <= (con->rect.height / 2))
+        corner |= BORDER_TOP;
+    else corner |= BORDER_BOTTOM;
 
-        struct resize_window_callback_params params = { corner, proportional, event };
+    struct resize_window_callback_params params = { corner, proportional, event };
 
-        drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback, &params);
+    drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback, &params);
 }
 
-
 /*
  * This function grabs your pointer and lets you drag stuff around (borders).
  * Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received
@@ -377,84 +398,95 @@ void floating_resize_window(xcb_connection_t *conn, Client *client,
  * the event and the new coordinates (x, y).
  *
  */
-void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event,
-                  xcb_window_t confine_to, border_t border, callback_t callback, void *extra) {
-        uint32_t new_x, new_y;
-        Rect old_rect;
-        if (client != NULL)
-                memcpy(&old_rect, &(client->rect), sizeof(Rect));
-
-        /* Grab the pointer */
-        /* TODO: returncode */
-        xcb_grab_pointer(conn, 
-                        false,               /* get all pointer events specified by the following mask */
-                        root,                /* grab the root window */
-                        XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */
-                        XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */
-                        XCB_GRAB_MODE_ASYNC, /* keyboard mode */
-                        confine_to,          /* confine_to = in which window should the cursor stay */
-                        XCB_NONE,            /* don’t display a special cursor */
-                        XCB_CURRENT_TIME);
-
-        /* Go into our own event loop */
-        xcb_flush(conn);
+void drag_pointer(Con *con, xcb_button_press_event_t *event, xcb_window_t
+                confine_to, border_t border, callback_t callback, void *extra)
+{
+    uint32_t new_x, new_y;
+    Rect old_rect;
+    if (con != NULL)
+        memcpy(&old_rect, &(con->rect), sizeof(Rect));
+
+    /* Grab the pointer */
+    xcb_grab_pointer_cookie_t cookie;
+    xcb_grab_pointer_reply_t *reply;
+    cookie = xcb_grab_pointer(conn,
+        false,               /* get all pointer events specified by the following mask */
+        root,                /* grab the root window */
+        XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */
+        XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */
+        XCB_GRAB_MODE_ASYNC, /* keyboard mode */
+        confine_to,          /* confine_to = in which window should the cursor stay */
+        XCB_NONE,            /* don’t display a special cursor */
+        XCB_CURRENT_TIME);
+
+    if ((reply = xcb_grab_pointer_reply(conn, cookie, NULL)) == NULL) {
+        ELOG("Could not grab pointer\n");
+        return;
+    }
+
+    free(reply);
+
+    /* Go into our own event loop */
+    xcb_flush(conn);
+
+    xcb_generic_event_t *inside_event, *last_motion_notify = NULL;
+    bool loop_done = false;
+    /* I’ve always wanted to have my own eventhandler… */
+    while (!loop_done && (inside_event = xcb_wait_for_event(conn))) {
+        /* We now handle all events we can get using xcb_poll_for_event */
+        do {
+            /* skip x11 errors */
+            if (inside_event->response_type == 0) {
+                free(inside_event);
+                continue;
+            }
+            /* Strip off the highest bit (set if the event is generated) */
+            int type = (inside_event->response_type & 0x7F);
+
+            switch (type) {
+                case XCB_BUTTON_RELEASE:
+                    loop_done = true;
+                    break;
+
+                case XCB_MOTION_NOTIFY:
+                    /* motion_notify events are saved for later */
+                    FREE(last_motion_notify);
+                    last_motion_notify = inside_event;
+                    break;
+
+                case XCB_UNMAP_NOTIFY:
+                case XCB_KEY_PRESS:
+                case XCB_KEY_RELEASE:
+                    DLOG("Unmap-notify, aborting\n");
+                    handle_event(type, inside_event);
+                    loop_done = true;
+                    break;
 
-        xcb_generic_event_t *inside_event, *last_motion_notify = NULL;
-        /* I’ve always wanted to have my own eventhandler… */
-        while ((inside_event = xcb_wait_for_event(conn))) {
-                /* We now handle all events we can get using xcb_poll_for_event */
-                do {
-                        /* Same as get_event_handler in xcb */
-                        int nr = inside_event->response_type;
-                        if (nr == 0) {
-                                /* An error occured */
-                                handle_event(NULL, conn, inside_event);
-                                free(inside_event);
-                                continue;
-                        }
-                        assert(nr < 256);
-                        nr &= XCB_EVENT_RESPONSE_TYPE_MASK;
-                        assert(nr >= 2);
-
-                        switch (nr) {
-                                case XCB_BUTTON_RELEASE:
-                                        goto done;
-
-                                case XCB_MOTION_NOTIFY:
-                                        /* motion_notify events are saved for later */
-                                        FREE(last_motion_notify);
-                                        last_motion_notify = inside_event;
-                                        break;
-
-                                case XCB_UNMAP_NOTIFY:
-                                        DLOG("Unmap-notify, aborting\n");
-                                        xcb_event_handle(&evenths, inside_event);
-                                        goto done;
-
-                                default:
-                                        DLOG("Passing to original handler\n");
-                                        /* Use original handler */
-                                        xcb_event_handle(&evenths, inside_event);
-                                        break;
-                        }
-                        if (last_motion_notify != inside_event)
-                                free(inside_event);
-                } while ((inside_event = xcb_poll_for_event(conn)) != NULL);
-
-                if (last_motion_notify == NULL)
-                        continue;
-
-                new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x;
-                new_y = ((xcb_motion_notify_event_t*)last_motion_notify)->root_y;
-
-                callback(conn, client, &old_rect, new_x, new_y, extra);
-                FREE(last_motion_notify);
-        }
-done:
-        xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
-        xcb_flush(conn);
+                default:
+                    DLOG("Passing to original handler\n");
+                    /* Use original handler */
+                    handle_event(type, inside_event);
+                    break;
+            }
+            if (last_motion_notify != inside_event)
+                free(inside_event);
+        } while ((inside_event = xcb_poll_for_event(conn)) != NULL);
+
+        if (last_motion_notify == NULL)
+            continue;
+
+        new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x;
+        new_y = ((xcb_motion_notify_event_t*)last_motion_notify)->root_y;
+
+        callback(con, &old_rect, new_x, new_y, extra);
+        FREE(last_motion_notify);
+    }
+
+    xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
+    xcb_flush(conn);
 }
 
+#if 0
 /*
  * Changes focus in the given direction for floating clients.
  *
@@ -487,11 +519,6 @@ void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused,
 void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_t direction) {
         DLOG("floating move\n");
 
-        if (currently_focused->fullscreen) {
-                DLOG("Cannot move fullscreen windows\n");
-                return;
-        }
-
         Rect destination = currently_focused->rect;
         Rect *screen = &(currently_focused->workspace->output->rect);
 
@@ -559,3 +586,4 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace) {
 
         xcb_flush(conn);
 }
+#endif
index d531b951fd5143bd223f7012ef5197a241369722..0fd7dbbd7187e7cb83fbb35aeb9533339261cb19 100644 (file)
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
-#include <stdio.h>
-#include <assert.h>
-#include <string.h>
-#include <stdlib.h>
 #include <time.h>
+#include <limits.h>
 
-#include <xcb/xcb.h>
-#include <xcb/xcb_atom.h>
-#include <xcb/xcb_icccm.h>
 #include <xcb/randr.h>
 
 #include <X11/XKBlib.h>
 
-#include "i3.h"
-#include "debug.h"
-#include "table.h"
-#include "layout.h"
-#include "commands.h"
-#include "data.h"
-#include "xcb.h"
-#include "util.h"
-#include "randr.h"
-#include "config.h"
-#include "queue.h"
-#include "resize.h"
-#include "client.h"
-#include "manage.h"
-#include "floating.h"
-#include "workspace.h"
-#include "log.h"
-#include "container.h"
-#include "ipc.h"
+#include "all.h"
+
+int randr_base = -1;
 
 /* After mapping/unmapping windows, a notify event is generated. However, we don’t want it,
    since it’d trigger an infinite loop of switching between the different windows when
    changing workspaces */
 static SLIST_HEAD(ignore_head, Ignore_Event) ignore_events;
 
-static void add_ignore_event(const int sequence) {
-        struct Ignore_Event *event = smalloc(sizeof(struct Ignore_Event));
+/*
+ * Adds the given sequence to the list of events which are ignored.
+ * If this ignore should only affect a specific response_type, pass
+ * response_type, otherwise, pass -1.
+ *
+ * Every ignored sequence number gets garbage collected after 5 seconds.
+ *
+ */
+void add_ignore_event(const int sequence, const int response_type) {
+    struct Ignore_Event *event = smalloc(sizeof(struct Ignore_Event));
 
-        event->sequence = sequence;
-        event->added = time(NULL);
+    event->sequence = sequence;
+    event->response_type = response_type;
+    event->added = time(NULL);
 
-        SLIST_INSERT_HEAD(&ignore_events, event, ignore_events);
+    SLIST_INSERT_HEAD(&ignore_events, event, ignore_events);
 }
 
 /*
  * Checks if the given sequence is ignored and returns true if so.
  *
  */
-static bool event_is_ignored(const int sequence) {
-        struct Ignore_Event *event;
-        time_t now = time(NULL);
-        for (event = SLIST_FIRST(&ignore_events); event != SLIST_END(&ignore_events);) {
-                if ((now - event->added) > 5) {
-                        struct Ignore_Event *save = event;
-                        event = SLIST_NEXT(event, ignore_events);
-                        SLIST_REMOVE(&ignore_events, save, Ignore_Event, ignore_events);
-                        free(save);
-                } else event = SLIST_NEXT(event, ignore_events);
-        }
-
-        SLIST_FOREACH(event, &ignore_events, ignore_events) {
-                if (event->sequence == sequence) {
-                        SLIST_REMOVE(&ignore_events, event, Ignore_Event, ignore_events);
-                        free(event);
-                        return true;
-                }
-        }
-
-        return false;
+bool event_is_ignored(const int sequence, const int response_type) {
+    struct Ignore_Event *event;
+    time_t now = time(NULL);
+    for (event = SLIST_FIRST(&ignore_events); event != SLIST_END(&ignore_events);) {
+        if ((now - event->added) > 5) {
+            struct Ignore_Event *save = event;
+            event = SLIST_NEXT(event, ignore_events);
+            SLIST_REMOVE(&ignore_events, save, Ignore_Event, ignore_events);
+            free(save);
+        } else event = SLIST_NEXT(event, ignore_events);
+    }
+
+    SLIST_FOREACH(event, &ignore_events, ignore_events) {
+        if (event->sequence != sequence)
+            continue;
+
+        if (event->response_type != -1 &&
+            event->response_type != response_type)
+            continue;
+
+        /* instead of removing a sequence number we better wait until it gets
+         * garbage collected. it may generate multiple events (there are multiple
+         * enter_notifies for one configure_request, for example). */
+        //SLIST_REMOVE(&ignore_events, event, Ignore_Event, ignore_events);
+        //free(event);
+        return true;
+    }
+
+    return false;
 }
 
+
 /*
  * There was a key press. We compare this key code with our bindings table and pass
  * the bound action to parse_command().
  *
  */
-int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
-        DLOG("Keypress %d, state raw = %d\n", event->detail, event->state);
-
-        /* Remove the numlock bit, all other bits are modifiers we can bind to */
-        uint16_t state_filtered = event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK);
-        DLOG("(removed numlock, state = %d)\n", state_filtered);
-        /* Only use the lower 8 bits of the state (modifier masks) so that mouse
-         * button masks are filtered out */
-        state_filtered &= 0xFF;
-        DLOG("(removed upper 8 bits, state = %d)\n", state_filtered);
-
-        if (xkb_current_group == XkbGroup2Index)
-                state_filtered |= BIND_MODE_SWITCH;
-
-        DLOG("(checked mode_switch, state %d)\n", state_filtered);
-
-        /* Find the binding */
-        Binding *bind = get_binding(state_filtered, event->detail);
-
-        /* No match? Then the user has Mode_switch enabled but does not have a
-         * specific keybinding. Fall back to the default keybindings (without
-         * Mode_switch). Makes it much more convenient for users of a hybrid
-         * layout (like us, ru). */
-        if (bind == NULL) {
-                state_filtered &= ~(BIND_MODE_SWITCH);
-                DLOG("no match, new state_filtered = %d\n", state_filtered);
-                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;
-                }
+static int handle_key_press(xcb_key_press_event_t *event) {
+    DLOG("Keypress %d, state raw = %d\n", event->detail, event->state);
+
+    /* Remove the numlock bit, all other bits are modifiers we can bind to */
+    uint16_t state_filtered = event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK);
+    DLOG("(removed numlock, state = %d)\n", state_filtered);
+    /* Only use the lower 8 bits of the state (modifier masks) so that mouse
+     * button masks are filtered out */
+    state_filtered &= 0xFF;
+    DLOG("(removed upper 8 bits, state = %d)\n", state_filtered);
+
+    if (xkb_current_group == XkbGroup2Index)
+        state_filtered |= BIND_MODE_SWITCH;
+
+    DLOG("(checked mode_switch, state %d)\n", state_filtered);
+
+    /* Find the binding */
+    Binding *bind = get_binding(state_filtered, event->detail);
+
+    /* No match? Then the user has Mode_switch enabled but does not have a
+     * specific keybinding. Fall back to the default keybindings (without
+     * Mode_switch). Makes it much more convenient for users of a hybrid
+     * layout (like us, ru). */
+    if (bind == NULL) {
+        state_filtered &= ~(BIND_MODE_SWITCH);
+        DLOG("no match, new state_filtered = %d\n", state_filtered);
+        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;
         }
+    }
 
-        parse_command(conn, bind->command);
-        return 1;
+    parse_cmd(bind->command);
+    return 1;
 }
 
 /*
@@ -131,101 +124,100 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_
  *
  */
 static void check_crossing_screen_boundary(uint32_t x, uint32_t y) {
-        Output *output;
+    Output *output;
 
-        if ((output = get_output_containing(x, y)) == NULL) {
-                ELOG("ERROR: No such screen\n");
-                return;
-        }
-        if (output == c_ws->output)
-                return;
-
-        c_ws->current_row = current_row;
-        c_ws->current_col = current_col;
-        c_ws = output->current_workspace;
-        current_row = c_ws->current_row;
-        current_col = c_ws->current_col;
-        DLOG("We're now on output %p\n", output);
-
-        /* While usually this function is only called when the user switches
-         * to a different output using his mouse (and thus the output is
-         * empty), it may be that the following race condition occurs:
-         * 1) the user actives a new output (say VGA1).
-         * 2) the cursor is sent to the first pixel of the new VGA1, thus
-         *    generating an enter_notify for the screen (the enter_notify
-         *    is not yet received by i3).
-         * 3) i3 requeries screen configuration and maps a workspace onto the
-         *    new output.
-         * 4) the enter_notify event arrives and c_ws is set to the new
-         *    workspace but the existing windows on the new workspace are not
-         *    focused.
-         *
-         * Therefore, we re-set the focus here to be sure it’s correct. */
-        Client *first_client = SLIST_FIRST(&(c_ws->focus_stack));
-        if (first_client != NULL)
-                set_focus(global_conn, first_client, true);
+    /* If the user disable focus follows mouse, we have nothing to do here */
+    if (config.disable_focus_follows_mouse)
+        return;
+
+    if ((output = get_output_containing(x, y)) == NULL) {
+        ELOG("ERROR: No such screen\n");
+        return;
+    }
+
+    if (output->con == NULL) {
+        ELOG("ERROR: The screen is not recognized by i3 (no container associated)\n");
+        return;
+    }
+
+    /* Focus the output on which the user moved his cursor */
+    Con *old_focused = focused;
+    con_focus(con_descend_focused(output_get_content(output->con)));
+
+    /* If the focus changed, we re-render to get updated decorations */
+    if (old_focused != focused)
+        tree_render();
 }
 
 /*
  * When the user moves the mouse pointer onto a window, this callback gets called.
  *
  */
-int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_event_t *event) {
-        DLOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n", event->event, event->mode, event->detail, event->sequence);
-        if (event->mode != XCB_NOTIFY_MODE_NORMAL) {
-                DLOG("This was not a normal notify, ignoring\n");
-                return 1;
-        }
-        /* 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))
-                return 1;
-
-        /* This was either a focus for a client’s parent (= titlebar)… */
-        Client *client = table_get(&by_parent, event->event);
-        /* …or the client itself */
-        if (client == NULL)
-                client = table_get(&by_child, event->event);
-
-        /* Check for stack windows */
-        if (client == NULL) {
-                struct Stack_Window *stack_win;
-                SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
-                        if (stack_win->window == event->event) {
-                                client = stack_win->container->currently_focused;
-                                break;
-                        }
-        }
-
-
-        /* If not, then the user moved his cursor to the root window. In that case, we adjust c_ws */
-        if (client == 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;
-        }
-
-        /* Do plausibility checks: This event may be useless for us if it occurs on a window
-           which is in a stacked container but not the focused one */
-        if (client->container != NULL &&
-            client->container->mode == MODE_STACK &&
-            client->container->currently_focused != client) {
-                DLOG("Plausibility check says: no\n");
-                return 1;
-        }
+static int handle_enter_notify(xcb_enter_notify_event_t *event) {
+    Con *con;
+
+    DLOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n",
+         event->event, event->mode, event->detail, event->sequence);
+    DLOG("coordinates %d, %d\n", event->event_x, event->event_y);
+    if (event->mode != XCB_NOTIFY_MODE_NORMAL) {
+        DLOG("This was not a normal notify, ignoring\n");
+        return 1;
+    }
+    /* 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;
+    }
+
+    bool enter_child = false;
+    /* Get container by frame or by child window */
+    if ((con = con_by_frame_id(event->event)) == NULL) {
+        con = con_by_window_id(event->event);
+        enter_child = true;
+    }
+
+    /* If not, then the user moved his cursor to the root window. In that case, we adjust c_ws */
+    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;
+    }
 
-        if (client->workspace != c_ws && client->workspace->output == c_ws->output) {
-                /* This can happen when a client gets assigned to a different workspace than
-                 * the current one (see src/mainx.c:reparent_window). Shortly after it was created,
-                 * an enter_notify will follow. */
-                DLOG("enter_notify for a client on a different workspace but the same screen, ignoring\n");
-                return 1;
-        }
+    if (con->parent->type == CT_DOCKAREA) {
+        DLOG("Ignoring, this is a dock client\n");
+        return 1;
+    }
+
+    /* see if the user entered the window on a certain window decoration */
+    int layout = (enter_child ? con->parent->layout : con->layout);
+    if (layout == L_DEFAULT) {
+        Con *child;
+        TAILQ_FOREACH(child, &(con->nodes_head), nodes)
+            if (rect_contains(child->deco_rect, event->event_x, event->event_y)) {
+                LOG("using child %p / %s instead!\n", child, child->name);
+                con = child;
+                break;
+            }
+    }
+
+#if 0
+    if (client->workspace != c_ws && client->workspace->output == c_ws->output) {
+            /* This can happen when a client gets assigned to a different workspace than
+             * the current one (see src/mainx.c:reparent_window). Shortly after it was created,
+             * an enter_notify will follow. */
+            DLOG("enter_notify for a client on a different workspace but the same screen, ignoring\n");
+            return 1;
+    }
+#endif
+
+    if (config.disable_focus_follows_mouse)
+        return 1;
 
-        if (!config.disable_focus_follows_mouse)
-                set_focus(conn, client, false);
+    con_focus(con_descend_focused(con));
+    tree_render();
 
-        return 1;
+    return 1;
 }
 
 /*
@@ -234,15 +226,40 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
  * and crossing virtual screen boundaries), this callback gets called.
  *
  */
-int handle_motion_notify(void *ignored, xcb_connection_t *conn, xcb_motion_notify_event_t *event) {
-        /* 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;
+static int handle_motion_notify(xcb_motion_notify_event_t *event) {
+    /* 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;
 
+    Con *con;
+    if ((con = con_by_frame_id(event->event)) == NULL) {
         check_crossing_screen_boundary(event->root_x, event->root_y);
+        return 1;
+    }
+
+    if (config.disable_focus_follows_mouse)
+        return 1;
 
+    if (con->layout != L_DEFAULT)
         return 1;
+
+    /* see over which rect the user is */
+    Con *current;
+    TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
+        if (!rect_contains(current->deco_rect, event->event_x, event->event_y))
+            continue;
+
+        /* We found the rect, let’s see if this window is focused */
+        if (TAILQ_FIRST(&(con->focus_head)) == current)
+            return 1;
+
+        con_focus(current);
+        x_push_changes(croot);
+        return 1;
+    }
+
+    return 1;
 }
 
 /*
@@ -250,37 +267,38 @@ int handle_motion_notify(void *ignored, xcb_connection_t *conn, xcb_motion_notif
  * we need to update our key bindings then (re-translate symbols).
  *
  */
-int handle_mapping_notify(void *ignored, xcb_connection_t *conn, xcb_mapping_notify_event_t *event) {
-        if (event->request != XCB_MAPPING_KEYBOARD &&
-            event->request != XCB_MAPPING_MODIFIER)
-                return 0;
+static int handle_mapping_notify(xcb_mapping_notify_event_t *event) {
+    if (event->request != XCB_MAPPING_KEYBOARD &&
+        event->request != XCB_MAPPING_MODIFIER)
+        return 0;
 
-        DLOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n");
-        xcb_refresh_keyboard_mapping(keysyms, event);
+    DLOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n");
+    xcb_refresh_keyboard_mapping(keysyms, event);
 
-        xcb_get_numlock_mask(conn);
+    xcb_get_numlock_mask(conn);
 
-        ungrab_all_keys(conn);
-        translate_keysyms();
-        grab_all_keys(conn, false);
+    ungrab_all_keys(conn);
+    translate_keysyms();
+    grab_all_keys(conn, false);
 
-        return 0;
+    return 0;
 }
 
 /*
  * A new window appeared on the screen (=was mapped), so let’s manage it.
  *
  */
-int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_event_t *event) {
-        xcb_get_window_attributes_cookie_t cookie;
+static int 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);
+    cookie = xcb_get_window_attributes_unchecked(conn, event->window);
 
-        DLOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence);
-        add_ignore_event(event->sequence);
+    DLOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence);
+    add_ignore_event(event->sequence, -1);
 
-        manage_window(prophs, conn, event->window, cookie, false);
-        return 1;
+    manage_window(event->window, cookie, false);
+    x_push_changes(croot);
+    return 1;
 }
 
 /*
@@ -289,109 +307,77 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_eve
  * We generate a synthethic configure notify event to signalize the client its "new" position.
  *
  */
-int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure_request_event_t *event) {
-        DLOG("window 0x%08x wants to be at %dx%d with %dx%d\n",
-            event->window, event->x, event->y, event->width, event->height);
+static int handle_configure_request(xcb_configure_request_event_t *event) {
+    Con *con;
 
-        Client *client = table_get(&by_child, event->window);
-        if (client == NULL) {
-                uint32_t mask = 0;
-                uint32_t values[7];
-                int c = 0;
+    DLOG("window 0x%08x wants to be at %dx%d with %dx%d\n",
+        event->window, event->x, event->y, event->width, event->height);
+
+    /* For unmanaged windows, we just execute the configure request. As soon as
+     * it gets mapped, we will take over anyways. */
+    if ((con = con_by_window_id(event->window)) == NULL) {
+        DLOG("Configure request for unmanaged window, can do that.\n");
+
+        uint32_t mask = 0;
+        uint32_t values[7];
+        int c = 0;
 #define COPY_MASK_MEMBER(mask_member, event_member) do { \
-                if (event->value_mask & mask_member) { \
-                        mask |= mask_member; \
-                        values[c++] = event->event_member; \
-                } \
+        if (event->value_mask & mask_member) { \
+            mask |= mask_member; \
+            values[c++] = event->event_member; \
+        } \
 } while (0)
 
-                COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_X, x);
-                COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_Y, y);
-                COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_WIDTH, width);
-                COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_HEIGHT, height);
-                COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_BORDER_WIDTH, border_width);
-                COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_SIBLING, sibling);
-                COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_STACK_MODE, stack_mode);
+        COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_X, x);
+        COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_Y, y);
+        COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_WIDTH, width);
+        COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_HEIGHT, height);
+        COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_BORDER_WIDTH, border_width);
+        COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_SIBLING, sibling);
+        COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_STACK_MODE, stack_mode);
 
-                xcb_configure_window(conn, event->window, mask, values);
-                xcb_flush(conn);
+        xcb_configure_window(conn, event->window, mask, values);
+        xcb_flush(conn);
 
-                return 1;
+        return 1;
+    }
+
+    DLOG("Configure request!\n");
+    if (con_is_floating(con) && con_is_leaf(con)) {
+        /* find the height for the decorations */
+        int deco_height = config.font.height + 5;
+        /* we actually need to apply the size/position changes to the *parent*
+         * container */
+        Rect bsr = con_border_style_rect(con);
+        if (con->border_style == BS_NORMAL) {
+            bsr.y += deco_height;
+            bsr.height -= deco_height;
         }
-
-        if (client->fullscreen) {
-                DLOG("Client is in fullscreen mode\n");
-
-                Rect child_rect = client->workspace->rect;
-                child_rect.x = child_rect.y = 0;
-                fake_configure_notify(conn, child_rect, client->child);
-
-                return 1;
+        con = con->parent;
+        DLOG("Container is a floating leaf node, will do that.\n");
+        if (event->value_mask & XCB_CONFIG_WINDOW_X) {
+            con->rect.x = event->x + (-1) * bsr.x;
+            DLOG("proposed x = %d, new x is %d\n", event->x, con->rect.x);
         }
-
-        /* Floating clients can be reconfigured */
-        if (client_is_floating(client)) {
-                i3Font *font = load_font(conn, config.font);
-                int mode = (client->container != NULL ? client->container->mode : MODE_DEFAULT);
-                /* TODO: refactor this code. we need a function to translate
-                 * coordinates of child_rect/rect. */
-
-                if (event->value_mask & XCB_CONFIG_WINDOW_X) {
-                        if (mode == MODE_STACK || mode == MODE_TABBED) {
-                                client->rect.x = event->x - 2;
-                        } else {
-                                if (client->titlebar_position == TITLEBAR_OFF && client->borderless)
-                                        client->rect.x = event->x;
-                                else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless)
-                                        client->rect.x = event->x - 1;
-                                else client->rect.x = event->x - 2;
-                        }
-                }
-                if (event->value_mask & XCB_CONFIG_WINDOW_Y) {
-                        if (mode == MODE_STACK || mode == MODE_TABBED) {
-                                client->rect.y = event->y - 2;
-                        } else {
-                                if (client->titlebar_position == TITLEBAR_OFF && client->borderless)
-                                        client->rect.y = event->y;
-                                else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless)
-                                        client->rect.y = event->y - 1;
-                                else client->rect.y = event->y - font->height - 2 - 2;
-                        }
-                }
-                if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH) {
-                        if (mode == MODE_STACK || mode == MODE_TABBED) {
-                                client->rect.width = event->width + 2 + 2;
-                        } else {
-                                if (client->titlebar_position == TITLEBAR_OFF && client->borderless)
-                                        client->rect.width = event->width;
-                                else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless)
-                                        client->rect.width = event->width + (1 + 1);
-                                else client->rect.width = event->width + (2 + 2);
-                        }
-                }
-                if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT) {
-                        if (mode == MODE_STACK || mode == MODE_TABBED) {
-                                client->rect.height = event->height + 2;
-                        } else {
-                                if (client->titlebar_position == TITLEBAR_OFF && client->borderless)
-                                        client->rect.height = event->height;
-                                else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless)
-                                        client->rect.height = event->height + (1 + 1);
-                                else client->rect.height = event->height + (font->height + 2 + 2) + 2;
-                        }
-                }
-
-                DLOG("Accepted new position/size for floating client: (%d, %d) size %d x %d\n",
-                    client->rect.x, client->rect.y, client->rect.width, client->rect.height);
-
-                /* Push the new position/size to X11 */
-                reposition_client(conn, client);
-                resize_client(conn, client);
-                xcb_flush(conn);
-
-                return 1;
+        if (event->value_mask & XCB_CONFIG_WINDOW_Y) {
+            con->rect.y = event->y + (-1) * bsr.y;
+            DLOG("proposed y = %d, new y is %d\n", event->y, con->rect.y);
+        }
+        if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH) {
+            con->rect.width = event->width + (-1) * bsr.width;
+            DLOG("proposed width = %d, new width is %d\n", event->width, con->rect.width);
         }
+        if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT) {
+            con->rect.height = event->height + (-1) * bsr.height;
+            DLOG("proposed height = %d, new height is %d\n", event->height, con->rect.height);
+        }
+        tree_render();
+    }
+
+    fake_absolute_configure_notify(con);
 
+    return 1;
+#if 0
         /* Dock clients can be reconfigured in their height */
         if (client->dock) {
                 DLOG("Reconfiguring height of this dock client\n");
@@ -421,7 +407,9 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
         fake_absolute_configure_notify(conn, client);
 
         return 1;
+#endif
 }
+#if 0
 
 /*
  * Configuration notifies are only handled because we need to set up ignore for
@@ -429,47 +417,69 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
  *
  */
 int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event) {
+    DLOG("configure_event, sequence %d\n", event->sequence);
         /* We ignore this sequence twice because events for child and frame should be ignored */
         add_ignore_event(event->sequence);
         add_ignore_event(event->sequence);
 
         return 1;
 }
+#endif
 
 /*
  * Gets triggered upon a RandR screen change event, that is when the user
  * changes the screen configuration in any way (mode, position, …)
  *
  */
-int handle_screen_change(void *prophs, xcb_connection_t *conn,
-                         xcb_generic_event_t *e) {
-        DLOG("RandR screen change\n");
+static int handle_screen_change(xcb_generic_event_t *e) {
+    DLOG("RandR screen change\n");
 
-        randr_query_outputs(conn);
+    randr_query_outputs();
 
-        ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}");
+    ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}");
 
-        return 1;
+    return 1;
 }
 
 /*
- * Our window decorations were unmapped. That means, the window will be killed now,
- * so we better clean up before.
+ * Our window decorations were unmapped. That means, the window will be killed
+ * now, so we better clean up before.
  *
  */
-int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event) {
-        add_ignore_event(event->sequence);
-
-        Client *client = table_get(&by_child, event->window);
-        /* First, we need to check if the client is awaiting an unmap-request which
-           was generated by us reparenting the window. In that case, we just ignore it. */
-        if (client != NULL && client->awaiting_useless_unmap) {
-                client->awaiting_useless_unmap = false;
-                return 1;
+static int handle_unmap_notify_event(xcb_unmap_notify_event_t *event) {
+    // XXX: this is commented out because in src/x.c we disable EnterNotify events
+    /* we need to ignore EnterNotify events which will be generated because a
+     * different window is visible now */
+    //add_ignore_event(event->sequence, XCB_ENTER_NOTIFY);
+
+    DLOG("UnmapNotify for 0x%08x (received from 0x%08x), serial %d\n", event->window, event->event, event->sequence);
+    Con *con = con_by_window_id(event->window);
+    if (con == NULL) {
+        /* This could also be an UnmapNotify for the frame. We need to
+         * decrement the ignore_unmap counter. */
+        con = con_by_frame_id(event->window);
+        if (con == NULL) {
+            LOG("Not a managed window, ignoring UnmapNotify event\n");
+            return 1;
         }
+        if (con->ignore_unmap > 0)
+            con->ignore_unmap--;
+        DLOG("ignore_unmap = %d for frame of container %p\n", con->ignore_unmap, con);
+        return 1;
+    }
+
+    if (con->ignore_unmap > 0) {
+        DLOG("ignore_unmap = %d, dec\n", con->ignore_unmap);
+        con->ignore_unmap--;
+        return 1;
+    }
+
+    tree_close(con, DONT_KILL_WINDOW, false);
+    tree_render();
+    x_push_changes(croot);
+    return 1;
 
-        DLOG("event->window = %08x, event->event = %08x\n", event->window, event->event);
-        DLOG("UnmapNotify for 0x%08x (received from 0x%08x)\n", event->window, event->event);
+#if 0
         if (client == NULL) {
                 DLOG("not a managed window. Ignoring.\n");
 
@@ -481,57 +491,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
 
                 return 0;
         }
+#endif
 
-        client = table_remove(&by_child, event->window);
-
-        /* If this was the fullscreen client, we need to unset it from all
-         * workspaces it was on (global fullscreen) */
-        if (client->fullscreen) {
-                Workspace *ws;
-                TAILQ_FOREACH(ws, workspaces, workspaces)
-                        if (ws->fullscreen_client == client)
-                                ws->fullscreen_client = NULL;
-        }
-
-        /* Clients without a container are either floating or dock windows */
-        if (client->container != NULL) {
-                Container *con = client->container;
-
-                /* Remove the client from the list of clients */
-                client_remove_from_container(conn, client, con, true);
-
-                /* Set focus to the last focused client in this container */
-                con->currently_focused = get_last_focused_client(conn, con, NULL);
-
-                /* Only if this is the active container, we need to really change focus */
-                if ((con->currently_focused != NULL) && ((con == CUR_CELL) || client->fullscreen))
-                        set_focus(conn, con->currently_focused, true);
-        } else if (client_is_floating(client)) {
-                DLOG("Removing from floating clients\n");
-                TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
-                SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
-        }
-
-        if (client->dock) {
-                DLOG("Removing from dock clients\n");
-                SLIST_REMOVE(&(client->workspace->output->dock_clients), client, Client, dock_clients);
-        }
-
-        DLOG("child of 0x%08x.\n", client->frame);
-        xcb_reparent_window(conn, client->child, root, 0, 0);
-
-        client_unmap(conn, client);
-
-        xcb_destroy_window(conn, client->frame);
-        xcb_flush(conn);
-        table_remove(&by_parent, client->frame);
-
-        if (client->container != NULL) {
-                Workspace *workspace = client->container->workspace;
-                cleanup_table(conn, workspace);
-                fix_colrowspan(conn, workspace);
-        }
 
+#if 0
         /* Let’s see how many clients there are left on the workspace to delete it if it’s empty */
         bool workspace_empty = SLIST_EMPTY(&(client->workspace->focus_stack));
         bool workspace_focused = (c_ws == client->workspace);
@@ -550,26 +513,8 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
         client->urgent = false;
         workspace_update_urgent_flag(client->workspace);
 
-        FREE(client->window_class_instance);
-        FREE(client->window_class_class);
-        FREE(client->name);
-        free(client);
-
         render_layout(conn);
-
-        /* Ensure the focus is set to the next client in the focus stack or to
-         * the screen itself (if we do not focus the screen, it can happen that
-         * the focus is "nowhere" and thus keypress events will not be received
-         * by i3, thus the user cannot use any hotkeys). */
-        if (workspace_focused) {
-                if (to_focus != NULL)
-                        set_focus(conn, to_focus, true);
-                else {
-                        DLOG("Restoring focus to root screen\n");
-                        xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
-                        xcb_flush(conn);
-                }
-        }
+#endif
 
         return 1;
 }
@@ -583,283 +528,137 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
  * important fields in the event data structure).
  *
  */
-int handle_destroy_notify_event(void *data, xcb_connection_t *conn, xcb_destroy_notify_event_t *event) {
-        DLOG("destroy notify for 0x%08x, 0x%08x\n", event->event, event->window);
+static int handle_destroy_notify_event(xcb_destroy_notify_event_t *event) {
+    DLOG("destroy notify for 0x%08x, 0x%08x\n", event->event, event->window);
 
-        xcb_unmap_notify_event_t unmap;
-        unmap.sequence = event->sequence;
-        unmap.event = event->event;
-        unmap.window = event->window;
+    xcb_unmap_notify_event_t unmap;
+    unmap.sequence = event->sequence;
+    unmap.event = event->event;
+    unmap.window = event->window;
 
-        return handle_unmap_notify_event(NULL, conn, &unmap);
+    return handle_unmap_notify_event(&unmap);
 }
 
 /*
  * Called when a window changes its title
  *
  */
-int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
+static bool handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
                                 xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
-        if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
-                DLOG("_NET_WM_NAME not specified, not changing\n");
-                return 1;
-        }
-        Client *client = table_get(&by_child, window);
-        if (client == NULL)
-                return 1;
-
-        /* Save the old pointer to make the update atomic */
-        char *new_name;
-        int new_len;
-        if (asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)) == -1) {
-                perror("asprintf");
-                LOG("Could not format _NET_WM_NAME, ignoring new hint\n");
-                return 1;
-        }
-        /* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */
-        char *ucs2_name = convert_utf8_to_ucs2(new_name, &new_len);
-        LOG("_NET_WM_NAME changed to \"%s\"\n", new_name);
-        free(new_name);
-        if (ucs2_name == NULL) {
-                LOG("Could not convert _NET_WM_NAME to UCS-2, ignoring new hint\n");
-                return 1;
-        }
-
-        /* Check if they are the same and don’t update if so.
-           Note the use of new_len * 2 to check all bytes as each glyph takes 2 bytes.
-           Also note the use of memcmp() instead of strncmp() because the latter stops on nullbytes,
-           but UCS-2 uses nullbytes to fill up glyphs which only use one byte. */
-        if ((new_len == client->name_len) &&
-            (client->name != NULL) &&
-            (memcmp(client->name, ucs2_name, new_len * 2) == 0)) {
-                free(ucs2_name);
-                return 1;
-        }
-
-        char *old_name = client->name;
-        client->name = ucs2_name;
-        client->name_len = new_len;
-        client->uses_net_wm_name = true;
-
-        FREE(old_name);
-
-        /* If the client is a dock window, we don’t need to render anything */
-        if (client->dock)
-                return 1;
+    Con *con;
+    if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
+        return false;
 
-        if (!workspace_is_visible(client->workspace))
-                return 1;
+    window_update_name(con->window, prop, false);
 
-        int mode = container_mode(client->container, true);
-        if (mode == MODE_STACK || mode == MODE_TABBED)
-                render_container(conn, client->container);
-        else decorate_window(conn, client, client->frame, client->titlegc, 0, 0);
-        xcb_flush(conn);
+    x_push_changes(croot);
 
-        return 1;
+    return true;
 }
 
 /*
- * We handle legacy window names (titles) which are in COMPOUND_TEXT encoding. However, we
- * just pass them along, so when containing non-ASCII characters, those will be rendering
- * incorrectly. In order to correctly render unicode window titles in i3, an application
- * has to set _NET_WM_NAME, which is in UTF-8 encoding.
- *
- * On every update, a message is put out to the user, so he may improve the situation and
- * update applications which display filenames in their title to correctly use
- * _NET_WM_NAME and therefore support unicode.
+ * Handles legacy window name updates (WM_NAME), see also src/window.c,
+ * window_update_name_legacy().
  *
  */
-int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t state,
+static bool handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t state,
                                 xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
-        if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
-                DLOG("prop == NULL\n");
-                return 1;
-        }
-        Client *client = table_get(&by_child, window);
-        if (client == NULL)
-                return 1;
-
-        /* Client capable of _NET_WM_NAME, ignore legacy name changes */
-        if (client->uses_net_wm_name)
-                return 1;
-
-        /* Save the old pointer to make the update atomic */
-        char *new_name;
-        if (asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)) == -1) {
-                perror("Could not get old name");
-                DLOG("Could not get old name\n");
-                return 1;
-        }
-        /* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */
-        LOG("WM_NAME changed to \"%s\"\n", new_name);
-
-        /* Check if they are the same and don’t update if so. */
-        if (client->name != NULL &&
-            strlen(new_name) == strlen(client->name) &&
-            strcmp(client->name, new_name) == 0) {
-                free(new_name);
-                return 1;
-        }
-
-        LOG("Using legacy window title. Note that in order to get Unicode window titles in i3, "
-            "the application has to set _NET_WM_NAME which is in UTF-8 encoding.\n");
-
-        char *old_name = client->name;
-        client->name = new_name;
-        client->name_len = -1;
-
-        if (old_name != NULL)
-                free(old_name);
+    Con *con;
+    if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
+        return false;
 
-        /* If the client is a dock window, we don’t need to render anything */
-        if (client->dock)
-                return 1;
+    window_update_name_legacy(con->window, prop, false);
 
-        if (!workspace_is_visible(client->workspace))
-                return 1;
-
-        if (client->container != NULL &&
-            (client->container->mode == MODE_STACK ||
-             client->container->mode == MODE_TABBED))
-                render_container(conn, client->container);
-        else decorate_window(conn, client, client->frame, client->titlegc, 0, 0);
-        xcb_flush(conn);
+    x_push_changes(croot);
 
-        return 1;
+    return true;
 }
 
+#if 0
 /*
  * Updates the client’s WM_CLASS property
  *
  */
-int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state,
+static int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state,
                              xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
-        if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
-                DLOG("prop == NULL\n");
-                return 1;
-        }
-        Client *client = table_get(&by_child, window);
-        if (client == NULL)
-                return 1;
-
-        /* We cannot use asprintf here since this property contains two
-         * null-terminated strings (for compatibility reasons). Instead, we
-         * use strdup() on both strings */
-        char *new_class = xcb_get_property_value(prop);
-
-        FREE(client->window_class_instance);
-        FREE(client->window_class_class);
+    Con *con;
+    if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
+        return 1;
 
-        client->window_class_instance = strdup(new_class);
-        if ((strlen(new_class) + 1) < xcb_get_property_value_length(prop))
-                client->window_class_class = strdup(new_class + strlen(new_class) + 1);
-        else client->window_class_class = NULL;
-        LOG("WM_CLASS changed to %s (instance), %s (class)\n",
-            client->window_class_instance, client->window_class_class);
+    window_update_class(con->window, prop, false);
 
-        return 0;
+    return 0;
 }
+#endif
 
 /*
  * Expose event means we should redraw our windows (= title bar)
  *
  */
-int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *event) {
-        /* 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);
+static int handle_expose_event(xcb_expose_event_t *event) {
+    Con *parent;
 
-        Client *client = table_get(&by_parent, event->window);
-        if (client == NULL) {
-                /* There was no client in the table, so this is probably an expose event for
-                   one of our stack_windows. */
-                struct Stack_Window *stack_win;
-                SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
-                        if (stack_win->window == event->window) {
-                                render_container(conn, stack_win->container);
-                                return 1;
-                        }
-
-                /* …or one of the bars? */
-                Output *output;
-                TAILQ_FOREACH(output, &outputs, outputs)
-                        if (output->bar == event->window)
-                                render_layout(conn);
-                return 1;
-        }
+    /* 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;
 
-        if (client->dock)
-                return 1;
+    DLOG("window = %08x\n", event->window);
 
-        if (container_mode(client->container, true) == MODE_DEFAULT)
-                decorate_window(conn, client, client->frame, client->titlegc, 0, 0);
-        else {
-                uint32_t background_color;
-                if (client->urgent)
-                        background_color = config.client.urgent.background;
-                /* Distinguish if the window is currently focused… */
-                else if (CUR_CELL != NULL && CUR_CELL->currently_focused == client)
-                        background_color = config.client.focused.background;
-                /* …or if it is the focused window in a not focused container */
-                else background_color = config.client.focused_inactive.background;
-
-                /* Set foreground color to current focused color, line width to 2 */
-                uint32_t values[] = {background_color, 2};
-                xcb_change_gc(conn, client->titlegc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
-
-                /* Draw the border, the ±1 is for line width = 2 */
-                xcb_point_t points[] = {{1, 0},                                           /* left upper edge */
-                                        {1, client->rect.height-1},                       /* left bottom edge */
-                                        {client->rect.width-1, client->rect.height-1},    /* right bottom edge */
-                                        {client->rect.width-1, 0}};                       /* right upper edge */
-                xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, client->frame, client->titlegc, 4, points);
-
-                /* Draw the background */
-                xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, config.client.background);
-                if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) {
-                        xcb_rectangle_t crect = {1, 0, client->rect.width - (1 + 1), client->rect.height - 1};
-                        xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
-                } else {
-                        xcb_rectangle_t crect = {2, 0, client->rect.width - (2 + 2), client->rect.height - 2};
-                        xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
-                }
-        }
-        xcb_flush(conn);
+    if ((parent = con_by_frame_id(event->window)) == NULL) {
+        LOG("expose event for unknown window, ignoring\n");
         return 1;
+    }
+
+    /* re-render the parent (recursively, if it’s a split con) */
+    x_deco_recurse(parent);
+    xcb_flush(conn);
+
+    return 1;
 }
 
 /*
  * Handle client messages (EWMH)
  *
  */
-int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message_event_t *event) {
-        if (event->type == atoms[_NET_WM_STATE]) {
-                if (event->format != 32 || event->data.data32[1] != atoms[_NET_WM_STATE_FULLSCREEN])
-                        return 0;
-
-                Client *client = table_get(&by_child, event->window);
-                if (client == NULL)
-                        return 0;
-
-                /* Check if the fullscreen state should be toggled */
-                if ((client->fullscreen &&
-                     (event->data.data32[0] == _NET_WM_STATE_REMOVE ||
-                      event->data.data32[0] == _NET_WM_STATE_TOGGLE)) ||
-                    (!client->fullscreen &&
-                     (event->data.data32[0] == _NET_WM_STATE_ADD ||
-                      event->data.data32[0] == _NET_WM_STATE_TOGGLE)))
-                        client_toggle_fullscreen(conn, client);
-        } else {
-                ELOG("unhandled clientmessage\n");
-                return 0;
+static int handle_client_message(xcb_client_message_event_t *event) {
+    LOG("ClientMessage for window 0x%08x\n", event->window);
+    if (event->type == A__NET_WM_STATE) {
+        if (event->format != 32 || event->data.data32[1] != A__NET_WM_STATE_FULLSCREEN) {
+            DLOG("atom in clientmessage is %d, fullscreen is %d\n",
+                    event->data.data32[1], A__NET_WM_STATE_FULLSCREEN);
+            DLOG("not about fullscreen atom\n");
+            return 0;
         }
 
-        return 1;
+        Con *con = con_by_window_id(event->window);
+        if (con == NULL) {
+            DLOG("Could not get window for client message\n");
+            return 0;
+        }
+
+        /* Check if the fullscreen state should be toggled */
+        if ((con->fullscreen_mode != CF_NONE &&
+             (event->data.data32[0] == _NET_WM_STATE_REMOVE ||
+              event->data.data32[0] == _NET_WM_STATE_TOGGLE)) ||
+            (con->fullscreen_mode == CF_NONE &&
+             (event->data.data32[0] == _NET_WM_STATE_ADD ||
+              event->data.data32[0] == _NET_WM_STATE_TOGGLE))) {
+            DLOG("toggling fullscreen\n");
+            con_toggle_fullscreen(con, CF_OUTPUT);
+        }
+
+        tree_render();
+        x_push_changes(croot);
+    } else {
+        ELOG("unhandled clientmessage\n");
+        return 0;
+    }
+
+    return 1;
 }
 
+#if 0
 int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
                         xcb_atom_t atom, xcb_get_property_reply_t *property) {
         /* TODO: Implement this one. To do this, implement a little test program which sleep(1)s
@@ -867,6 +666,7 @@ int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_wi
         ELOG("_NET_WM_WINDOW_TYPE changed, this is not yet implemented.\n");
         return 0;
 }
+#endif
 
 /*
  * Handles the size hints set by a window, but currently only the part necessary for displaying
@@ -875,222 +675,401 @@ int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_wi
  * See ICCCM 4.1.2.3 for more details
  *
  */
-int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
+static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
                         xcb_atom_t name, xcb_get_property_reply_t *reply) {
-        Client *client = table_get(&by_child, window);
-        if (client == NULL) {
-                DLOG("Received WM_SIZE_HINTS for unknown client\n");
-                return 1;
-        }
-        xcb_size_hints_t size_hints;
-
-        CLIENT_LOG(client);
+    Con *con = con_by_window_id(window);
+    if (con == NULL) {
+        DLOG("Received WM_NORMAL_HINTS for unknown client\n");
+        return false;
+    }
 
-        /* If the hints were already in this event, use them, if not, request them */
-        if (reply != NULL)
-                xcb_get_wm_size_hints_from_reply(&size_hints, reply);
-        else
-                xcb_get_wm_normal_hints_reply(conn, xcb_get_wm_normal_hints_unchecked(conn, client->child), &size_hints, NULL);
+    xcb_size_hints_t size_hints;
 
-        if ((size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE)) {
-                // TODO: Minimum size is not yet implemented
-                DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height);
-        }
+        //CLIENT_LOG(client);
 
-        bool changed = false;
-        if ((size_hints.flags & XCB_SIZE_HINT_P_RESIZE_INC)) {
-                if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF)
-                        if (client->width_increment != size_hints.width_inc) {
-                                client->width_increment = size_hints.width_inc;
-                                changed = true;
-                        }
-                if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF)
-                        if (client->height_increment != size_hints.height_inc) {
-                                client->height_increment = size_hints.height_inc;
-                                changed = true;
-                        }
-
-                if (changed)
-                        DLOG("resize increments changed\n");
-        }
+    /* If the hints were already in this event, use them, if not, request them */
+    if (reply != NULL)
+        xcb_icccm_get_wm_size_hints_from_reply(&size_hints, reply);
+    else
+        xcb_icccm_get_wm_normal_hints_reply(conn, xcb_icccm_get_wm_normal_hints_unchecked(conn, con->window->id), &size_hints, NULL);
 
-        int base_width = 0, base_height = 0;
-
-        /* base_width/height are the desired size of the window.
-           We check if either the program-specified size or the program-specified
-           min-size is available */
-        if (size_hints.flags & XCB_SIZE_HINT_BASE_SIZE) {
-                base_width = size_hints.base_width;
-                base_height = size_hints.base_height;
-        } else if (size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE) {
-                /* TODO: is this right? icccm says not */
-                base_width = size_hints.min_width;
-                base_height = size_hints.min_height;
-        }
+    if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE)) {
+        // TODO: Minimum size is not yet implemented
+        DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height);
+    }
 
-        if (base_width != client->base_width ||
-            base_height != client->base_height) {
-                client->base_width = base_width;
-                client->base_height = base_height;
-                DLOG("client's base_height changed to %d\n", base_height);
-                DLOG("client's base_width changed to %d\n", base_width);
+    bool changed = false;
+    if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC)) {
+        if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF)
+            if (con->width_increment != size_hints.width_inc) {
+                con->width_increment = size_hints.width_inc;
                 changed = true;
+            }
+        if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF)
+            if (con->height_increment != size_hints.height_inc) {
+                con->height_increment = size_hints.height_inc;
+                changed = true;
+            }
+
+        if (changed)
+            DLOG("resize increments changed\n");
+    }
+
+    int base_width = 0, base_height = 0;
+
+    /* base_width/height are the desired size of the window.
+       We check if either the program-specified size or the program-specified
+       min-size is available */
+    if (size_hints.flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE) {
+        base_width = size_hints.base_width;
+        base_height = size_hints.base_height;
+    } else if (size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) {
+        /* TODO: is this right? icccm says not */
+        base_width = size_hints.min_width;
+        base_height = size_hints.min_height;
+    }
+
+    if (base_width != con->base_width ||
+        base_height != con->base_height) {
+        con->base_width = base_width;
+        con->base_height = base_height;
+        DLOG("client's base_height changed to %d\n", base_height);
+        DLOG("client's base_width changed to %d\n", base_width);
+        changed = true;
+    }
+
+    /* If no aspect ratio was set or if it was invalid, we ignore the hints */
+    if (!(size_hints.flags & XCB_ICCCM_SIZE_HINT_P_ASPECT) ||
+        (size_hints.min_aspect_num <= 0) ||
+        (size_hints.min_aspect_den <= 0)) {
+        goto render_and_return;
+    }
+
+    /* XXX: do we really use rect here, not window_rect? */
+    double width = con->rect.width - base_width;
+    double height = con->rect.height - base_height;
+    /* Convert numerator/denominator to a double */
+    double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den;
+    double max_aspect = (double)size_hints.max_aspect_num / size_hints.min_aspect_den;
+
+    DLOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect);
+    DLOG("width = %f, height = %f\n", width, height);
+
+    /* Sanity checks, this is user-input, in a way */
+    if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0)
+        goto render_and_return;
+
+    /* Check if we need to set proportional_* variables using the correct ratio */
+    if ((width / height) < min_aspect) {
+        if (con->proportional_width != width ||
+            con->proportional_height != (width / min_aspect)) {
+            con->proportional_width = width;
+            con->proportional_height = width / min_aspect;
+            changed = true;
         }
-
-        if (changed) {
-                if (client->fullscreen)
-                        DLOG("Not resizing client, it is in fullscreen mode\n");
-                else {
-                        resize_client(conn, client);
-                        xcb_flush(conn);
-                }
-        }
-
-        /* If no aspect ratio was set or if it was invalid, we ignore the hints */
-        if (!(size_hints.flags & XCB_SIZE_HINT_P_ASPECT) ||
-            (size_hints.min_aspect_num <= 0) ||
-            (size_hints.min_aspect_den <= 0)) {
-                return 1;
-        }
-
-        double width = client->rect.width - base_width;
-        double height = client->rect.height - base_height;
-        /* Convert numerator/denominator to a double */
-        double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den;
-        double max_aspect = (double)size_hints.max_aspect_num / size_hints.min_aspect_den;
-
-        DLOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect);
-        DLOG("width = %f, height = %f\n", width, height);
-
-        /* Sanity checks, this is user-input, in a way */
-        if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0)
-                return 1;
-
-        /* Check if we need to set proportional_* variables using the correct ratio */
-        if ((width / height) < min_aspect) {
-                client->proportional_width = width;
-                client->proportional_height = width / min_aspect;
-        } else if ((width / height) > max_aspect) {
-                client->proportional_width = width;
-                client->proportional_height = width / max_aspect;
-        } else return 1;
-
-        client->force_reconfigure = true;
-
-        if (client->container != NULL && workspace_is_visible(client->workspace)) {
-                render_container(conn, client->container);
-                xcb_flush(conn);
+    } else if ((width / height) > max_aspect) {
+        if (con->proportional_width != width ||
+            con->proportional_height != (width / max_aspect)) {
+            con->proportional_width = width;
+            con->proportional_height = width / max_aspect;
+            changed = true;
         }
+    } else goto render_and_return;
 
-        return 1;
+render_and_return:
+    if (changed)
+        tree_render();
+    FREE(reply);
+    return true;
 }
 
 /*
  * Handles the WM_HINTS property for extracting the urgency state of the window.
  *
  */
-int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
+static bool handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
                   xcb_atom_t name, xcb_get_property_reply_t *reply) {
-        Client *client = table_get(&by_child, window);
-        if (client == NULL) {
-                DLOG("Received WM_HINTS for unknown client\n");
-                return 1;
-        }
-        xcb_wm_hints_t hints;
+    Con *con = con_by_window_id(window);
+    if (con == NULL) {
+        DLOG("Received WM_HINTS for unknown client\n");
+        return false;
+    }
+
+    xcb_icccm_wm_hints_t hints;
+
+    if (reply != NULL) {
+        if (!xcb_icccm_get_wm_hints_from_reply(&hints, reply))
+            return false;
+    } else {
+        if (!xcb_icccm_get_wm_hints_reply(conn, xcb_icccm_get_wm_hints_unchecked(conn, con->window->id), &hints, NULL))
+            return false;
+    }
+
+    if (!con->urgent && focused == con) {
+        DLOG("Ignoring urgency flag for current client\n");
+        FREE(reply);
+        return true;
+    }
+
+    /* Update the flag on the client directly */
+    con->urgent = (xcb_icccm_wm_hints_get_urgency(&hints) != 0);
+    //CLIENT_LOG(con);
+    LOG("Urgency flag changed to %d\n", con->urgent);
+
+    Con *ws;
+    /* Set the urgency flag on the workspace, if a workspace could be found
+     * (for dock clients, that is not the case). */
+    if ((ws = con_get_workspace(con)) != NULL)
+        workspace_update_urgent_flag(ws);
+
+    tree_render();
+
+#if 0
+    /* If the workspace this client is on is not visible, we need to redraw
+     * the workspace bar */
+    if (!workspace_is_visible(client->workspace)) {
+            Output *output = client->workspace->output;
+            render_workspace(conn, output, output->current_workspace);
+            xcb_flush(conn);
+    }
+#endif
+
+    FREE(reply);
+    return true;
+}
 
-        if (reply != NULL) {
-                if (!xcb_get_wm_hints_from_reply(&hints, reply))
-                        return 1;
-        } else {
-                if (!xcb_get_wm_hints_reply(conn, xcb_get_wm_hints_unchecked(conn, client->child), &hints, NULL))
-                        return 1;
-        }
+/*
+ * Handles the transient for hints set by a window, signalizing that this window is a popup window
+ * for some other window.
+ *
+ * See ICCCM 4.1.2.6 for more details
+ *
+ */
+static bool handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
+                         xcb_atom_t name, xcb_get_property_reply_t *prop) {
+    Con *con;
 
-        Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
-        if (!client->urgent && client == last_focused) {
-                DLOG("Ignoring urgency flag for current client\n");
-                return 1;
-        }
+    if ((con = con_by_window_id(window)) == NULL || con->window == NULL) {
+        DLOG("No such window\n");
+        return false;
+    }
+
+    if (prop == NULL) {
+        prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
+                                false, window, A_WM_TRANSIENT_FOR, A_WINDOW, 0, 32), NULL);
+        if (prop == NULL)
+            return false;
+    }
+
+    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;
+}
 
-        /* Update the flag on the client directly */
-        client->urgent = (xcb_wm_hints_get_urgency(&hints) != 0);
-        CLIENT_LOG(client);
-        LOG("Urgency flag changed to %d\n", client->urgent);
+/*
+ * Handles changes of the WM_CLIENT_LEADER atom which specifies if this is a
+ * toolwindow (or similar) and to which window it belongs (logical parent).
+ *
+ */
+static bool handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
+                        xcb_atom_t name, xcb_get_property_reply_t *prop) {
+    Con *con;
+    if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
+        return false;
 
-        workspace_update_urgent_flag(client->workspace);
+    if (prop == NULL) {
+        prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
+                                false, window, A_WM_CLIENT_LEADER, A_WINDOW, 0, 32), NULL);
+        if (prop == NULL)
+            return false;
+    }
 
-        /* If the workspace this client is on is not visible, we need to redraw
-         * the workspace bar */
-        if (!workspace_is_visible(client->workspace)) {
-                Output *output = client->workspace->output;
-                render_workspace(conn, output, output->current_workspace);
-                xcb_flush(conn);
-        } else {
-                redecorate_window(conn, client);
-        }
+    window_update_leader(con->window, prop);
 
-        return 1;
+    return true;
 }
 
 /*
- * Handles the transient for hints set by a window, signalizing that this window is a popup window
- * for some other window.
- *
- * See ICCCM 4.1.2.6 for more details
+ * Handles FocusIn events which are generated by clients (i3’s focus changes
+ * don’t generate FocusIn events due to a different EventMask) and updates the
+ * decorations accordingly.
  *
  */
-int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
-                         xcb_atom_t name, xcb_get_property_reply_t *reply) {
-        Client *client = table_get(&by_child, window);
-        if (client == NULL) {
-                DLOG("No such client\n");
-                return 1;
-        }
-
-        xcb_window_t transient_for;
+static int 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;
+    DLOG("That is con %p / %s\n", con, con->name);
 
-        if (reply != NULL) {
-                if (!xcb_get_wm_transient_for_from_reply(&transient_for, reply))
-                        return 1;
-        } else {
-                if (!xcb_get_wm_transient_for_reply(conn, xcb_get_wm_transient_for_unchecked(conn, window),
-                                                    &transient_for, NULL))
-                        return 1;
-        }
+    if (event->mode == XCB_NOTIFY_MODE_GRAB ||
+        event->mode == XCB_NOTIFY_MODE_UNGRAB) {
+        DLOG("FocusIn event for grab/ungrab, ignoring\n");
+        return 1;
+    }
 
-        if (client->floating == FLOATING_AUTO_OFF) {
-                DLOG("This is a popup window, putting into floating\n");
-                toggle_floating_mode(conn, client, true);
-        }
+    if (event->detail == XCB_NOTIFY_DETAIL_POINTER) {
+        DLOG("notify detail is pointer, ignoring this event\n");
+        return 1;
+    }
 
+    if (focused_id == event->event) {
+        DLOG("focus matches the currently focused window, not doing anything\n");
         return 1;
+    }
+
+    DLOG("focus is different, updating decorations\n");
+    con_focus(con);
+    /* We update focused_id because we don’t need to set focus again */
+    focused_id = event->event;
+    x_push_changes(croot);
+    return 1;
 }
 
+/* Returns false if the event could not be processed (e.g. the window could not
+ * be found), true otherwise */
+typedef bool (*cb_property_handler_t)(void *data, xcb_connection_t *c, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *property);
+
+struct property_handler_t {
+    xcb_atom_t atom;
+    uint32_t long_len;
+    cb_property_handler_t cb;
+};
+
+static struct property_handler_t property_handlers[] = {
+    { 0, 128, handle_windowname_change },
+    { 0, UINT_MAX, handle_hints },
+    { 0, 128, handle_windowname_change_legacy },
+    { 0, UINT_MAX, handle_normal_hints },
+    { 0, UINT_MAX, handle_clientleader_change },
+    { 0, UINT_MAX, handle_transient_for }
+};
+#define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t))
+
 /*
- * Handles changes of the WM_CLIENT_LEADER atom which specifies if this is a
- * toolwindow (or similar) and to which window it belongs (logical parent).
+ * Sets the appropriate atoms for the property handlers after the atoms were
+ * received from X11
  *
  */
-int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
-                        xcb_atom_t name, xcb_get_property_reply_t *prop) {
-        if (prop == NULL) {
-                prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
-                                        false, window, WM_CLIENT_LEADER, WINDOW, 0, 32), NULL);
-                if (prop == NULL)
-                        return 1;
-        }
+void property_handlers_init() {
+    property_handlers[0].atom = A__NET_WM_NAME;
+    property_handlers[1].atom = A_WM_HINTS;
+    property_handlers[2].atom = A_WM_NAME;
+    property_handlers[3].atom = A_WM_NORMAL_HINTS;
+    property_handlers[4].atom = A_WM_CLIENT_LEADER;
+    property_handlers[5].atom = A_WM_TRANSIENT_FOR;
+}
 
-        Client *client = table_get(&by_child, window);
-        if (client == NULL)
-                return 1;
+static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) {
+    struct property_handler_t *handler = NULL;
+    xcb_get_property_reply_t *propr = NULL;
 
-        xcb_window_t *leader = xcb_get_property_value(prop);
-        if (leader == NULL)
-                return 1;
+    for (int c = 0; c < sizeof(property_handlers) / sizeof(struct property_handler_t); c++) {
+        if (property_handlers[c].atom != atom)
+            continue;
 
-        DLOG("Client leader changed to %08x\n", *leader);
+        handler = &property_handlers[c];
+        break;
+    }
 
-        client->leader = *leader;
+    if (handler == NULL) {
+        DLOG("Unhandled property notify for atom %d (0x%08x)\n", atom, atom);
+        return;
+    }
 
-        return 1;
+    if (state != XCB_PROPERTY_DELETE) {
+        xcb_get_property_cookie_t cookie = xcb_get_property(conn, 0, window, atom, XCB_GET_PROPERTY_TYPE_ANY, 0, handler->long_len);
+        propr = xcb_get_property_reply(conn, cookie, 0);
+    }
+
+    /* the handler will free() the reply unless it returns false */
+    if (!handler->cb(NULL, conn, state, window, atom, propr))
+        FREE(propr);
+}
+
+/*
+ * Takes an xcb_generic_event_t and calls the appropriate handler, based on the
+ * event type.
+ *
+ */
+void handle_event(int type, xcb_generic_event_t *event) {
+    if (randr_base > -1 &&
+        type == randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY) {
+        handle_screen_change(event);
+        return;
+    }
+
+    switch (type) {
+        case XCB_KEY_PRESS:
+            handle_key_press((xcb_key_press_event_t*)event);
+            break;
+
+        case XCB_BUTTON_PRESS:
+            handle_button_press((xcb_button_press_event_t*)event);
+            break;
+
+        case XCB_MAP_REQUEST:
+            handle_map_request((xcb_map_request_event_t*)event);
+            break;
+
+        case XCB_UNMAP_NOTIFY:
+            handle_unmap_notify_event((xcb_unmap_notify_event_t*)event);
+            break;
+
+        case XCB_DESTROY_NOTIFY:
+            handle_destroy_notify_event((xcb_destroy_notify_event_t*)event);
+            break;
+
+        case XCB_EXPOSE:
+            handle_expose_event((xcb_expose_event_t*)event);
+            break;
+
+        case XCB_MOTION_NOTIFY:
+            handle_motion_notify((xcb_motion_notify_event_t*)event);
+            break;
+
+        /* Enter window = user moved his mouse over the window */
+        case XCB_ENTER_NOTIFY:
+            handle_enter_notify((xcb_enter_notify_event_t*)event);
+            break;
+
+        /* Client message are sent to the root window. The only interesting
+         * client message for us is _NET_WM_STATE, we honour
+         * _NET_WM_STATE_FULLSCREEN */
+        case XCB_CLIENT_MESSAGE:
+            handle_client_message((xcb_client_message_event_t*)event);
+            break;
+
+        /* Configure request = window tried to change size on its own */
+        case XCB_CONFIGURE_REQUEST:
+            handle_configure_request((xcb_configure_request_event_t*)event);
+            break;
+
+        /* Mapping notify = keyboard mapping changed (Xmodmap), re-grab bindings */
+        case XCB_MAPPING_NOTIFY:
+            handle_mapping_notify((xcb_mapping_notify_event_t*)event);
+            break;
+
+        case XCB_FOCUS_IN:
+            handle_focus_in((xcb_focus_in_event_t*)event);
+            break;
+
+        case XCB_PROPERTY_NOTIFY:
+            DLOG("Property notify\n");
+            xcb_property_notify_event_t *e = (xcb_property_notify_event_t*)event;
+            property_notify(e->state, e->window, e->atom);
+            break;
+
+        default:
+            DLOG("Unhandled event of type %d\n", type);
+            break;
+    }
 }
index 13bc4ffbc9926ca619e1c0c76ae6633eba11395d..b2cd482c3f1ba4023a3d15d09b4db7b90540e472 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -1,5 +1,5 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
  *
  * ipc.c: Everything about the UNIX domain sockets for IPC
  *
  */
-#include <sys/types.h>
 #include <sys/socket.h>
-#include <sys/stat.h>
 #include <sys/un.h>
 #include <fcntl.h>
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-#include <err.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <stdio.h>
 #include <libgen.h>
 #include <ev.h>
 #include <yajl/yajl_gen.h>
 #include <yajl/yajl_parse.h>
 #include <yajl/yajl_version.h>
 
-#include "queue.h"
-#include "ipc.h"
-#include "i3.h"
-#include "util.h"
-#include "commands.h"
-#include "log.h"
-#include "table.h"
-#include "randr.h"
-#include "config.h"
+#include "all.h"
+
+char *current_socketpath = NULL;
 
 /* Shorter names for all those yajl_gen_* functions */
 #define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__)
@@ -51,10 +36,10 @@ TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all
  *
  */
 static void set_nonblock(int sockfd) {
-        int flags = fcntl(sockfd, F_GETFL, 0);
-        flags |= O_NONBLOCK;
-        if (fcntl(sockfd, F_SETFL, flags) < 0)
-                err(-1, "Could not set O_NONBLOCK");
+    int flags = fcntl(sockfd, F_GETFL, 0);
+    flags |= O_NONBLOCK;
+    if (fcntl(sockfd, F_SETFL, flags) < 0)
+        err(-1, "Could not set O_NONBLOCK");
 }
 
 /*
@@ -62,56 +47,56 @@ static void set_nonblock(int sockfd) {
  *
  */
 static bool mkdirp(const char *path) {
-        if (mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0)
-                return true;
-        if (errno != ENOENT) {
-                ELOG("mkdir(%s) failed: %s\n", path, strerror(errno));
-                return false;
-        }
-        char *copy = strdup(path);
-        /* strip trailing slashes, if any */
-        while (copy[strlen(copy)-1] == '/')
-                copy[strlen(copy)-1] = '\0';
-
-        char *sep = strrchr(copy, '/');
-        if (sep == NULL)
-                return false;
-        *sep = '\0';
-        bool result = false;
-        if (mkdirp(copy))
-                result = mkdirp(path);
-        free(copy);
-
-        return result;
+    if (mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0)
+        return true;
+    if (errno != ENOENT) {
+        ELOG("mkdir(%s) failed: %s\n", path, strerror(errno));
+        return false;
+    }
+    char *copy = strdup(path);
+    /* strip trailing slashes, if any */
+    while (copy[strlen(copy)-1] == '/')
+        copy[strlen(copy)-1] = '\0';
+
+    char *sep = strrchr(copy, '/');
+    if (sep == NULL)
+        return false;
+    *sep = '\0';
+    bool result = false;
+    if (mkdirp(copy))
+        result = mkdirp(path);
+    free(copy);
+
+    return result;
 }
 
 static void ipc_send_message(int fd, const unsigned char *payload,
                              int message_type, int message_size) {
-        int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) +
-                          sizeof(uint32_t) + message_size;
-        char msg[buffer_size];
-        char *walk = msg;
-
-        strncpy(walk, "i3-ipc", buffer_size - 1);
-        walk += strlen("i3-ipc");
-        memcpy(walk, &message_size, sizeof(uint32_t));
-        walk += sizeof(uint32_t);
-        memcpy(walk, &message_type, sizeof(uint32_t));
-        walk += sizeof(uint32_t);
-        memcpy(walk, payload, message_size);
-
-        int sent_bytes = 0;
-        int bytes_to_go = buffer_size;
-        while (sent_bytes < bytes_to_go) {
-                int n = write(fd, msg + sent_bytes, bytes_to_go);
-                if (n == -1) {
-                        DLOG("write() failed: %s\n", strerror(errno));
-                        return;
-                }
-
-                sent_bytes += n;
-                bytes_to_go -= n;
+    int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) +
+                      sizeof(uint32_t) + message_size;
+    char msg[buffer_size];
+    char *walk = msg;
+
+    strncpy(walk, "i3-ipc", buffer_size - 1);
+    walk += strlen("i3-ipc");
+    memcpy(walk, &message_size, sizeof(uint32_t));
+    walk += sizeof(uint32_t);
+    memcpy(walk, &message_type, sizeof(uint32_t));
+    walk += sizeof(uint32_t);
+    memcpy(walk, payload, message_size);
+
+    int sent_bytes = 0;
+    int bytes_to_go = buffer_size;
+    while (sent_bytes < bytes_to_go) {
+        int n = write(fd, msg + sent_bytes, bytes_to_go);
+        if (n == -1) {
+            DLOG("write() failed: %s\n", strerror(errno));
+            return;
         }
+
+        sent_bytes += n;
+        bytes_to_go -= n;
+    }
 }
 
 /*
@@ -120,22 +105,22 @@ static void ipc_send_message(int fd, const unsigned char *payload,
  *
  */
 void ipc_send_event(const char *event, uint32_t message_type, const char *payload) {
-        ipc_client *current;
-        TAILQ_FOREACH(current, &all_clients, clients) {
-                /* see if this client is interested in this event */
-                bool interested = false;
-                for (int i = 0; i < current->num_events; i++) {
-                        if (strcasecmp(current->events[i], event) != 0)
-                                continue;
-                        interested = true;
-                        break;
-                }
-                if (!interested)
-                        continue;
-
-                ipc_send_message(current->fd, (const unsigned char*)payload,
-                                 message_type, strlen(payload));
+    ipc_client *current;
+    TAILQ_FOREACH(current, &all_clients, clients) {
+        /* see if this client is interested in this event */
+        bool interested = false;
+        for (int i = 0; i < current->num_events; i++) {
+            if (strcasecmp(current->events[i], event) != 0)
+                continue;
+            interested = true;
+            break;
         }
+        if (!interested)
+            continue;
+
+        ipc_send_message(current->fd, (const unsigned char*)payload,
+                         message_type, strlen(payload));
+    }
 }
 
 /*
@@ -144,11 +129,11 @@ void ipc_send_event(const char *event, uint32_t message_type, const char *payloa
  *
  */
 void ipc_shutdown() {
-        ipc_client *current;
-        TAILQ_FOREACH(current, &all_clients, clients) {
-                shutdown(current->fd, SHUT_RDWR);
-                close(current->fd);
-        }
+    ipc_client *current;
+    TAILQ_FOREACH(current, &all_clients, clients) {
+        shutdown(current->fd, SHUT_RDWR);
+        close(current->fd);
+    }
 }
 
 /*
@@ -157,18 +142,192 @@ void ipc_shutdown() {
  *
  */
 IPC_HANDLER(command) {
-        /* To get a properly terminated buffer, we copy
-         * message_size bytes out of the buffer */
-        char *command = scalloc(message_size);
-        strncpy(command, (const char*)message, message_size);
-        parse_command(global_conn, (const char*)command);
-        free(command);
-
-        /* For now, every command gets a positive acknowledge
-         * (will change with the new command parser) */
-        const char *reply = "{\"success\":true}";
-        ipc_send_message(fd, (const unsigned char*)reply,
-                         I3_IPC_REPLY_TYPE_COMMAND, strlen(reply));
+    /* To get a properly terminated buffer, we copy
+     * message_size bytes out of the buffer */
+    char *command = scalloc(message_size + 1);
+    strncpy(command, (const char*)message, message_size);
+    LOG("IPC: received: *%s*\n", command);
+    const char *reply = parse_cmd((const char*)command);
+    free(command);
+
+    /* If no reply was provided, we just use the default success message */
+    if (reply == NULL)
+        reply = "{\"success\":true}";
+    ipc_send_message(fd, (const unsigned char*)reply,
+                     I3_IPC_REPLY_TYPE_COMMAND, strlen(reply));
+}
+
+static void dump_rect(yajl_gen gen, const char *name, Rect r) {
+    ystr(name);
+    y(map_open);
+    ystr("x");
+    y(integer, r.x);
+    ystr("y");
+    y(integer, r.y);
+    ystr("width");
+    y(integer, r.width);
+    ystr("height");
+    y(integer, r.height);
+    y(map_close);
+}
+
+void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
+    y(map_open);
+    ystr("id");
+    y(integer, (long int)con);
+
+    ystr("type");
+    y(integer, con->type);
+
+    ystr("orientation");
+    switch (con->orientation) {
+        case NO_ORIENTATION:
+            ystr("none");
+            break;
+        case HORIZ:
+            ystr("horizontal");
+            break;
+        case VERT:
+            ystr("vertical");
+            break;
+    }
+
+    ystr("percent");
+    if (con->percent == 0.0)
+        y(null);
+    else y(double, con->percent);
+
+    ystr("urgent");
+    y(bool, con->urgent);
+
+    ystr("focused");
+    y(bool, (con == focused));
+
+    ystr("layout");
+    switch (con->layout) {
+        case L_DEFAULT:
+            ystr("default");
+            break;
+        case L_STACKED:
+            ystr("stacked");
+            break;
+        case L_TABBED:
+            ystr("tabbed");
+            break;
+        case L_DOCKAREA:
+            ystr("dockarea");
+            break;
+        case L_OUTPUT:
+            ystr("output");
+            break;
+    }
+
+    ystr("border");
+    switch (con->border_style) {
+        case BS_NORMAL:
+            ystr("normal");
+            break;
+        case BS_NONE:
+            ystr("none");
+            break;
+        case BS_1PIXEL:
+            ystr("1pixel");
+            break;
+    }
+
+    dump_rect(gen, "rect", con->rect);
+    dump_rect(gen, "window_rect", con->window_rect);
+    dump_rect(gen, "geometry", con->geometry);
+
+    ystr("name");
+    ystr(con->name);
+
+    if (con->type == CT_WORKSPACE) {
+        ystr("num");
+        y(integer, con->num);
+    }
+
+    ystr("window");
+    if (con->window)
+        y(integer, con->window->id);
+    else y(null);
+
+    ystr("nodes");
+    y(array_open);
+    Con *node;
+    if (con->type != CT_DOCKAREA || !inplace_restart) {
+        TAILQ_FOREACH(node, &(con->nodes_head), nodes) {
+            dump_node(gen, node, inplace_restart);
+        }
+    }
+    y(array_close);
+
+    ystr("floating_nodes");
+    y(array_open);
+    TAILQ_FOREACH(node, &(con->floating_head), floating_windows) {
+        dump_node(gen, node, inplace_restart);
+    }
+    y(array_close);
+
+    ystr("focus");
+    y(array_open);
+    TAILQ_FOREACH(node, &(con->focus_head), nodes) {
+        y(integer, (long int)node);
+    }
+    y(array_close);
+
+    ystr("fullscreen_mode");
+    y(integer, con->fullscreen_mode);
+
+    ystr("swallows");
+    y(array_open);
+    Match *match;
+    TAILQ_FOREACH(match, &(con->swallow_head), matches) {
+        if (match->dock != -1) {
+            y(map_open);
+            ystr("dock");
+            y(integer, match->dock);
+            ystr("insert_where");
+            y(integer, match->insert_where);
+            y(map_close);
+        }
+
+        /* TODO: the other swallow keys */
+    }
+
+    if (inplace_restart) {
+        if (con->window != NULL) {
+            y(map_open);
+            ystr("id");
+            y(integer, con->window->id);
+            y(map_close);
+        }
+    }
+    y(array_close);
+
+    y(map_close);
+}
+
+IPC_HANDLER(tree) {
+    setlocale(LC_NUMERIC, "C");
+#if YAJL_MAJOR >= 2
+    yajl_gen gen = yajl_gen_alloc(NULL);
+#else
+    yajl_gen gen = yajl_gen_alloc(NULL, NULL);
+#endif
+    dump_node(gen, croot, false);
+    setlocale(LC_NUMERIC, "");
+
+    const unsigned char *payload;
+#if YAJL_MAJOR >= 2
+    size_t length;
+#else
+    unsigned int length;
+#endif
+    y(get_buf, &payload, &length);
+
+    ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_TREE, length);
+    y(free);
 }
 
 /*
@@ -177,69 +336,70 @@ IPC_HANDLER(command) {
  *
  */
 IPC_HANDLER(get_workspaces) {
-        Workspace *ws;
-
-        Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
-        if (last_focused == SLIST_END(&(c_ws->focus_stack)))
-                last_focused = NULL;
-
 #if YAJL_MAJOR >= 2
-        yajl_gen gen = yajl_gen_alloc(NULL);
+    yajl_gen gen = yajl_gen_alloc(NULL);
 #else
-        yajl_gen gen = yajl_gen_alloc(NULL, NULL);
+    yajl_gen gen = yajl_gen_alloc(NULL, NULL);
 #endif
-        y(array_open);
-
-        TAILQ_FOREACH(ws, workspaces, workspaces) {
-                if (ws->output == NULL)
-                        continue;
-
-                y(map_open);
-                ystr("num");
-                y(integer, ws->num + 1);
-
-                ystr("name");
-                ystr(ws->utf8_name);
-
-                ystr("visible");
-                y(bool, ws->output->current_workspace == ws);
-
-                ystr("focused");
-                y(bool, c_ws == ws);
-
-                ystr("rect");
-                y(map_open);
-                ystr("x");
-                y(integer, ws->rect.x);
-                ystr("y");
-                y(integer, ws->rect.y);
-                ystr("width");
-                y(integer, ws->rect.width);
-                ystr("height");
-                y(integer, ws->rect.height);
-                y(map_close);
-
-                ystr("output");
-                ystr(ws->output->name);
-
-                ystr("urgent");
-                y(bool, ws->urgent);
-
-                y(map_close);
+    y(array_open);
+
+    Con *focused_ws = con_get_workspace(focused);
+
+    Con *output;
+    TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
+        Con *ws;
+        TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
+            assert(ws->type == CT_WORKSPACE);
+            y(map_open);
+
+            ystr("num");
+            if (ws->num == -1)
+                y(null);
+            else y(integer, ws->num);
+
+            ystr("name");
+            ystr(ws->name);
+
+            ystr("visible");
+            y(bool, workspace_is_visible(ws));
+
+            ystr("focused");
+            y(bool, ws == focused_ws);
+
+            ystr("rect");
+            y(map_open);
+            ystr("x");
+            y(integer, ws->rect.x);
+            ystr("y");
+            y(integer, ws->rect.y);
+            ystr("width");
+            y(integer, ws->rect.width);
+            ystr("height");
+            y(integer, ws->rect.height);
+            y(map_close);
+
+            ystr("output");
+            ystr(output->name);
+
+            ystr("urgent");
+            y(bool, ws->urgent);
+
+            y(map_close);
         }
+    }
 
-        y(array_close);
+    y(array_close);
 
-        const unsigned char *payload;
+    const unsigned char *payload;
 #if YAJL_MAJOR >= 2
-        size_t length;
+    size_t length;
 #else
-        unsigned int length;
+    unsigned int length;
 #endif
-        y(get_buf, &payload, &length);
+    y(get_buf, &payload, &length);
 
-        ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_WORKSPACES, length);
-        y(free);
+    ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_WORKSPACES, length);
+    y(free);
 }
 
 /*
@@ -248,87 +408,87 @@ IPC_HANDLER(get_workspaces) {
  *
  */
 IPC_HANDLER(get_outputs) {
-        Output *output;
-
 #if YAJL_MAJOR >= 2
-        yajl_gen gen = yajl_gen_alloc(NULL);
+    yajl_gen gen = yajl_gen_alloc(NULL);
 #else
-        yajl_gen gen = yajl_gen_alloc(NULL, NULL);
+    yajl_gen gen = yajl_gen_alloc(NULL, NULL);
 #endif
-        y(array_open);
-
-        TAILQ_FOREACH(output, &outputs, outputs) {
-                y(map_open);
-
-                ystr("name");
-                ystr(output->name);
-
-                ystr("active");
-                y(bool, output->active);
-
-                ystr("rect");
-                y(map_open);
-                ystr("x");
-                y(integer, output->rect.x);
-                ystr("y");
-                y(integer, output->rect.y);
-                ystr("width");
-                y(integer, output->rect.width);
-                ystr("height");
-                y(integer, output->rect.height);
-                y(map_close);
-
-                ystr("current_workspace");
-                if (output->current_workspace == NULL)
-                        y(null);
-                else y(integer, output->current_workspace->num + 1);
-
-                y(map_close);
-        }
-
-        y(array_close);
-
-        const unsigned char *payload;
+    y(array_open);
+
+    Output *output;
+    TAILQ_FOREACH(output, &outputs, outputs) {
+        y(map_open);
+
+        ystr("name");
+        ystr(output->name);
+
+        ystr("active");
+        y(bool, output->active);
+
+        ystr("rect");
+        y(map_open);
+        ystr("x");
+        y(integer, output->rect.x);
+        ystr("y");
+        y(integer, output->rect.y);
+        ystr("width");
+        y(integer, output->rect.width);
+        ystr("height");
+        y(integer, output->rect.height);
+        y(map_close);
+
+        ystr("current_workspace");
+        Con *ws = NULL;
+        if (output->con && (ws = con_get_fullscreen_con(output->con, CF_OUTPUT)))
+            ystr(ws->name);
+        else y(null);
+
+        y(map_close);
+    }
+
+    y(array_close);
+
+    const unsigned char *payload;
 #if YAJL_MAJOR >= 2
-        size_t length;
+    size_t length;
 #else
-        unsigned int length;
+    unsigned int length;
 #endif
-        y(get_buf, &payload, &length);
+    y(get_buf, &payload, &length);
 
-        ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_OUTPUTS, length);
-        y(free);
+    ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_OUTPUTS, length);
+    y(free);
 }
 
 /*
  * Callback for the YAJL parser (will be called when a string is parsed).
  *
  */
-#if YAJL_MAJOR >= 2
+#if YAJL_MAJOR < 2
 static int add_subscription(void *extra, const unsigned char *s,
-                            size_t len) {
+                            unsigned int len) {
 #else
 static int add_subscription(void *extra, const unsigned char *s,
-                            unsigned int len) {
+                            size_t len) {
 #endif
-        ipc_client *client = extra;
+    ipc_client *client = extra;
 
-        DLOG("should add subscription to extra %p, sub %.*s\n", client, len, s);
-        int event = client->num_events;
+    DLOG("should add subscription to extra %p, sub %.*s\n", client, len, s);
+    int event = client->num_events;
 
-        client->num_events++;
-        client->events = realloc(client->events, client->num_events * sizeof(char*));
-        /* We copy the string because it is not null-terminated and strndup()
-         * is missing on some BSD systems */
-        client->events[event] = scalloc(len+1);
-        memcpy(client->events[event], s, len);
+    client->num_events++;
+    client->events = realloc(client->events, client->num_events * sizeof(char*));
+    /* We copy the string because it is not null-terminated and strndup()
+     * is missing on some BSD systems */
+    client->events[event] = scalloc(len+1);
+    memcpy(client->events[event], s, len);
 
-        DLOG("client is now subscribed to:\n");
-        for (int i = 0; i < client->num_events; i++)
-                DLOG("event %s\n", client->events[i]);
-        DLOG("(done)\n");
+    DLOG("client is now subscribed to:\n");
+    for (int i = 0; i < client->num_events; i++)
+        DLOG("event %s\n", client->events[i]);
+    DLOG("(done)\n");
 
-        return 1;
+    return 1;
 }
 
 /*
@@ -337,61 +497,62 @@ static int add_subscription(void *extra, const unsigned char *s,
  *
  */
 IPC_HANDLER(subscribe) {
-        yajl_handle p;
-        yajl_callbacks callbacks;
-        yajl_status stat;
-        ipc_client *current, *client = NULL;
+    yajl_handle p;
+    yajl_callbacks callbacks;
+    yajl_status stat;
+    ipc_client *current, *client = NULL;
 
-        /* Search the ipc_client structure for this connection */
-        TAILQ_FOREACH(current, &all_clients, clients) {
-                if (current->fd != fd)
-                        continue;
+    /* Search the ipc_client structure for this connection */
+    TAILQ_FOREACH(current, &all_clients, clients) {
+        if (current->fd != fd)
+            continue;
 
-                client = current;
-                break;
-        }
+        client = current;
+        break;
+    }
 
-        if (client == NULL) {
-                ELOG("Could not find ipc_client data structure for fd %d\n", fd);
-                return;
-        }
+    if (client == NULL) {
+        ELOG("Could not find ipc_client data structure for fd %d\n", fd);
+        return;
+    }
 
-        /* Setup the JSON parser */
-        memset(&callbacks, 0, sizeof(yajl_callbacks));
-        callbacks.yajl_string = add_subscription;
+    /* Setup the JSON parser */
+    memset(&callbacks, 0, sizeof(yajl_callbacks));
+    callbacks.yajl_string = add_subscription;
 
 #if YAJL_MAJOR >= 2
-        p = yajl_alloc(&callbacks, NULL, (void*)client);
+    p = yajl_alloc(&callbacks, NULL, (void*)client);
 #else
-        p = yajl_alloc(&callbacks, NULL, NULL, (void*)client);
+    p = yajl_alloc(&callbacks, NULL, NULL, (void*)client);
 #endif
-        stat = yajl_parse(p, (const unsigned char*)message, message_size);
-        if (stat != yajl_status_ok) {
-                unsigned char *err;
-                err = yajl_get_error(p, true, (const unsigned char*)message,
-                                     message_size);
-                ELOG("YAJL parse error: %s\n", err);
-                yajl_free_error(p, err);
-
-                const char *reply = "{\"success\":false}";
-                ipc_send_message(fd, (const unsigned char*)reply,
-                                 I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply));
-                yajl_free(p);
-                return;
-        }
-        yajl_free(p);
-        const char *reply = "{\"success\":true}";
+    stat = yajl_parse(p, (const unsigned char*)message, message_size);
+    if (stat != yajl_status_ok) {
+        unsigned char *err;
+        err = yajl_get_error(p, true, (const unsigned char*)message,
+                             message_size);
+        ELOG("YAJL parse error: %s\n", err);
+        yajl_free_error(p, err);
+
+        const char *reply = "{\"success\":false}";
         ipc_send_message(fd, (const unsigned char*)reply,
                          I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply));
+        yajl_free(p);
+        return;
+    }
+    yajl_free(p);
+    const char *reply = "{\"success\":true}";
+    ipc_send_message(fd, (const unsigned char*)reply,
+                     I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply));
 }
 
 /* The index of each callback function corresponds to the numeric
  * value of the message type (see include/i3/ipc.h) */
-handler_t handlers[4] = {
-        handle_command,
-        handle_get_workspaces,
-        handle_subscribe,
-        handle_get_outputs
+handler_t handlers[5] = {
+    handle_command,
+    handle_get_workspaces,
+    handle_subscribe,
+    handle_get_outputs,
+    handle_tree
 };
 
 /*
@@ -405,89 +566,89 @@ handler_t handlers[4] = {
  *
  */
 static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
-        char buf[2048];
-        int n = read(w->fd, buf, sizeof(buf));
+    char buf[2048];
+    int n = read(w->fd, buf, sizeof(buf));
 
-        /* On error or an empty message, we close the connection */
-        if (n <= 0) {
+    /* On error or an empty message, we close the connection */
+    if (n <= 0) {
 #if 0
-                /* FIXME: I get these when closing a client socket,
-                 * therefore we just treat them as an error. Is this
-                 * correct? */
-                if (errno == EAGAIN || errno == EWOULDBLOCK)
-                        return;
-#endif
-
-                /* If not, there was some kind of error. We don’t bother
-                 * and close the connection */
-                close(w->fd);
-
-                /* Delete the client from the list of clients */
-                ipc_client *current;
-                TAILQ_FOREACH(current, &all_clients, clients) {
-                        if (current->fd != w->fd)
-                                continue;
-
-                        for (int i = 0; i < current->num_events; i++)
-                                free(current->events[i]);
-                        /* We can call TAILQ_REMOVE because we break out of the
-                         * TAILQ_FOREACH afterwards */
-                        TAILQ_REMOVE(&all_clients, current, clients);
-                        break;
-                }
-
-                ev_io_stop(EV_A_ w);
-
-                DLOG("IPC: client disconnected\n");
+        /* FIXME: I get these when closing a client socket,
+         * therefore we just treat them as an error. Is this
+         * correct? */
+        if (errno == EAGAIN || errno == EWOULDBLOCK)
                 return;
-        }
+#endif
 
-        /* Terminate the message correctly */
-        buf[n] = '\0';
+        /* If not, there was some kind of error. We don’t bother
+         * and close the connection */
+        close(w->fd);
 
-        /* Check if the message starts with the i3 IPC magic code */
-        if (n < strlen(I3_IPC_MAGIC)) {
-                DLOG("IPC: message too short, ignoring\n");
-                return;
+        /* Delete the client from the list of clients */
+        ipc_client *current;
+        TAILQ_FOREACH(current, &all_clients, clients) {
+            if (current->fd != w->fd)
+                continue;
+
+            for (int i = 0; i < current->num_events; i++)
+                free(current->events[i]);
+            /* We can call TAILQ_REMOVE because we break out of the
+             * TAILQ_FOREACH afterwards */
+            TAILQ_REMOVE(&all_clients, current, clients);
+            break;
         }
 
-        if (strncmp(buf, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) {
-                DLOG("IPC: message does not start with the IPC magic\n");
-                return;
+        ev_io_stop(EV_A_ w);
+
+        DLOG("IPC: client disconnected\n");
+        return;
+    }
+
+    /* Terminate the message correctly */
+    buf[n] = '\0';
+
+    /* Check if the message starts with the i3 IPC magic code */
+    if (n < strlen(I3_IPC_MAGIC)) {
+        DLOG("IPC: message too short, ignoring\n");
+        return;
+    }
+
+    if (strncmp(buf, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) {
+        DLOG("IPC: message does not start with the IPC magic\n");
+        return;
+    }
+
+    uint8_t *message = (uint8_t*)buf;
+    while (n > 0) {
+        DLOG("IPC: n = %d\n", n);
+        message += strlen(I3_IPC_MAGIC);
+        n -= strlen(I3_IPC_MAGIC);
+
+        /* The next 32 bit after the magic are the message size */
+        uint32_t message_size;
+        memcpy(&message_size, (uint32_t*)message, sizeof(uint32_t));
+        message += sizeof(uint32_t);
+        n -= sizeof(uint32_t);
+
+        if (message_size > n) {
+            DLOG("IPC: Either the message size was wrong or the message was not read completely, dropping\n");
+            return;
         }
 
-        uint8_t *message = (uint8_t*)buf;
-        while (n > 0) {
-                DLOG("IPC: n = %d\n", n);
-                message += strlen(I3_IPC_MAGIC);
-                n -= strlen(I3_IPC_MAGIC);
-
-                /* The next 32 bit after the magic are the message size */
-                uint32_t message_size;
-                memcpy(&message_size, (uint32_t*)message, sizeof(uint32_t));
-                message += sizeof(uint32_t);
-                n -= sizeof(uint32_t);
-
-                if (message_size > n) {
-                        DLOG("IPC: Either the message size was wrong or the message was not read completely, dropping\n");
-                        return;
-                }
-
-                /* The last 32 bits of the header are the message type */
-                uint32_t message_type;
-                memcpy(&message_type, (uint32_t*)message, sizeof(uint32_t));
-                message += sizeof(uint32_t);
-                n -= sizeof(uint32_t);
-
-                if (message_type >= (sizeof(handlers) / sizeof(handler_t)))
-                        DLOG("Unhandled message type: %d\n", message_type);
-                else {
-                        handler_t h = handlers[message_type];
-                        h(w->fd, message, n, message_size, message_type);
-                }
-                n -= message_size;
-                message += message_size;
+        /* The last 32 bits of the header are the message type */
+        uint32_t message_type;
+        memcpy(&message_type, (uint32_t*)message, sizeof(uint32_t));
+        message += sizeof(uint32_t);
+        n -= sizeof(uint32_t);
+
+        if (message_type >= (sizeof(handlers) / sizeof(handler_t)))
+            DLOG("Unhandled message type: %d\n", message_type);
+        else {
+            handler_t h = handlers[message_type];
+            h(w->fd, message, n, message_size, message_type);
         }
+        n -= message_size;
+        message += message_size;
+    }
 }
 
 /*
@@ -498,28 +659,28 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
  *
  */
 void ipc_new_client(EV_P_ struct ev_io *w, int revents) {
-        struct sockaddr_un peer;
-        socklen_t len = sizeof(struct sockaddr_un);
-        int client;
-        if ((client = accept(w->fd, (struct sockaddr*)&peer, &len)) < 0) {
-                if (errno == EINTR)
-                        return;
-                else perror("accept()");
-                return;
-        }
+    struct sockaddr_un peer;
+    socklen_t len = sizeof(struct sockaddr_un);
+    int client;
+    if ((client = accept(w->fd, (struct sockaddr*)&peer, &len)) < 0) {
+        if (errno == EINTR)
+            return;
+        else perror("accept()");
+        return;
+    }
 
-        set_nonblock(client);
+    set_nonblock(client);
 
-        struct ev_io *package = scalloc(sizeof(struct ev_io));
-        ev_io_init(package, ipc_receive_message, client, EV_READ);
-        ev_io_start(EV_A_ package);
+    struct ev_io *package = scalloc(sizeof(struct ev_io));
+    ev_io_init(package, ipc_receive_message, client, EV_READ);
+    ev_io_start(EV_A_ package);
 
-        DLOG("IPC: new client connected\n");
+    DLOG("IPC: new client connected\n");
 
-        ipc_client *new = scalloc(sizeof(ipc_client));
-        new->fd = client;
+    ipc_client *new = scalloc(sizeof(ipc_client));
+    new->fd = client;
 
-        TAILQ_INSERT_TAIL(&all_clients, new, clients);
+    TAILQ_INSERT_TAIL(&all_clients, new, clients);
 }
 
 /*
@@ -528,44 +689,47 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) {
  *
  */
 int ipc_create_socket(const char *filename) {
-        int sockfd;
-
-        char *resolved = resolve_tilde(filename);
-        DLOG("Creating IPC-socket at %s\n", resolved);
-        char *copy = sstrdup(resolved);
-        const char *dir = dirname(copy);
-        if (!path_exists(dir))
-                mkdirp(dir);
-        free(copy);
-
-        /* Unlink the unix domain socket before */
-        unlink(resolved);
-
-        if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
-                perror("socket()");
-                free(resolved);
-                return -1;
-        }
+    int sockfd;
 
-        (void)fcntl(sockfd, F_SETFD, FD_CLOEXEC);
+    FREE(current_socketpath);
 
-        struct sockaddr_un addr;
-        memset(&addr, 0, sizeof(struct sockaddr_un));
-        addr.sun_family = AF_LOCAL;
-        strncpy(addr.sun_path, resolved, sizeof(addr.sun_path) - 1);
-        if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) {
-                perror("bind()");
-                free(resolved);
-                return -1;
-        }
+    char *resolved = resolve_tilde(filename);
+    DLOG("Creating IPC-socket at %s\n", resolved);
+    char *copy = sstrdup(resolved);
+    const char *dir = dirname(copy);
+    if (!path_exists(dir))
+        mkdirp(dir);
+    free(copy);
 
+    /* Unlink the unix domain socket before */
+    unlink(resolved);
+
+    if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
+        perror("socket()");
         free(resolved);
-        set_nonblock(sockfd);
+        return -1;
+    }
 
-        if (listen(sockfd, 5) < 0) {
-                perror("listen()");
-                return -1;
-        }
+    (void)fcntl(sockfd, F_SETFD, FD_CLOEXEC);
+
+    struct sockaddr_un addr;
+    memset(&addr, 0, sizeof(struct sockaddr_un));
+    addr.sun_family = AF_LOCAL;
+    strncpy(addr.sun_path, resolved, sizeof(addr.sun_path) - 1);
+    if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) {
+        perror("bind()");
+        free(resolved);
+        return -1;
+    }
+
+    set_nonblock(sockfd);
+
+    if (listen(sockfd, 5) < 0) {
+        perror("listen()");
+        free(resolved);
+        return -1;
+    }
 
-        return sockfd;
+    current_socketpath = resolved;
+    return sockfd;
 }
diff --git a/src/layout.c b/src/layout.c
deleted file mode 100644 (file)
index c8a21bf..0000000
+++ /dev/null
@@ -1,812 +0,0 @@
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * layout.c: Functions handling layout/drawing of window decorations
- *
- */
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <xcb/xcb.h>
-#include <assert.h>
-#include <math.h>
-
-#include "config.h"
-#include "i3.h"
-#include "xcb.h"
-#include "table.h"
-#include "util.h"
-#include "randr.h"
-#include "layout.h"
-#include "client.h"
-#include "floating.h"
-#include "handlers.h"
-#include "workspace.h"
-#include "log.h"
-#include "container.h"
-
-/*
- * Gets the unoccupied space (= space which is available for windows which were resized by the user)
- * for the given row. This is necessary to render both, customly resized windows and never touched
- * windows correctly, meaning that the aspect ratio will be maintained when opening new windows.
- *
- */
-int get_unoccupied_x(Workspace *workspace) {
-        double unoccupied = workspace->rect.width;
-        double default_factor = 1.0 / workspace->cols;
-
-        DLOG("get_unoccupied_x(), starting with %f, default_factor = %f\n", unoccupied, default_factor);
-
-        for (int cols = 0; cols < workspace->cols; cols++) {
-                DLOG("width_factor[%d] = %f, unoccupied = %f\n", cols, workspace->width_factor[cols], unoccupied);
-                if (workspace->width_factor[cols] == 0)
-                        unoccupied -= workspace->rect.width * default_factor;
-        }
-
-        DLOG("unoccupied space: %f\n", unoccupied);
-        return unoccupied;
-}
-
-/* See get_unoccupied_x() */
-int get_unoccupied_y(Workspace *workspace) {
-        int height = workspace_height(workspace);
-        double unoccupied = height;
-        double default_factor = 1.0 / workspace->rows;
-
-        DLOG("get_unoccupied_y(), starting with %f, default_factor = %f\n", unoccupied, default_factor);
-
-        for (int rows = 0; rows < workspace->rows; rows++) {
-                DLOG("height_factor[%d] = %f, unoccupied = %f\n", rows, workspace->height_factor[rows], unoccupied);
-                if (workspace->height_factor[rows] == 0)
-                        unoccupied -= height * default_factor;
-        }
-
-        DLOG("unoccupied space: %f\n", unoccupied);
-        return unoccupied;
-}
-
-/*
- * Redecorates the given client correctly by checking if it’s in a stacking container and
- * re-rendering the stack window or just calling decorate_window if it’s not in a stacking
- * container.
- *
- */
-void redecorate_window(xcb_connection_t *conn, Client *client) {
-        if (client->container != NULL &&
-            (client->container->mode == MODE_STACK ||
-             client->container->mode == MODE_TABBED)) {
-                render_container(conn, client->container);
-                /* We clear the frame to generate exposure events, because the color used
-                   in drawing may be different */
-                xcb_clear_area(conn, true, client->frame, 0, 0, client->rect.width, client->rect.height);
-        } else decorate_window(conn, client, client->frame, client->titlegc, 0, 0);
-        xcb_flush(conn);
-}
-
-/*
- * (Re-)draws window decorations for a given Client onto the given drawable/graphic context.
- * When in stacking mode, the window decorations are drawn onto an own window.
- *
- */
-void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t drawable,
-                     xcb_gcontext_t gc, int offset_x, int offset_y) {
-        i3Font *font = load_font(conn, config.font);
-        int decoration_height = font->height + 2 + 2;
-        struct Colortriple *color;
-        Client *last_focused;
-
-        /* Clients without a container (docks) won’t get decorated */
-        if (client->dock)
-                return;
-
-        last_focused = SLIST_FIRST(&(client->workspace->focus_stack));
-        /* Is the window urgent? */
-        if (client->urgent)
-                color = &(config.client.urgent);
-        else {
-                if (client_is_floating(client)) {
-                        if (last_focused == client)
-                                color = &(config.client.focused);
-                        else color = &(config.client.unfocused);
-                } else {
-                        if (client->container->currently_focused == client) {
-                                /* Distinguish if the window is currently focused… */
-                                if (last_focused == client && c_ws == client->workspace)
-                                        color = &(config.client.focused);
-                                /* …or if it is the focused window in a not focused container */
-                                else color = &(config.client.focused_inactive);
-                        } else color = &(config.client.unfocused);
-                }
-        }
-
-        /* Our plan is the following:
-           - Draw a rect around the whole client in color->background
-           - Draw two lines in a lighter color
-           - Draw the window’s title
-         */
-        int mode = container_mode(client->container, true);
-
-        /* Draw a rectangle in background color around the window */
-        if (client->borderless && mode == MODE_DEFAULT)
-                xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, config.client.background);
-        else xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, color->background);
-
-        /* In stacking mode, we only render the rect for this specific decoration */
-        if (mode == MODE_STACK || mode == MODE_TABBED) {
-                /* We need to use the container’s width because it is the more recent value - when
-                   in stacking mode, clients get reconfigured only on demand (the not active client
-                   is not reconfigured), so the client’s rect.width would be wrong */
-                xcb_rectangle_t rect = {offset_x, offset_y,
-                                        offset_x + client->container->width,
-                                        offset_y + decoration_height };
-                xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
-        } else {
-                xcb_rectangle_t rect = {0, 0, client->rect.width, client->rect.height};
-                xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
-
-                /* Draw the inner background to a frame around clients (such as mplayer)
-                   which cannot be resized exactly in our frames and therefore are centered */
-                xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, config.client.background);
-                if (client->titlebar_position == TITLEBAR_OFF && client->borderless) {
-                        xcb_rectangle_t crect = {0, 0, client->rect.width, client->rect.height};
-                        xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
-                } else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) {
-                        xcb_rectangle_t crect = {1, 1, client->rect.width - (1 + 1), client->rect.height - (1 + 1)};
-                        xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
-                } else {
-                        xcb_rectangle_t crect = {2, decoration_height,
-                                                 client->rect.width - (2 + 2), client->rect.height - 2 - decoration_height};
-                        xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
-                }
-        }
-
-        mode = container_mode(client->container, false);
-
-        if (client->titlebar_position != TITLEBAR_OFF) {
-                /* Draw the lines */
-                xcb_draw_line(conn, drawable, gc, color->border, offset_x, offset_y, offset_x + client->rect.width, offset_y);
-                xcb_draw_line(conn, drawable, gc, color->border,
-                              offset_x + 2, /* x */
-                              offset_y + font->height + 3, /* y */
-                              offset_x + client->rect.width - 3, /* to_x */
-                              offset_y + font->height + 3 /* to_y */);
-        }
-
-        /* If the client has a title, we draw it */
-        if (client->name != NULL &&
-            (mode != MODE_DEFAULT || client->titlebar_position != TITLEBAR_OFF)) {
-                /* Draw the font */
-                uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
-                uint32_t values[] = { color->text, color->background, font->id };
-                xcb_change_gc(conn, gc, mask, values);
-
-                /* name_len == -1 means this is a legacy application which does not specify _NET_WM_NAME,
-                   and we don’t handle the old window name (COMPOUND_TEXT) but only _NET_WM_NAME, which
-                   is UTF-8 */
-                if (client->name_len == -1)
-                        xcb_image_text_8(conn, strlen(client->name), drawable, gc, offset_x + 3 /* X */,
-                                         offset_y + font->height /* Y = baseline of font */, client->name);
-                else
-                        xcb_image_text_16(conn, client->name_len, drawable, gc, offset_x + 3 /* X */,
-                                          offset_y + font->height /* Y = baseline of font */, (xcb_char2b_t*)client->name);
-        }
-}
-
-/*
- * Pushes the client’s x and y coordinates to X11
- *
- */
-void reposition_client(xcb_connection_t *conn, Client *client) {
-        Output *output;
-
-        DLOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y);
-        /* Note: We can use a pointer to client->x like an array of uint32_ts
-           because it is followed by client->y by definition */
-        xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x));
-
-        if (!client_is_floating(client))
-                return;
-
-        /* If the client is floating, we need to check if we moved it to a different workspace */
-        output = get_output_containing(client->rect.x + (client->rect.width / 2),
-                                       client->rect.y + (client->rect.height / 2));
-        if (client->workspace->output == output)
-                return;
-
-        if (output == NULL) {
-                DLOG("Boundary checking disabled, no output found for (%d, %d)\n", client->rect.x, client->rect.y);
-                return;
-        }
-
-        if (output->current_workspace == NULL) {
-                DLOG("Boundary checking deferred, no current workspace on output\n");
-                client->force_reconfigure = true;
-                return;
-        }
-
-        DLOG("Client is on workspace %p with output %p\n", client->workspace, client->workspace->output);
-        DLOG("but output at %d, %d is %p\n", client->rect.x, client->rect.y, output);
-        floating_assign_to_workspace(client, output->current_workspace);
-
-        set_focus(conn, client, true);
-}
-
-/*
- * Pushes the client’s width/height to X11 and resizes the child window. This
- * function also updates the client’s position, so if you work on tiling clients
- * only, you can use this function instead of separate calls to reposition_client
- * and resize_client to reduce flickering.
- *
- */
-void resize_client(xcb_connection_t *conn, Client *client) {
-        i3Font *font = load_font(conn, config.font);
-
-        DLOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y);
-        DLOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height);
-        xcb_set_window_rect(conn, client->frame, client->rect);
-
-        /* Adjust the position of the child inside its frame.
-         * The coordinates of the child are relative to its frame, we
-         * add a border of 2 pixel to each value */
-        Rect *rect = &(client->child_rect);
-        switch (container_mode(client->container, true)) {
-                case MODE_STACK:
-                case MODE_TABBED:
-                        rect->x = 2;
-                        rect->y = 0;
-                        rect->width = client->rect.width - (2 + 2);
-                        rect->height = client->rect.height - 2;
-                        break;
-                default:
-                        if (client->titlebar_position == TITLEBAR_OFF && client->borderless) {
-                                rect->x = 0;
-                                rect->y = 0;
-                                rect->width = client->rect.width;
-                                rect->height = client->rect.height;
-                        } else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) {
-                                rect->x = 1;
-                                rect->y = 1;
-                                rect->width = client->rect.width - 1 - 1;
-                                rect->height = client->rect.height - 1 - 1;
-                        } else {
-                                rect->x = 2;
-                                rect->y = font->height + 2 + 2;
-                                rect->width = client->rect.width - (2 + 2);
-                                rect->height = client->rect.height - ((font->height + 2 + 2) + 2);
-                        }
-                        break;
-        }
-
-        rect->width -= (2 * client->border_width);
-        rect->height -= (2 * client->border_width);
-
-        /* Obey the ratio, if any */
-        if (client->proportional_height != 0 &&
-            client->proportional_width != 0) {
-                DLOG("proportional height = %d, width = %d\n", client->proportional_height, client->proportional_width);
-                double new_height = rect->height + 1;
-                int new_width = rect->width;
-
-                while (new_height > rect->height) {
-                        new_height = ((double)client->proportional_height / client->proportional_width) * new_width;
-
-                        if (new_height > rect->height)
-                                new_width--;
-                }
-                /* Center the window */
-                rect->y += ceil(rect->height / 2) - floor(new_height / 2);
-                rect->x += ceil(rect->width / 2) - floor(new_width / 2);
-
-                rect->height = new_height;
-                rect->width = new_width;
-                DLOG("new_height = %f, new_width = %d\n", new_height, new_width);
-        }
-
-        if (client->height_increment > 1) {
-                int old_height = rect->height;
-                rect->height -= (rect->height - client->base_height) % client->height_increment;
-                DLOG("Lost %d pixel due to client's height_increment (%d px, base_height = %d)\n",
-                    old_height - rect->height, client->height_increment, client->base_height);
-        }
-
-        if (client->width_increment > 1) {
-                int old_width = rect->width;
-                rect->width -= (rect->width - client->base_width) % client->width_increment;
-                DLOG("Lost %d pixel due to client's width_increment (%d px, base_width = %d)\n",
-                    old_width - rect->width, client->width_increment, client->base_width);
-        }
-
-        DLOG("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height);
-
-        xcb_set_window_rect(conn, client->child, *rect);
-
-        /* After configuring a child window we need to fake a configure_notify_event (see ICCCM 4.2.3).
-         * This is necessary to inform the client of its position relative to the root window,
-         * not relative to its frame (as done in the configure_notify_event by the x server). */
-        fake_absolute_configure_notify(conn, client);
-
-        /* Force redrawing after resizing the window because any now lost
-         * pixels could contain old garbage. */
-        xcb_expose_event_t generated;
-        generated.window = client->frame;
-        generated.count = 0;
-        handle_expose_event(NULL, conn, &generated);
-}
-
-/*
- * Renders the given container. Is called by render_layout() or individually (for example
- * when focus changes in a stacking container)
- *
- */
-void render_container(xcb_connection_t *conn, Container *container) {
-        Client *client;
-        int num_clients = 0, current_client = 0;
-
-        CIRCLEQ_FOREACH(client, &(container->clients), clients)
-                num_clients++;
-
-        if (container->mode == MODE_DEFAULT) {
-                int height = (container->height / max(1, num_clients));
-                int rest_pixels = (container->height % max(1, num_clients));
-                DLOG("height per client = %d, rest = %d\n", height, rest_pixels);
-
-                CIRCLEQ_FOREACH(client, &(container->clients), clients) {
-                        /* If the client is in fullscreen mode, it does not get reconfigured */
-                        if (container->workspace->fullscreen_client == client) {
-                                current_client++;
-                                continue;
-                        }
-
-                        /* If we have some pixels left to distribute, add one
-                         * pixel to each client as long as possible. */
-                        int this_height = height;
-                        if (rest_pixels > 0) {
-                                height++;
-                                rest_pixels--;
-                        }
-                        /* Check if we changed client->x or client->y by updating it.
-                         * Note the bitwise OR instead of logical OR to force evaluation of both statements */
-                        if (client->force_reconfigure |
-                            update_if_necessary(&(client->rect.x), container->x) |
-                            update_if_necessary(&(client->rect.y), container->y +
-                                        (container->height / num_clients) * current_client) |
-                            update_if_necessary(&(client->rect.width), container->width) |
-                            update_if_necessary(&(client->rect.height), this_height))
-                                resize_client(conn, client);
-
-                        /* TODO: vertical default layout */
-
-                        client->force_reconfigure = false;
-
-                        current_client++;
-                }
-        } else {
-                i3Font *font = load_font(conn, config.font);
-                int decoration_height = (font->height + 2 + 2);
-                struct Stack_Window *stack_win = &(container->stack_win);
-                /* The size for each tab (width), necessary as a separate variable
-                 * because num_clients gets fixed to 1 in tabbed mode. */
-                int size_each = (num_clients == 0 ? container->width : container->width / num_clients);
-                int stack_lines = num_clients;
-
-                /* Check if we need to remap our stack title window, it gets unmapped when the container
-                   is empty in src/handlers.c:unmap_notify() */
-                if (stack_win->rect.height == 0 && num_clients > 1) {
-                        DLOG("remapping stack win\n");
-                        xcb_map_window(conn, stack_win->window);
-                } else DLOG("not remapping stackwin, height = %d, num_clients = %d\n",
-                                stack_win->rect.height, num_clients);
-
-                if (container->mode == MODE_TABBED) {
-                        /* By setting num_clients to 1 we force that the stack window will be only one line
-                         * high. The rest of the code is useful in both cases. */
-                        DLOG("tabbed mode, setting num_clients = 1\n");
-                        if (stack_lines > 1)
-                                stack_lines = 1;
-                }
-
-                if (container->stack_limit == STACK_LIMIT_COLS) {
-                        stack_lines = ceil((float)num_clients / container->stack_limit_value);
-                } else if (container->stack_limit == STACK_LIMIT_ROWS) {
-                        stack_lines = min(num_clients, container->stack_limit_value);
-                }
-
-                int height = decoration_height * stack_lines;
-                if (num_clients == 1) {
-                        height = 0;
-                        stack_win->rect.height = 0;
-                        xcb_unmap_window(conn, stack_win->window);
-
-                        DLOG("Just one client, setting height to %d\n", height);
-                }
-
-                /* Check if we need to reconfigure our stack title window */
-                if (height > 0 && (
-                     update_if_necessary(&(stack_win->rect.x), container->x) |
-                     update_if_necessary(&(stack_win->rect.y), container->y) |
-                     update_if_necessary(&(stack_win->rect.width), container->width) |
-                     update_if_necessary(&(stack_win->rect.height), height))) {
-
-                        /* Configuration can happen in two slightly different ways:
-
-                           If there is no client in fullscreen mode, 5 parameters are passed
-                           (x, y, width, height, stack mode is set to above which means top-most position).
-
-                           If there is a fullscreen client, the fourth parameter is set to to the
-                           fullscreen window as sibling and the stack mode is set to below, which means
-                           that the stack_window will be placed just below the sibling, that is, under
-                           the fullscreen window.
-                         */
-                        uint32_t values[] = { stack_win->rect.x, stack_win->rect.y,
-                                              stack_win->rect.width, stack_win->rect.height,
-                                              XCB_STACK_MODE_ABOVE, XCB_STACK_MODE_BELOW };
-                        uint32_t mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
-                                        XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
-                                        XCB_CONFIG_WINDOW_STACK_MODE;
-
-                        /* Raise the stack window, but keep it below the first floating client
-                         * and below the fullscreen client (if any) */
-                        Client *first_floating = TAILQ_FIRST(&(container->workspace->floating_clients));
-                        if (container->workspace->fullscreen_client != NULL) {
-                                mask |= XCB_CONFIG_WINDOW_SIBLING;
-                                values[4] = container->workspace->fullscreen_client->frame;
-                        } else if (first_floating != TAILQ_END(&(container->workspace->floating_clients))) {
-                                mask |= XCB_CONFIG_WINDOW_SIBLING;
-                                values[4] = first_floating->frame;
-                        }
-
-                        xcb_configure_window(conn, stack_win->window, mask, values);
-                }
-
-                /* Prepare the pixmap for usage */
-                if (num_clients > 1)
-                        cached_pixmap_prepare(conn, &(stack_win->pixmap));
-
-                int current_row = 0, current_col = 0;
-                int wrap = 0;
-
-                if (container->stack_limit == STACK_LIMIT_COLS) {
-                        /* wrap stores the number of rows after which we will
-                         * wrap to a new column. */
-                        wrap = ceil((float)num_clients / container->stack_limit_value);
-                } else if (container->stack_limit == STACK_LIMIT_ROWS) {
-                        /* When limiting rows, the wrap variable serves a
-                         * slightly different purpose: it holds the number of
-                         * pixels which each client will get. This is constant
-                         * during the following loop, so it saves us some
-                         * divisions and ceil()ing. */
-                        wrap = (stack_win->rect.width / ceil((float)num_clients / container->stack_limit_value));
-                }
-
-                /* Render the decorations of all clients */
-                CIRCLEQ_FOREACH(client, &(container->clients), clients) {
-                        /* If the client is in fullscreen mode, it does not get reconfigured */
-                        if (container->workspace->fullscreen_client == client) {
-                                current_client++;
-                                continue;
-                        }
-
-                        /* Check if we changed client->x or client->y by updating it.
-                         * Note the bitwise OR instead of logical OR to force evaluation of all statements */
-                        if (client->force_reconfigure |
-                            update_if_necessary(&(client->rect.x), container->x) |
-                            update_if_necessary(&(client->rect.y), container->y + height) |
-                            update_if_necessary(&(client->rect.width), container->width) |
-                            update_if_necessary(&(client->rect.height), container->height - height))
-                                resize_client(conn, client);
-
-                        client->force_reconfigure = false;
-
-                        int offset_x = 0;
-                        int offset_y = 0;
-                        if (container->mode == MODE_STACK ||
-                            (container->mode == MODE_TABBED &&
-                             container->stack_limit == STACK_LIMIT_COLS)) {
-                                if (container->stack_limit == STACK_LIMIT_COLS) {
-                                        offset_x = current_col * (stack_win->rect.width / container->stack_limit_value);
-                                        offset_y = current_row * decoration_height;
-                                        current_row++;
-                                        if ((current_row % wrap) == 0) {
-                                                current_col++;
-                                                current_row = 0;
-                                        }
-                                } else if (container->stack_limit == STACK_LIMIT_ROWS) {
-                                        offset_x = current_col * wrap;
-                                        offset_y = current_row * decoration_height;
-                                        current_row++;
-                                        if ((current_row % container->stack_limit_value) == 0) {
-                                                current_col++;
-                                                current_row = 0;
-                                        }
-                                } else {
-                                        offset_y = current_client * decoration_height;
-                                }
-                                current_client++;
-                        } else if (container->mode == MODE_TABBED) {
-                                if (container->stack_limit == STACK_LIMIT_ROWS) {
-                                        LOG("You limited a tabbed container in its rows. "
-                                            "This makes no sense in tabbing mode.\n");
-                                }
-                                offset_x = current_client++ * size_each;
-                        }
-                        if (stack_win->pixmap.id != XCB_NONE)
-                                decorate_window(conn, client, stack_win->pixmap.id,
-                                                stack_win->pixmap.gc, offset_x, offset_y);
-                        else
-                                decorate_window(conn, client, client->frame, client->titlegc, 0, 0);
-                }
-
-                /* Check if we need to fill one column because of an uneven
-                 * amount of windows */
-                if (container->mode == MODE_STACK) {
-                        if (container->stack_limit == STACK_LIMIT_COLS && (current_col % 2) != 0) {
-                                xcb_change_gc_single(conn, stack_win->pixmap.gc, XCB_GC_FOREGROUND, config.client.background);
-
-                                int offset_x = current_col * (stack_win->rect.width / container->stack_limit_value);
-                                int offset_y = current_row * decoration_height;
-                                xcb_rectangle_t rect = {offset_x, offset_y,
-                                                        offset_x + container->width,
-                                                        offset_y + decoration_height };
-                                xcb_poly_fill_rectangle(conn, stack_win->pixmap.id, stack_win->pixmap.gc, 1, &rect);
-                        } else if (container->stack_limit == STACK_LIMIT_ROWS && (current_row % 2) != 0) {
-                                xcb_change_gc_single(conn, stack_win->pixmap.gc, XCB_GC_FOREGROUND, config.client.background);
-
-                                int offset_x = current_col * wrap;
-                                int offset_y = current_row * decoration_height;
-                                xcb_rectangle_t rect = {offset_x, offset_y,
-                                                        offset_x + container->width,
-                                                        offset_y + decoration_height };
-                                xcb_poly_fill_rectangle(conn, stack_win->pixmap.id, stack_win->pixmap.gc, 1, &rect);
-                        }
-                }
-
-                if (stack_win->pixmap.id == XCB_NONE)
-                        return;
-                xcb_copy_area(conn, stack_win->pixmap.id, stack_win->window, stack_win->pixmap.gc,
-                              0, 0, 0, 0, stack_win->rect.width, stack_win->rect.height);
-        }
-}
-
-static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int *height) {
-        Client *client;
-        SLIST_FOREACH(client, &(r_ws->output->dock_clients), dock_clients) {
-                DLOG("client is at %d, should be at %d\n", client->rect.y, *height);
-                if (client->force_reconfigure |
-                    update_if_necessary(&(client->rect.x), r_ws->rect.x) |
-                    update_if_necessary(&(client->rect.y), *height))
-                        reposition_client(conn, client);
-
-                if (client->force_reconfigure |
-                    update_if_necessary(&(client->rect.width), width) |
-                    update_if_necessary(&(client->rect.height), client->desired_height))
-                        resize_client(conn, client);
-
-                client->force_reconfigure = false;
-                DLOG("desired_height = %d\n", client->desired_height);
-                *height += client->desired_height;
-        }
-}
-
-static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int width, int height) {
-        i3Font *font = load_font(conn, config.font);
-        Output *output = r_ws->output;
-        enum { SET_NORMAL = 0, SET_FOCUSED = 1 };
-
-        /* Fill the whole bar in black */
-        xcb_change_gc_single(conn, output->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
-        xcb_rectangle_t rect = {0, 0, width, height};
-        xcb_poly_fill_rectangle(conn, output->bar, output->bargc, 1, &rect);
-
-        /* Set font */
-        xcb_change_gc_single(conn, output->bargc, XCB_GC_FONT, font->id);
-
-        int drawn = 0;
-        Workspace *ws;
-        TAILQ_FOREACH(ws, workspaces, workspaces) {
-                if (ws->output != output)
-                        continue;
-
-                struct Colortriple *color;
-
-                if (output->current_workspace == ws)
-                        color = &(config.bar.focused);
-                else if (ws->urgent)
-                        color = &(config.bar.urgent);
-                else color = &(config.bar.unfocused);
-
-                /* Draw the outer rect */
-                xcb_draw_rect(conn, output->bar, output->bargc, color->border,
-                              drawn,              /* x */
-                              1,                  /* y */
-                              ws->text_width + 5 + 5, /* width = text width + 5 px left + 5px right */
-                              height - 2          /* height = max. height - 1 px upper and 1 px bottom border */);
-
-                /* Draw the background of this rect */
-                xcb_draw_rect(conn, output->bar, output->bargc, color->background,
-                              drawn + 1,
-                              2,
-                              ws->text_width + 4 + 4,
-                              height - 4);
-
-                xcb_change_gc_single(conn, output->bargc, XCB_GC_FOREGROUND, color->text);
-                xcb_change_gc_single(conn, output->bargc, XCB_GC_BACKGROUND, color->background);
-                xcb_image_text_16(conn, ws->name_len, output->bar, output->bargc, drawn + 5 /* X */,
-                                  font->height + 1 /* Y = baseline of font */,
-                                  (xcb_char2b_t*)ws->name);
-                drawn += ws->text_width + 12;
-        }
-}
-
-/*
- * Modifies the event mask of all clients on the given workspace to either ignore or to handle
- * enter notifies. It is handy to ignore notifies because they will be sent when a window is mapped
- * under the cursor, thus when the user didn’t enter the window actively at all.
- *
- */
-void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, bool ignore_enter_notify) {
-        Client *client;
-        uint32_t values[1];
-
-        FOR_TABLE(workspace) {
-                if (workspace->table[cols][rows] == NULL)
-                        continue;
-
-                CIRCLEQ_FOREACH(client, &(workspace->table[cols][rows]->clients), clients) {
-                        /* Change event mask for the decorations */
-                        values[0] = FRAME_EVENT_MASK;
-                        if (ignore_enter_notify)
-                                values[0] &= ~(XCB_EVENT_MASK_ENTER_WINDOW);
-                        xcb_change_window_attributes(conn, client->frame, XCB_CW_EVENT_MASK, values);
-
-                        /* Change event mask for the child itself */
-                        values[0] = CHILD_EVENT_MASK;
-                        if (ignore_enter_notify)
-                                values[0] &= ~(XCB_EVENT_MASK_ENTER_WINDOW);
-                        xcb_change_window_attributes(conn, client->child, XCB_CW_EVENT_MASK, values);
-                }
-        }
-}
-
-/*
- * Renders the given workspace on the given screen
- *
- */
-void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws) {
-        i3Font *font = load_font(conn, config.font);
-        int width = r_ws->rect.width;
-        int height = r_ws->rect.height;
-
-        /* Reserve space for dock clients */
-        Client *client;
-        SLIST_FOREACH(client, &(output->dock_clients), dock_clients)
-                height -= client->desired_height;
-
-        /* Space for the internal bar */
-        if (!config.disable_workspace_bar)
-                height -= (font->height + 6);
-
-        int xoffset[r_ws->rows];
-        int yoffset[r_ws->cols];
-        /* Initialize offsets */
-        for (int cols = 0; cols < r_ws->cols; cols++)
-                yoffset[cols] = r_ws->rect.y;
-        for (int rows = 0; rows < r_ws->rows; rows++)
-                xoffset[rows] = r_ws->rect.x;
-
-        ignore_enter_notify_forall(conn, r_ws, true);
-
-        /* Get the width of the cols */
-        int col_width[r_ws->cols];
-        int unoccupied_x = get_unoccupied_x(r_ws);
-        int default_col_width = unoccupied_x / r_ws->cols;
-        int total_col_width = 0;
-        for (int i = 0; i < r_ws->cols; ++i) {
-                col_width[i] = r_ws->width_factor[i] == 0 ? default_col_width : unoccupied_x * r_ws->width_factor[i];
-                total_col_width += col_width[i];
-        }
-
-        /* Correct rounding errors */
-        int error = r_ws->rect.width - total_col_width, error_index = r_ws->cols - 1;
-        int signal = error < 0 ? 1 : -1;
-        while (error) {
-                col_width[error_index] -= signal;
-                error += signal;
-                error_index = error_index == 0 ? r_ws->cols - 1 : error_index - 1;
-        }
-
-        /* Get the height of the rows */
-        int row_height[r_ws->rows];
-        int unoccupied_y = get_unoccupied_y(r_ws);
-        int default_row_height = unoccupied_y / r_ws->rows;
-        int total_row_height = 0;
-        for (int i = 0; i < r_ws->rows; ++i) {
-                row_height[i] = r_ws->height_factor[i] == 0 ? default_row_height : unoccupied_y * r_ws->height_factor[i];
-                total_row_height += row_height[i];
-        }
-
-        /* Correct rounding errors */
-        error = workspace_height(r_ws) - total_row_height;
-        error_index = r_ws->rows - 1;
-        signal = error < 0 ? 1 : -1;
-        while (error) {
-                row_height[error_index] -= signal;
-                error += signal;
-                error_index = error_index == 0 ? r_ws->rows - 1 : error_index - 1;
-        }
-
-        /* Go through the whole table and render what’s necessary */
-        FOR_TABLE(r_ws) {
-                Container *container = r_ws->table[cols][rows];
-                if (container == NULL)
-                        continue;
-                int single_width = -1, single_height = -1;
-                /* Update position of the container */
-                container->row = rows;
-                container->col = cols;
-                container->x = xoffset[rows];
-                container->y = yoffset[cols];
-                container->width = 0;
-
-                for (int c = 0; c < container->colspan; c++) {
-                        container->width += col_width[cols + c];
-                        if (single_width == -1)
-                                single_width = container->width;
-                }
-
-                DLOG("height is %d\n", height);
-
-                container->height = 0;
-
-                for (int c = 0; c < container->rowspan; c++) {
-                        container->height += row_height[rows + c];
-                        if (single_height == -1)
-                                single_height = container->height;
-                }
-
-                /* Render the container if it is not empty */
-                render_container(conn, container);
-
-                xoffset[rows] += single_width;
-                yoffset[cols] += single_height;
-        }
-
-        /* Reposition all floating clients with force_reconfigure == true */
-        TAILQ_FOREACH(client, &(r_ws->floating_clients), floating_clients) {
-                if (!client->force_reconfigure)
-                        continue;
-
-                client->force_reconfigure = false;
-                reposition_client(conn, client);
-                resize_client(conn, client);
-        }
-
-        ignore_enter_notify_forall(conn, r_ws, false);
-
-        render_bars(conn, r_ws, width, &height);
-        if (!config.disable_workspace_bar)
-                render_internal_bar(conn, r_ws, width, font->height + 6);
-}
-
-/*
- * Renders the whole layout, that is: Go through each screen, each workspace, each container
- * and render each client. This also renders the bars.
- *
- * If you don’t need to render *everything*, you should call render_container on the container
- * you want to refresh.
- *
- */
-void render_layout(xcb_connection_t *conn) {
-        Output *output;
-
-        TAILQ_FOREACH(output, &outputs, outputs)
-                if (output->current_workspace != NULL)
-                        render_workspace(conn, output, output->current_workspace);
-
-        xcb_flush(conn);
-}
diff --git a/src/load_layout.c b/src/load_layout.c
new file mode 100644 (file)
index 0000000..6e311f1
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ */
+#include <yajl/yajl_common.h>
+#include <yajl/yajl_gen.h>
+#include <yajl/yajl_parse.h>
+#include <yajl/yajl_version.h>
+
+#include "all.h"
+
+/* TODO: refactor the whole parsing thing */
+
+static char *last_key;
+static Con *json_node;
+static Con *to_focus;
+static bool parsing_swallows;
+static bool parsing_rect;
+static bool parsing_window_rect;
+static bool parsing_geometry;
+struct Match *current_swallow;
+
+static int json_start_map(void *ctx) {
+    LOG("start of map, last_key = %s\n", last_key);
+    if (parsing_swallows) {
+        LOG("creating new swallow\n");
+        current_swallow = smalloc(sizeof(Match));
+        match_init(current_swallow);
+        TAILQ_INSERT_TAIL(&(json_node->swallow_head), current_swallow, matches);
+    } else {
+        if (!parsing_rect && !parsing_window_rect && !parsing_geometry) {
+            if (last_key && strcasecmp(last_key, "floating_nodes") == 0) {
+                DLOG("New floating_node\n");
+                Con *ws = con_get_workspace(json_node);
+                json_node = con_new(NULL, NULL);
+                json_node->parent = ws;
+                DLOG("Parent is workspace = %p\n", ws);
+            } else {
+                Con *parent = json_node;
+                json_node = con_new(NULL, NULL);
+                json_node->parent = parent;
+            }
+        }
+    }
+    return 1;
+}
+
+static int json_end_map(void *ctx) {
+    LOG("end of map\n");
+    if (!parsing_swallows && !parsing_rect && !parsing_window_rect && !parsing_geometry) {
+        LOG("attaching\n");
+        con_attach(json_node, json_node->parent, true);
+        json_node = json_node->parent;
+    }
+    if (parsing_rect)
+        parsing_rect = false;
+    if (parsing_window_rect)
+        parsing_window_rect = false;
+    if (parsing_geometry)
+        parsing_geometry = false;
+    return 1;
+}
+
+static int json_end_array(void *ctx) {
+    LOG("end of array\n");
+    parsing_swallows = false;
+    return 1;
+}
+
+#if YAJL_MAJOR < 2
+static int json_key(void *ctx, const unsigned char *val, unsigned int len) {
+#else
+static int json_key(void *ctx, const unsigned char *val, size_t len) {
+#endif
+    LOG("key: %.*s\n", (int)len, val);
+    FREE(last_key);
+    last_key = scalloc((len+1) * sizeof(char));
+    memcpy(last_key, val, len);
+    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;
+    return 1;
+}
+
+#if YAJL_MAJOR >= 2
+static int json_string(void *ctx, const unsigned char *val, size_t len) {
+#else
+static int json_string(void *ctx, const unsigned char *val, unsigned int len) {
+#endif
+    LOG("string: %.*s for key %s\n", len, val, last_key);
+    if (parsing_swallows) {
+        /* TODO: the other swallowing keys */
+        if (strcasecmp(last_key, "class") == 0) {
+            current_swallow->class = scalloc((len+1) * sizeof(char));
+            memcpy(current_swallow->class, val, len);
+        }
+        LOG("unhandled yet: swallow\n");
+    } else {
+        if (strcasecmp(last_key, "name") == 0) {
+            json_node->name = scalloc((len+1) * sizeof(char));
+            memcpy(json_node->name, val, len);
+        } else if (strcasecmp(last_key, "sticky_group") == 0) {
+            json_node->sticky_group = scalloc((len+1) * sizeof(char));
+            memcpy(json_node->sticky_group, val, len);
+            LOG("sticky_group of this container is %s\n", json_node->sticky_group);
+        } else if (strcasecmp(last_key, "orientation") == 0) {
+            char *buf = NULL;
+            asprintf(&buf, "%.*s", (int)len, val);
+            if (strcasecmp(buf, "none") == 0)
+                json_node->orientation = NO_ORIENTATION;
+            else if (strcasecmp(buf, "horizontal") == 0)
+                json_node->orientation = HORIZ;
+            else if (strcasecmp(buf, "vertical") == 0)
+                json_node->orientation = VERT;
+            else LOG("Unhandled orientation: %s\n", buf);
+            free(buf);
+        } else if (strcasecmp(last_key, "border") == 0) {
+            char *buf = NULL;
+            asprintf(&buf, "%.*s", (int)len, val);
+            if (strcasecmp(buf, "none") == 0)
+                json_node->border_style = BS_NONE;
+            else if (strcasecmp(buf, "1pixel") == 0)
+                json_node->border_style = BS_1PIXEL;
+            else if (strcasecmp(buf, "normal") == 0)
+                json_node->border_style = BS_NORMAL;
+            else LOG("Unhandled \"border\": %s\n", buf);
+            free(buf);
+        } else if (strcasecmp(last_key, "layout") == 0) {
+            char *buf = NULL;
+            asprintf(&buf, "%.*s", (int)len, val);
+            if (strcasecmp(buf, "default") == 0)
+                json_node->layout = L_DEFAULT;
+            else if (strcasecmp(buf, "stacked") == 0)
+                json_node->layout = L_STACKED;
+            else if (strcasecmp(buf, "tabbed") == 0)
+                json_node->layout = L_TABBED;
+            else if (strcasecmp(buf, "dockarea") == 0)
+                json_node->layout = L_DOCKAREA;
+            else if (strcasecmp(buf, "output") == 0)
+                json_node->layout = L_OUTPUT;
+            else LOG("Unhandled \"layout\": %s\n", buf);
+            free(buf);
+        }
+    }
+    return 1;
+}
+
+#if YAJL_MAJOR >= 2
+static int json_int(void *ctx, long long val) {
+#else
+static int json_int(void *ctx, long val) {
+#endif
+    LOG("int %d for key %s\n", val, last_key);
+    // TODO: remove this after the next preview release
+    if (strcasecmp(last_key, "layout") == 0) {
+        json_node->layout = val;
+    }
+    if (strcasecmp(last_key, "type") == 0) {
+        json_node->type = val;
+    }
+    if (strcasecmp(last_key, "fullscreen_mode") == 0) {
+        json_node->fullscreen_mode = val;
+    }
+    if (strcasecmp(last_key, "focused") == 0 && val == 1) {
+        to_focus = json_node;
+    }
+
+    if (strcasecmp(last_key, "num") == 0)
+        json_node->num = val;
+
+    if (parsing_rect || parsing_window_rect || parsing_geometry) {
+        Rect *r;
+        if (parsing_rect)
+            r = &(json_node->rect);
+        else if (parsing_window_rect)
+            r = &(json_node->window_rect);
+        else r = &(json_node->geometry);
+        if (strcasecmp(last_key, "x") == 0)
+            r->x = val;
+        else if (strcasecmp(last_key, "y") == 0)
+            r->y = val;
+        else if (strcasecmp(last_key, "width") == 0)
+            r->width = val;
+        else if (strcasecmp(last_key, "height") == 0)
+            r->height = val;
+        else printf("WARNING: unknown key %s in rect\n", last_key);
+        printf("rect now: (%d, %d, %d, %d)\n",
+                r->x, r->y, r->width, r->height);
+    }
+    if (parsing_swallows) {
+        if (strcasecmp(last_key, "id") == 0) {
+            current_swallow->id = val;
+        }
+        if (strcasecmp(last_key, "dock") == 0) {
+            current_swallow->dock = val;
+        }
+        if (strcasecmp(last_key, "insert_where") == 0) {
+            current_swallow->insert_where = val;
+        }
+    }
+
+    return 1;
+}
+
+static int json_double(void *ctx, double val) {
+    LOG("double %f for key %s\n", val, last_key);
+    if (strcasecmp(last_key, "percent") == 0) {
+        json_node->percent = val;
+    }
+    return 1;
+}
+
+void tree_append_json(const char *filename) {
+    /* TODO: percent of other windows are not correctly fixed at the moment */
+    FILE *f;
+    if ((f = fopen(filename, "r")) == NULL) {
+        LOG("Cannot open file\n");
+        return;
+    }
+    char *buf = malloc(65535); /* TODO */
+    int n = fread(buf, 1, 65535, f);
+    LOG("read %d bytes\n", n);
+    yajl_gen g;
+    yajl_handle hand;
+    yajl_callbacks callbacks;
+    memset(&callbacks, '\0', sizeof(yajl_callbacks));
+    callbacks.yajl_start_map = json_start_map;
+    callbacks.yajl_end_map = json_end_map;
+    callbacks.yajl_end_array = json_end_array;
+    callbacks.yajl_string = json_string;
+    callbacks.yajl_map_key = json_key;
+    callbacks.yajl_integer = json_int;
+    callbacks.yajl_double = json_double;
+#if YAJL_MAJOR >= 2
+    g = yajl_gen_alloc(NULL);
+    hand = yajl_alloc(&callbacks, NULL, (void*)g);
+#else
+    g = yajl_gen_alloc(NULL, NULL);
+    hand = yajl_alloc(&callbacks, NULL, NULL, (void*)g);
+#endif
+    yajl_status stat;
+    json_node = focused;
+    to_focus = NULL;
+    parsing_rect = false;
+    parsing_window_rect = false;
+    parsing_geometry = false;
+    setlocale(LC_NUMERIC, "C");
+    stat = yajl_parse(hand, (const unsigned char*)buf, n);
+    if (stat != yajl_status_ok)
+    {
+        unsigned char * str = yajl_get_error(hand, 1, (const unsigned char*)buf, n);
+        fprintf(stderr, "%s\n", (const char *) str);
+        yajl_free_error(hand, str);
+    }
+
+    setlocale(LC_NUMERIC, "");
+#if YAJL_MAJOR >= 2
+    yajl_complete_parse(hand);
+#else
+    yajl_parse_complete(hand);
+#endif
+
+    fclose(f);
+    if (to_focus)
+        con_focus(to_focus);
+}
index 1fcf70cbd5ae44beb56dfe90035b153d6c4c70de..22b7fffe8774986b63fabce036f0cbfcde597cc0 100644 (file)
--- a/src/log.c
+++ b/src/log.c
@@ -1,9 +1,9 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
  *
- * © 2009 Michael Stapelberg and contributors
+ * © 2009-2010 Michael Stapelberg and contributors
  *
  * See file LICENSE for license information.
  *
@@ -14,6 +14,8 @@
 #include <stdio.h>
 #include <string.h>
 #include <stdbool.h>
+#include <stdlib.h>
+#include <sys/time.h>
 
 #include "util.h"
 #include "log.h"
 /* loglevels.h is autogenerated at make time */
 #include "loglevels.h"
 
-static uint32_t loglevel = 0;
-static bool verbose = false;
+static uint64_t loglevel = 0;
+static bool verbose = true;
+static FILE *errorfile;
+char *errorfilename;
 
-/**
+/*
+ * Initializes logging by creating an error logfile in /tmp (or
+ * XDG_RUNTIME_DIR, see get_process_filename()).
+ *
+ */
+void init_logging() {
+    errorfilename = get_process_filename("errorlog");
+    if (errorfilename == NULL) {
+        ELOG("Could not initialize errorlog\n");
+        return;
+    }
+
+    errorfile = fopen(errorfilename, "w");
+}
+
+/*
  * Set verbosity of i3. If verbose is set to true, informative messages will
  * be printed to stdout. If verbose is set to false, only errors will be
  * printed.
  *
  */
 void set_verbosity(bool _verbose) {
-        verbose = _verbose;
+    verbose = _verbose;
 }
 
-/**
+/*
  * Enables the given loglevel.
  *
  */
 void add_loglevel(const char *level) {
-       /* Handle the special loglevel "all" */
-       if (strcasecmp(level, "all") == 0) {
-               loglevel = UINT32_MAX;
-               return;
-       }
-
-       for (int i = 0; i < sizeof(loglevels) / sizeof(char*); i++) {
-               if (strcasecmp(loglevels[i], level) != 0)
-                       continue;
-
-               /* The position in the array (plus one) is the amount of times
-                * which we need to shift 1 to the left to get our bitmask for
-                * the specific loglevel. */
-               loglevel |= (1 << (i+1));
-               break;
-       }
+    /* Handle the special loglevel "all" */
+    if (strcasecmp(level, "all") == 0) {
+        loglevel = UINT64_MAX;
+        return;
+    }
+
+    for (int i = 0; i < sizeof(loglevels) / sizeof(char*); i++) {
+        if (strcasecmp(loglevels[i], level) != 0)
+            continue;
+
+        /* The position in the array (plus one) is the amount of times
+         * which we need to shift 1 to the left to get our bitmask for
+         * the specific loglevel. */
+        loglevel |= (1 << (i+1));
+        break;
+    }
 }
 
 /*
@@ -63,44 +82,56 @@ void add_loglevel(const char *level) {
  *
  */
 void vlog(char *fmt, va_list args) {
-        char timebuf[64];
-
-        /* Get current time */
-        time_t t = time(NULL);
-        /* Convert time to local time (determined by the locale) */
-        struct tm *tmp = localtime(&t);
-        /* Generate time prefix */
-        strftime(timebuf, sizeof(timebuf), "%x %X - ", tmp);
-        printf("%s", timebuf);
-        vprintf(fmt, args);
+    char timebuf[64];
+
+    /* Get current time */
+    time_t t = time(NULL);
+    /* Convert time to local time (determined by the locale) */
+    struct tm *tmp = localtime(&t);
+    /* Generate time prefix */
+    strftime(timebuf, sizeof(timebuf), "%x %X - ", tmp);
+#ifdef DEBUG_TIMING
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    printf("%s%d.%d - ", timebuf, tv.tv_sec, tv.tv_usec);
+#else
+    printf("%s", timebuf);
+#endif
+    vprintf(fmt, args);
 }
 
-/**
+/*
  * Logs the given message to stdout while prefixing the current time to it,
  * but only if verbose mode is activated.
  *
  */
 void verboselog(char *fmt, ...) {
-        va_list args;
+    va_list args;
 
-       if (!verbose)
-               return;
+    if (!verbose)
+        return;
 
-        va_start(args, fmt);
-        vlog(fmt, args);
-        va_end(args);
+    va_start(args, fmt);
+    vlog(fmt, args);
+    va_end(args);
 }
 
-/**
+/*
  * Logs the given message to stdout while prefixing the current time to it.
  *
  */
 void errorlog(char *fmt, ...) {
-        va_list args;
+    va_list args;
+
+    va_start(args, fmt);
+    vlog(fmt, args);
+    va_end(args);
 
-        va_start(args, fmt);
-        vlog(fmt, args);
-        va_end(args);
+    /* also log to the error logfile, if opened */
+    va_start(args, fmt);
+    vfprintf(errorfile, fmt, args);
+    fflush(errorfile);
+    va_end(args);
 }
 
 /*
@@ -109,13 +140,13 @@ void errorlog(char *fmt, ...) {
  * This is to be called by DLOG() which includes filename/linenumber
  *
  */
-void debuglog(int lev, char *fmt, ...) {
-       va_list args;
+void debuglog(uint64_t lev, char *fmt, ...) {
+    va_list args;
 
-       if ((loglevel & lev) == 0)
-               return;
+    if ((loglevel & lev) == 0)
+        return;
 
-        va_start(args, fmt);
-        vlog(fmt, args);
-        va_end(args);
+    va_start(args, fmt);
+    vlog(fmt, args);
+    va_end(args);
 }
diff --git a/src/main.c b/src/main.c
new file mode 100644 (file)
index 0000000..d7a5aed
--- /dev/null
@@ -0,0 +1,499 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ */
+#include <ev.h>
+#include <fcntl.h>
+#include <limits.h>
+#include "all.h"
+
+static int xkb_event_base;
+
+int xkb_current_group;
+
+extern Con *focused;
+
+char **start_argv;
+
+xcb_connection_t *conn;
+
+xcb_screen_t *root_screen;
+xcb_window_t root;
+uint8_t root_depth;
+
+struct ev_loop *main_loop;
+
+xcb_key_symbols_t *keysyms;
+
+/* Those are our connections to X11 for use with libXcursor and XKB */
+Display *xlibdpy, *xkbdpy;
+
+/* The list of key bindings */
+struct bindings_head *bindings;
+
+/* The list of exec-lines */
+struct autostarts_head autostarts = TAILQ_HEAD_INITIALIZER(autostarts);
+
+/* The list of exec_always lines */
+struct autostarts_always_head autostarts_always = TAILQ_HEAD_INITIALIZER(autostarts_always);
+
+/* The list of assignments */
+struct assignments_head assignments = TAILQ_HEAD_INITIALIZER(assignments);
+
+/* The list of workspace assignments (which workspace should end up on which
+ * output) */
+struct ws_assignments_head ws_assignments = TAILQ_HEAD_INITIALIZER(ws_assignments);
+
+/* We hope that those are supported and set them to true */
+bool xcursor_supported = true;
+bool xkb_supported = true;
+
+/*
+ * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
+ * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
+ *
+ */
+static void xcb_got_event(EV_P_ struct ev_io *w, int revents) {
+    /* empty, because xcb_prepare_cb and xcb_check_cb are used */
+}
+
+/*
+ * Flush before blocking (and waiting for new events)
+ *
+ */
+static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) {
+    xcb_flush(conn);
+}
+
+/*
+ * Instead of polling the X connection socket we leave this to
+ * xcb_poll_for_event() which knows better than we can ever know.
+ *
+ */
+static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
+    xcb_generic_event_t *event;
+
+    while ((event = xcb_poll_for_event(conn)) != NULL) {
+        if (event->response_type == 0) {
+            if (event_is_ignored(event->sequence, 0))
+                DLOG("Expected X11 Error received for sequence %x\n", event->sequence);
+            else {
+                xcb_generic_error_t *error = (xcb_generic_error_t*)event;
+                ELOG("X11 Error received! sequence 0x%x, error_code = %d\n",
+                     error->sequence, error->error_code);
+            }
+            free(event);
+            continue;
+        }
+
+        /* Strip off the highest bit (set if the event is generated) */
+        int type = (event->response_type & 0x7F);
+
+        handle_event(type, event);
+
+        free(event);
+    }
+}
+
+
+/*
+ * When using xmodmap to change the keyboard mapping, this event
+ * is only sent via XKB. Therefore, we need this special handler.
+ *
+ */
+static void xkb_got_event(EV_P_ struct ev_io *w, int revents) {
+    DLOG("Handling XKB event\n");
+    XkbEvent ev;
+
+    /* When using xmodmap, every change (!) gets an own event.
+     * Therefore, we just read all events and only handle the
+     * mapping_notify once. */
+    bool mapping_changed = false;
+    while (XPending(xkbdpy)) {
+        XNextEvent(xkbdpy, (XEvent*)&ev);
+        /* While we should never receive a non-XKB event,
+         * better do sanity checking */
+        if (ev.type != xkb_event_base)
+            continue;
+
+        if (ev.any.xkb_type == XkbMapNotify) {
+            mapping_changed = true;
+            continue;
+        }
+
+        if (ev.any.xkb_type != XkbStateNotify) {
+            ELOG("Unknown XKB event received (type %d)\n", ev.any.xkb_type);
+            continue;
+        }
+
+        /* See The XKB Extension: Library Specification, section 14.1 */
+        /* We check if the current group (each group contains
+         * two levels) has been changed. Mode_switch activates
+         * group XkbGroup2Index */
+        if (xkb_current_group == ev.state.group)
+            continue;
+
+        xkb_current_group = ev.state.group;
+
+        if (ev.state.group == XkbGroup2Index) {
+            DLOG("Mode_switch enabled\n");
+            grab_all_keys(conn, true);
+        }
+
+        if (ev.state.group == XkbGroup1Index) {
+            DLOG("Mode_switch disabled\n");
+            ungrab_all_keys(conn);
+            grab_all_keys(conn, false);
+        }
+    }
+
+    if (!mapping_changed)
+        return;
+
+    DLOG("Keyboard mapping changed, updating keybindings\n");
+    xcb_key_symbols_free(keysyms);
+    keysyms = xcb_key_symbols_alloc(conn);
+
+    xcb_get_numlock_mask(conn);
+
+    ungrab_all_keys(conn);
+    DLOG("Re-grabbing...\n");
+    translate_keysyms();
+    grab_all_keys(conn, (xkb_current_group == XkbGroup2Index));
+    DLOG("Done\n");
+}
+
+int main(int argc, char *argv[]) {
+    //parse_cmd("[ foo ] attach, attach ; focus");
+    int screens;
+    char *override_configpath = NULL;
+    bool autostart = true;
+    char *layout_path = NULL;
+    bool delete_layout_path = false;
+    bool only_check_config = false;
+    bool force_xinerama = false;
+    bool disable_signalhandler = false;
+    static struct option long_options[] = {
+        {"no-autostart", no_argument, 0, 'a'},
+        {"config", required_argument, 0, 'c'},
+        {"version", no_argument, 0, 'v'},
+        {"help", no_argument, 0, 'h'},
+        {"layout", required_argument, 0, 'L'},
+        {"restart", required_argument, 0, 0},
+        {"force-xinerama", no_argument, 0, 0},
+        {"disable-signalhandler", no_argument, 0, 0},
+        {0, 0, 0, 0}
+    };
+    int option_index = 0, opt;
+
+    setlocale(LC_ALL, "");
+
+    /* Disable output buffering to make redirects in .xsession actually useful for debugging */
+    if (!isatty(fileno(stdout)))
+        setbuf(stdout, NULL);
+
+    init_logging();
+
+    start_argv = argv;
+
+    while ((opt = getopt_long(argc, argv, "c:CvaL:hld:V", long_options, &option_index)) != -1) {
+        switch (opt) {
+            case 'a':
+                LOG("Autostart disabled using -a\n");
+                autostart = false;
+                break;
+            case 'L':
+                FREE(layout_path);
+                layout_path = sstrdup(optarg);
+                delete_layout_path = false;
+                break;
+            case 'c':
+                FREE(override_configpath);
+                override_configpath = sstrdup(optarg);
+                break;
+            case 'C':
+                LOG("Checking configuration file only (-C)\n");
+                only_check_config = true;
+                break;
+            case 'v':
+                printf("i3 version " I3_VERSION " © 2009-2011 Michael Stapelberg and contributors\n");
+                exit(EXIT_SUCCESS);
+            case 'V':
+                set_verbosity(true);
+                break;
+            case 'd':
+                LOG("Enabling debug loglevel %s\n", optarg);
+                add_loglevel(optarg);
+                break;
+            case 'l':
+                /* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */
+                break;
+            case 0:
+                if (strcmp(long_options[option_index].name, "force-xinerama") == 0) {
+                    force_xinerama = true;
+                    ELOG("Using Xinerama instead of RandR. This option should be "
+                         "avoided at all cost because it does not refresh the list "
+                         "of screens, so you cannot configure displays at runtime. "
+                         "Please check if your driver really does not support RandR "
+                         "and disable this option as soon as you can.\n");
+                    break;
+                } else if (strcmp(long_options[option_index].name, "disable-signalhandler") == 0) {
+                    disable_signalhandler = true;
+                    break;
+                } else if (strcmp(long_options[option_index].name, "restart") == 0) {
+                    FREE(layout_path);
+                    layout_path = sstrdup(optarg);
+                    delete_layout_path = true;
+                    break;
+                }
+                /* fall-through */
+            default:
+                fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V] [-C]\n", argv[0]);
+                fprintf(stderr, "\n");
+                fprintf(stderr, "-a: disable autostart\n");
+                fprintf(stderr, "-L <layoutfile>: load the layout from <layoutfile>\n");
+                fprintf(stderr, "-v: display version and exit\n");
+                fprintf(stderr, "-V: enable verbose mode\n");
+                fprintf(stderr, "-d <loglevel>: enable debug loglevel <loglevel>\n");
+                fprintf(stderr, "-c <configfile>: use the provided configfile instead\n");
+                fprintf(stderr, "-C: check configuration file and exit\n");
+                fprintf(stderr, "--force-xinerama: Use Xinerama instead of RandR. This "
+                                "option should only be used if you are stuck with the "
+                                "nvidia closed source driver which does not support RandR.\n");
+                exit(EXIT_FAILURE);
+        }
+    }
+
+    LOG("i3 (tree) version " I3_VERSION " starting\n");
+
+    conn = xcb_connect(NULL, &screens);
+    if (xcb_connection_has_error(conn))
+        errx(EXIT_FAILURE, "Cannot open display\n");
+
+    /* Initialize the libev event loop. This needs to be done before loading
+     * the config file because the parser will install an ev_child watcher
+     * for the nagbar when config errors are found. */
+    main_loop = EV_DEFAULT;
+    if (main_loop == NULL)
+            die("Could not initialize libev. Bad LIBEV_FLAGS?\n");
+
+    root_screen = xcb_aux_get_screen(conn, screens);
+    root = root_screen->root;
+    root_depth = root_screen->root_depth;
+    xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(conn, root);
+
+    load_configuration(conn, override_configpath, false);
+    if (only_check_config) {
+        LOG("Done checking configuration file. Exiting.\n");
+        exit(0);
+    }
+
+    if (config.ipc_socket_path == NULL) {
+        /* Fall back to a file name in /tmp/ based on the PID */
+        if ((config.ipc_socket_path = getenv("I3SOCK")) == NULL)
+            config.ipc_socket_path = get_process_filename("ipc-socket");
+        else
+            config.ipc_socket_path = sstrdup(config.ipc_socket_path);
+    }
+
+    uint32_t mask = XCB_CW_EVENT_MASK;
+    uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
+                          XCB_EVENT_MASK_STRUCTURE_NOTIFY |         /* when the user adds a screen (e.g. video
+                                                                           projector), the root window gets a
+                                                                           ConfigureNotify */
+                          XCB_EVENT_MASK_POINTER_MOTION |
+                          XCB_EVENT_MASK_PROPERTY_CHANGE |
+                          XCB_EVENT_MASK_ENTER_WINDOW };
+    xcb_void_cookie_t cookie;
+    cookie = xcb_change_window_attributes_checked(conn, root, mask, values);
+    check_error(conn, cookie, "Another window manager seems to be running");
+
+    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");
+        return 1;
+    }
+    DLOG("root geometry reply: (%d, %d) %d x %d\n", greply->x, greply->y, greply->width, greply->height);
+
+    /* Place requests for the atoms we need as soon as possible */
+    #define xmacro(atom) \
+        xcb_intern_atom_cookie_t atom ## _cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom);
+    #include "atoms.xmacro"
+    #undef xmacro
+
+    /* Initialize the Xlib connection */
+    xlibdpy = xkbdpy = XOpenDisplay(NULL);
+
+    /* Try to load the X cursors and initialize the XKB extension */
+    if (xlibdpy == NULL) {
+        ELOG("ERROR: XOpenDisplay() failed, disabling libXcursor/XKB support\n");
+        xcursor_supported = false;
+        xkb_supported = false;
+    } else if (fcntl(ConnectionNumber(xlibdpy), F_SETFD, FD_CLOEXEC) == -1) {
+        ELOG("Could not set FD_CLOEXEC on xkbdpy\n");
+        return 1;
+    } else {
+        xcursor_load_cursors();
+        /*init_xkb();*/
+    }
+
+    /* Set a cursor for the root window (otherwise the root window will show no
+       cursor until the first client is launched). */
+    if (xcursor_supported) {
+        xcursor_set_root_cursor();
+    } else {
+        xcb_cursor_t cursor_id = xcb_generate_id(conn);
+        i3Font cursor_font = load_font("cursor", false);
+        int xcb_cursor = xcursor_get_xcb_cursor(XCURSOR_CURSOR_POINTER);
+        xcb_create_glyph_cursor(conn, cursor_id, cursor_font.id, cursor_font.id,
+                xcb_cursor, xcb_cursor + 1, 0, 0, 0, 65535, 65535, 65535);
+        xcb_change_window_attributes(conn, root, XCB_CW_CURSOR, &cursor_id);
+        xcb_free_cursor(conn, cursor_id);
+    }
+
+    if (xkb_supported) {
+        int errBase,
+            major = XkbMajorVersion,
+            minor = XkbMinorVersion;
+
+        if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) {
+            fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n");
+            return 1;
+        }
+
+        int i1;
+        if (!XkbQueryExtension(xkbdpy,&i1,&xkb_event_base,&errBase,&major,&minor)) {
+            fprintf(stderr, "XKB not supported by X-server\n");
+            return 1;
+        }
+        /* end of ugliness */
+
+        if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd,
+                             XkbMapNotifyMask | XkbStateNotifyMask,
+                             XkbMapNotifyMask | XkbStateNotifyMask)) {
+            fprintf(stderr, "Could not set XKB event mask\n");
+            return 1;
+        }
+    }
+
+    /* Setup NetWM atoms */
+    #define xmacro(name) \
+        do { \
+            xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, name ## _cookie, NULL); \
+            if (!reply) { \
+                ELOG("Could not get atom " #name "\n"); \
+                exit(-1); \
+            } \
+            A_ ## name = reply->atom; \
+            free(reply); \
+        } while (0);
+    #include "atoms.xmacro"
+    #undef xmacro
+
+    property_handlers_init();
+
+    /* Set up the atoms we support */
+    xcb_atom_t supported_atoms[] = {
+#define xmacro(atom) A_ ## atom,
+#include "atoms.xmacro"
+#undef xmacro
+    };
+    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, A_ATOM, 32, 15, supported_atoms);
+    /* Set up the window manager’s name */
+    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTING_WM_CHECK, A_WINDOW, 32, 1, &root);
+    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3");
+
+    keysyms = xcb_key_symbols_alloc(conn);
+
+    xcb_get_numlock_mask(conn);
+
+    translate_keysyms();
+    grab_all_keys(conn, false);
+
+    bool needs_tree_init = true;
+    if (layout_path) {
+        LOG("Trying to restore the layout from %s...", layout_path);
+        needs_tree_init = !tree_restore(layout_path, greply);
+        if (delete_layout_path)
+            unlink(layout_path);
+        free(layout_path);
+    }
+    if (needs_tree_init)
+        tree_init(greply);
+
+    free(greply);
+
+    if (force_xinerama) {
+        xinerama_init();
+    } else {
+        DLOG("Checking for XRandR...\n");
+        randr_init(&randr_base);
+    }
+
+    tree_render();
+
+    /* Create the UNIX domain socket for IPC */
+    int ipc_socket = ipc_create_socket(config.ipc_socket_path);
+    if (ipc_socket == -1) {
+        ELOG("Could not create the IPC socket, IPC disabled\n");
+    } else {
+        free(config.ipc_socket_path);
+        struct ev_io *ipc_io = scalloc(sizeof(struct ev_io));
+        ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ);
+        ev_io_start(main_loop, ipc_io);
+    }
+
+    /* Set up i3 specific atoms like I3_SOCKET_PATH and I3_CONFIG_PATH */
+    x_set_i3_atoms();
+
+    struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io));
+    struct ev_io *xkb = scalloc(sizeof(struct ev_io));
+    struct ev_check *xcb_check = scalloc(sizeof(struct ev_check));
+    struct ev_prepare *xcb_prepare = scalloc(sizeof(struct ev_prepare));
+
+    ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
+    ev_io_start(main_loop, xcb_watcher);
+
+
+    if (xkb_supported) {
+        ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ);
+        ev_io_start(main_loop, xkb);
+
+        /* Flush the buffer so that libev can properly get new events */
+        XFlush(xkbdpy);
+    }
+
+    ev_check_init(xcb_check, xcb_check_cb);
+    ev_check_start(main_loop, xcb_check);
+
+    ev_prepare_init(xcb_prepare, xcb_prepare_cb);
+    ev_prepare_start(main_loop, xcb_prepare);
+
+    xcb_flush(conn);
+
+    manage_existing_windows(root);
+
+    if (!disable_signalhandler)
+        setup_signal_handler();
+
+    /* Ignore SIGPIPE to survive errors when an IPC client disconnects
+     * while we are sending him a message */
+    signal(SIGPIPE, SIG_IGN);
+
+    /* Autostarting exec-lines */
+    if (autostart) {
+        struct Autostart *exec;
+        TAILQ_FOREACH(exec, &autostarts, autostarts) {
+            LOG("auto-starting %s\n", exec->command);
+            start_application(exec->command);
+        }
+    }
+
+    /* Autostarting exec_always-lines */
+    struct Autostart *exec_always;
+    TAILQ_FOREACH(exec_always, &autostarts_always, autostarts_always) {
+        LOG("auto-starting (always!) %s\n", exec_always->command);
+        start_application(exec_always->command);
+    }
+
+    ev_loop(main_loop, 0);
+}
diff --git a/src/mainx.c b/src/mainx.c
deleted file mode 100644 (file)
index 4126fe5..0000000
+++ /dev/null
@@ -1,602 +0,0 @@
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <stdbool.h>
-#include <assert.h>
-#include <limits.h>
-#include <locale.h>
-#include <fcntl.h>
-#include <getopt.h>
-
-#include <X11/XKBlib.h>
-#include <X11/extensions/XKB.h>
-
-#include <xcb/xcb.h>
-#include <xcb/xcb_atom.h>
-#include <xcb/xcb_aux.h>
-#include <xcb/xcb_event.h>
-#include <xcb/xcb_property.h>
-#include <xcb/xcb_keysyms.h>
-#include <xcb/xcb_icccm.h>
-
-#include <ev.h>
-
-#include "config.h"
-#include "data.h"
-#include "debug.h"
-#include "handlers.h"
-#include "click.h"
-#include "i3.h"
-#include "layout.h"
-#include "queue.h"
-#include "table.h"
-#include "util.h"
-#include "xcb.h"
-#include "randr.h"
-#include "xinerama.h"
-#include "manage.h"
-#include "ipc.h"
-#include "log.h"
-#include "sighandler.h"
-
-static int xkb_event_base;
-
-int xkb_current_group;
-
-xcb_connection_t *global_conn;
-
-/* This is the path to i3, copied from argv[0] when starting up */
-char **start_argv;
-
-/* This is our connection to X11 for use with XKB */
-Display *xkbdpy;
-
-xcb_key_symbols_t *keysyms;
-
-/* The list of key bindings */
-struct bindings_head *bindings;
-
-/* The list of exec-lines */
-struct autostarts_head autostarts = TAILQ_HEAD_INITIALIZER(autostarts);
-
-/* The list of assignments */
-struct assignments_head assignments = TAILQ_HEAD_INITIALIZER(assignments);
-
-/* This is a list of Stack_Windows, global, for easier/faster access on expose events */
-struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins);
-
-/* The event handlers need to be global because they are accessed by our custom event handler
-   in handle_button_press(), needed for graphical resizing */
-xcb_event_handlers_t evenths;
-xcb_atom_t atoms[NUM_ATOMS];
-
-xcb_window_t root;
-int num_screens = 0;
-
-/* The depth of the root screen (used e.g. for creating new pixmaps later) */
-uint8_t root_depth;
-
-/* We hope that XKB is supported and set this to false */
-bool xkb_supported = true;
-
-/*
- * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
- * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
- *
- */
-static void xcb_got_event(EV_P_ struct ev_io *w, int revents) {
-        /* empty, because xcb_prepare_cb and xcb_check_cb are used */
-}
-
-/*
- * Flush before blocking (and waiting for new events)
- *
- */
-static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) {
-        xcb_flush(evenths.c);
-}
-
-/*
- * Instead of polling the X connection socket we leave this to
- * xcb_poll_for_event() which knows better than we can ever know.
- *
- */
-static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
-        xcb_generic_event_t *event;
-
-        while ((event = xcb_poll_for_event(evenths.c)) != NULL) {
-                xcb_event_handle(&evenths, event);
-                free(event);
-        }
-}
-
-/*
- * When using xmodmap to change the keyboard mapping, this event
- * is only sent via XKB. Therefore, we need this special handler.
- *
- */
-static void xkb_got_event(EV_P_ struct ev_io *w, int revents) {
-        DLOG("Handling XKB event\n");
-        XkbEvent ev;
-
-        /* When using xmodmap, every change (!) gets an own event.
-         * Therefore, we just read all events and only handle the
-         * mapping_notify once. */
-        bool mapping_changed = false;
-        while (XPending(xkbdpy)) {
-                XNextEvent(xkbdpy, (XEvent*)&ev);
-                /* While we should never receive a non-XKB event,
-                 * better do sanity checking */
-                if (ev.type != xkb_event_base)
-                        continue;
-
-                if (ev.any.xkb_type == XkbMapNotify) {
-                        mapping_changed = true;
-                        continue;
-                }
-
-                if (ev.any.xkb_type != XkbStateNotify) {
-                        ELOG("Unknown XKB event received (type %d)\n", ev.any.xkb_type);
-                        continue;
-                }
-
-                /* See The XKB Extension: Library Specification, section 14.1 */
-                /* We check if the current group (each group contains
-                 * two levels) has been changed. Mode_switch activates
-                 * group XkbGroup2Index */
-                if (xkb_current_group == ev.state.group)
-                        continue;
-
-                xkb_current_group = ev.state.group;
-
-                if (ev.state.group == XkbGroup2Index) {
-                        DLOG("Mode_switch enabled\n");
-                        grab_all_keys(global_conn, true);
-                }
-
-                if (ev.state.group == XkbGroup1Index) {
-                        DLOG("Mode_switch disabled\n");
-                        ungrab_all_keys(global_conn);
-                        grab_all_keys(global_conn, false);
-                }
-        }
-
-        if (!mapping_changed)
-                return;
-
-        DLOG("Keyboard mapping changed, updating keybindings\n");
-        xcb_key_symbols_free(keysyms);
-        keysyms = xcb_key_symbols_alloc(global_conn);
-
-        xcb_get_numlock_mask(global_conn);
-
-        ungrab_all_keys(global_conn);
-        DLOG("Re-grabbing...\n");
-        translate_keysyms();
-        grab_all_keys(global_conn, (xkb_current_group == XkbGroup2Index));
-        DLOG("Done\n");
-}
-
-
-int main(int argc, char *argv[], char *env[]) {
-        int i, screens, opt;
-        char *override_configpath = NULL;
-        bool autostart = true;
-        bool only_check_config = false;
-        bool force_xinerama = false;
-        xcb_connection_t *conn;
-        xcb_property_handlers_t prophs;
-        xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
-        static struct option long_options[] = {
-                {"no-autostart", no_argument, 0, 'a'},
-                {"config", required_argument, 0, 'c'},
-                {"version", no_argument, 0, 'v'},
-                {"help", no_argument, 0, 'h'},
-                {"force-xinerama", no_argument, 0, 0},
-                {0, 0, 0, 0}
-        };
-        int option_index = 0;
-
-        setlocale(LC_ALL, "");
-
-        /* Disable output buffering to make redirects in .xsession actually useful for debugging */
-        if (!isatty(fileno(stdout)))
-                setbuf(stdout, NULL);
-
-        start_argv = argv;
-
-        while ((opt = getopt_long(argc, argv, "c:Cvahld:V", long_options, &option_index)) != -1) {
-                switch (opt) {
-                        case 'a':
-                                LOG("Autostart disabled using -a\n");
-                                autostart = false;
-                                break;
-                        case 'c':
-                                override_configpath = sstrdup(optarg);
-                                break;
-                        case 'C':
-                                LOG("Checking configuration file only (-C)\n");
-                                only_check_config = true;
-                                break;
-                        case 'v':
-                                printf("i3 version " I3_VERSION " © 2009-2010 Michael Stapelberg and contributors\n");
-                                exit(EXIT_SUCCESS);
-                        case 'V':
-                                set_verbosity(true);
-                                break;
-                        case 'd':
-                                LOG("Enabling debug loglevel %s\n", optarg);
-                                add_loglevel(optarg);
-                                break;
-                        case 'l':
-                                /* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */
-                                break;
-                        case 0:
-                                if (strcmp(long_options[option_index].name, "force-xinerama") == 0) {
-                                        force_xinerama = true;
-                                        ELOG("Using Xinerama instead of RandR. This option should be "
-                                             "avoided at all cost because it does not refresh the list "
-                                             "of screens, so you cannot configure displays at runtime. "
-                                             "Please check if your driver really does not support RandR "
-                                             "and disable this option as soon as you can.\n");
-                                        break;
-                                }
-                                /* fall-through */
-                        default:
-                                fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V] [-C]\n", argv[0]);
-                                fprintf(stderr, "\n");
-                                fprintf(stderr, "-a: disable autostart\n");
-                                fprintf(stderr, "-v: display version and exit\n");
-                                fprintf(stderr, "-V: enable verbose mode\n");
-                                fprintf(stderr, "-d <loglevel>: enable debug loglevel <loglevel>\n");
-                                fprintf(stderr, "-c <configfile>: use the provided configfile instead\n");
-                                fprintf(stderr, "-C: check configuration file and exit\n");
-                                fprintf(stderr, "--force-xinerama: Use Xinerama instead of RandR. This "
-                                                "option should only be used if you are stuck with the "
-                                                "nvidia closed source driver which does not support RandR.\n");
-                                exit(EXIT_FAILURE);
-                }
-        }
-
-        LOG("i3 version " I3_VERSION " starting\n");
-
-        /* Initialize the table data structures for each workspace */
-        init_table();
-
-        memset(&evenths, 0, sizeof(xcb_event_handlers_t));
-        memset(&prophs, 0, sizeof(xcb_property_handlers_t));
-
-        conn = global_conn = xcb_connect(NULL, &screens);
-
-        if (xcb_connection_has_error(conn))
-                die("Cannot open display\n");
-
-        /* Get the root window */
-        xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
-        root = root_screen->root;
-        root_depth = root_screen->root_depth;
-
-        load_configuration(conn, override_configpath, false);
-        if (only_check_config) {
-                LOG("Done checking configuration file. Exiting.\n");
-                exit(0);
-        }
-
-        /* Create the initial container on the first workspace. This used to
-         * be part of init_table, but since it possibly requires an X
-         * connection and a loaded configuration (default mode for new
-         * containers may be stacking, which requires a new window to be
-         * created), it had to be delayed. */
-        expand_table_cols(TAILQ_FIRST(workspaces));
-        expand_table_rows(TAILQ_FIRST(workspaces));
-
-        /* Place requests for the atoms we need as soon as possible */
-        #define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(conn, 0, strlen(#name), #name);
-
-        REQUEST_ATOM(_NET_SUPPORTED);
-        REQUEST_ATOM(_NET_WM_STATE_FULLSCREEN);
-        REQUEST_ATOM(_NET_SUPPORTING_WM_CHECK);
-        REQUEST_ATOM(_NET_WM_NAME);
-        REQUEST_ATOM(_NET_WM_STATE);
-        REQUEST_ATOM(_NET_WM_WINDOW_TYPE);
-        REQUEST_ATOM(_NET_WM_DESKTOP);
-        REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
-        REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DIALOG);
-        REQUEST_ATOM(_NET_WM_WINDOW_TYPE_UTILITY);
-        REQUEST_ATOM(_NET_WM_WINDOW_TYPE_TOOLBAR);
-        REQUEST_ATOM(_NET_WM_WINDOW_TYPE_SPLASH);
-        REQUEST_ATOM(_NET_WM_STRUT_PARTIAL);
-        REQUEST_ATOM(WM_PROTOCOLS);
-        REQUEST_ATOM(WM_DELETE_WINDOW);
-        REQUEST_ATOM(UTF8_STRING);
-        REQUEST_ATOM(WM_STATE);
-        REQUEST_ATOM(WM_CLIENT_LEADER);
-        REQUEST_ATOM(_NET_CURRENT_DESKTOP);
-        REQUEST_ATOM(_NET_ACTIVE_WINDOW);
-        REQUEST_ATOM(_NET_WORKAREA);
-
-        /* TODO: this has to be more beautiful somewhen */
-        int major, minor, error;
-
-        major = XkbMajorVersion;
-        minor = XkbMinorVersion;
-
-        int errBase;
-
-        if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &xkb_event_base, &errBase, &major, &minor, &error)) == NULL) {
-                ELOG("ERROR: XkbOpenDisplay() failed, disabling XKB support\n");
-                xkb_supported = false;
-        }
-
-        if (xkb_supported) {
-                if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) {
-                        fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n");
-                        return 1;
-                }
-
-                int i1;
-                if (!XkbQueryExtension(xkbdpy,&i1,&xkb_event_base,&errBase,&major,&minor)) {
-                        fprintf(stderr, "XKB not supported by X-server\n");
-                        return 1;
-                }
-                /* end of ugliness */
-
-                if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd,
-                                     XkbMapNotifyMask | XkbStateNotifyMask,
-                                     XkbMapNotifyMask | XkbStateNotifyMask)) {
-                        fprintf(stderr, "Could not set XKB event mask\n");
-                        return 1;
-                }
-        }
-
-        /* Initialize event loop using libev */
-        struct ev_loop *loop = ev_loop_new(0);
-        if (loop == NULL)
-                die("Could not initialize libev. Bad LIBEV_FLAGS?\n");
-
-        struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io));
-        struct ev_io *xkb = scalloc(sizeof(struct ev_io));
-        struct ev_check *xcb_check = scalloc(sizeof(struct ev_check));
-        struct ev_prepare *xcb_prepare = scalloc(sizeof(struct ev_prepare));
-
-        ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
-        ev_io_start(loop, xcb_watcher);
-
-        if (xkb_supported) {
-                ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ);
-                ev_io_start(loop, xkb);
-
-                /* Flush the buffer so that libev can properly get new events */
-                XFlush(xkbdpy);
-        }
-
-        ev_check_init(xcb_check, xcb_check_cb);
-        ev_check_start(loop, xcb_check);
-
-        ev_prepare_init(xcb_prepare, xcb_prepare_cb);
-        ev_prepare_start(loop, xcb_prepare);
-
-        /* Grab the server to delay any events until we enter the eventloop */
-        xcb_grab_server(conn);
-
-        xcb_event_handlers_init(conn, &evenths);
-
-        /* DEBUG: Trap all events and print them */
-        for (i = 2; i < 128; ++i)
-                xcb_event_set_handler(&evenths, i, handle_event, 0);
-
-        for (i = 0; i < 256; ++i)
-                xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0);
-
-        /* Expose = an Application should redraw itself, in this case it’s our titlebars. */
-        xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL);
-
-        /* Key presses are pretty obvious, I think */
-        xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL);
-
-        /* Enter window = user moved his mouse over the window */
-        xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL);
-
-        /* Button press = user pushed a mouse button over one of our windows */
-        xcb_event_set_button_press_handler(&evenths, handle_button_press, NULL);
-
-        /* Map notify = there is a new window */
-        xcb_event_set_map_request_handler(&evenths, handle_map_request, &prophs);
-
-        /* Unmap notify = window disappeared. When sent from a client, we don’t manage
-           it any longer. Usually, the client destroys the window shortly afterwards. */
-        xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL);
-
-        /* Destroy notify is handled the same as unmap notify */
-        xcb_event_set_destroy_notify_handler(&evenths, handle_destroy_notify_event, NULL);
-
-        /* Configure notify = window’s configuration (geometry, stacking, …). We only need
-           it to set up ignore the following enter_notify events */
-        xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL);
-
-        /* Configure request = window tried to change size on its own */
-        xcb_event_set_configure_request_handler(&evenths, handle_configure_request, NULL);
-
-        /* Motion notify = user moved his cursor (over the root window and may
-         * cross virtual screen boundaries doing that) */
-        xcb_event_set_motion_notify_handler(&evenths, handle_motion_notify, NULL);
-
-        /* Mapping notify = keyboard mapping changed (Xmodmap), re-grab bindings */
-        xcb_event_set_mapping_notify_handler(&evenths, handle_mapping_notify, NULL);
-
-        /* Client message are sent to the root window. The only interesting client message
-           for us is _NET_WM_STATE, we honour _NET_WM_STATE_FULLSCREEN */
-        xcb_event_set_client_message_handler(&evenths, handle_client_message, NULL);
-
-        /* Initialize the property handlers */
-        xcb_property_handlers_init(&prophs, &evenths);
-
-        /* Watch size hints (to obey correct aspect ratio) */
-        xcb_property_set_handler(&prophs, WM_NORMAL_HINTS, UINT_MAX, handle_normal_hints, NULL);
-
-        /* set event mask */
-        uint32_t mask = XCB_CW_EVENT_MASK;
-        uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
-                              XCB_EVENT_MASK_STRUCTURE_NOTIFY |         /* when the user adds a screen (e.g. video
-                                                                           projector), the root window gets a
-                                                                           ConfigureNotify */
-                              XCB_EVENT_MASK_POINTER_MOTION |
-                              XCB_EVENT_MASK_PROPERTY_CHANGE |
-                              XCB_EVENT_MASK_ENTER_WINDOW };
-        xcb_void_cookie_t cookie;
-        cookie = xcb_change_window_attributes_checked(conn, root, mask, values);
-        check_error(conn, cookie, "Another window manager seems to be running");
-
-        /* Setup NetWM atoms */
-        #define GET_ATOM(name) { \
-                xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \
-                if (!reply) { \
-                        ELOG("Could not get atom " #name "\n"); \
-                        exit(-1); \
-                } \
-                atoms[name] = reply->atom; \
-                free(reply); \
-        }
-
-        GET_ATOM(_NET_SUPPORTED);
-        GET_ATOM(_NET_WM_STATE_FULLSCREEN);
-        GET_ATOM(_NET_SUPPORTING_WM_CHECK);
-        GET_ATOM(_NET_WM_NAME);
-        GET_ATOM(_NET_WM_STATE);
-        GET_ATOM(_NET_WM_WINDOW_TYPE);
-        GET_ATOM(_NET_WM_DESKTOP);
-        GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
-        GET_ATOM(_NET_WM_WINDOW_TYPE_DIALOG);
-        GET_ATOM(_NET_WM_WINDOW_TYPE_UTILITY);
-        GET_ATOM(_NET_WM_WINDOW_TYPE_TOOLBAR);
-        GET_ATOM(_NET_WM_WINDOW_TYPE_SPLASH);
-        GET_ATOM(_NET_WM_STRUT_PARTIAL);
-        GET_ATOM(WM_PROTOCOLS);
-        GET_ATOM(WM_DELETE_WINDOW);
-        GET_ATOM(UTF8_STRING);
-        GET_ATOM(WM_STATE);
-        GET_ATOM(WM_CLIENT_LEADER);
-        GET_ATOM(_NET_CURRENT_DESKTOP);
-        GET_ATOM(_NET_ACTIVE_WINDOW);
-        GET_ATOM(_NET_WORKAREA);
-
-        xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, handle_window_type, NULL);
-        /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
-
-        /* Watch _NET_WM_NAME (= title of the window in UTF-8) property */
-        xcb_property_set_handler(&prophs, atoms[_NET_WM_NAME], 128, handle_windowname_change, NULL);
-
-        /* Watch WM_TRANSIENT_FOR property (to which client this popup window belongs) */
-        xcb_property_set_handler(&prophs, WM_TRANSIENT_FOR, UINT_MAX, handle_transient_for, NULL);
-
-        /* Watch WM_NAME (= title of the window in compound text) property for legacy applications */
-        xcb_watch_wm_name(&prophs, 128, handle_windowname_change_legacy, NULL);
-
-        /* Watch WM_CLASS (= class of the window) */
-        xcb_property_set_handler(&prophs, WM_CLASS, 128, handle_windowclass_change, NULL);
-
-        /* Watch WM_CLIENT_LEADER (= logical parent window for toolbars etc.) */
-        xcb_property_set_handler(&prophs, atoms[WM_CLIENT_LEADER], UINT_MAX, handle_clientleader_change, NULL);
-
-        /* Watch WM_HINTS (contains the urgent property) */
-        xcb_property_set_handler(&prophs, WM_HINTS, UINT_MAX, handle_hints, NULL);
-
-        /* Set up the atoms we support */
-        check_error(conn, xcb_change_property_checked(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED],
-                       ATOM, 32, 16, atoms), "Could not set _NET_SUPPORTED");
-        /* Set up the window manager’s name */
-        xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
-        xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3");
-
-        keysyms = xcb_key_symbols_alloc(conn);
-
-        xcb_get_numlock_mask(conn);
-
-        translate_keysyms();
-        grab_all_keys(conn, false);
-
-        int randr_base = -1;
-        if (force_xinerama) {
-                initialize_xinerama(conn);
-        } else {
-                DLOG("Checking for XRandR...\n");
-                initialize_randr(conn, &randr_base);
-
-                if (randr_base != -1)
-                    xcb_event_set_handler(&evenths,
-                                          randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY,
-                                          handle_screen_change,
-                                          NULL);
-        }
-
-        xcb_flush(conn);
-
-        /* Get pointer position to see on which screen we’re starting */
-        xcb_query_pointer_reply_t *reply;
-        if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) {
-                ELOG("Could not get pointer position\n");
-                return 1;
-        }
-
-        Output *screen = get_output_containing(reply->root_x, reply->root_y);
-        if (screen == NULL) {
-                ELOG("ERROR: No screen at %d x %d, starting on the first screen\n",
-                    reply->root_x, reply->root_y);
-                screen = get_first_output();
-        }
-
-        DLOG("Starting on %p\n", screen->current_workspace);
-        c_ws = screen->current_workspace;
-
-        manage_existing_windows(conn, &prophs, root);
-
-        /* Create the UNIX domain socket for IPC */
-        if (config.ipc_socket_path != NULL) {
-                int ipc_socket = ipc_create_socket(config.ipc_socket_path);
-                if (ipc_socket == -1) {
-                        ELOG("Could not create the IPC socket, IPC disabled\n");
-                } else {
-                        struct ev_io *ipc_io = scalloc(sizeof(struct ev_io));
-                        ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ);
-                        ev_io_start(loop, ipc_io);
-                }
-        }
-
-        /* Handle the events which arrived until now */
-        xcb_check_cb(NULL, NULL, 0);
-
-        setup_signal_handler();
-
-        /* Ignore SIGPIPE to survive errors when an IPC client disconnects
-         * while we are sending him a message */
-        signal(SIGPIPE, SIG_IGN);
-
-        /* Ungrab the server to receive events and enter libev’s eventloop */
-        xcb_ungrab_server(conn);
-
-        /* Autostarting exec-lines */
-        struct Autostart *exec;
-        if (autostart) {
-                TAILQ_FOREACH(exec, &autostarts, autostarts) {
-                        LOG("auto-starting %s\n", exec->command);
-                        start_application(exec->command);
-                }
-        }
-
-        ev_loop(loop, 0);
-
-        /* not reached */
-        return 0;
-}
index 06f4e664338f736dceca5034a1486c64cea13951..68c91e72221904a66009d2fa26a42b52a8aebf2b 100644 (file)
@@ -1,66 +1,44 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * src/manage.c: Contains all functions for initially managing new windows
- *               (or existing ones on restart).
+ * manage.c: Contains all functions for initially managing new windows
+ *           (or existing ones on restart).
  *
  */
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-
-#include <xcb/xcb.h>
-#include <xcb/xcb_icccm.h>
-
-#include "xcb.h"
-#include "data.h"
-#include "util.h"
-#include "i3.h"
-#include "table.h"
-#include "config.h"
-#include "handlers.h"
-#include "layout.h"
-#include "manage.h"
-#include "floating.h"
-#include "client.h"
-#include "workspace.h"
-#include "log.h"
-#include "ewmh.h"
+
+#include "all.h"
 
 /*
  * Go through all existing windows (if the window manager is restarted) and manage them
  *
  */
-void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
-        xcb_query_tree_reply_t *reply;
-        int i, len;
-        xcb_window_t *children;
-        xcb_get_window_attributes_cookie_t *cookies;
-
-        /* Get the tree of windows whose parent is the root window (= all) */
-        if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
-                return;
-
-        len = xcb_query_tree_children_length(reply);
-        cookies = smalloc(len * sizeof(*cookies));
-
-        /* Request the window attributes for every window */
-        children = xcb_query_tree_children(reply);
-        for (i = 0; i < len; ++i)
-                cookies[i] = xcb_get_window_attributes(conn, children[i]);
-
-        /* Call manage_window with the attributes for every window */
-        for (i = 0; i < len; ++i)
-                manage_window(prophs, conn, children[i], cookies[i], true);
-
-        free(reply);
-        free(cookies);
+void manage_existing_windows(xcb_window_t root) {
+    xcb_query_tree_reply_t *reply;
+    int i, len;
+    xcb_window_t *children;
+    xcb_get_window_attributes_cookie_t *cookies;
+
+    /* Get the tree of windows whose parent is the root window (= all) */
+    if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
+        return;
+
+    len = xcb_query_tree_children_length(reply);
+    cookies = smalloc(len * sizeof(*cookies));
+
+    /* Request the window attributes for every window */
+    children = xcb_query_tree_children(reply);
+    for (i = 0; i < len; ++i)
+        cookies[i] = xcb_get_window_attributes(conn, children[i]);
+
+    /* Call manage_window with the attributes for every window */
+    for (i = 0; i < len; ++i)
+        manage_window(children[i], cookies[i], true);
+
+    free(reply);
+    free(cookies);
 }
 
 /*
@@ -71,451 +49,316 @@ void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *pr
  * side-effects which are to be expected when continuing to run i3.
  *
  */
-void restore_geometry(xcb_connection_t *conn) {
-        Workspace *ws;
-        Client *client;
-        DLOG("Restoring geometry\n");
-
-        TAILQ_FOREACH(ws, workspaces, workspaces)
-                SLIST_FOREACH(client, &(ws->focus_stack), focus_clients)
-                        xcb_reparent_window(conn, client->child, root,
-                                            client->rect.x, client->rect.y);
-
-        /* Make sure our changes reach the X server, we restart/exit now */
-        xcb_flush(conn);
+void restore_geometry() {
+    DLOG("Restoring geometry\n");
+
+    Con *con;
+    TAILQ_FOREACH(con, &all_cons, all_cons)
+        if (con->window) {
+            DLOG("Re-adding X11 border of %d px\n", con->border_width);
+            con->window_rect.width += (2 * con->border_width);
+            con->window_rect.height += (2 * con->border_width);
+            xcb_set_window_rect(conn, con->window->id, con->window_rect);
+            DLOG("placing window %08x at %d %d\n", con->window->id, con->rect.x, con->rect.y);
+            xcb_reparent_window(conn, con->window->id, root,
+                                con->rect.x, con->rect.y);
+        }
+
+    /* Make sure our changes reach the X server, we restart/exit now */
+    xcb_flush(conn);
 }
 
 /*
  * Do some sanity checks and then reparent the window.
  *
  */
-void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn,
-                   xcb_window_t window, xcb_get_window_attributes_cookie_t cookie,
+void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cookie,
                    bool needs_to_be_mapped) {
-        xcb_drawable_t d = { window };
-        xcb_get_geometry_cookie_t geomc;
-        xcb_get_geometry_reply_t *geom;
-        xcb_get_window_attributes_reply_t *attr = 0;
-
-        geomc = xcb_get_geometry(conn, d);
-
-        /* Check if the window is mapped (it could be not mapped when intializing and
-           calling manage_window() for every window) */
-        if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) {
-                ELOG("Could not get attributes\n");
-                return;
-        }
-
-        if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE)
-                goto out;
+    xcb_drawable_t d = { window };
+    xcb_get_geometry_cookie_t geomc;
+    xcb_get_geometry_reply_t *geom;
+    xcb_get_window_attributes_reply_t *attr = NULL;
 
-        /* Don’t manage clients with the override_redirect flag */
-        if (attr->override_redirect)
-                goto out;
+    DLOG("---> looking at window 0x%08x\n", window);
 
-        /* Check if the window is already managed */
-        if (table_get(&by_child, window))
-                goto out;
+    xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
+                              utf8_title_cookie, title_cookie,
+                              class_cookie, leader_cookie, transient_cookie;
 
-        /* Get the initial geometry (position, size, …) */
-        if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
-                goto out;
 
-        /* Reparent the window and add it to our list of managed windows */
-        reparent_window(conn, window, attr->visual, geom->root, geom->depth,
-                        geom->x, geom->y, geom->width, geom->height,
-                        geom->border_width);
+    geomc = xcb_get_geometry(conn, d);
+#define FREE_GEOMETRY() do { \
+    if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) != NULL) \
+        free(geom); \
+} while (0)
 
-        /* Generate callback events for every property we watch */
-        xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS);
-        xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
-        xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
-        xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_HINTS);
-        xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_TRANSIENT_FOR);
-        xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[WM_CLIENT_LEADER]);
-        xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
-
-        free(geom);
-out:
-        free(attr);
+    /* Check if the window is mapped (it could be not mapped when intializing and
+       calling manage_window() for every window) */
+    if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) {
+        DLOG("Could not get attributes\n");
+        FREE_GEOMETRY();
         return;
-}
-
-/*
- * reparent_window() gets called when a new window was opened and becomes a child of the root
- * window, or it gets called by us when we manage the already existing windows at startup.
- *
- * Essentially, this is the point where we take over control.
- *
- */
-void reparent_window(xcb_connection_t *conn, xcb_window_t child,
-                     xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
-                     int16_t x, int16_t y, uint16_t width, uint16_t height,
-                     uint32_t border_width) {
-
-        xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
-                                  utf8_title_cookie, title_cookie,
-                                  class_cookie, leader_cookie;
-        uint32_t mask = 0;
-        uint32_t values[3];
-        uint16_t original_height = height;
-        bool map_frame = true;
-
-        /* We are interested in property changes */
-        mask = XCB_CW_EVENT_MASK;
-        values[0] = CHILD_EVENT_MASK;
-        xcb_change_window_attributes(conn, child, mask, values);
-
-        /* Place requests for properties ASAP */
-        wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
-        strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
-        state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
-        utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
-        leader_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[WM_CLIENT_LEADER], UINT32_MAX);
-        title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128);
-        class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128);
-
-        Client *new = table_get(&by_child, child);
-
-        /* Events for already managed windows should already be filtered in manage_window() */
-        assert(new == NULL);
-
-        LOG("Managing window 0x%08x\n", child);
-        DLOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
-        new = scalloc(sizeof(Client));
-        new->force_reconfigure = true;
-
-        /* Update the data structures */
-        Client *old_focused = CUR_CELL->currently_focused;
-
-        new->container = CUR_CELL;
-        new->workspace = new->container->workspace;
-
-        /* Minimum useful size for managed windows is 75x50 (primarily affects floating) */
-        width = max(width, 75);
-        height = max(height, 50);
-
-        new->frame = xcb_generate_id(conn);
-        new->child = child;
-        new->rect.width = width;
-        new->rect.height = height;
-        new->width_increment = 1;
-        new->height_increment = 1;
-        new->border_width = border_width;
-        /* Pre-initialize the values for floating */
-        new->floating_rect.x = -1;
-        new->floating_rect.width = width;
-        new->floating_rect.height = height;
-
-        if (config.default_border != NULL)
-                client_init_border(conn, new, config.default_border[1]);
-
-        mask = 0;
-
-        /* Don’t generate events for our new window, it should *not* be managed */
-        mask |= XCB_CW_OVERRIDE_REDIRECT;
-        values[0] = 1;
-
-        /* We want to know when… */
-        mask |= XCB_CW_EVENT_MASK;
-        values[1] = FRAME_EVENT_MASK;
-
-        i3Font *font = load_font(conn, config.font);
-        width = min(width, c_ws->rect.x + c_ws->rect.width);
-        height = min(height, c_ws->rect.y + c_ws->rect.height);
-
-        Rect framerect = {x, y,
-                          width + 2 + 2,                  /* 2 px border at each side */
-                          height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
-
-        /* Yo dawg, I heard you like windows, so I create a window around your window… */
-        new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values);
-
-        /* Put the client inside the save set. Upon termination (whether killed or normal exit
-           does not matter) of the window manager, these clients will be correctly reparented
-           to their most closest living ancestor (= cleanup) */
-        xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
-
-        /* Generate a graphics context for the titlebar */
-        new->titlegc = xcb_generate_id(conn);
-        xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
-
-        /* Moves the original window into the new frame we've created for it */
-        new->awaiting_useless_unmap = true;
-        xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
-        if (xcb_request_check(conn, cookie) != NULL) {
-                DLOG("Could not reparent the window, aborting\n");
-                xcb_destroy_window(conn, new->frame);
-                free(new);
-                return;
+    }
+
+    if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE) {
+        DLOG("map_state unviewable\n");
+        FREE_GEOMETRY();
+        goto out;
+    }
+
+    /* Don’t manage clients with the override_redirect flag */
+    DLOG("override_redirect is %d\n", attr->override_redirect);
+    if (attr->override_redirect) {
+        FREE_GEOMETRY();
+        goto out;
+    }
+
+    /* Check if the window is already managed */
+    if (con_by_window_id(window) != NULL) {
+        DLOG("already managed (by con %p)\n", con_by_window_id(window));
+        FREE_GEOMETRY();
+        goto out;
+    }
+
+    /* Get the initial geometry (position, size, …) */
+    if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL) {
+        DLOG("could not get geometry\n");
+        goto out;
+    }
+
+    uint32_t values[1];
+
+    /* Set a temporary event mask for the new window, consisting only of
+     * PropertyChange. We need to be notified of PropertyChanges because the
+     * client can change its properties *after* we requested them but *before*
+     * we actually reparented it and have set our final event mask. */
+    values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE;
+    xcb_change_window_attributes(conn, window, XCB_CW_EVENT_MASK, values);
+
+#define GET_PROPERTY(atom, len) xcb_get_property(conn, false, window, atom, XCB_GET_PROPERTY_TYPE_ANY, 0, len)
+
+    wm_type_cookie = GET_PROPERTY(A__NET_WM_WINDOW_TYPE, UINT32_MAX);
+    strut_cookie = GET_PROPERTY(A__NET_WM_STRUT_PARTIAL, UINT32_MAX);
+    state_cookie = GET_PROPERTY(A__NET_WM_STATE, UINT32_MAX);
+    utf8_title_cookie = GET_PROPERTY(A__NET_WM_NAME, 128);
+    leader_cookie = GET_PROPERTY(A_WM_CLIENT_LEADER, UINT32_MAX);
+    transient_cookie = GET_PROPERTY(A_WM_TRANSIENT_FOR, UINT32_MAX);
+    title_cookie = GET_PROPERTY(A_WM_NAME, 128);
+    class_cookie = GET_PROPERTY(A_WM_CLASS, 128);
+    /* TODO: also get wm_normal_hints here. implement after we got rid of xcb-event */
+
+    DLOG("reparenting!\n");
+
+    i3Window *cwindow = scalloc(sizeof(i3Window));
+    cwindow->id = window;
+
+    /* We need to grab the mouse buttons for click to focus */
+    xcb_grab_button(conn, false, window, XCB_EVENT_MASK_BUTTON_PRESS,
+                    XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
+                    1 /* left mouse button */,
+                    XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
+
+    xcb_grab_button(conn, false, window, XCB_EVENT_MASK_BUTTON_PRESS,
+                    XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
+                    3 /* right mouse button */,
+                    XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
+
+
+    /* update as much information as possible so far (some replies may be NULL) */
+    window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL), true);
+    window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL), true);
+    window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL), true);
+    window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL));
+    window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL));
+    window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL));
+
+    /* check if the window needs WM_TAKE_FOCUS */
+    cwindow->needs_take_focus = window_supports_protocol(cwindow->id, A_WM_TAKE_FOCUS);
+
+    /* Where to start searching for a container that swallows the new one? */
+    Con *search_at = croot;
+
+    xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
+    if (xcb_reply_contains_atom(reply, A__NET_WM_WINDOW_TYPE_DOCK)) {
+        LOG("This window is of type dock\n");
+        Output *output = get_output_containing(geom->x, geom->y);
+        if (output != NULL) {
+            DLOG("Starting search at output %s\n", output->name);
+            search_at = output->con;
         }
 
-        /* Put our data structure (Client) into the table */
-        table_put(&by_parent, new->frame, new);
-        table_put(&by_child, child, new);
-
-        /* We need to grab the mouse buttons for click to focus */
-        xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
-                        XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
-                        1 /* left mouse button */,
-                        XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
-
-        xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
-                        XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
-                        3 /* right mouse button */,
-                        XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
-
-        /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
-        xcb_atom_t *atom;
-        xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
-        if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
-                for (int i = 0; i < xcb_get_property_value_length(preply); i++)
-                        if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
-                                DLOG("Window is a dock.\n");
-                                Output *t_out = get_output_containing(x, y);
-                                if (t_out == NULL)
-                                        t_out = c_ws->output;
-                                if (t_out != c_ws->output) {
-                                        DLOG("Dock client requested to be on output %s by geometry (%d, %d)\n",
-                                                        t_out->name, x, y);
-                                        new->workspace = t_out->current_workspace;
-                                }
-                                new->dock = true;
-                                new->borderless = true;
-                                new->titlebar_position = TITLEBAR_OFF;
-                                new->force_reconfigure = true;
-                                new->container = NULL;
-                                SLIST_INSERT_HEAD(&(t_out->dock_clients), new, dock_clients);
-                                /* If it’s a dock we can’t make it float, so we break */
-                                new->floating = FLOATING_AUTO_OFF;
-                                break;
-                        } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
-                                   atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
-                                   atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
-                                   atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
-                                /* Set the dialog window to automatically floating, will be used below */
-                                new->floating = FLOATING_AUTO_ON;
-                                DLOG("dialog/utility/toolbar/splash window, automatically floating\n");
-                        }
-        }
-
-        /* All clients which have a leader should be floating */
-        if (!new->dock && !client_is_floating(new) && new->leader != 0) {
-                DLOG("Client has WM_CLIENT_LEADER hint set, setting floating\n");
-                new->floating = FLOATING_AUTO_ON;
-        }
-
-        if (new->workspace->auto_float) {
-                new->floating = FLOATING_AUTO_ON;
-                DLOG("workspace is in autofloat mode, setting floating\n");
-        }
-
-        if (new->dock) {
-                /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
-                uint32_t *strut;
-                preply = xcb_get_property_reply(conn, strut_cookie, NULL);
-                if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
-                        /* We only use a subset of the provided values, namely the reserved space at the top/bottom
-                           of the screen. This is because the only possibility for bars is at to be at the top/bottom
-                           with maximum horizontal size.
-                           TODO: bars at the top */
-                        new->desired_height = strut[3];
-                        if (new->desired_height == 0) {
-                                DLOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
-                                new->desired_height = original_height;
-                        }
-                        DLOG("the client wants to be %d pixels high\n", new->desired_height);
-                } else {
-                        DLOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
-                        new->desired_height = original_height;
-                }
+        /* find out the desired position of this dock window */
+        if (cwindow->reserved.top > 0 && cwindow->reserved.bottom == 0) {
+            DLOG("Top dock client\n");
+            cwindow->dock = W_DOCK_TOP;
+        } else if (cwindow->reserved.top == 0 && cwindow->reserved.bottom > 0) {
+            DLOG("Bottom dock client\n");
+            cwindow->dock = W_DOCK_BOTTOM;
         } else {
-                /* If it’s not a dock, we can check on which workspace we should put it. */
-
-                /* Firstly, we need to get the window’s class / title. We asked for the properties at the
-                 * top of this function, get them now and pass them to our callback function for window class / title
-                 * changes. It is important that the client was already inserted into the by_child table,
-                 * because the callbacks won’t work otherwise. */
-                preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
-                handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
-
-                preply = xcb_get_property_reply(conn, title_cookie, NULL);
-                handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
-
-                preply = xcb_get_property_reply(conn, class_cookie, NULL);
-                handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
-
-                preply = xcb_get_property_reply(conn, leader_cookie, NULL);
-                handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply);
-
-                /* if WM_CLIENT_LEADER is set, we put the new window on the
-                 * same window as its leader. This might be overwritten by
-                 * assignments afterwards. */
-                if (new->leader != XCB_NONE) {
-                        DLOG("client->leader is set (to 0x%08x)\n", new->leader);
-                        Client *parent = table_get(&by_child, new->leader);
-                        if (parent != NULL && parent->container != NULL) {
-                                Workspace *t_ws = parent->workspace;
-                                new->container = t_ws->table[parent->container->col][parent->container->row];
-                                new->workspace = t_ws;
-                                old_focused = new->container->currently_focused;
-                                map_frame = workspace_is_visible(t_ws);
-                                new->urgent = true;
-                                /* This is a little tricky: we cannot use
-                                 * workspace_update_urgent_flag() because the
-                                 * new window was not yet inserted into the
-                                 * focus stack on t_ws. */
-                                t_ws->urgent = true;
-                        } else {
-                                DLOG("parent is not usable\n");
-                        }
-                }
-
-                struct Assignment *assign;
-                TAILQ_FOREACH(assign, &assignments, assignments) {
-                        if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
-                                continue;
-
-                        if (assign->floating == ASSIGN_FLOATING_ONLY ||
-                            assign->floating == ASSIGN_FLOATING) {
-                                new->floating = FLOATING_AUTO_ON;
-                                LOG("Assignment matches, putting client into floating mode\n");
-                                if (assign->floating == ASSIGN_FLOATING_ONLY)
-                                        break;
-                        }
-
-                        LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
-                            assign->windowclass_title, assign->workspace);
-
-                        if (c_ws->output->current_workspace->num == (assign->workspace-1)) {
-                                DLOG("We are already there, no need to do anything\n");
-                                break;
-                        }
-
-                        DLOG("Changing container/workspace and unmapping the client\n");
-                        Workspace *t_ws = workspace_get(assign->workspace-1);
-                        workspace_initialize(t_ws, c_ws->output, false);
-
-                        new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
-                        new->workspace = t_ws;
-                        old_focused = new->container->currently_focused;
-
-                        map_frame = workspace_is_visible(t_ws);
-                        break;
-                }
+            DLOG("Ignoring invalid reserved edges (_NET_WM_STRUT_PARTIAL), using position as fallback:\n");
+            if (geom->y < (search_at->rect.height / 2)) {
+                DLOG("geom->y = %d < rect.height / 2 = %d, it is a top dock client\n",
+                     geom->y, (search_at->rect.height / 2));
+                cwindow->dock = W_DOCK_TOP;
+            } else {
+                DLOG("geom->y = %d >= rect.height / 2 = %d, it is a bottom dock client\n",
+                     geom->y, (search_at->rect.height / 2));
+                cwindow->dock = W_DOCK_BOTTOM;
+            }
         }
-
-        if (new->workspace->fullscreen_client != NULL) {
-                DLOG("Setting below fullscreen window\n");
-
-                /* If we are in fullscreen, we should place the window below
-                 * the fullscreen window to not be annoying */
-                uint32_t values[] = {
-                        new->workspace->fullscreen_client->frame,
-                        XCB_STACK_MODE_BELOW
-                };
-                xcb_configure_window(conn, new->frame,
-                                     XCB_CONFIG_WINDOW_SIBLING |
-                                     XCB_CONFIG_WINDOW_STACK_MODE, values);
+    }
+
+    DLOG("Initial geometry: (%d, %d, %d, %d)\n", geom->x, geom->y, geom->width, geom->height);
+
+    Con *nc = NULL;
+    Match *match;
+    Assignment *assignment;
+
+    /* TODO: two matches for one container */
+
+    /* See if any container swallows this new window */
+    nc = con_for_window(search_at, cwindow, &match);
+    if (nc == NULL) {
+        /* If not, check if it is assigned to a specific workspace / output */
+        if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE | A_TO_OUTPUT))) {
+            DLOG("Assignment matches (%p)\n", match);
+            if (assignment->type == A_TO_WORKSPACE) {
+                nc = con_descend_focused(workspace_get(assignment->dest.workspace, NULL));
+                DLOG("focused on ws %s: %p / %s\n", assignment->dest.workspace, nc, nc->name);
+                if (nc->type == CT_WORKSPACE)
+                    nc = tree_open_con(nc, cwindow);
+                else nc = tree_open_con(nc->parent, cwindow);
+            }
+        /* TODO: handle assignments with type == A_TO_OUTPUT */
+        } else {
+            /* If not, insert it at the currently focused position */
+            if (focused->type == CT_CON && con_accepts_window(focused)) {
+                LOG("using current container, focused = %p, focused->name = %s\n",
+                                focused, focused->name);
+                nc = focused;
+            } else nc = tree_open_con(NULL, cwindow);
         }
-
-        /* Insert into the currently active container, if it’s not a dock window */
-        if (!new->dock && !client_is_floating(new)) {
-                /* Insert after the old active client, if existing. If it does not exist, the
-                   container is empty and it does not matter, where we insert it */
-                if (old_focused != NULL && !old_focused->dock)
-                        CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
-                else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
-
-                if (new->container->workspace->fullscreen_client != NULL)
-                        SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
-                else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
-
-                client_set_below_floating(conn, new);
+    } else {
+        /* M_BELOW inserts the new window as a child of the one which was
+         * matched (e.g. dock areas) */
+        if (match != NULL && match->insert_where == M_BELOW) {
+            nc = tree_open_con(nc, cwindow);
         }
-
-        if (client_is_floating(new)) {
-                SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
-
-                /* Add the client to the list of floating clients for its workspace */
-                TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
-
-                new->container = NULL;
-
-                new->rect.width = new->floating_rect.width + 2 + 2;
-                new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
-
-                /* Some clients (like GIMP’s color picker window) get mapped
-                 * to (0, 0), so we push them to a reasonable position
-                 * (centered over their leader) */
-                if (new->leader != 0 && x == 0 && y == 0) {
-                        DLOG("Floating client wants to (0x0), moving it over its leader instead\n");
-                        Client *leader = table_get(&by_child, new->leader);
-                        if (leader == NULL) {
-                                DLOG("leader is NULL, centering it over current workspace\n");
-
-                                x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2);
-                                y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2);
-                        } else {
-                                x = leader->rect.x + (leader->rect.width / 2) - (new->rect.width / 2);
-                                y = leader->rect.y + (leader->rect.height / 2) - (new->rect.height / 2);
-                        }
-                }
-                new->floating_rect.x = new->rect.x = x;
-                new->floating_rect.y = new->rect.y = y;
-                DLOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
-                                new->floating_rect.x, new->floating_rect.y,
-                                new->floating_rect.width, new->floating_rect.height);
-                DLOG("outer rect (%d, %d) size (%d, %d)\n",
-                                new->rect.x, new->rect.y, new->rect.width, new->rect.height);
-
-                /* Make sure it is on top of the other windows */
-                xcb_raise_window(conn, new->frame);
-                reposition_client(conn, new);
-                resize_client(conn, new);
-                /* redecorate_window flushes */
-                redecorate_window(conn, new);
+    }
+
+    DLOG("new container = %p\n", nc);
+    nc->window = cwindow;
+    x_reinit(nc);
+
+    nc->border_width = geom->border_width;
+
+    char *name;
+    asprintf(&name, "[i3 con] container around %p", cwindow);
+    x_set_name(nc, name);
+    free(name);
+
+    Con *ws = con_get_workspace(nc);
+    Con *fs = (ws ? con_get_fullscreen_con(ws, CF_OUTPUT) : NULL);
+    if (fs == NULL)
+        fs = con_get_fullscreen_con(croot, CF_GLOBAL);
+
+    if (fs == NULL) {
+        DLOG("Not in fullscreen mode, focusing\n");
+        if (!cwindow->dock)
+            con_focus(nc);
+        else DLOG("dock, not focusing\n");
+    } else {
+        DLOG("fs = %p, ws = %p, not focusing\n", fs, ws);
+        /* Insert the new container in focus stack *after* the currently
+         * focused (fullscreen) con. This way, the new container will be
+         * focused after we return from fullscreen mode */
+        Con *first = TAILQ_FIRST(&(nc->parent->focus_head));
+        if (first != nc) {
+            /* We only modify the focus stack if the container is not already
+             * the first one. This can happen when existing containers swallow
+             * new windows, for example when restarting. */
+            TAILQ_REMOVE(&(nc->parent->focus_head), nc, focused);
+            TAILQ_INSERT_AFTER(&(nc->parent->focus_head), first, nc, focused);
         }
-
-        new->initialized = true;
-
-        /* Check if the window already got the fullscreen hint set */
-        xcb_atom_t *state;
-        if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
-            (state = xcb_get_property_value(preply)) != NULL)
-                /* Check all set _NET_WM_STATEs */
-                for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
-                        if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
-                                continue;
-                        /* If the window got the fullscreen state, we just toggle fullscreen
-                           and don’t event bother to redraw the layout – that would not change
-                           anything anyways */
-                        client_toggle_fullscreen(conn, new);
-                        goto map;
-                }
-
-        render_layout(conn);
-
-map:
-        /* Map the window first to avoid flickering */
-        xcb_map_window(conn, child);
-        if (map_frame)
-                client_map(conn, new);
-
-        if ((CUR_CELL->workspace->fullscreen_client == NULL || new->fullscreen) && !new->dock) {
-                /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
-                if ((new->workspace->fullscreen_client == NULL) || new->fullscreen) {
-                        if (!client_is_floating(new)) {
-                                new->container->currently_focused = new;
-                                if (map_frame)
-                                        render_container(conn, new->container);
-                        }
-                        if (new->container == CUR_CELL || client_is_floating(new)) {
-                                xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
-                                ewmh_update_active_window(new->child);
-                        }
-                }
+    }
+
+    /* set floating if necessary */
+    bool want_floating = false;
+    if (xcb_reply_contains_atom(reply, A__NET_WM_WINDOW_TYPE_DIALOG) ||
+        xcb_reply_contains_atom(reply, A__NET_WM_WINDOW_TYPE_UTILITY) ||
+        xcb_reply_contains_atom(reply, A__NET_WM_WINDOW_TYPE_TOOLBAR) ||
+        xcb_reply_contains_atom(reply, A__NET_WM_WINDOW_TYPE_SPLASH)) {
+        LOG("This window is a dialog window, setting floating\n");
+        want_floating = true;
+    }
+
+    FREE(reply);
+
+    if (cwindow->transient_for != XCB_NONE ||
+        (cwindow->leader != XCB_NONE &&
+         cwindow->leader != cwindow->id &&
+         con_by_window_id(cwindow->leader) != NULL)) {
+        LOG("This window is transiert for another window, setting floating\n");
+        want_floating = true;
+
+        if (config.popup_during_fullscreen == PDF_LEAVE_FULLSCREEN &&
+            fs != NULL) {
+            LOG("There is a fullscreen window, leaving fullscreen mode\n");
+            con_toggle_fullscreen(fs, CF_OUTPUT);
         }
-
-        xcb_flush(conn);
+    }
+
+    /* dock clients cannot be floating, that makes no sense */
+    if (cwindow->dock)
+        want_floating = false;
+
+    /* Store the requested geometry. The width/height gets raised to at least
+     * 75x50 when entering floating mode, which is the minimum size for a
+     * window to be useful (smaller windows are usually overlays/toolbars/…
+     * which are not managed by the wm anyways). We store the original geometry
+     * here because it’s used for dock clients. */
+    nc->geometry = (Rect){ geom->x, geom->y, geom->width, geom->height };
+
+    if (want_floating) {
+        DLOG("geometry = %d x %d\n", nc->geometry.width, nc->geometry.height);
+        floating_enable(nc, false);
+    }
+
+    /* to avoid getting an UnmapNotify event due to reparenting, we temporarily
+     * declare no interest in any state change event of this window */
+    values[0] = XCB_NONE;
+    xcb_change_window_attributes(conn, window, XCB_CW_EVENT_MASK, values);
+
+    xcb_void_cookie_t rcookie = xcb_reparent_window_checked(conn, window, nc->frame, 0, 0);
+    if (xcb_request_check(conn, rcookie) != NULL) {
+        LOG("Could not reparent the window, aborting\n");
+        goto geom_out;
+    }
+
+    values[0] = CHILD_EVENT_MASK & ~XCB_EVENT_MASK_ENTER_WINDOW;
+    xcb_change_window_attributes(conn, window, XCB_CW_EVENT_MASK, values);
+    xcb_flush(conn);
+
+    reply = xcb_get_property_reply(conn, state_cookie, NULL);
+    if (xcb_reply_contains_atom(reply, A__NET_WM_STATE_FULLSCREEN))
+        con_toggle_fullscreen(nc, CF_OUTPUT);
+
+    FREE(reply);
+
+    /* Put the client inside the save set. Upon termination (whether killed or
+     * normal exit does not matter) of the window manager, these clients will
+     * be correctly reparented to their most closest living ancestor (=
+     * cleanup) */
+    xcb_change_save_set(conn, XCB_SET_MODE_INSERT, window);
+
+    /* Check if any assignments match */
+    run_assignments(cwindow);
+
+    tree_render();
+
+geom_out:
+    free(geom);
+out:
+    free(attr);
+    return;
 }
diff --git a/src/match.c b/src/match.c
new file mode 100644 (file)
index 0000000..3a34611
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * A "match" is a data structure which acts like a mask or expression to match
+ * certain windows or not. For example, when using commands, you can specify a
+ * command like this: [title="*Firefox*"] kill. The title member of the match
+ * data structure will then be filled and i3 will check each window using
+ * match_matches_window() to find the windows affected by this command.
+ *
+ */
+
+#include "all.h"
+
+/*
+ * Initializes the Match data structure. This function is necessary because the
+ * members representing boolean values (like dock) need to be initialized with
+ * -1 instead of 0.
+ *
+ */
+void match_init(Match *match) {
+    memset(match, 0, sizeof(Match));
+    match->dock = -1;
+}
+
+/*
+ * Check if a match is empty. This is necessary while parsing commands to see
+ * whether the user specified a match at all.
+ *
+ */
+bool match_is_empty(Match *match) {
+    /* we cannot simply use memcmp() because the structure is part of a
+     * TAILQ and I don’t want to start with things like assuming that the
+     * last member of a struct really is at the end in memory… */
+    return (match->title == NULL &&
+            match->mark == NULL &&
+            match->application == NULL &&
+            match->class == NULL &&
+            match->instance == NULL &&
+            match->id == XCB_NONE &&
+            match->con_id == NULL &&
+            match->dock == -1 &&
+            match->floating == M_ANY);
+}
+
+/*
+ * Copies the data of a match from src to dest.
+ *
+ */
+void match_copy(Match *dest, Match *src) {
+    memcpy(dest, src, sizeof(Match));
+
+#define STRDUP(field) do { \
+    if (src->field != NULL) \
+        dest->field = sstrdup(src->field); \
+} while (0)
+
+    STRDUP(title);
+    STRDUP(mark);
+    STRDUP(application);
+    STRDUP(class);
+    STRDUP(instance);
+}
+
+/*
+ * Check if a match data structure matches the given window.
+ *
+ */
+bool match_matches_window(Match *match, i3Window *window) {
+    LOG("checking window %d (%s)\n", window->id, window->class_class);
+
+    /* TODO: pcre, full matching, … */
+    if (match->class != NULL) {
+        if (window->class_class != NULL && strcasecmp(match->class, window->class_class) == 0) {
+            LOG("window class matches (%s)\n", window->class_class);
+        } else {
+            LOG("window class does not match\n");
+            return false;
+        }
+    }
+
+    if (match->instance != NULL) {
+        if (window->class_instance != NULL && strcasecmp(match->instance, window->class_instance) == 0) {
+            LOG("window instance matches (%s)\n", window->class_instance);
+        } else {
+            LOG("window instance does not match\n");
+            return false;
+        }
+    }
+
+    if (match->id != XCB_NONE) {
+        if (window->id == match->id) {
+            LOG("match made by window id (%d)\n", window->id);
+        } else {
+            LOG("window id does not match\n");
+            return false;
+        }
+    }
+
+    /* TODO: pcre match */
+    if (match->title != NULL) {
+        if (window->name_json != NULL && strcasecmp(match->title, window->name_json) == 0) {
+            LOG("title matches (%s)\n", window->name_json);
+        } else {
+            LOG("title does not match\n");
+            return false;
+        }
+    }
+
+    if (match->dock != -1) {
+        LOG("match->dock = %d, window->dock = %d\n", match->dock, window->dock);
+        if ((window->dock == W_DOCK_TOP && match->dock == M_DOCK_TOP) ||
+         (window->dock == W_DOCK_BOTTOM && match->dock == M_DOCK_BOTTOM) ||
+         ((window->dock == W_DOCK_TOP || window->dock == W_DOCK_BOTTOM) &&
+          match->dock == M_DOCK_ANY) ||
+         (window->dock == W_NODOCK && match->dock == M_NODOCK)) {
+            LOG("dock status matches\n");
+        } else {
+            LOG("dock status does not match\n");
+            return false;
+        }
+    }
+
+    /* We don’t check the mark because this function is not even called when
+     * the mark would have matched - it is checked in cmdparse.y itself */
+    if (match->mark != NULL) {
+        LOG("mark does not match\n");
+        return false;
+    }
+
+    return true;
+}
diff --git a/src/move.c b/src/move.c
new file mode 100644 (file)
index 0000000..37fc0d3
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ */
+
+#include "all.h"
+#include "cmdparse.tab.h"
+
+typedef enum { BEFORE, AFTER } position_t;
+
+/*
+ * This function detaches 'con' from its parent and inserts it either before or
+ * after 'target'.
+ *
+ */
+static void insert_con_into(Con *con, Con *target, position_t position) {
+    Con *parent = target->parent;
+    /* We need to preserve the old con->parent. While it might still be used to
+     * insert the entry before/after it, we call the on_remove_child callback
+     * afterwards which might then close the con if it is empty. */
+    Con *old_parent = con->parent;
+
+    con_detach(con);
+    con_fix_percent(con->parent);
+
+    /* When moving to a workspace, we respect the user’s configured
+     * workspace_layout */
+    if (parent->type == CT_WORKSPACE) {
+        Con *split = workspace_attach_to(parent);
+        if (split != parent) {
+            DLOG("Got a new split con, using that one instead\n");
+            con->parent = split;
+            con_attach(con, split, false);
+            DLOG("attached\n");
+            con->percent = 0.0;
+            con_fix_percent(split);
+            con = split;
+            DLOG("ok, continuing with con %p instead\n", con);
+            con_detach(con);
+        }
+    }
+
+    con->parent = parent;
+
+    if (position == BEFORE) {
+        TAILQ_INSERT_BEFORE(target, con, nodes);
+        TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused);
+    } else if (position == AFTER) {
+        TAILQ_INSERT_AFTER(&(parent->nodes_head), target, con, nodes);
+        TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused);
+    }
+
+    /* Pretend the con was just opened with regards to size percent values.
+     * Since the con is moved to a completely different con, the old value
+     * does not make sense anyways. */
+    con->percent = 0.0;
+    con_fix_percent(parent);
+
+    CALL(old_parent, on_remove_child);
+}
+
+/*
+ * This function detaches 'con' from its parent and inserts it at the given
+ * workspace.
+ *
+ */
+static void attach_to_workspace(Con *con, Con *ws) {
+    con_detach(con);
+    con_fix_percent(con->parent);
+
+    CALL(con->parent, on_remove_child);
+
+    con->parent = ws;
+
+    TAILQ_INSERT_TAIL(&(ws->nodes_head), con, nodes);
+    TAILQ_INSERT_TAIL(&(ws->focus_head), con, focused);
+
+    /* Pretend the con was just opened with regards to size percent values.
+     * Since the con is moved to a completely different con, the old value
+     * does not make sense anyways. */
+    con->percent = 0.0;
+    con_fix_percent(ws);
+}
+
+/*
+ * Moves the current container in the given direction (TOK_LEFT, TOK_RIGHT,
+ * TOK_UP, TOK_DOWN from cmdparse.l)
+ *
+ */
+void tree_move(int direction) {
+    DLOG("Moving in direction %d\n", direction);
+    /* 1: get the first parent with the same orientation */
+    Con *con = focused;
+
+    if (con->type == CT_WORKSPACE) {
+        DLOG("Not moving workspace\n");
+        return;
+    }
+
+    if (con->parent->type == CT_WORKSPACE && con_num_children(con->parent) == 1) {
+        DLOG("This is the only con on this workspace, not doing anything\n");
+        return;
+    }
+
+    orientation_t o = (direction == TOK_LEFT || direction == TOK_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
+     * same_orientation, see the very last lines before the end of this block
+     * */
+    do {
+        /* There is no parent container with the same orientation */
+        if (!same_orientation) {
+            if (con_is_floating(con)) {
+                /* this is a floating con, we just disable floating */
+                floating_disable(con, true);
+                return;
+            }
+            if (con_inside_floating(con)) {
+                /* 'con' should be moved out of a floating container */
+                DLOG("Inside floating, moving to workspace\n");
+                attach_to_workspace(con, con_get_workspace(con));
+                goto end;
+            }
+            DLOG("Force-changing orientation\n");
+            ws_force_orientation(con_get_workspace(con), o);
+            same_orientation = con_parent_with_orientation(con, o);
+        }
+
+        /* easy case: the move is within this container */
+        if (same_orientation == con->parent) {
+            DLOG("We are in the same container\n");
+            Con *swap;
+            if ((swap = (direction == TOK_LEFT || direction == TOK_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)
+                    TAILQ_SWAP(swap, con, &(swap->parent->nodes_head), nodes);
+                else TAILQ_SWAP(con, swap, &(swap->parent->nodes_head), nodes);
+
+                TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
+                TAILQ_INSERT_HEAD(&(swap->parent->focus_head), con, focused);
+
+                DLOG("Swapped.\n");
+                return;
+            }
+
+            /* If there was no con with which we could swap the current one, search
+             * again, but starting one level higher. If we are on the workspace
+             * level, don’t do that. The result would be a force change of
+             * workspace orientation, which is not necessary. */
+            if (con->parent == con_get_workspace(con))
+                return;
+            same_orientation = con_parent_with_orientation(con->parent, o);
+        }
+    } while (same_orientation == NULL);
+
+    /* this time, we have to move to another container */
+    /* This is the container *above* 'con' (an ancestor of con) which is inside
+     * 'same_orientation' */
+    Con *above = con;
+    while (above->parent != same_orientation)
+        above = above->parent;
+
+    DLOG("above = %p\n", above);
+    Con *next;
+    position_t position;
+    if (direction == TOK_UP || direction == TOK_LEFT) {
+        position = BEFORE;
+        next = TAILQ_PREV(above, nodes_head, nodes);
+    } else {
+        position = AFTER;
+        next = TAILQ_NEXT(above, nodes);
+    }
+
+    /* special case: there is a split container in the direction we are moving
+     * to, so descend and append */
+    if (next && !con_is_leaf(next))
+        insert_con_into(con, con_descend_focused(next), AFTER);
+    else
+        insert_con_into(con, above, position);
+
+end:
+    /* We need to call con_focus() to fix the focus stack "above" the container
+     * we just inserted the focused container into (otherwise, the parent
+     * container(s) would still point to the old container(s)). */
+    con_focus(con);
+
+    tree_flatten(croot);
+}
diff --git a/src/output.c b/src/output.c
new file mode 100644 (file)
index 0000000..6848792
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ */
+
+#include "all.h"
+
+/*
+ * Returns the output container below the given output container.
+ *
+ */
+Con *output_get_content(Con *output) {
+    Con *child;
+
+    TAILQ_FOREACH(child, &(output->nodes_head), nodes)
+        if (child->type == CT_CON)
+            return child;
+
+    ELOG("output_get_content() called on non-output %p\n", output);
+    assert(false);
+}
index 5a48b1762e95792dc0ea7b037b09ee67ee4346cb..e57549b73d6257b4471a287d10604c36f524a432 100644 (file)
@@ -1,9 +1,9 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
  *
- * © 2009-2010 Michael Stapelberg and contributors
+ * © 2009-2011 Michael Stapelberg and contributors
  *
  * See file LICENSE for license information.
  *
  * (take your time to read it completely, it answers all questions).
  *
  */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdbool.h>
-#include <assert.h>
 #include <time.h>
-#include <unistd.h>
 
-#include <xcb/xcb.h>
 #include <xcb/randr.h>
 
-#include "queue.h"
-#include "i3.h"
-#include "data.h"
-#include "table.h"
-#include "util.h"
-#include "layout.h"
-#include "xcb.h"
-#include "config.h"
-#include "workspace.h"
-#include "log.h"
-#include "ewmh.h"
-#include "ipc.h"
-#include "client.h"
+#include "all.h"
 
 /* While a clean namespace is usually a pretty good thing, we really need
  * to use shorter names than the whole xcb_randr_* default names. */
@@ -43,6 +24,9 @@ typedef xcb_randr_get_crtc_info_reply_t crtc_info;
 typedef xcb_randr_mode_info_t mode_info;
 typedef xcb_randr_get_screen_resources_current_reply_t resources_reply;
 
+/* Pointer to the result of the query for primary output */
+xcb_randr_get_output_primary_reply_t *primary;
+
 /* Stores all outputs available in your current session. */
 struct outputs_head outputs = TAILQ_HEAD_INITIALIZER(outputs);
 
@@ -55,12 +39,12 @@ static bool randr_disabled = false;
  *
  */
 static Output *get_output_by_id(xcb_randr_output_t id) {
-        Output *output;
-        TAILQ_FOREACH(output, &outputs, outputs)
-                if (output->id == id)
-                        return output;
+    Output *output;
+    TAILQ_FOREACH(output, &outputs, outputs)
+        if (output->id == id)
+            return output;
 
-        return NULL;
+    return NULL;
 }
 
 /*
@@ -68,13 +52,13 @@ static Output *get_output_by_id(xcb_randr_output_t id) {
  *
  */
 Output *get_output_by_name(const char *name) {
-        Output *output;
-        TAILQ_FOREACH(output, &outputs, outputs)
-                if (output->active &&
-                    strcasecmp(output->name, name) == 0)
-                        return output;
+    Output *output;
+    TAILQ_FOREACH(output, &outputs, outputs)
+        if (output->active &&
+            strcasecmp(output->name, name) == 0)
+            return output;
 
-        return NULL;
+    return NULL;
 }
 
 /*
@@ -82,13 +66,13 @@ Output *get_output_by_name(const char *name) {
  *
  */
 Output *get_first_output() {
-        Output *output;
+    Output *output;
 
-        TAILQ_FOREACH(output, &outputs, outputs)
-                if (output->active)
-                        return output;
+    TAILQ_FOREACH(output, &outputs, outputs)
+        if (output->active)
+            return output;
 
-        return NULL;
+    return NULL;
 }
 
 /*
@@ -97,18 +81,18 @@ Output *get_first_output() {
  *
  */
 Output *get_output_containing(int x, int y) {
-        Output *output;
-        TAILQ_FOREACH(output, &outputs, outputs) {
-                if (!output->active)
-                        continue;
-                DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
-                                x, y, output->rect.x, output->rect.y, output->rect.width, output->rect.height);
-                if (x >= output->rect.x && x < (output->rect.x + output->rect.width) &&
-                    y >= output->rect.y && y < (output->rect.y + output->rect.height))
-                        return output;
-        }
-
-        return NULL;
+    Output *output;
+    TAILQ_FOREACH(output, &outputs, outputs) {
+        if (!output->active)
+            continue;
+        DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
+                        x, y, output->rect.x, output->rect.y, output->rect.width, output->rect.height);
+        if (x >= output->rect.x && x < (output->rect.x + output->rect.width) &&
+            y >= output->rect.y && y < (output->rect.y + output->rect.height))
+            return output;
+    }
+
+    return NULL;
 }
 
 /*
@@ -120,116 +104,322 @@ Output *get_output_containing(int x, int y) {
  *
  */
 Output *get_output_most(direction_t direction, Output *current) {
-        Output *output, *candidate = NULL;
-        int position = 0;
-        TAILQ_FOREACH(output, &outputs, outputs) {
-                if (!output->active)
-                        continue;
+    Output *output, *candidate = NULL;
+    int position = 0;
+    TAILQ_FOREACH(output, &outputs, outputs) {
+        if (!output->active)
+            continue;
+
+        /* Repeated calls of WIN determine the winner of the comparison */
+        #define WIN(variable, condition) \
+            if (variable condition) { \
+                candidate = output; \
+                position = variable; \
+            } \
+            break;
+
+        if (((direction == D_UP) || (direction == D_DOWN)) &&
+            (current->rect.x != output->rect.x))
+            continue;
+
+        if (((direction == D_LEFT) || (direction == D_RIGHT)) &&
+            (current->rect.y != output->rect.y))
+            continue;
+
+        switch (direction) {
+            case D_UP:
+                WIN(output->rect.y, <= position);
+            case D_DOWN:
+                WIN(output->rect.y, >= position);
+            case D_LEFT:
+                WIN(output->rect.x, <= position);
+            case D_RIGHT:
+                WIN(output->rect.x, >= position);
+        }
+    }
 
-                /* Repeated calls of WIN determine the winner of the comparison */
-                #define WIN(variable, condition) \
-                        if (variable condition) { \
-                                candidate = output; \
-                                position = variable; \
-                        } \
-                        break;
+    assert(candidate != NULL);
 
-                if (((direction == D_UP) || (direction == D_DOWN)) &&
-                    (current->rect.x != output->rect.x))
-                        continue;
+    return candidate;
+}
 
-                if (((direction == D_LEFT) || (direction == D_RIGHT)) &&
-                    (current->rect.y != output->rect.y))
-                        continue;
+/*
+ * Disables RandR support by creating exactly one output with the size of the
+ * X11 screen.
+ *
+ */
+void disable_randr(xcb_connection_t *conn) {
+    DLOG("RandR extension unusable, disabling.\n");
 
-                switch (direction) {
-                        case D_UP:
-                                WIN(output->rect.y, <= position);
-                        case D_DOWN:
-                                WIN(output->rect.y, >= position);
-                        case D_LEFT:
-                                WIN(output->rect.x, <= position);
-                        case D_RIGHT:
-                                WIN(output->rect.x, >= position);
-                }
-        }
+    Output *s = scalloc(sizeof(Output));
+
+    s->active = true;
+    s->rect.x = 0;
+    s->rect.y = 0;
+    s->rect.width = root_screen->width_in_pixels;
+    s->rect.height = root_screen->height_in_pixels;
+    s->name = "xroot-0";
+    output_init_con(s);
+    init_ws_for_output(s, output_get_content(s->con));
 
-        assert(candidate != NULL);
+    TAILQ_INSERT_TAIL(&outputs, s, outputs);
 
-        return candidate;
+    randr_disabled = true;
 }
 
 /*
- * Initializes the specified output, assigning the specified workspace to it.
+ * Initializes a CT_OUTPUT Con (searches existing ones from inplace restart
+ * before) to use for the given Output.
  *
  */
-void initialize_output(xcb_connection_t *conn, Output *output, Workspace *workspace) {
-        i3Font *font = load_font(conn, config.font);
-
-        workspace->output = output;
-        output->current_workspace = workspace;
-
-        /* Copy rect for the workspace */
-        memcpy(&(workspace->rect), &(output->rect), sizeof(Rect));
-
-        /* Map clients on the workspace, if any */
-        workspace_map_clients(conn, workspace);
-
-        /* Create a bar window on each output */
-        if (!config.disable_workspace_bar) {
-                Rect bar_rect = {output->rect.x,
-                                 output->rect.y + output->rect.height - (font->height + 6),
-                                 output->rect.x + output->rect.width,
-                                 font->height + 6};
-                uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
-                uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS};
-                output->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, true, mask, values);
-                output->bargc = xcb_generate_id(conn);
-                xcb_create_gc(conn, output->bargc, output->bar, 0, 0);
-        }
+void output_init_con(Output *output) {
+    Con *con = NULL, *current;
+    bool reused = false;
+
+    DLOG("init_con for output %s\n", output->name);
+
+    /* Search for a Con with that name directly below the root node. There
+     * might be one from a restored layout. */
+    TAILQ_FOREACH(current, &(croot->nodes_head), nodes) {
+        if (strcmp(current->name, output->name) != 0)
+            continue;
+
+        con = current;
+        reused = true;
+        DLOG("Using existing con %p / %s\n", con, con->name);
+        break;
+    }
+
+    if (con == NULL) {
+        con = con_new(croot, NULL);
+        FREE(con->name);
+        con->name = sstrdup(output->name);
+        con->type = CT_OUTPUT;
+        con->layout = L_OUTPUT;
+        con_fix_percent(croot);
+    }
+    con->rect = output->rect;
+    output->con = con;
+
+    char *name;
+    asprintf(&name, "[i3 con] output %s", con->name);
+    x_set_name(con, name);
+    FREE(name);
+
+    if (reused) {
+        DLOG("Not adding workspace, this was a reused con\n");
+        return;
+    }
+
+    DLOG("Changing layout, adding top/bottom dockarea\n");
+    Con *topdock = con_new(NULL, NULL);
+    topdock->type = CT_DOCKAREA;
+    topdock->layout = L_DOCKAREA;
+    topdock->orientation = VERT;
+    /* this container swallows dock clients */
+    Match *match = scalloc(sizeof(Match));
+    match_init(match);
+    match->dock = M_DOCK_TOP;
+    match->insert_where = M_BELOW;
+    TAILQ_INSERT_TAIL(&(topdock->swallow_head), match, matches);
+
+    FREE(topdock->name);
+    topdock->name = sstrdup("topdock");
+
+    asprintf(&name, "[i3 con] top dockarea %s", con->name);
+    x_set_name(topdock, name);
+    FREE(name);
+    DLOG("attaching\n");
+    con_attach(topdock, con, false);
+
+    /* 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");
+
+    asprintf(&name, "[i3 con] content %s", con->name);
+    x_set_name(content, name);
+    FREE(name);
+    con_attach(content, con, false);
+
+    /* bottom dock container */
+    Con *bottomdock = con_new(NULL, NULL);
+    bottomdock->type = CT_DOCKAREA;
+    bottomdock->layout = L_DOCKAREA;
+    bottomdock->orientation = VERT;
+    /* this container swallows dock clients */
+    match = scalloc(sizeof(Match));
+    match_init(match);
+    match->dock = M_DOCK_BOTTOM;
+    match->insert_where = M_BELOW;
+    TAILQ_INSERT_TAIL(&(bottomdock->swallow_head), match, matches);
+
+    FREE(bottomdock->name);
+    bottomdock->name = sstrdup("bottomdock");
+
+    asprintf(&name, "[i3 con] bottom dockarea %s", con->name);
+    x_set_name(bottomdock, name);
+    FREE(name);
+    DLOG("attaching\n");
+    con_attach(bottomdock, con, false);
+}
 
-        SLIST_INIT(&(output->dock_clients));
+/*
+ * Initializes at least one workspace for this output, trying the following
+ * steps until there is at least one workspace:
+ *
+ * • Move existing workspaces, which are assigned to be on the given output, to
+ *   the output.
+ * • Create the first assigned workspace for this output.
+ * • Create the first unused workspace.
+ *
+ */
+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) {
+        if (strcmp(assignment->output, output->name) != 0)
+            continue;
+
+        /* check if this workspace actually exists */
+        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;
+
+        /* check that this workspace is not already attached (that means the
+         * user configured this assignment twice) */
+        Con *workspace_out = con_get_output(workspace);
+        if (workspace_out == output->con) {
+            LOG("Workspace \"%s\" assigned to output \"%s\", but it is already "
+                "there. Do you have two assignment directives for the same "
+                "workspace in your configuration file?\n",
+                workspace->name, output->name);
+            continue;
+        }
 
-        ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
-        DLOG("initialized output at (%d, %d) with %d x %d\n",
-                        output->rect.x, output->rect.y, output->rect.width, output->rect.height);
+        /* if so, move it over */
+        LOG("Moving workspace \"%s\" from output \"%s\" to \"%s\" due to assignment\n",
+            workspace->name, workspace_out->name, output->name);
+
+        /* if the workspace is currently visible on that output, we need to
+         * switch to a different workspace - otherwise the output would end up
+         * with no active workspace */
+        bool visible = workspace_is_visible(workspace);
+        Con *previous = NULL;
+        if (visible && (previous = TAILQ_NEXT(workspace, focused))) {
+            LOG("Switching to previously used workspace \"%s\" on output \"%s\"\n",
+                previous->name, workspace_out->name);
+            workspace_show(previous->name);
+        }
 
-        DLOG("assigning configured workspaces to this output...\n");
-        Workspace *ws;
-        TAILQ_FOREACH(ws, workspaces, workspaces) {
-                if (ws == workspace)
-                        continue;
-                if (ws->preferred_output == NULL ||
-                    get_output_by_name(ws->preferred_output) != output)
+        con_detach(workspace);
+        con_attach(workspace, content, false);
+
+        /* In case the workspace we just moved was visible but there was no
+         * other workspace to switch to, we need to initialize the source
+         * output aswell */
+        if (visible && previous == NULL) {
+            LOG("There is no workspace left on \"%s\", re-initializing\n",
+                workspace_out->name);
+            init_ws_for_output(get_output_by_name(workspace_out->name),
+                               output_get_content(workspace_out));
+            DLOG("Done re-initializing, continuing with \"%s\"\n", output->name);
+        }
+    }
+
+    /* if a workspace exists, we are done now */
+    if (!TAILQ_EMPTY(&(content->nodes_head))) {
+        /* ensure that one of the workspaces is actually visible (in fullscreen
+         * mode), if they were invisible before, this might not be the case. */
+        Con *visible = NULL;
+        GREP_FIRST(visible, content, child->fullscreen_mode == CF_OUTPUT);
+        if (!visible) {
+            visible = TAILQ_FIRST(&(content->nodes_head));
+            focused = content;
+            workspace_show(visible->name);
+        }
+        return;
+    }
+
+    /* otherwise, we create the first assigned ws for this output */
+    TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
+        if (strcmp(assignment->output, output->name) != 0)
+            continue;
+
+        LOG("Initializing first assigned workspace \"%s\" for output \"%s\"\n",
+            assignment->name, assignment->output);
+        focused = content;
+        workspace_show(assignment->name);
+        return;
+    }
+
+    /* 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 *ws = con_new(NULL, NULL);
+    ws->type = CT_WORKSPACE;
+
+    /* get the next unused workspace number */
+    DLOG("Getting next unused workspace\n");
+    int c = 0;
+    bool exists = true;
+    while (exists) {
+        Con *out, *current, *child;
+
+        c++;
+
+        FREE(ws->name);
+        asprintf(&(ws->name), "%d", c);
+
+        exists = false;
+        TAILQ_FOREACH(out, &(croot->nodes_head), nodes) {
+            TAILQ_FOREACH(current, &(out->nodes_head), nodes) {
+                if (current->type != CT_CON)
+                    continue;
+
+                TAILQ_FOREACH(child, &(current->nodes_head), nodes) {
+                    if (strcasecmp(child->name, ws->name) != 0)
                         continue;
 
-                DLOG("assigning ws %d\n", ws->num + 1);
-                workspace_assign_to(ws, output, true);
+                    exists = true;
+                    break;
+                }
+            }
         }
-}
 
-/*
- * Disables RandR support by creating exactly one output with the size of the
- * X11 screen.
- *
- */
-void disable_randr(xcb_connection_t *conn) {
-        xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
+        DLOG("result for ws %s / %d: exists = %d\n", ws->name, c, exists);
+    }
+    ws->num = c;
+    con_attach(ws, content, false);
 
-        DLOG("RandR extension unusable, disabling.\n");
+    asprintf(&name, "[i3 con] workspace %s", ws->name);
+    x_set_name(ws, name);
+    free(name);
 
-        Output *s = scalloc(sizeof(Output));
+    ws->fullscreen_mode = CF_OUTPUT;
 
-        s->active = true;
-        s->rect.x = 0;
-        s->rect.y = 0;
-        s->rect.width = root_screen->width_in_pixels;
-        s->rect.height = root_screen->height_in_pixels;
-        s->name = "xroot-0";
+    /* 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;
+    }
 
-        TAILQ_INSERT_TAIL(&outputs, s, outputs);
 
-        randr_disabled = true;
+    /* TODO: Set focus in main.c */
+    con_focus(ws);
 }
 
 /*
@@ -244,52 +434,35 @@ void disable_randr(xcb_connection_t *conn) {
  *
  */
 static void output_change_mode(xcb_connection_t *conn, Output *output) {
-        i3Font *font = load_font(conn, config.font);
-        Workspace *ws;
-        Client *client;
-
-        DLOG("Output mode changed, reconfiguring bar, updating workspaces\n");
-        Rect bar_rect = {output->rect.x,
-                         output->rect.y + output->rect.height - (font->height + 6),
-                         output->rect.x + output->rect.width,
-                         font->height + 6};
-
-        xcb_set_window_rect(conn, output->bar, bar_rect);
-
-        /* go through all workspaces and set force_reconfigure */
-        TAILQ_FOREACH(ws, workspaces, workspaces) {
-                if (ws->output != output)
-                        continue;
-
-                SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) {
-                        client->force_reconfigure = true;
-                        if (!client_is_floating(client))
-                                continue;
-                        /* For floating clients we need to translate the
-                         * coordinates (old workspace to new workspace) */
-                        DLOG("old: (%x, %x)\n", client->rect.x, client->rect.y);
-                        client->rect.x -= ws->rect.x;
-                        client->rect.y -= ws->rect.y;
-                        client->rect.x += ws->output->rect.x;
-                        client->rect.y += ws->output->rect.y;
-                        DLOG("new: (%x, %x)\n", client->rect.x, client->rect.y);
-                }
-
-                /* Update dimensions from output */
-                memcpy(&(ws->rect), &(ws->output->rect), sizeof(Rect));
-
-                /* Update the dimensions of a fullscreen client, if any */
-                if (ws->fullscreen_client != NULL) {
-                        DLOG("Updating fullscreen client size\n");
-                        client = ws->fullscreen_client;
-                        Rect r = ws->rect;
-                        xcb_set_window_rect(conn, client->frame, r);
-
-                        r.x = 0;
-                        r.y = 0;
-                        xcb_set_window_rect(conn, client->child, r);
-                }
+    //i3Font *font = load_font(conn, config.font);
+
+    DLOG("Output mode changed, updating rect\n");
+    assert(output->con != NULL);
+    output->con->rect = output->rect;
+
+    Con *content, *workspace, *child;
+
+    /* Point content to the container of the workspaces */
+    content = output_get_content(output->con);
+
+    /* If default_orientation is NO_ORIENTATION, we change the orientation of
+     * the workspaces and their childs depending on output resolution. This is
+     * only done for workspaces with maximum one child. */
+    if (config.default_orientation == NO_ORIENTATION) {
+        TAILQ_FOREACH(workspace, &(content->nodes_head), nodes) {
+            /* Workspaces with more than one child are left untouched because
+             * we do not want to change an existing layout. */
+            if (con_num_children(workspace) > 1)
+                continue;
+
+            workspace->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ;
+            DLOG("Setting workspace [%d,%s]'s orientation to %d.\n", workspace->num, workspace->name, workspace->orientation);
+            if ((child = TAILQ_FIRST(&(workspace->nodes_head)))) {
+                child->orientation = workspace->orientation;
+                DLOG("Setting child [%d,%s]'s orientation to %d.\n", child->num, child->name, child->orientation);
+            }
         }
+    }
 }
 
 /*
@@ -303,215 +476,287 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) {
 static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id,
                           xcb_randr_get_output_info_reply_t *output,
                           xcb_timestamp_t cts, resources_reply *res) {
-        /* each CRT controller has a position in which we are interested in */
-        crtc_info *crtc;
-
-        Output *new = get_output_by_id(id);
-        bool existing = (new != NULL);
-        if (!existing)
-                new = scalloc(sizeof(Output));
-        new->id = id;
-        FREE(new->name);
-        asprintf(&new->name, "%.*s",
-                        xcb_randr_get_output_info_name_length(output),
-                        xcb_randr_get_output_info_name(output));
-
-        DLOG("found output with name %s\n", new->name);
-
-        /* Even if no CRTC is used at the moment, we store the output so that
-         * we do not need to change the list ever again (we only update the
-         * position/size) */
-        if (output->crtc == XCB_NONE) {
-                if (!existing)
-                        TAILQ_INSERT_TAIL(&outputs, new, outputs);
-                else if (new->active)
-                        new->to_be_disabled = true;
-                return;
+    /* each CRT controller has a position in which we are interested in */
+    crtc_info *crtc;
+
+    Output *new = get_output_by_id(id);
+    bool existing = (new != NULL);
+    if (!existing)
+        new = scalloc(sizeof(Output));
+    new->id = id;
+    new->primary = (primary && primary->output == id);
+    FREE(new->name);
+    asprintf(&new->name, "%.*s",
+            xcb_randr_get_output_info_name_length(output),
+            xcb_randr_get_output_info_name(output));
+
+    DLOG("found output with name %s\n", new->name);
+
+    /* Even if no CRTC is used at the moment, we store the output so that
+     * we do not need to change the list ever again (we only update the
+     * position/size) */
+    if (output->crtc == XCB_NONE) {
+        if (!existing) {
+            if (new->primary)
+                TAILQ_INSERT_HEAD(&outputs, new, outputs);
+            else TAILQ_INSERT_TAIL(&outputs, new, outputs);
+        } else if (new->active)
+            new->to_be_disabled = true;
+        return;
+    }
+
+    xcb_randr_get_crtc_info_cookie_t icookie;
+    icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts);
+    if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) {
+        DLOG("Skipping output %s: could not get CRTC (%p)\n",
+             new->name, crtc);
+        free(new);
+        return;
+    }
+
+    bool updated = update_if_necessary(&(new->rect.x), crtc->x) |
+                   update_if_necessary(&(new->rect.y), crtc->y) |
+                   update_if_necessary(&(new->rect.width), crtc->width) |
+                   update_if_necessary(&(new->rect.height), crtc->height);
+    free(crtc);
+    new->active = (new->rect.width != 0 && new->rect.height != 0);
+    if (!new->active) {
+        DLOG("width/height 0/0, disabling output\n");
+        return;
+    }
+
+    DLOG("mode: %dx%d+%d+%d\n", new->rect.width, new->rect.height,
+                                new->rect.x, new->rect.y);
+
+    /* If we don’t need to change an existing output or if the output
+     * does not exist in the first place, the case is simple: we either
+     * need to insert the new output or we are done. */
+    if (!updated || !existing) {
+        if (!existing) {
+            if (new->primary)
+                TAILQ_INSERT_HEAD(&outputs, new, outputs);
+            else TAILQ_INSERT_TAIL(&outputs, new, outputs);
         }
+        return;
+    }
 
-        xcb_randr_get_crtc_info_cookie_t icookie;
-        icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts);
-        if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) {
-                DLOG("Skipping output %s: could not get CRTC (%p)\n",
-                     new->name, crtc);
-                free(new);
-                return;
-        }
-
-        bool updated = update_if_necessary(&(new->rect.x), crtc->x) |
-                       update_if_necessary(&(new->rect.y), crtc->y) |
-                       update_if_necessary(&(new->rect.width), crtc->width) |
-                       update_if_necessary(&(new->rect.height), crtc->height);
-        free(crtc);
-        new->active = (new->rect.width != 0 && new->rect.height != 0);
-        if (!new->active) {
-                DLOG("width/height 0/0, disabling output\n");
-                return;
-        }
-
-        DLOG("mode: %dx%d+%d+%d\n", new->rect.width, new->rect.height,
-                                    new->rect.x, new->rect.y);
-
-        /* If we don’t need to change an existing output or if the output
-         * does not exist in the first place, the case is simple: we either
-         * need to insert the new output or we are done. */
-        if (!updated || !existing) {
-                if (!existing)
-                        TAILQ_INSERT_TAIL(&outputs, new, outputs);
-                return;
-        }
-
-        new->changed = true;
-}
-
-static void init_workspaces() {
-        Output *output;
-        Workspace *ws;
-
-        /* Just go through each active output and associate one workspace */
-        TAILQ_FOREACH(output, &outputs, outputs) {
-                if (!output->active || output->current_workspace != NULL)
-                        continue;
-                ws = get_first_workspace_for_output(output);
-                initialize_output(global_conn, output, ws);
-        }
-
-        /* render_layout flushes */
-        render_layout(global_conn);
+    new->changed = true;
 }
 
 /*
  * (Re-)queries the outputs via RandR and stores them in the list of outputs.
  *
  */
-void randr_query_outputs(xcb_connection_t *conn) {
-        Workspace *ws;
-        Output *output, *other, *first;
-        xcb_randr_get_screen_resources_current_cookie_t rcookie;
-        resources_reply *res;
-        /* timestamp of the configuration so that we get consistent replies to all
-         * requests (if the configuration changes between our different calls) */
-        xcb_timestamp_t cts;
-
-        /* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */
-        xcb_randr_output_t *randr_outputs;
-
-        if (randr_disabled)
-                return;
-
-        /* Get screen resources (crtcs, outputs, modes) */
-        rcookie = xcb_randr_get_screen_resources_current(conn, root);
-        if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) {
-                disable_randr(conn);
-                init_workspaces();
-                return;
+void randr_query_outputs() {
+    Output *output, *other, *first;
+    xcb_randr_get_output_primary_cookie_t pcookie;
+    xcb_randr_get_screen_resources_current_cookie_t rcookie;
+    resources_reply *res;
+
+    /* timestamp of the configuration so that we get consistent replies to all
+     * requests (if the configuration changes between our different calls) */
+    xcb_timestamp_t cts;
+
+    /* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */
+    xcb_randr_output_t *randr_outputs;
+
+    if (randr_disabled)
+        return;
+
+    /* Get screen resources (primary output, crtcs, outputs, modes) */
+    rcookie = xcb_randr_get_screen_resources_current(conn, root);
+    pcookie = xcb_randr_get_output_primary(conn, root);
+
+    if ((primary = xcb_randr_get_output_primary_reply(conn, pcookie, NULL)) == NULL)
+        ELOG("Could not get RandR primary output\n");
+    else DLOG("primary output is %08x\n", primary->output);
+    if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) {
+        disable_randr(conn);
+        return;
+    }
+    cts = res->config_timestamp;
+
+    int len = xcb_randr_get_screen_resources_current_outputs_length(res);
+    randr_outputs = xcb_randr_get_screen_resources_current_outputs(res);
+
+    /* Request information for each output */
+    xcb_randr_get_output_info_cookie_t ocookie[len];
+    for (int i = 0; i < len; i++)
+        ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts);
+
+    /* Loop through all outputs available for this X11 screen */
+    for (int i = 0; i < len; i++) {
+        xcb_randr_get_output_info_reply_t *output;
+
+        if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL)
+            continue;
+
+        handle_output(conn, randr_outputs[i], output, cts, res);
+        free(output);
+    }
+
+    /* Check for clones, disable the clones and reduce the mode to the
+     * lowest common mode */
+    TAILQ_FOREACH(output, &outputs, outputs) {
+        if (!output->active || output->to_be_disabled)
+            continue;
+        DLOG("output %p / %s, position (%d, %d), checking for clones\n",
+                output, output->name, output->rect.x, output->rect.y);
+
+        for (other = output;
+             other != TAILQ_END(&outputs);
+             other = TAILQ_NEXT(other, outputs)) {
+            if (other == output || !other->active || other->to_be_disabled)
+                continue;
+
+            if (other->rect.x != output->rect.x ||
+                other->rect.y != output->rect.y)
+                continue;
+
+            DLOG("output %p has the same position, his mode = %d x %d\n",
+                            other, other->rect.width, other->rect.height);
+            uint32_t width = min(other->rect.width, output->rect.width);
+            uint32_t height = min(other->rect.height, output->rect.height);
+
+            if (update_if_necessary(&(output->rect.width), width) |
+                update_if_necessary(&(output->rect.height), height))
+                output->changed = true;
+
+            update_if_necessary(&(other->rect.width), width);
+            update_if_necessary(&(other->rect.height), height);
+
+            DLOG("disabling output %p (%s)\n", other, other->name);
+            other->to_be_disabled = true;
+
+            DLOG("new output mode %d x %d, other mode %d x %d\n",
+                            output->rect.width, output->rect.height,
+                            other->rect.width, other->rect.height);
         }
-        cts = res->config_timestamp;
-
-        int len = xcb_randr_get_screen_resources_current_outputs_length(res);
-        randr_outputs = xcb_randr_get_screen_resources_current_outputs(res);
-
-        /* Request information for each output */
-        xcb_randr_get_output_info_cookie_t ocookie[len];
-        for (int i = 0; i < len; i++)
-                ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts);
-
-        /* Loop through all outputs available for this X11 screen */
-        for (int i = 0; i < len; i++) {
-                xcb_randr_get_output_info_reply_t *output;
-
-                if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL)
-                        continue;
-
-                handle_output(conn, randr_outputs[i], output, cts, res);
-                free(output);
+    }
+
+    /* Ensure that all outputs which are active also have a con. This is
+     * necessary because in the next step, a clone might get disabled. Example:
+     * LVDS1 active, VGA1 gets activated as a clone of LVDS1 (has no con).
+     * LVDS1 gets disabled. */
+    TAILQ_FOREACH(output, &outputs, outputs) {
+        if (output->active && output->con == NULL) {
+            DLOG("Need to initialize a Con for output %s\n", output->name);
+            output_init_con(output);
+            output->changed = false;
         }
+    }
+
+    /* Handle outputs which have a new mode or are disabled now (either
+     * because the user disabled them or because they are clones) */
+    TAILQ_FOREACH(output, &outputs, outputs) {
+        if (output->to_be_disabled) {
+            output->active = false;
+            DLOG("Output %s disabled, re-assigning workspaces/docks\n", output->name);
+
+            if ((first = get_first_output()) == NULL)
+                die("No usable outputs available\n");
+
+            /* TODO: refactor the following code into a nice function. maybe
+             * use an on_destroy callback which is implement differently for
+             * different container types (CT_CONTENT vs. CT_DOCKAREA)? */
+            Con *first_content = output_get_content(first->con);
+
+            if (output->con != NULL) {
+                /* We need to move the workspaces from the disappearing output to the first output */
+                /* 1: Get the con to focus next, if the disappearing ws is focused */
+                Con *next = NULL;
+                if (TAILQ_FIRST(&(croot->focus_head)) == output->con) {
+                    DLOG("This output (%p) was focused! Getting next\n", output->con);
+                    next = con_next_focused(output->con);
+                    DLOG("next = %p\n", next);
+                }
 
-        free(res);
-        /* Check for clones, disable the clones and reduce the mode to the
-         * lowest common mode */
-        TAILQ_FOREACH(output, &outputs, outputs) {
-                if (!output->active || output->to_be_disabled)
-                        continue;
-                DLOG("output %p, position (%d, %d), checking for clones\n",
-                        output, output->rect.x, output->rect.y);
-
-                for (other = output;
-                     other != TAILQ_END(&outputs);
-                     other = TAILQ_NEXT(other, outputs)) {
-                        if (other == output || !other->active || other->to_be_disabled)
-                                continue;
-
-                        if (other->rect.x != output->rect.x ||
-                            other->rect.y != output->rect.y)
-                                continue;
-
-                        DLOG("output %p has the same position, his mode = %d x %d\n",
-                                        other, other->rect.width, other->rect.height);
-                        uint32_t width = min(other->rect.width, output->rect.width);
-                        uint32_t height = min(other->rect.height, output->rect.height);
-
-                        if (update_if_necessary(&(output->rect.width), width) |
-                            update_if_necessary(&(output->rect.height), height))
-                                output->changed = true;
-
-                        update_if_necessary(&(other->rect.width), width);
-                        update_if_necessary(&(other->rect.height), height);
-
-                        DLOG("disabling output %p (%s)\n", other, other->name);
-                        other->to_be_disabled = true;
+                /* 2: iterate through workspaces and re-assign them */
+                Con *current;
+                Con *old_content = output_get_content(output->con);
+                while (!TAILQ_EMPTY(&(old_content->nodes_head))) {
+                    current = TAILQ_FIRST(&(old_content->nodes_head));
+                    DLOG("Detaching current = %p / %s\n", current, current->name);
+                    con_detach(current);
+                    DLOG("Re-attaching current = %p / %s\n", current, current->name);
+                    con_attach(current, first_content, false);
+                    DLOG("Done, next\n");
+                }
+                DLOG("re-attached all workspaces\n");
 
-                        DLOG("new output mode %d x %d, other mode %d x %d\n",
-                                        output->rect.width, output->rect.height,
-                                        other->rect.width, other->rect.height);
+                if (next) {
+                    DLOG("now focusing next = %p\n", next);
+                    con_focus(next);
                 }
-        }
 
-        /* Handle outputs which have a new mode or are disabled now (either
-         * because the user disabled them or because they are clones) */
-        TAILQ_FOREACH(output, &outputs, outputs) {
-                if (output->to_be_disabled) {
-                        output->active = false;
-                        DLOG("Output %s disabled, re-assigning workspaces/docks\n", output->name);
-
-                        if ((first = get_first_output()) == NULL)
-                                die("No usable outputs available\n");
-
-                        bool needs_init = (first->current_workspace == NULL);
-
-                        TAILQ_FOREACH(ws, workspaces, workspaces) {
-                                if (ws->output != output)
-                                        continue;
-
-                                workspace_assign_to(ws, first, true);
-                                if (!needs_init)
-                                        continue;
-                                initialize_output(conn, first, ws);
-                                needs_init = false;
-                        }
-
-                        Client *dock;
-                        while (!SLIST_EMPTY(&(output->dock_clients))) {
-                                dock = SLIST_FIRST(&(output->dock_clients));
-                                SLIST_REMOVE_HEAD(&(output->dock_clients), dock_clients);
-                                SLIST_INSERT_HEAD(&(first->dock_clients), dock, dock_clients);
-                        }
-                        output->current_workspace = NULL;
-                        output->to_be_disabled = false;
-                } else if (output->changed) {
-                        output_change_mode(conn, output);
-                        output->changed = false;
+                /* 3: move the dock clients to the first output */
+                Con *child;
+                TAILQ_FOREACH(child, &(output->con->nodes_head), nodes) {
+                    if (child->type != CT_DOCKAREA)
+                        continue;
+                    DLOG("Handling dock con %p\n", child);
+                    Con *dock;
+                    while (!TAILQ_EMPTY(&(child->nodes_head))) {
+                        dock = TAILQ_FIRST(&(child->nodes_head));
+                        Con *nc;
+                        Match *match;
+                        nc = con_for_window(first->con, dock->window, &match);
+                        DLOG("Moving dock client %p to nc %p\n", dock, nc);
+                        con_detach(dock);
+                        DLOG("Re-attaching\n");
+                        con_attach(dock, nc, false);
+                        DLOG("Done\n");
+                    }
                 }
-        }
 
-        if (TAILQ_EMPTY(&outputs)) {
-                ELOG("No outputs found via RandR, disabling\n");
-                disable_randr(conn);
-        }
+                DLOG("destroying disappearing con %p\n", output->con);
+                tree_close(output->con, DONT_KILL_WINDOW, true);
+                DLOG("Done. Should be fine now\n");
+                output->con = NULL;
+            }
 
-        ewmh_update_workarea();
+            output->to_be_disabled = false;
+            output->changed = false;
+        }
 
-        init_workspaces();
+        if (output->changed) {
+            output_change_mode(conn, output);
+            output->changed = false;
+        }
+    }
+
+    if (TAILQ_EMPTY(&outputs)) {
+        ELOG("No outputs found via RandR, disabling\n");
+        disable_randr(conn);
+    }
+
+    ewmh_update_workarea();
+
+    /* Just go through each active output and assign one workspace */
+    TAILQ_FOREACH(output, &outputs, outputs) {
+        if (!output->active)
+            continue;
+        Con *content = output_get_content(output->con);
+        if (!TAILQ_EMPTY(&(content->nodes_head)))
+            continue;
+        DLOG("Should add ws for output %s\n", output->name);
+        init_ws_for_output(output, content);
+    }
+
+    /* Focus the primary screen, if possible */
+    TAILQ_FOREACH(output, &outputs, outputs) {
+        if (!output->primary || !output->con)
+            continue;
+
+        DLOG("Focusing primary output %s\n", output->name);
+        con_focus(con_descend_focused(output->con));
+    }
+
+    /* render_layout flushes */
+    tree_render();
+
+    FREE(res);
+    FREE(primary);
 }
 
 /*
@@ -519,26 +764,22 @@ void randr_query_outputs(xcb_connection_t *conn) {
  * XRandR information to setup workspaces for each screen.
  *
  */
-void initialize_randr(xcb_connection_t *conn, int *event_base) {
-        const xcb_query_extension_reply_t *extreply;
-
-        extreply = xcb_get_extension_data(conn, &xcb_randr_id);
-        if (!extreply->present) {
-                disable_randr(conn);
-                init_workspaces();
-                return;
-        }
+void randr_init(int *event_base) {
+    const xcb_query_extension_reply_t *extreply;
 
-        randr_query_outputs(conn);
+    extreply = xcb_get_extension_data(conn, &xcb_randr_id);
+    if (!extreply->present)
+        disable_randr(conn);
+    else randr_query_outputs();
 
-        if (event_base != NULL)
-                *event_base = extreply->first_event;
+    if (event_base != NULL)
+        *event_base = extreply->first_event;
 
-        xcb_randr_select_input(conn, root,
-                XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE |
-                XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE |
-                XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE |
-                XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY);
+    xcb_randr_select_input(conn, root,
+            XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE |
+            XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE |
+            XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE |
+            XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY);
 
-        xcb_flush(conn);
+    xcb_flush(conn);
 }
diff --git a/src/render.c b/src/render.c
new file mode 100644 (file)
index 0000000..a7a9be5
--- /dev/null
@@ -0,0 +1,366 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ */
+
+#include "all.h"
+
+/* change this to 'true' if you want to have additional borders around every
+ * container (for debugging purposes) */
+static bool show_debug_borders = false;
+
+/*
+ * Renders a container with layout L_OUTPUT. In this layout, all CT_DOCKAREAs
+ * get the height of their content and the remaining CT_CON gets the rest.
+ *
+ */
+static void render_l_output(Con *con) {
+    Con *child, *dockchild;
+
+    int x = con->rect.x;
+    int y = con->rect.y;
+    int height = con->rect.height;
+    DLOG("Available height: %d\n", height);
+
+    /* Find the content container and ensure that there is exactly one. Also
+     * check for any non-CT_DOCKAREA clients. */
+    Con *content = NULL;
+    TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+        if (child->type == CT_CON) {
+            if (content != NULL) {
+                DLOG("More than one CT_CON on output container\n");
+                assert(false);
+            }
+            content = child;
+        } else if (child->type != CT_DOCKAREA) {
+            DLOG("Child %p of type %d is inside the OUTPUT con\n", child, child->type);
+            assert(false);
+        }
+    }
+
+    assert(content != NULL);
+
+    /* We need to find out if there is a fullscreen con on the current workspace
+     * and take the short-cut to render it directly (the user does not want to
+     * see the dockareas in that case) */
+    Con *ws = con_get_fullscreen_con(content, CF_OUTPUT);
+    Con *fullscreen = con_get_fullscreen_con(ws, CF_OUTPUT);
+    if (fullscreen) {
+        DLOG("got fs node: %p\n", fullscreen);
+        fullscreen->rect = con->rect;
+        x_raise_con(fullscreen);
+        render_con(fullscreen, true);
+        return;
+    }
+
+    /* First pass: determine the height of all CT_DOCKAREAs (the sum of their
+     * children) and figure out how many pixels we have left for the rest */
+    TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+        if (child->type != CT_DOCKAREA)
+            continue;
+
+        child->rect.height = 0;
+        TAILQ_FOREACH(dockchild, &(child->nodes_head), nodes)
+            child->rect.height += dockchild->geometry.height;
+        DLOG("This dockarea's height: %d\n", child->rect.height);
+
+        height -= child->rect.height;
+    }
+
+    DLOG("Remaining: %d\n", height);
+
+    /* Second pass: Set the widths/heights */
+    TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+        if (child->type == CT_CON) {
+            child->rect.x = x;
+            child->rect.y = y;
+            child->rect.width = con->rect.width;
+            child->rect.height = height;
+        }
+
+        child->rect.x = x;
+        child->rect.y = y;
+        child->rect.width = con->rect.width;
+
+        child->deco_rect.x = 0;
+        child->deco_rect.y = 0;
+        child->deco_rect.width = 0;
+        child->deco_rect.height = 0;
+
+        y += child->rect.height;
+
+        DLOG("child at (%d, %d) with (%d x %d)\n",
+                child->rect.x, child->rect.y, child->rect.width, child->rect.height);
+        DLOG("x now %d, y now %d\n", x, y);
+        x_raise_con(child);
+        render_con(child, false);
+    }
+}
+
+/*
+ * "Renders" the given container (and its children), meaning that all rects are
+ * updated correctly. Note that this function does not call any xcb_*
+ * functions, so the changes are completely done in memory only (and
+ * side-effect free). As soon as you call x_push_changes(), the changes will be
+ * updated in X11.
+ *
+ */
+void render_con(Con *con, bool render_fullscreen) {
+    DLOG("currently rendering node %p / %s / layout %d\n",
+            con, con->name, con->layout);
+    int children = con_num_children(con);
+    DLOG("children: %d, orientation = %d\n", children, con->orientation);
+
+    /* Copy container rect, subtract container border */
+    /* This is the actually usable space inside this container for clients */
+    Rect rect = con->rect;
+
+    /* Display a border if this is a leaf node. For container nodes, we don’t
+     * draw borders (except when in debug mode) */
+    if (show_debug_borders) {
+        rect.x += 2;
+        rect.y += 2;
+        rect.width -= 2 * 2;
+        rect.height -= 2 * 2;
+    }
+
+    int x = rect.x;
+    int y = rect.y;
+
+    int i = 0;
+
+    con->mapped = true;
+
+    /* if this container contains a window, set the coordinates */
+    if (con->window) {
+        /* depending on the border style, the rect of the child window
+         * needs to be smaller */
+        Rect *inset = &(con->window_rect);
+        *inset = (Rect){0, 0, con->rect.width, con->rect.height};
+        if (!render_fullscreen)
+            *inset = rect_add(*inset, con_border_style_rect(con));
+
+        DLOG("Starting with inset = (%d, %d) %d x %d\n", inset->x, inset->y, inset->width, inset->height);
+        /* Obey x11 border */
+        DLOG("X11 border: %d\n", con->border_width);
+        inset->width -= (2 * con->border_width);
+        inset->height -= (2 * con->border_width);
+
+        /* Obey the aspect ratio, if any */
+        if (con->proportional_height != 0 &&
+            con->proportional_width != 0) {
+            DLOG("proportional height = %d, width = %d\n", con->proportional_height, con->proportional_width);
+            double new_height = inset->height + 1;
+            int new_width = inset->width;
+
+            while (new_height > inset->height) {
+                new_height = ((double)con->proportional_height / con->proportional_width) * new_width;
+
+                if (new_height > inset->height)
+                    new_width--;
+            }
+            /* Center the window */
+            inset->y += ceil(inset->height / 2) - floor(new_height / 2);
+            inset->x += ceil(inset->width / 2) - floor(new_width / 2);
+
+            inset->height = new_height;
+            inset->width = new_width;
+            DLOG("new_height = %f, new_width = %d\n", new_height, new_width);
+        }
+
+        if (con->height_increment > 1) {
+            int old_height = inset->height;
+            inset->height -= (inset->height - con->base_height) % con->height_increment;
+            DLOG("Lost %d pixel due to client's height_increment (%d px, base_height = %d)\n",
+                old_height - inset->height, con->height_increment, con->base_height);
+        }
+
+        if (con->width_increment > 1) {
+            int old_width = inset->width;
+            inset->width -= (inset->width - con->base_width) % con->width_increment;
+            DLOG("Lost %d pixel due to client's width_increment (%d px, base_width = %d)\n",
+                old_width - inset->width, con->width_increment, con->base_width);
+        }
+
+        DLOG("child will be at %dx%d with size %dx%d\n", inset->x, inset->y, inset->width, inset->height);
+    }
+
+    /* Check for fullscreen nodes */
+    Con *fullscreen = NULL;
+    if (con->type != CT_OUTPUT) {
+        fullscreen = con_get_fullscreen_con(con, (con->type == CT_ROOT ? CF_GLOBAL : CF_OUTPUT));
+    }
+    if (fullscreen) {
+        DLOG("got fs node: %p\n", fullscreen);
+        fullscreen->rect = rect;
+        x_raise_con(fullscreen);
+        render_con(fullscreen, true);
+        return;
+    }
+
+    /* find the height for the decorations */
+    int deco_height = config.font.height + 5;
+
+    /* precalculate the sizes to be able to correct rounding errors */
+    int sizes[children];
+    if (con->layout == L_DEFAULT && children > 0) {
+        assert(!TAILQ_EMPTY(&con->nodes_head));
+        Con *child;
+        int i = 0, assigned = 0;
+        int total = con->orientation == HORIZ ? rect.width : rect.height;
+        TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+            double percentage = child->percent > 0.0 ? child->percent : 1.0 / children;
+            assigned += sizes[i++] = percentage * total;
+        }
+        assert(assigned == total ||
+                (assigned > total && assigned - total <= children * 2) ||
+                (assigned < total && total - assigned <= children * 2));
+        int signal = assigned < total ? 1 : -1;
+        while (assigned != total) {
+            for (i = 0; i < children && assigned != total; ++i) {
+                sizes[i] += signal;
+                assigned += signal;
+            }
+        }
+    }
+
+    if (con->layout == L_OUTPUT) {
+        render_l_output(con);
+    } else if (con->type == CT_ROOT) {
+        DLOG("Root node, rendering outputs\n");
+        Con *child;
+        TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+            render_con(child, false);
+        }
+    } else {
+
+        /* FIXME: refactor this into separate functions: */
+    Con *child;
+    TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+        assert(children > 0);
+
+        /* default layout */
+        if (con->layout == L_DEFAULT) {
+            if (con->orientation == HORIZ) {
+                child->rect.x = x;
+                child->rect.y = y;
+                child->rect.width = sizes[i];
+                child->rect.height = rect.height;
+                x += child->rect.width;
+            } else {
+                child->rect.x = x;
+                child->rect.y = y;
+                child->rect.width = rect.width;
+                child->rect.height = sizes[i];
+                y += child->rect.height;
+            }
+
+            /* first we have the decoration, if this is a leaf node */
+            if (con_is_leaf(child) && child->border_style == BS_NORMAL) {
+                DLOG("that child is a leaf node, subtracting deco\n");
+                /* TODO: make a function for relative coords? */
+                child->deco_rect.x = child->rect.x - con->rect.x;
+                child->deco_rect.y = child->rect.y - con->rect.y;
+
+                child->rect.y += deco_height;
+                child->rect.height -= deco_height;
+
+                child->deco_rect.width = child->rect.width;
+                child->deco_rect.height = deco_height;
+            }
+        }
+
+        /* stacked layout */
+        else if (con->layout == L_STACKED) {
+            DLOG("stacked con\n");
+            child->rect.x = x;
+            child->rect.y = y;
+            child->rect.width = rect.width;
+            child->rect.height = rect.height;
+
+            child->deco_rect.x = x - con->rect.x;
+            child->deco_rect.y = y - con->rect.y + (i * deco_height);
+            child->deco_rect.width = child->rect.width;
+            child->deco_rect.height = deco_height;
+
+            if (children > 1 || (child->border_style != BS_1PIXEL && child->border_style != BS_NONE)) {
+                child->rect.y += (deco_height * children);
+                child->rect.height -= (deco_height * children);
+            }
+        }
+
+        /* tabbed layout */
+        else if (con->layout == L_TABBED) {
+            DLOG("tabbed con\n");
+            child->rect.x = x;
+            child->rect.y = y;
+            child->rect.width = rect.width;
+            child->rect.height = rect.height;
+
+            child->deco_rect.width = child->rect.width / children;
+            child->deco_rect.height = deco_height;
+            child->deco_rect.x = x - con->rect.x + i * child->deco_rect.width;
+            child->deco_rect.y = y - con->rect.y;
+
+            if (children > 1 || (child->border_style != BS_1PIXEL && child->border_style != BS_NONE)) {
+                child->rect.y += deco_height;
+                child->rect.height -= deco_height;
+            }
+        }
+
+        /* dockarea layout */
+        else if (con->layout == L_DOCKAREA) {
+            DLOG("dockarea con\n");
+            child->rect.x = x;
+            child->rect.y = y;
+            child->rect.width = rect.width;
+            child->rect.height = child->geometry.height;
+
+            child->deco_rect.x = 0;
+            child->deco_rect.y = 0;
+            child->deco_rect.width = 0;
+            child->deco_rect.height = 0;
+            y += child->rect.height;
+        }
+
+        DLOG("child at (%d, %d) with (%d x %d)\n",
+                child->rect.x, child->rect.y, child->rect.width, child->rect.height);
+        DLOG("x now %d, y now %d\n", x, y);
+        x_raise_con(child);
+        render_con(child, false);
+        i++;
+    }
+
+    /* in a stacking or tabbed container, we ensure the focused client is raised */
+    if (con->layout == L_STACKED || con->layout == L_TABBED) {
+        DLOG("stacked/tabbed, raising focused reverse\n");
+        TAILQ_FOREACH_REVERSE(child, &(con->focus_head), focus_head, focused)
+            x_raise_con(child);
+        DLOG("done\n");
+        if ((child = TAILQ_FIRST(&(con->focus_head)))) {
+            DLOG("con %p is stacking, raising %p\n", con, child);
+            /* By rendering the stacked container again, we handle the case
+             * that we have a non-leaf-container inside the stack. In that
+             * case, the children of the non-leaf-container need to be raised
+             * aswell. */
+            render_con(child, false);
+        }
+
+        if (children != 1)
+            /* Raise the stack con itself. This will put the stack decoration on
+             * top of every stack window. That way, when a new window is opened in
+             * the stack, the old window will not obscure part of the decoration
+             * (it’s unmapped afterwards). */
+            x_raise_con(con);
+    }
+    }
+
+    Con *child;
+    TAILQ_FOREACH(child, &(con->floating_head), floating_windows) {
+        DLOG("render floating:\n");
+        DLOG("floating child at (%d,%d) with %d x %d\n", child->rect.x, child->rect.y, child->rect.width, child->rect.height);
+        x_raise_con(child);
+        render_con(child, false);
+    }
+
+    DLOG("-- level up\n");
+}
index f6b6da74a644e4f9f27429635e5ce8a27e24dfe2..9c465e183ea812700ab46236ad25306a49aab46c 100644 (file)
@@ -1,34 +1,9 @@
 /*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * This file contains the functions for resizing table columns/rows because
- * it’s actually lots of work, compared to the other handlers.
- *
+ * vim:ts=4:sw=4:expandtab
  */
-#include <stdlib.h>
-#include <assert.h>
-
-#include <xcb/xcb.h>
-#include <xcb/xcb_event.h>
-
-#include "i3.h"
-#include "data.h"
-#include "resize.h"
-#include "util.h"
-#include "xcb.h"
-#include "debug.h"
-#include "layout.h"
-#include "randr.h"
-#include "config.h"
-#include "floating.h"
-#include "workspace.h"
-#include "log.h"
+#include "all.h"
+
+extern xcb_connection_t *conn;
 
 /*
  * This is an ugly data structure which we need because there is no standard
  *
  */
 struct callback_params {
-        resize_orientation_t orientation;
-        Output *screen;
-        xcb_window_t helpwin;
-        uint32_t *new_position;
+    orientation_t orientation;
+    Con *output;
+    xcb_window_t helpwin;
+    uint32_t *new_position;
 };
 
 DRAGGING_CB(resize_callback) {
-        struct callback_params *params = extra;
-        Output *screen = params->screen;
-        DLOG("new x = %d, y = %d\n", new_x, new_y);
-        if (params->orientation == O_VERTICAL) {
-                /* Check if the new coordinates are within screen boundaries */
-                if (new_x > (screen->rect.x + screen->rect.width - 25) ||
-                    new_x < (screen->rect.x + 25))
-                        return;
-
-                *(params->new_position) = new_x;
-                xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_X, params->new_position);
-        } else {
-                if (new_y > (screen->rect.y + screen->rect.height - 25) ||
-                    new_y < (screen->rect.y + 25))
-                        return;
-
-                *(params->new_position) = new_y;
-                xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_Y, params->new_position);
-        }
-
-        xcb_flush(conn);
+    struct callback_params *params = extra;
+    Con *output = params->output;
+    DLOG("new x = %d, y = %d\n", new_x, new_y);
+    if (params->orientation == HORIZ) {
+        /* Check if the new coordinates are within screen boundaries */
+        if (new_x > (output->rect.x + output->rect.width - 25) ||
+            new_x < (output->rect.x + 25))
+            return;
+
+        *(params->new_position) = new_x;
+        xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_X, params->new_position);
+    } else {
+        if (new_y > (output->rect.y + output->rect.height - 25) ||
+            new_y < (output->rect.y + 25))
+            return;
+
+        *(params->new_position) = new_y;
+        xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_Y, params->new_position);
+    }
+
+    xcb_flush(conn);
 }
 
-/*
- * Renders the resize window between the first/second container and resizes
- * the table column/row.
- *
- */
-int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, int second,
-                             resize_orientation_t orientation, xcb_button_press_event_t *event) {
-        uint32_t new_position;
-        Output *screen = get_output_containing(event->root_x, event->root_y);
-        if (screen == NULL) {
-                ELOG("BUG: No screen found at this position (%d, %d)\n", event->root_x, event->root_y);
-                return 1;
-        }
-
-        /* We cannot use the X root window's width_in_pixels or height_in_pixels
-         * attributes here since they are not updated when you configure new
-         * screens during runtime. Instead, we just use the most right and most
-         * bottom Xinerama screen and use their position + width/height to get
-         * the area of pixels currently in use */
-        Output *most_right = get_output_most(D_RIGHT, screen),
-               *most_bottom = get_output_most(D_DOWN, screen);
-
-        DLOG("event->event_x = %d, event->root_x = %d\n", event->event_x, event->root_x);
-
-        DLOG("Screen dimensions: (%d, %d) %d x %d\n", screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
-
-        uint32_t mask = 0;
-        uint32_t values[2];
-
-        mask = XCB_CW_OVERRIDE_REDIRECT;
-        values[0] = 1;
-
-        /* Open a new window, the resizebar. Grab the pointer and move the window around
-           as the user moves the pointer. */
-        Rect grabrect = {0,
-                         0,
-                         most_right->rect.x + most_right->rect.width,
-                         most_bottom->rect.x + most_bottom->rect.height};
-        xcb_window_t grabwin = create_window(conn, grabrect, XCB_WINDOW_CLASS_INPUT_ONLY, -1, true, mask, values);
-
-        Rect helprect;
-        if (orientation == O_VERTICAL) {
-                helprect.x = event->root_x;
-                helprect.y = screen->rect.y;
-                helprect.width = 2;
-                helprect.height = screen->rect.height;
-                new_position = event->root_x;
-        } else {
-                helprect.x = screen->rect.x;
-                helprect.y = event->root_y;
-                helprect.width = screen->rect.width;
-                helprect.height = 2;
-                new_position = event->root_y;
-        }
-
-        mask = XCB_CW_BACK_PIXEL;
-        values[0] = config.client.focused.border;
-
-        mask |= XCB_CW_OVERRIDE_REDIRECT;
-        values[1] = 1;
-
-        xcb_window_t helpwin = create_window(conn, helprect, XCB_WINDOW_CLASS_INPUT_OUTPUT,
-                                             (orientation == O_VERTICAL ?
-                                              XCB_CURSOR_SB_H_DOUBLE_ARROW :
-                                              XCB_CURSOR_SB_V_DOUBLE_ARROW), true, mask, values);
-
-        xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin);
-
-        xcb_flush(conn);
-
-        struct callback_params params = { orientation, screen, helpwin, &new_position };
-
-        drag_pointer(conn, NULL, event, grabwin, BORDER_TOP, resize_callback, &params);
-
-        xcb_destroy_window(conn, helpwin);
-        xcb_destroy_window(conn, grabwin);
-        xcb_flush(conn);
-
-        int pixels;
-        if (orientation == O_VERTICAL)
-                pixels = (new_position - event->root_x);
-        else pixels = (new_position - event->root_y);
-        resize_container(conn, ws, first, second, orientation, pixels);
-
-        return 1;
-}
+int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, xcb_button_press_event_t *event) {
+    DLOG("resize handler\n");
 
-/*
- * Adjusts the container size factors according to the resizing parameters.
- * This is an abstraction used by resize_container.
- */
-static void adjust_container_factors(float *factors, int ws_size, int unoccupied_size,
-                int num_items, int first, int second, int pixels) {
-        /* Find the current sizes */
-        int sizes[num_items];
-        for (int i = 0; i < num_items; ++i)
-                sizes[i] = factors[i] == 0 ? ws_size / num_items : unoccupied_size * factors[i];
-
-        /* Adjust them */
-        sizes[first] += pixels;
-        sizes[second] -= pixels;
-
-        /* Calculate the new unoccupied size */
-        if (factors[first] == 0) unoccupied_size += ws_size / num_items;
-        if (factors[second] == 0) unoccupied_size += ws_size / num_items;
-
-        /* Calculate the new factors */
-        for (int i = 0; i < num_items; ++i) {
-                if (factors[i] != 0 || i == first || i == second)
-                        factors[i] = (float)sizes[i] / unoccupied_size;
-        }
-}
+    uint32_t new_position;
 
-/*
- * Resizes a column/row by the given amount of pixels. Called by
- * resize_graphical_handler (the user clicked) or parse_resize_command (the
- * user issued the command)
- *
- */
-void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int second,
-                      resize_orientation_t orientation, int pixels) {
-        if (orientation == O_VERTICAL) {
-                adjust_container_factors(ws->width_factor, ws->rect.width,
-                                get_unoccupied_x(ws), ws->cols, first, second, pixels);
-        }
-        else {
-                adjust_container_factors(ws->height_factor, workspace_height(ws),
-                                get_unoccupied_y(ws), ws->rows, first, second, pixels);
-        }
-
-        render_layout(conn);
+    /* TODO: previously, we were getting a rect containing all screens. why? */
+    Con *output = con_get_output(first);
+    DLOG("x = %d, width = %d\n", output->rect.x, output->rect.width);
+
+    uint32_t mask = 0;
+    uint32_t values[2];
+
+    mask = XCB_CW_OVERRIDE_REDIRECT;
+    values[0] = 1;
+
+    /* Open a new window, the resizebar. Grab the pointer and move the window around
+       as the user moves the pointer. */
+    xcb_window_t grabwin = create_window(conn, output->rect, XCB_WINDOW_CLASS_INPUT_ONLY, XCURSOR_CURSOR_POINTER, true, mask, values);
+
+    Rect helprect;
+    if (orientation == HORIZ) {
+        helprect.x = event->root_x;
+        helprect.y = output->rect.y;
+        helprect.width = 2;
+        helprect.height = output->rect.height;
+        new_position = event->root_x;
+    } else {
+        helprect.x = output->rect.x;
+        helprect.y = event->root_y;
+        helprect.width = output->rect.width;
+        helprect.height = 2;
+        new_position = event->root_y;
+    }
+
+    mask = XCB_CW_BACK_PIXEL;
+    values[0] = config.client.focused.border;
+
+    mask |= XCB_CW_OVERRIDE_REDIRECT;
+    values[1] = 1;
+
+    xcb_window_t helpwin = create_window(conn, helprect, XCB_WINDOW_CLASS_INPUT_OUTPUT,
+                                         (orientation == HORIZ ?
+                                          XCURSOR_CURSOR_RESIZE_HORIZONTAL :
+                                          XCURSOR_CURSOR_RESIZE_VERTICAL), true, mask, values);
+
+    xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin);
+
+    xcb_flush(conn);
+
+    struct callback_params params = { orientation, output, helpwin, &new_position };
+
+    drag_pointer(NULL, event, grabwin, BORDER_TOP, resize_callback, &params);
+
+    xcb_destroy_window(conn, helpwin);
+    xcb_destroy_window(conn, grabwin);
+    xcb_flush(conn);
+
+    int pixels;
+    if (orientation == HORIZ)
+        pixels = (new_position - event->root_x);
+    else pixels = (new_position - event->root_y);
+
+    DLOG("Done, pixels = %d\n", pixels);
+
+    // if we got thus far, the containers must have
+    // percentages associated with them
+    assert(first->percent > 0.0);
+    assert(second->percent > 0.0);
+
+    // calculate the new percentage for the first container
+    double new_percent, difference;
+    double percent = first->percent;
+    DLOG("percent = %f\n", percent);
+    int original = (orientation == HORIZ ? first->rect.width : first->rect.height);
+    DLOG("original = %d\n", original);
+    new_percent = (original + pixels) * (percent / original);
+    difference = percent - new_percent;
+    DLOG("difference = %f\n", difference);
+    DLOG("new percent = %f\n", new_percent);
+    first->percent = new_percent;
+
+    // calculate the new percentage for the second container
+    double s_percent = second->percent;
+    second->percent = s_percent + difference;
+    DLOG("second->percent = %f\n", second->percent);
+
+    // now we must make sure that the sum of the percentages remain 1.0
+    con_fix_percent(first->parent);
+
+    return 0;
 }
index d789f30b8148917f0ebe32c65d24d584b95690c9..1d3e4ab719979832be0066b2314509e2aa3ec58b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
  *
 
 #include <X11/keysym.h>
 
-#include "i3.h"
-#include "util.h"
-#include "xcb.h"
-#include "log.h"
-#include "config.h"
-#include "randr.h"
+#include "all.h"
 
 static xcb_gcontext_t pixmap_gc;
 static xcb_pixmap_t pixmap;
 static int raised_signal;
 
 static char *crash_text[] = {
-        "i3 just crashed.",
-        "To debug this problem, either attach gdb now",
-        "or press 'e' to exit and get a core-dump.",
-        "If you want to keep your session,",
-        "press 'r' to restart i3 in-place."
+    "i3 just crashed.",
+    "To debug this problem, either attach gdb now",
+    "or press",
+    "- 'e' to exit and get a core-dump,",
+    "- 'r' to restart i3 in-place or",
+    "- 'f' to forget the current layout and restart"
 };
-static int crash_text_longest = 1;
+static int crash_text_longest = 5;
 
 /*
  * Draw the window containing the info text
  *
  */
-static int sig_draw_window(xcb_connection_t *conn, xcb_window_t win, int width, int height, int font_height) {
-        /* re-draw the background */
-        xcb_rectangle_t border = { 0, 0, width, height},
-                        inner = { 2, 2, width - 4, height - 4};
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000"));
-        xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
-        xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
-
-        /* restore font color */
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
-
-        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);
-        }
-
-        /* Copy the contents of the pixmap to the real window */
-        xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, width, height);
-        xcb_flush(conn);
-
-        return 1;
+static int sig_draw_window(xcb_window_t win, int width, int height, int font_height) {
+    /* re-draw the background */
+    xcb_rectangle_t border = { 0, 0, width, height},
+                    inner = { 2, 2, width - 4, height - 4};
+    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel("#FF0000"));
+    xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
+    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel("#000000"));
+    xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
+
+    /* restore font color */
+    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel("#FFFFFF"));
+
+    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);
+    }
+
+    /* Copy the contents of the pixmap to the real window */
+    xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, width, height);
+    xcb_flush(conn);
+
+    return 1;
 }
 
 /*
@@ -83,25 +79,28 @@ static int sig_draw_window(xcb_connection_t *conn, xcb_window_t win, int width,
  *
  */
 static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
-        uint16_t state = event->state;
+    uint16_t state = event->state;
 
-        /* Apparantly, after activating numlock once, the numlock modifier
-         * stays turned on (use xev(1) to verify). So, to resolve useful
-         * keysyms, we remove the numlock flag from the event state */
-        state &= ~xcb_numlock_mask;
+    /* Apparantly, after activating numlock once, the numlock modifier
+     * stays turned on (use xev(1) to verify). So, to resolve useful
+     * keysyms, we remove the numlock flag from the event state */
+    state &= ~xcb_numlock_mask;
 
-        xcb_keysym_t sym = xcb_key_press_lookup_keysym(keysyms, event, state);
+    xcb_keysym_t sym = xcb_key_press_lookup_keysym(keysyms, event, state);
 
-        if (sym == 'e') {
-                DLOG("User issued exit-command, raising error again.\n");
-                raise(raised_signal);
-                exit(1);
-        }
+    if (sym == 'e') {
+        DLOG("User issued exit-command, raising error again.\n");
+        raise(raised_signal);
+        exit(1);
+    }
+
+    if (sym == 'r')
+        i3_restart(false);
 
-        if (sym == 'r')
-                i3_restart();
+    if (sym == 'f')
+        i3_restart(true);
 
-        return 1;
+    return 1;
 }
 
 /*
@@ -109,36 +108,36 @@ static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_p
  *
  */
 static xcb_window_t open_input_window(xcb_connection_t *conn, Rect screen_rect, uint32_t width, uint32_t height) {
-        xcb_window_t win = xcb_generate_id(conn);
+    xcb_window_t win = xcb_generate_id(conn);
 
-        uint32_t mask = 0;
-        uint32_t values[2];
+    uint32_t mask = 0;
+    uint32_t values[2];
 
-        mask |= XCB_CW_BACK_PIXEL;
-        values[0] = 0;
+    mask |= XCB_CW_BACK_PIXEL;
+    values[0] = 0;
 
-        mask |= XCB_CW_OVERRIDE_REDIRECT;
-        values[1] = 1;
+    mask |= XCB_CW_OVERRIDE_REDIRECT;
+    values[1] = 1;
 
-        /* center each popup on the specified screen */
-        uint32_t x = screen_rect.x + ((screen_rect.width / 2) - (width / 2)),
-                 y = screen_rect.y + ((screen_rect.height / 2) - (height / 2));
+    /* center each popup on the specified screen */
+    uint32_t x = screen_rect.x + ((screen_rect.width / 2) - (width / 2)),
+             y = screen_rect.y + ((screen_rect.height / 2) - (height / 2));
 
-        xcb_create_window(conn,
-                        XCB_COPY_FROM_PARENT,
-                        win, /* the window id */
-                        root, /* parent == root */
-                        x, y, width, height, /* dimensions */
-                        0, /* border = 0, we draw our own */
-                        XCB_WINDOW_CLASS_INPUT_OUTPUT,
-                        XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
-                        mask,
-                        values);
+    xcb_create_window(conn,
+                      XCB_COPY_FROM_PARENT,
+                      win, /* the window id */
+                      root, /* parent == root */
+                      x, y, width, height, /* dimensions */
+                      0, /* border = 0, we draw our own */
+                      XCB_WINDOW_CLASS_INPUT_OUTPUT,
+                      XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
+                      mask,
+                      values);
 
-        /* Map the window (= make it visible) */
-        xcb_map_window(conn, win);
+    /* Map the window (= make it visible) */
+    xcb_map_window(conn, win);
 
-        return win;
+    return win;
 }
 
 /*
@@ -148,62 +147,61 @@ static xcb_window_t open_input_window(xcb_connection_t *conn, Rect screen_rect,
  *
  */
 void handle_signal(int sig, siginfo_t *info, void *data) {
-        DLOG("i3 crashed. SIG: %d\n", sig);
-
-        struct sigaction action;
-        action.sa_handler = SIG_DFL;
-        sigaction(sig, &action, NULL);
-        raised_signal = sig;
-
-        xcb_connection_t *conn = global_conn;
-
-        /* setup event handler for key presses */
-        xcb_event_handlers_t sig_evenths;
-        memset(&sig_evenths, 0, sizeof(xcb_event_handlers_t));
-        xcb_event_handlers_init(conn, &sig_evenths);
-        xcb_event_set_key_press_handler(&sig_evenths, sig_handle_key_press, NULL);
-
-        i3Font *font = load_font(conn, config.font);
-
-        /* width and height of the popup window, so that the text fits in */
-        int crash_text_num = sizeof(crash_text) / sizeof(char*);
-        int height = 13 + (crash_text_num * 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(conn, config.font, longest_text, text_len);
-        int width = font_width + 20;
-
-        /* Open a popup window on each virtual screen */
-        Output *screen;
-        xcb_window_t win;
-        TAILQ_FOREACH(screen, &outputs, outputs) {
-                if (!screen->active)
-                        continue;
-                win = open_input_window(conn, screen->rect, width, height);
-
-                /* Create pixmap */
-                pixmap = xcb_generate_id(conn);
-                pixmap_gc = xcb_generate_id(conn);
-                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_single(conn, pixmap_gc, XCB_GC_FONT, 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);
-
-                /* Grab the cursor inside the popup */
-                xcb_grab_pointer(conn, false, win, XCB_NONE, XCB_GRAB_MODE_ASYNC,
-                                 XCB_GRAB_MODE_ASYNC, win, XCB_NONE, XCB_CURRENT_TIME);
-
-                sig_draw_window(conn, win, width, height, font->height);
-                xcb_flush(conn);
+    DLOG("i3 crashed. SIG: %d\n", sig);
+
+    struct sigaction action;
+    action.sa_handler = SIG_DFL;
+    sigaction(sig, &action, NULL);
+    raised_signal = sig;
+
+    /* width and height of the popup window, so that the text fits in */
+    int crash_text_num = sizeof(crash_text) / sizeof(char*);
+    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);
+    int width = font_width + 20;
+
+    /* Open a popup window on each virtual screen */
+    Output *screen;
+    xcb_window_t win;
+    TAILQ_FOREACH(screen, &outputs, outputs) {
+        if (!screen->active)
+            continue;
+        win = open_input_window(conn, screen->rect, width, height);
+
+        /* Create pixmap */
+        pixmap = xcb_generate_id(conn);
+        pixmap_gc = xcb_generate_id(conn);
+        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_single(conn, pixmap_gc, XCB_GC_FONT, 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);
+
+        /* Grab the cursor inside the popup */
+        xcb_grab_pointer(conn, false, win, XCB_NONE, XCB_GRAB_MODE_ASYNC,
+                         XCB_GRAB_MODE_ASYNC, win, XCB_NONE, XCB_CURRENT_TIME);
+
+        sig_draw_window(win, width, height, config.font.height);
+        xcb_flush(conn);
+    }
+
+    xcb_generic_event_t *event;
+    /* Yay, more own eventhandlers… */
+    while ((event = xcb_wait_for_event(conn))) {
+        /* Strip off the highest bit (set if the event is generated) */
+        int type = (event->response_type & 0x7F);
+        if (type == XCB_KEY_PRESS) {
+            sig_handle_key_press(NULL, conn, (xcb_key_press_event_t*)event);
         }
-
-        xcb_event_wait_for_event_loop(&sig_evenths);
+        free(event);
+    }
 }
 
 /*
@@ -211,13 +209,14 @@ void handle_signal(int sig, siginfo_t *info, void *data) {
  *
  */
 void setup_signal_handler() {
-        struct sigaction action;
+    struct sigaction action;
 
-        action.sa_sigaction = handle_signal;
-        action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
-        sigemptyset(&action.sa_mask);
+    action.sa_sigaction = handle_signal;
+    action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
+    sigemptyset(&action.sa_mask);
 
-        if (sigaction(SIGSEGV, &action, NULL) == -1 ||
-            sigaction(SIGFPE, &action, NULL) == -1)
-                ELOG("Could not setup signal handler");
+    if (sigaction(SIGSEGV, &action, NULL) == -1 ||
+        sigaction(SIGABRT, &action, NULL) == -1 ||
+        sigaction(SIGFPE, &action, NULL) == -1)
+        ELOG("Could not setup signal handler");
 }
diff --git a/src/table.c b/src/table.c
deleted file mode 100644 (file)
index 7108101..0000000
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * table.c: Functions/macros for easy modifying/accessing of _the_ table (defining our
- *          layout).
- *
- */
-#include <stdio.h>
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <stdbool.h>
-#include <assert.h>
-
-#include "data.h"
-#include "table.h"
-#include "util.h"
-#include "i3.h"
-#include "layout.h"
-#include "config.h"
-#include "workspace.h"
-#include "log.h"
-
-int current_workspace = 0;
-int num_workspaces = 1;
-struct workspaces_head *workspaces;
-/* Convenience pointer to the current workspace */
-Workspace *c_ws;
-int current_col = 0;
-int current_row = 0;
-
-/*
- * Initialize table
- *
- */
-void init_table() {
-        workspaces = scalloc(sizeof(struct workspaces_head));
-        TAILQ_INIT(workspaces);
-
-        c_ws = scalloc(sizeof(Workspace));
-        workspace_set_name(c_ws, NULL);
-        TAILQ_INIT(&(c_ws->floating_clients));
-        TAILQ_INSERT_TAIL(workspaces, c_ws, workspaces);
-}
-
-static void new_container(Workspace *workspace, Container **container, int col, int row, bool skip_layout_switch) {
-        Container *new;
-        new = *container = scalloc(sizeof(Container));
-        CIRCLEQ_INIT(&(new->clients));
-        new->colspan = 1;
-        new->rowspan = 1;
-        new->col = col;
-        new->row = row;
-        new->workspace = workspace;
-        if (!skip_layout_switch)
-                switch_layout_mode(global_conn, new, config.container_mode);
-        new->stack_limit = config.container_stack_limit;
-        new->stack_limit_value = config.container_stack_limit_value;
-}
-
-/*
- * Add one row to the table
- *
- */
-void expand_table_rows(Workspace *workspace) {
-        workspace->rows++;
-
-        workspace->height_factor = realloc(workspace->height_factor, sizeof(float) * workspace->rows);
-        workspace->height_factor[workspace->rows-1] = 0;
-
-        for (int c = 0; c < workspace->cols; c++) {
-                workspace->table[c] = realloc(workspace->table[c], sizeof(Container*) * workspace->rows);
-                new_container(workspace, &(workspace->table[c][workspace->rows-1]), c, workspace->rows-1, true);
-        }
-
-        /* We need to switch the layout in a separate step because it could
-         * happen that render_layout() (being called by switch_layout_mode())
-         * would access containers which were not yet initialized. */
-        for (int c = 0; c < workspace->cols; c++)
-                switch_layout_mode(global_conn, workspace->table[c][workspace->rows-1], config.container_mode);
-}
-
-/*
- * Adds one row at the head of the table
- *
- */
-void expand_table_rows_at_head(Workspace *workspace) {
-        workspace->rows++;
-
-        workspace->height_factor = realloc(workspace->height_factor, sizeof(float) * workspace->rows);
-
-        DLOG("rows = %d\n", workspace->rows);
-        for (int rows = (workspace->rows - 1); rows >= 1; rows--) {
-                DLOG("Moving height_factor %d (%f) to %d\n", rows-1, workspace->height_factor[rows-1], rows);
-                workspace->height_factor[rows] = workspace->height_factor[rows-1];
-        }
-
-        workspace->height_factor[0] = 0;
-
-        for (int cols = 0; cols < workspace->cols; cols++)
-                workspace->table[cols] = realloc(workspace->table[cols], sizeof(Container*) * workspace->rows);
-
-        /* Move the other rows */
-        for (int cols = 0; cols < workspace->cols; cols++)
-                for (int rows = workspace->rows - 1; rows > 0; rows--) {
-                        DLOG("Moving row %d to %d\n", rows-1, rows);
-                        workspace->table[cols][rows] = workspace->table[cols][rows-1];
-                        workspace->table[cols][rows]->row = rows;
-                }
-
-        for (int cols = 0; cols < workspace->cols; cols++)
-                new_container(workspace, &(workspace->table[cols][0]), cols, 0, false);
-}
-
-/*
- * Add one column to the table
- *
- */
-void expand_table_cols(Workspace *workspace) {
-        workspace->cols++;
-
-        workspace->width_factor = realloc(workspace->width_factor, sizeof(float) * workspace->cols);
-        workspace->width_factor[workspace->cols-1] = 0;
-
-        workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols);
-        workspace->table[workspace->cols-1] = scalloc(sizeof(Container*) * workspace->rows);
-
-        for (int c = 0; c < workspace->rows; c++)
-                new_container(workspace, &(workspace->table[workspace->cols-1][c]), workspace->cols-1, c, true);
-
-        for (int c = 0; c < workspace->rows; c++)
-                switch_layout_mode(global_conn, workspace->table[workspace->cols-1][c], config.container_mode);
-}
-
-/*
- * Inserts one column at the table’s head
- *
- */
-void expand_table_cols_at_head(Workspace *workspace) {
-        workspace->cols++;
-
-        workspace->width_factor = realloc(workspace->width_factor, sizeof(float) * workspace->cols);
-
-        DLOG("cols = %d\n", workspace->cols);
-        for (int cols = (workspace->cols - 1); cols >= 1; cols--) {
-                DLOG("Moving width_factor %d (%f) to %d\n", cols-1, workspace->width_factor[cols-1], cols);
-                workspace->width_factor[cols] = workspace->width_factor[cols-1];
-        }
-
-        workspace->width_factor[0] = 0;
-
-        workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols);
-        workspace->table[workspace->cols-1] = scalloc(sizeof(Container*) * workspace->rows);
-
-        /* Move the other columns */
-        for (int rows = 0; rows < workspace->rows; rows++)
-                for (int cols = workspace->cols - 1; cols > 0; cols--) {
-                        DLOG("Moving col %d to %d\n", cols-1, cols);
-                        workspace->table[cols][rows] = workspace->table[cols-1][rows];
-                        workspace->table[cols][rows]->col = cols;
-                }
-
-        for (int rows = 0; rows < workspace->rows; rows++)
-                new_container(workspace, &(workspace->table[0][rows]), 0, rows, false);
-}
-
-/*
- * Shrinks the table by one column.
- *
- * The containers themselves are freed in move_columns_from() or move_rows_from(). Therefore, this
- * function may only be called from move_*() or after making sure that the containers are freed
- * properly.
- *
- */
-static void shrink_table_cols(Workspace *workspace) {
-        float free_space = workspace->width_factor[workspace->cols-1];
-
-        workspace->cols--;
-
-        /* Shrink the width_factor array */
-        workspace->width_factor = realloc(workspace->width_factor, sizeof(float) * workspace->cols);
-
-        /* Free the container-pointers */
-        free(workspace->table[workspace->cols]);
-
-        /* Re-allocate the table */
-        workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols);
-
-        /* Distribute the free space */
-        if (free_space == 0)
-                return;
-
-        for (int cols = (workspace->cols-1); cols >= 0; cols--) {
-                if (workspace->width_factor[cols] == 0)
-                        continue;
-
-                DLOG("Added free space (%f) to %d (had %f)\n", free_space, cols,
-                                workspace->width_factor[cols]);
-                workspace->width_factor[cols] += free_space;
-                break;
-        }
-}
-
-/*
- * See shrink_table_cols()
- *
- */
-static void shrink_table_rows(Workspace *workspace) {
-        float free_space = workspace->height_factor[workspace->rows-1];
-
-        workspace->rows--;
-        for (int cols = 0; cols < workspace->cols; cols++)
-                workspace->table[cols] = realloc(workspace->table[cols], sizeof(Container*) * workspace->rows);
-
-        /* Shrink the height_factor array */
-        workspace->height_factor = realloc(workspace->height_factor, sizeof(float) * workspace->rows);
-
-        /* Distribute the free space */
-        if (free_space == 0)
-                return;
-
-        for (int rows = (workspace->rows-1); rows >= 0; rows--) {
-                if (workspace->height_factor[rows] == 0)
-                        continue;
-
-                DLOG("Added free space (%f) to %d (had %f)\n", free_space, rows,
-                                workspace->height_factor[rows]);
-                workspace->height_factor[rows] += free_space;
-                break;
-        }
-}
-
-/*
- * Performs simple bounds checking for the given column/row
- *
- */
-bool cell_exists(Workspace *ws, int col, int row) {
-        return (col >= 0 && col < ws->cols) &&
-               (row >= 0 && row < ws->rows);
-}
-
-static void free_container(xcb_connection_t *conn, Workspace *workspace, int col, int row) {
-        Container *old_container = workspace->table[col][row];
-
-        if (old_container->mode == MODE_STACK || old_container->mode == MODE_TABBED)
-                leave_stack_mode(conn, old_container);
-
-        free(old_container);
-}
-
-static void move_columns_from(xcb_connection_t *conn, Workspace *workspace, int cols) {
-        DLOG("firstly freeing \n");
-
-        /* Free the columns which are cleaned up */
-        for (int rows = 0; rows < workspace->rows; rows++)
-                free_container(conn, workspace, cols-1, rows);
-
-        for (; cols < workspace->cols; cols++)
-                for (int rows = 0; rows < workspace->rows; rows++) {
-                        DLOG("at col = %d, row = %d\n", cols, rows);
-                        Container *new_container = workspace->table[cols][rows];
-
-                        DLOG("moving cols = %d to cols -1 = %d\n", cols, cols-1);
-                        workspace->table[cols-1][rows] = new_container;
-
-                        new_container->row = rows;
-                        new_container->col = cols-1;
-                }
-}
-
-static void move_rows_from(xcb_connection_t *conn, Workspace *workspace, int rows) {
-        for (int cols = 0; cols < workspace->cols; cols++)
-                free_container(conn, workspace, cols, rows-1);
-
-        for (; rows < workspace->rows; rows++)
-                for (int cols = 0; cols < workspace->cols; cols++) {
-                        Container *new_container = workspace->table[cols][rows];
-
-                        DLOG("moving rows = %d to rows -1 = %d\n", rows, rows - 1);
-                        workspace->table[cols][rows-1] = new_container;
-
-                        new_container->row = rows-1;
-                        new_container->col = cols;
-                }
-}
-
-/*
- * Prints the table’s contents in human-readable form for debugging
- *
- */
-void dump_table(xcb_connection_t *conn, Workspace *workspace) {
-        DLOG("dump_table()\n");
-        FOR_TABLE(workspace) {
-                Container *con = workspace->table[cols][rows];
-                DLOG("----\n");
-                DLOG("at col=%d, row=%d\n", cols, rows);
-                DLOG("currently_focused = %p\n", con->currently_focused);
-                Client *loop;
-                CIRCLEQ_FOREACH(loop, &(con->clients), clients) {
-                        DLOG("got client %08x / %s\n", loop->child, loop->name);
-                }
-                DLOG("----\n");
-        }
-        DLOG("done\n");
-}
-
-/*
- * Shrinks the table by "compacting" it, that is, removing completely empty rows/columns
- *
- */
-void cleanup_table(xcb_connection_t *conn, Workspace *workspace) {
-        DLOG("cleanup_table()\n");
-
-        /* Check for empty columns if we got more than one column */
-        for (int cols = 0; (workspace->cols > 1) && (cols < workspace->cols);) {
-                bool completely_empty = true;
-                for (int rows = 0; rows < workspace->rows; rows++)
-                        if (workspace->table[cols][rows]->currently_focused != NULL) {
-                                completely_empty = false;
-                                break;
-                        }
-                if (completely_empty) {
-                        DLOG("Removing completely empty column %d\n", cols);
-                        if (cols < (workspace->cols - 1))
-                                move_columns_from(conn, workspace, cols+1);
-                        else {
-                                for (int rows = 0; rows < workspace->rows; rows++)
-                                        free_container(conn, workspace, cols, rows);
-                        }
-                        shrink_table_cols(workspace);
-
-                        if (workspace->current_col >= workspace->cols)
-                                workspace->current_col = workspace->cols - 1;
-                } else cols++;
-        }
-
-        /* Check for empty rows if we got more than one row */
-        for (int rows = 0; (workspace->rows > 1) && (rows < workspace->rows);) {
-                bool completely_empty = true;
-                DLOG("Checking row %d\n", rows);
-                for (int cols = 0; cols < workspace->cols; cols++)
-                        if (workspace->table[cols][rows]->currently_focused != NULL) {
-                                completely_empty = false;
-                                break;
-                        }
-                if (completely_empty) {
-                        DLOG("Removing completely empty row %d\n", rows);
-                        if (rows < (workspace->rows - 1))
-                                move_rows_from(conn, workspace, rows+1);
-                        else {
-                                for (int cols = 0; cols < workspace->cols; cols++)
-                                        free_container(conn, workspace, cols, rows);
-                        }
-                        shrink_table_rows(workspace);
-
-                        if (workspace->current_row >= workspace->rows)
-                                workspace->current_row = workspace->rows - 1;
-                } else rows++;
-        }
-
-        /* Boundary checking for current_col and current_row */
-        if (current_col >= c_ws->cols)
-                current_col = c_ws->cols-1;
-
-        if (current_row >= c_ws->rows)
-                current_row = c_ws->rows-1;
-
-        if (CUR_CELL->currently_focused != NULL)
-                set_focus(conn, CUR_CELL->currently_focused, true);
-}
-
-/*
- * Fixes col/rowspan (makes sure there are no overlapping windows, obeys borders).
- *
- */
-void fix_colrowspan(xcb_connection_t *conn, Workspace *workspace) {
-        DLOG("Fixing col/rowspan\n");
-
-        FOR_TABLE(workspace) {
-                Container *con = workspace->table[cols][rows];
-                if (con->colspan > 1) {
-                        DLOG("gots one with colspan %d (at %d c, %d r)\n", con->colspan, cols, rows);
-                        while (con->colspan > 1 &&
-                               (!cell_exists(workspace, cols + (con->colspan-1), rows) &&
-                                workspace->table[cols + (con->colspan - 1)][rows]->currently_focused != NULL))
-                                con->colspan--;
-                        DLOG("fixed it to %d\n", con->colspan);
-                }
-                if (con->rowspan > 1) {
-                        DLOG("gots one with rowspan %d (at %d c, %d r)\n", con->rowspan, cols, rows);
-                        while (con->rowspan > 1 &&
-                               (!cell_exists(workspace, cols, rows + (con->rowspan - 1)) &&
-                                workspace->table[cols][rows + (con->rowspan - 1)]->currently_focused != NULL))
-                                con->rowspan--;
-                        DLOG("fixed it to %d\n", con->rowspan);
-                }
-        }
-}
diff --git a/src/tree.c b/src/tree.c
new file mode 100644 (file)
index 0000000..272276f
--- /dev/null
@@ -0,0 +1,535 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ */
+
+#include "all.h"
+
+struct Con *croot;
+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).
+ *
+ */
+bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry) {
+    char *globbed = resolve_tilde(path);
+
+    if (!path_exists(globbed)) {
+        LOG("%s does not exist, not restoring tree\n", globbed);
+        free(globbed);
+        return false;
+    }
+
+    /* TODO: refactor the following */
+    croot = con_new(NULL, NULL);
+    croot->rect = (Rect){
+        geometry->x,
+        geometry->y,
+        geometry->width,
+        geometry->height
+    };
+    focused = croot;
+
+    tree_append_json(globbed);
+
+    printf("appended tree, using new root\n");
+    croot = TAILQ_FIRST(&(croot->nodes_head));
+    printf("new root = %p\n", croot);
+    Con *out = TAILQ_FIRST(&(croot->nodes_head));
+    printf("out = %p\n", out);
+    Con *ws = TAILQ_FIRST(&(out->nodes_head));
+    printf("ws = %p\n", ws);
+
+    return true;
+}
+
+/*
+ * Initializes the tree by creating the root node. The CT_OUTPUT Cons below the
+ * root node are created in randr.c for each Output.
+ *
+ */
+void tree_init(xcb_get_geometry_reply_t *geometry) {
+    croot = con_new(NULL, NULL);
+    FREE(croot->name);
+    croot->name = "root";
+    croot->type = CT_ROOT;
+    croot->rect = (Rect){
+        geometry->x,
+        geometry->y,
+        geometry->width,
+        geometry->height
+    };
+}
+
+/*
+ * Opens an empty container in the current container
+ *
+ */
+Con *tree_open_con(Con *con, i3Window *window) {
+    if (con == NULL) {
+        /* every focusable Con has a parent (outputs have parent root) */
+        con = focused->parent;
+        /* If the parent is an output, we are on a workspace. In this case,
+         * the new container needs to be opened as a leaf of the workspace. */
+        if (con->parent->type == CT_OUTPUT && con->type != CT_DOCKAREA) {
+            con = focused;
+        }
+
+        /* If the currently focused container is a floating container, we
+         * attach the new container to the currently focused spot in its
+         * workspace. */
+        if (con->type == CT_FLOATING_CON) {
+            con = con_descend_tiling_focused(con->parent);
+            if (con->type != CT_WORKSPACE)
+                con = con->parent;
+        }
+        DLOG("con = %p\n", con);
+    }
+
+    assert(con != NULL);
+
+    /* 3. create the container and attach it to its parent */
+    Con *new = con_new(con, window);
+
+    /* 4: re-calculate child->percent for each child */
+    con_fix_percent(con);
+
+    return new;
+}
+
+static bool _is_con_mapped(Con *con) {
+    Con *child;
+
+    TAILQ_FOREACH(child, &(con->nodes_head), nodes)
+        if (_is_con_mapped(child))
+            return true;
+
+    return con->mapped;
+}
+
+/*
+ * Closes the given container including all children.
+ * Returns true if the container was killed or false if just WM_DELETE was sent
+ * and the window is expected to kill itself.
+ *
+ */
+bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent) {
+    bool was_mapped = con->mapped;
+    Con *parent = con->parent;
+
+    if (!was_mapped) {
+        /* Even if the container itself is not mapped, its children may be
+         * mapped (for example split containers don't have a mapped window on
+         * their own but usually contain mapped children). */
+        was_mapped = _is_con_mapped(con);
+    }
+
+    /* Get the container which is next focused */
+    Con *next = con_next_focused(con);
+    DLOG("next = %p, focused = %p\n", next, focused);
+
+    DLOG("closing %p, kill_window = %d\n", con, kill_window);
+    Con *child, *nextchild;
+    bool abort_kill = false;
+    /* We cannot use TAILQ_FOREACH because the children get deleted
+     * in their parent’s nodes_head */
+    for (child = TAILQ_FIRST(&(con->nodes_head)); child; ) {
+        nextchild = TAILQ_NEXT(child, nodes);
+        DLOG("killing child=%p\n", child);
+        if (!tree_close(child, kill_window, true))
+            abort_kill = true;
+        child = nextchild;
+    }
+
+    if (abort_kill) {
+        DLOG("One of the children could not be killed immediately (WM_DELETE sent), aborting.\n");
+        return false;
+    }
+
+    if (con->window != NULL) {
+        if (kill_window != DONT_KILL_WINDOW) {
+            x_window_kill(con->window->id, kill_window);
+            return false;
+        } else {
+            xcb_void_cookie_t cookie;
+            /* un-parent the window */
+            cookie = xcb_reparent_window(conn, con->window->id, root, 0, 0);
+
+            /* Ignore X11 errors for the ReparentWindow request.
+             * X11 Errors are returned when the window was already destroyed */
+            add_ignore_event(cookie.sequence, 0);
+
+            /* We are no longer handling this window, thus set WM_STATE to
+             * WM_STATE_WITHDRAWN (see ICCCM 4.1.3.1) */
+            long data[] = { XCB_ICCCM_WM_STATE_WITHDRAWN, XCB_NONE };
+            cookie = xcb_change_property(conn, XCB_PROP_MODE_REPLACE,
+                        con->window->id, A_WM_STATE, A_WM_STATE, 32, 2, data);
+
+            /* Ignore X11 errors for the ReparentWindow request.
+             * X11 Errors are returned when the window was already destroyed */
+            add_ignore_event(cookie.sequence, 0);
+        }
+        FREE(con->window->class_class);
+        FREE(con->window->class_instance);
+        FREE(con->window->name_x);
+        FREE(con->window->name_json);
+        free(con->window);
+    }
+
+    /* kill the X11 part of this container */
+    x_con_kill(con);
+
+    con_detach(con);
+    if (con->type != CT_FLOATING_CON) {
+        /* If the container is *not* floating, we might need to re-distribute
+         * percentage values for the resized containers. */
+        con_fix_percent(parent);
+    }
+
+    if (con_is_floating(con)) {
+        Con *ws = con_get_workspace(con);
+        DLOG("Container was floating, killing floating container\n");
+        tree_close(parent, DONT_KILL_WINDOW, false);
+        DLOG("parent container killed\n");
+        if (con == focused) {
+            DLOG("This is the focused container, i need to find another one to focus. I start looking at ws = %p\n", ws);
+            /* go down the focus stack as far as possible */
+            next = con_descend_focused(ws);
+
+            dont_kill_parent = true;
+            DLOG("Alright, focusing %p\n", next);
+        } else {
+            next = NULL;
+        }
+    }
+
+    free(con->name);
+    FREE(con->deco_render_params);
+    TAILQ_REMOVE(&all_cons, con, all_cons);
+    free(con);
+
+    /* in the case of floating windows, we already focused another container
+     * when closing the parent, so we can exit now. */
+    if (!next) {
+        DLOG("No next container, i will just exit now\n");
+        return true;
+    }
+
+    if (was_mapped || con == focused) {
+        if ((kill_window != DONT_KILL_WINDOW) || !dont_kill_parent || con == focused) {
+            DLOG("focusing %p / %s\n", next, next->name);
+            /* TODO: check if the container (or one of its children) was focused */
+            if (next->type == CT_DOCKAREA) {
+                /* Instead of focusing the dockarea, we need to restore focus to the workspace */
+                con_focus(con_descend_focused(output_get_content(next->parent)));
+            } else {
+                con_focus(next);
+            }
+        }
+        else {
+            DLOG("not focusing because we're not killing anybody");
+        }
+    } else {
+        DLOG("not focusing, was not mapped\n");
+    }
+
+    /* check if the parent container is empty now and close it */
+    if (!dont_kill_parent)
+        CALL(parent, on_remove_child);
+
+    return true;
+}
+
+/*
+ * Closes the current container using tree_close().
+ *
+ */
+void tree_close_con(kill_window_t kill_window) {
+    assert(focused != NULL);
+    if (focused->type == CT_WORKSPACE) {
+        LOG("Cannot close workspace\n");
+        return;
+    }
+
+    /* There *should* be no possibility to focus outputs / root container */
+    assert(focused->type != CT_OUTPUT);
+    assert(focused->type != CT_ROOT);
+
+    /* Kill con */
+    tree_close(focused, kill_window, false);
+}
+
+/*
+ * Splits (horizontally or vertically) the given container by creating a new
+ * container which contains the old one and the future ones.
+ *
+ */
+void tree_split(Con *con, orientation_t orientation) {
+    /* for a workspace, we just need to change orientation */
+    if (con->type == CT_WORKSPACE) {
+        DLOG("Workspace, simply changing orientation to %d\n", orientation);
+        con->orientation = orientation;
+        return;
+    }
+
+    Con *parent = con->parent;
+    /* 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) */
+    if (con_num_children(parent) == 1) {
+        parent->orientation = orientation;
+        DLOG("Just changing orientation of existing container\n");
+        return;
+    }
+
+    DLOG("Splitting in orientation %d\n", orientation);
+
+    /* 2: replace it with a new Con */
+    Con *new = con_new(NULL, NULL);
+    TAILQ_REPLACE(&(parent->nodes_head), con, new, nodes);
+    TAILQ_REPLACE(&(parent->focus_head), con, new, focused);
+    new->parent = parent;
+    new->orientation = orientation;
+
+    /* 3: swap 'percent' (resize factor) */
+    new->percent = con->percent;
+    con->percent = 0.0;
+
+    /* 4: add it as a child to the new Con */
+    con_attach(con, new, false);
+}
+
+/*
+ * Moves focus one level up.
+ *
+ */
+void level_up() {
+    /* We cannot go up when we are in fullscreen mode at the moment, that would
+     * be totally not intuitive */
+    if (focused->fullscreen_mode != CF_NONE) {
+        LOG("Currently in fullscreen, not going up\n");
+        return;
+    }
+    /* We can focus up to the workspace, but not any higher in the tree */
+    if ((focused->parent->type != CT_CON &&
+        focused->parent->type != CT_WORKSPACE) ||
+        focused->type == CT_WORKSPACE) {
+        LOG("Cannot go up any further\n");
+        return;
+    }
+    con_focus(focused->parent);
+}
+
+/*
+ * Moves focus one level down.
+ *
+ */
+void level_down() {
+    /* Go down the focus stack of the current node */
+    Con *next = TAILQ_FIRST(&(focused->focus_head));
+    if (next == TAILQ_END(&(focused->focus_head))) {
+        printf("cannot go down\n");
+        return;
+    }
+    con_focus(next);
+}
+
+static void mark_unmapped(Con *con) {
+    Con *current;
+
+    con->mapped = false;
+    TAILQ_FOREACH(current, &(con->nodes_head), nodes)
+        mark_unmapped(current);
+    if (con->type == CT_WORKSPACE) {
+        /* We need to call mark_unmapped on floating nodes aswell since we can
+         * make containers floating. */
+        TAILQ_FOREACH(current, &(con->floating_head), floating_windows)
+            mark_unmapped(current);
+    }
+}
+
+/*
+ * Renders the tree, that is rendering all outputs using render_con() and
+ * pushing the changes to X11 using x_push_changes().
+ *
+ */
+void tree_render() {
+    if (croot == NULL)
+        return;
+
+    DLOG("-- BEGIN RENDERING --\n");
+    /* Reset map state for all nodes in tree */
+    /* TODO: a nicer method to walk all nodes would be good, maybe? */
+    mark_unmapped(croot);
+    croot->mapped = true;
+
+    render_con(croot, false);
+
+    x_push_changes(croot);
+    DLOG("-- END RENDERING --\n");
+}
+
+/*
+ * Recursive function to walk the tree until a con can be found to focus.
+ *
+ */
+static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) {
+    /* Stop recursing at workspaces */
+    if (con->type == CT_WORKSPACE)
+        return false;
+
+    if (con->type == CT_FLOATING_CON) {
+        /* TODO: implement focus for floating windows */
+        return false;
+    }
+
+    Con *parent = con->parent;
+
+    /* If the orientation does not match or there is no other con to focus, we
+     * need to go higher in the hierarchy */
+    if (con_orientation(parent) != orientation ||
+        con_num_children(parent) == 1)
+        return _tree_next(parent, way, orientation, wrap);
+
+    Con *current = TAILQ_FIRST(&(parent->focus_head));
+    /* TODO: when can the following happen (except for floating windows, which
+     * are handled above)? */
+    if (TAILQ_EMPTY(&(parent->nodes_head))) {
+        DLOG("nothing to focus\n");
+        return false;
+    }
+
+    Con *next;
+    if (way == 'n')
+        next = TAILQ_NEXT(current, nodes);
+    else next = TAILQ_PREV(current, nodes_head, nodes);
+
+    if (!next) {
+        if (!config.force_focus_wrapping) {
+            /* If there is no next/previous container, we check if we can focus one
+             * when going higher (without wrapping, though). If so, we are done, if
+             * not, we wrap */
+            if (_tree_next(parent, way, orientation, false))
+                return true;
+
+            if (!wrap)
+                return false;
+        }
+
+        if (way == 'n')
+            next = TAILQ_FIRST(&(parent->nodes_head));
+        else next = TAILQ_LAST(&(parent->nodes_head), nodes_head);
+    }
+
+    /* 3: focus choice comes in here. at the moment we will go down
+     * until we find a window */
+    /* TODO: check for window, atm we only go down as far as possible */
+    con_focus(con_descend_focused(next));
+    return true;
+}
+
+/*
+ * Changes focus in the given way (next/previous) and given orientation
+ * (horizontal/vertical).
+ *
+ */
+void tree_next(char way, orientation_t orientation) {
+    _tree_next(focused, way, orientation, true);
+}
+
+/*
+ * tree_flatten() removes pairs of redundant split containers, e.g.:
+ *       [workspace, horizontal]
+ *   [v-split]           [child3]
+ *   [h-split]
+ * [child1] [child2]
+ * In this example, the v-split and h-split container are redundant.
+ * Such a situation can be created by moving containers in a direction which is
+ * not the orientation of their parent container. i3 needs to create a new
+ * split container then and if you move containers this way multiple times,
+ * redundant chains of split-containers can be the result.
+ *
+ */
+void tree_flatten(Con *con) {
+    Con *current, *child, *parent = con->parent;
+    DLOG("Checking if I can flatten con = %p / %s\n", con, con->name);
+
+    /* We only consider normal containers without windows */
+    if (con->type != CT_CON || con->window != NULL)
+        goto recurse;
+
+    /* Ensure it got only one child */
+    child = TAILQ_FIRST(&(con->nodes_head));
+    if (child == NULL || TAILQ_NEXT(child, nodes) != NULL)
+        goto recurse;
+
+    /* The child must have a different orientation than the con but the same as
+     * the con’s parent to be redundant */
+    if (con->orientation == NO_ORIENTATION ||
+        child->orientation == NO_ORIENTATION ||
+        con->orientation == child->orientation ||
+        child->orientation != parent->orientation)
+        goto recurse;
+
+    DLOG("Alright, I have to flatten this situation now. Stay calm.\n");
+    /* 1: save focus */
+    Con *focus_next = TAILQ_FIRST(&(child->focus_head));
+
+    DLOG("detaching...\n");
+    /* 2: re-attach the children to the parent before con */
+    while (!TAILQ_EMPTY(&(child->nodes_head))) {
+        current = TAILQ_FIRST(&(child->nodes_head));
+        DLOG("detaching current=%p / %s\n", current, current->name);
+        con_detach(current);
+        DLOG("re-attaching\n");
+        /* We don’t use con_attach() here because for a CT_CON, the special
+         * case handling of con_attach() does not trigger. So all it would do
+         * is calling TAILQ_INSERT_AFTER, but with the wrong container. So we
+         * directly use the TAILQ macros. */
+        current->parent = parent;
+        TAILQ_INSERT_BEFORE(con, current, nodes);
+        DLOG("attaching to focus list\n");
+        TAILQ_INSERT_TAIL(&(parent->focus_head), current, focused);
+        current->percent = con->percent;
+    }
+    DLOG("re-attached all\n");
+
+    /* 3: restore focus, if con was focused */
+    if (focus_next != NULL &&
+        TAILQ_FIRST(&(parent->focus_head)) == con) {
+        DLOG("restoring focus to focus_next=%p\n", focus_next);
+        TAILQ_REMOVE(&(parent->focus_head), focus_next, focused);
+        TAILQ_INSERT_HEAD(&(parent->focus_head), focus_next, focused);
+        DLOG("restored focus.\n");
+    }
+
+    /* 4: close the redundant cons */
+    DLOG("closing redundant cons\n");
+    tree_close(con, DONT_KILL_WINDOW, true);
+
+    /* Well, we got to abort the recursion here because we destroyed the
+     * container. However, if tree_flatten() is called sufficiently often,
+     * there can’t be the situation of having two pairs of redundant containers
+     * at once. Therefore, we can safely abort the recursion on this level
+     * after flattening. */
+    return;
+
+recurse:
+    /* We cannot use normal foreach here because tree_flatten might close the
+     * current container. */
+    current = TAILQ_FIRST(&(con->nodes_head));
+    while (current != NULL) {
+        Con *next = TAILQ_NEXT(current, nodes);
+        tree_flatten(current);
+        current = next;
+    }
+
+    current = TAILQ_FIRST(&(con->floating_head));
+    while (current != NULL) {
+        Con *next = TAILQ_NEXT(current, floating_windows);
+        tree_flatten(current);
+        current = next;
+    }
+}
index cb37d30aa2619d56f6dbeb8052b6183c3e50328b..1ad43d3f5a431c77ff4e022af479f9b710460d9d 100644 (file)
@@ -1,52 +1,50 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
  *
- * © 2009 Michael Stapelberg and contributors
+ * © 2009-2011 Michael Stapelberg and contributors
  *
  * See file LICENSE for license information.
  *
  * util.c: Utility functions, which can be useful everywhere.
  *
  */
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
 #include <sys/wait.h>
 #include <stdarg.h>
-#include <assert.h>
 #include <iconv.h>
 #if defined(__OpenBSD__)
 #include <sys/cdefs.h>
 #endif
+#include <fcntl.h>
+#include <pwd.h>
+#include <yajl/yajl_version.h>
+#include <libgen.h>
 
-#include <xcb/xcb_icccm.h>
-
-#include "i3.h"
-#include "data.h"
-#include "table.h"
-#include "layout.h"
-#include "util.h"
-#include "xcb.h"
-#include "client.h"
-#include "log.h"
-#include "ewmh.h"
-#include "manage.h"
-#include "workspace.h"
-#include "ipc.h"
+#include "all.h"
 
 static iconv_t conversion_descriptor = 0;
-struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent);
-struct keyvalue_table_head by_child = TAILQ_HEAD_INITIALIZER(by_child);
 
 int min(int a, int b) {
-        return (a < b ? a : b);
+    return (a < b ? a : b);
 }
 
 int max(int a, int b) {
-        return (a > b ? a : b);
+    return (a > b ? a : b);
+}
+
+bool rect_contains(Rect rect, uint32_t x, uint32_t y) {
+    return (x >= rect.x &&
+            x <= (rect.x + rect.width) &&
+            y >= rect.y &&
+            y <= (rect.y + rect.height));
+}
+
+Rect rect_add(Rect a, Rect b) {
+    return (Rect){a.x + b.x,
+                  a.y + b.y,
+                  a.width + b.width,
+                  a.height + b.height};
 }
 
 /*
@@ -55,9 +53,9 @@ int max(int a, int b) {
  *
  */
 bool update_if_necessary(uint32_t *destination, const uint32_t new_value) {
-        uint32_t old_value = *destination;
+    uint32_t old_value = *destination;
 
-        return ((*destination = new_value) != old_value);
+    return ((*destination = new_value) != old_value);
 }
 
 /*
@@ -66,59 +64,27 @@ bool update_if_necessary(uint32_t *destination, const uint32_t new_value) {
  *
  */
 void *smalloc(size_t size) {
-        void *result = malloc(size);
-        exit_if_null(result, "Error: out of memory (malloc(%zd))\n", size);
-        return result;
+    void *result = malloc(size);
+    exit_if_null(result, "Error: out of memory (malloc(%zd))\n", size);
+    return result;
 }
 
 void *scalloc(size_t size) {
-        void *result = calloc(size, 1);
-        exit_if_null(result, "Error: out of memory (calloc(%zd))\n", size);
-        return result;
-}
-
-char *sstrdup(const char *str) {
-        char *result = strdup(str);
-        exit_if_null(result, "Error: out of memory (strdup())\n");
-        return result;
-}
-
-/*
- * The table_* functions emulate the behaviour of libxcb-wm, which in libxcb 0.3.4 suddenly
- * vanished. Great.
- *
- */
-bool table_put(struct keyvalue_table_head *head, uint32_t key, void *value) {
-        struct keyvalue_element *element = scalloc(sizeof(struct keyvalue_element));
-        element->key = key;
-        element->value = value;
-
-        TAILQ_INSERT_TAIL(head, element, elements);
-        return true;
+    void *result = calloc(size, 1);
+    exit_if_null(result, "Error: out of memory (calloc(%zd))\n", size);
+    return result;
 }
 
-void *table_remove(struct keyvalue_table_head *head, uint32_t key) {
-        struct keyvalue_element *element;
-
-        TAILQ_FOREACH(element, head, elements)
-                if (element->key == key) {
-                        void *value = element->value;
-                        TAILQ_REMOVE(head, element, elements);
-                        free(element);
-                        return value;
-                }
-
-        return NULL;
+void *srealloc(void *ptr, size_t size) {
+    void *result = realloc(ptr, size);
+    exit_if_null(result, "Error: out memory (realloc(%zd))\n", size);
+    return result;
 }
 
-void *table_get(struct keyvalue_table_head *head, uint32_t key) {
-        struct keyvalue_element *element;
-
-        TAILQ_FOREACH(element, head, elements)
-                if (element->key == key)
-                        return element->value;
-
-        return NULL;
+char *sstrdup(const char *str) {
+    char *result = strdup(str);
+    exit_if_null(result, "Error: out of memory (strdup())\n");
+    return result;
 }
 
 /*
@@ -132,23 +98,72 @@ void *table_get(struct keyvalue_table_head *head, uint32_t key) {
  *
  */
 void start_application(const char *command) {
+    LOG("executing: %s\n", command);
+    if (fork() == 0) {
+        /* Child process */
+        setsid();
         if (fork() == 0) {
-                /* Child process */
-                if (fork() == 0) {
-                        /* Stores the path of the shell */
-                        static const char *shell = NULL;
-
-                        if (shell == NULL)
-                                if ((shell = getenv("SHELL")) == NULL)
-                                        shell = "/bin/sh";
-
-                        /* This is the child */
-                        execl(shell, shell, "-c", command, (void*)NULL);
-                        /* not reached */
-                }
-                exit(0);
+            /* Stores the path of the shell */
+            static const char *shell = NULL;
+
+            if (shell == NULL)
+                if ((shell = getenv("SHELL")) == NULL)
+                    shell = "/bin/sh";
+
+            /* This is the child */
+            execl(shell, shell, "-c", command, (void*)NULL);
+            /* not reached */
         }
-        wait(0);
+        exit(0);
+    }
+    wait(0);
+}
+
+/*
+ * exec()s an i3 utility, for example the config file migration script or
+ * i3-nagbar. This function first searches $PATH for the given utility named,
+ * then falls back to the dirname() of the i3 executable path and then falls
+ * back to the dirname() of the target of /proc/self/exe (on linux).
+ *
+ * This function should be called after fork()ing.
+ *
+ * The first argument of the given argv vector will be overwritten with the
+ * executable name, so pass NULL.
+ *
+ * If the utility cannot be found in any of these locations, it exits with
+ * return code 2.
+ *
+ */
+void exec_i3_utility(char *name, char *argv[]) {
+    /* start the migration script, search PATH first */
+    char *migratepath = name;
+    argv[0] = migratepath;
+    execvp(migratepath, argv);
+
+    /* if the script is not in path, maybe the user installed to a strange
+     * location and runs the i3 binary with an absolute path. We use
+     * argv[0]’s dirname */
+    char *pathbuf = strdup(start_argv[0]);
+    char *dir = dirname(pathbuf);
+    asprintf(&migratepath, "%s/%s", dir, name);
+    argv[0] = migratepath;
+    execvp(migratepath, argv);
+
+#if defined(__linux__)
+    /* on linux, we have one more fall-back: dirname(/proc/self/exe) */
+    char buffer[BUFSIZ];
+    if (readlink("/proc/self/exe", buffer, BUFSIZ) == -1) {
+        warn("could not read /proc/self/exe");
+        exit(1);
+    }
+    dir = dirname(buffer);
+    asprintf(&migratepath, "%s/%s", dir, name);
+    argv[0] = migratepath;
+    execvp(migratepath, argv);
+#endif
+
+    warn("Could not start %s", name);
+    exit(2);
 }
 
 /*
@@ -157,12 +172,12 @@ void start_application(const char *command) {
  *
  */
 void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_message) {
-        xcb_generic_error_t *error = xcb_request_check(conn, cookie);
-        if (error != NULL) {
-                fprintf(stderr, "ERROR: %s (X error %d)\n", err_message , error->error_code);
-                xcb_disconnect(conn);
-                exit(-1);
-        }
+    xcb_generic_error_t *error = xcb_request_check(conn, cookie);
+    if (error != NULL) {
+        fprintf(stderr, "ERROR: %s (X error %d)\n", err_message , error->error_code);
+        xcb_disconnect(conn);
+        exit(-1);
+    }
 }
 
 /*
@@ -173,358 +188,267 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_mes
  *
  */
 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 */
+    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) {
-                conversion_descriptor = iconv_open("UCS-2BE", "UTF-8");
-                if (conversion_descriptor == 0) {
-                        fprintf(stderr, "error opening the conversion context\n");
-                        exit(1);
-                }
+            fprintf(stderr, "error opening the conversion context\n");
+            exit(1);
         }
+    }
 
-        /* Get the conversion descriptor back to original state */
-        iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
-
-        /* 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");
-                if (real_strlen != NULL)
-                        *real_strlen = 0;
-                return NULL;
-        }
+    /* Get the conversion descriptor back to original state */
+    iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
 
+    /* Convert our text */
+    int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
+    if (rc == (size_t)-1) {
+        perror("Converting to UCS-2 failed");
         if (real_strlen != NULL)
-                *real_strlen = ((buffer_size - output_size) / 2) - 1;
+            *real_strlen = 0;
+        return NULL;
+    }
+
+    if (real_strlen != NULL)
+        *real_strlen = ((buffer_size - output_size) / 2) - 1;
 
-        return buffer;
+    return buffer;
 }
 
 /*
- * Returns the client which comes next in focus stack (= was selected before) for
- * the given container, optionally excluding the given client.
+ * This function resolves ~ in pathnames.
+ * It may resolve wildcards in the first part of the path, but if no match
+ * or multiple matches are found, it just returns a copy of path as given.
  *
  */
-Client *get_last_focused_client(xcb_connection_t *conn, Container *container, Client *exclude) {
-        Client *current;
-        SLIST_FOREACH(current, &(container->workspace->focus_stack), focus_clients)
-                if ((current->container == container) && ((exclude == NULL) || (current != exclude)))
-                        return current;
-        return NULL;
-}
+char *resolve_tilde(const char *path) {
+        static glob_t globbuf;
+        char *head, *tail, *result;
+
+        tail = strchr(path, '/');
+        head = strndup(path, tail ? tail - path : strlen(path));
+
+        int res = glob(head, GLOB_TILDE, NULL, &globbuf);
+        free(head);
+        /* no match, or many wildcard matches are bad */
+        if (res == GLOB_NOMATCH || globbuf.gl_pathc != 1)
+                result = sstrdup(path);
+        else if (res != 0) {
+                die("glob() failed");
+        } else {
+                head = globbuf.gl_pathv[0];
+                result = scalloc(strlen(head) + (tail ? strlen(tail) : 0) + 1);
+                strncpy(result, head, strlen(head));
+                if (tail)
+                    strncat(result, tail, strlen(tail));
+        }
+        globfree(&globbuf);
 
+        return result;
+}
 
 /*
- * Sets the given client as focused by updating the data structures correctly,
- * updating the X input focus and finally re-decorating both windows (to signalize
- * the user the new focus situation)
+ * Checks if the given path exists by calling stat().
  *
  */
-void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
-        /* The dock window cannot be focused, but enter notifies are still handled correctly */
-        if (client->dock)
-                return;
-
-        /* Store the old client */
-        Client *old_client = SLIST_FIRST(&(c_ws->focus_stack));
-
-        /* Check if the focus needs to be changed at all */
-        if (!set_anyways && (old_client == client))
-                return;
-
-        /* Store current_row/current_col */
-        c_ws->current_row = current_row;
-        c_ws->current_col = current_col;
-        c_ws = client->workspace;
-        ewmh_update_current_desktop();
-        /* Load current_col/current_row if we switch to a client without a container */
-        current_col = c_ws->current_col;
-        current_row = c_ws->current_row;
-
-        /* Update container */
-        if (client->container != NULL) {
-                client->container->currently_focused = client;
-
-                current_col = client->container->col;
-                current_row = client->container->row;
-        }
-
-        CLIENT_LOG(client);
-        /* Set focus to the entered window, and flush xcb buffer immediately */
-        xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, client->child, XCB_CURRENT_TIME);
-        ewmh_update_active_window(client->child);
-        //xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, 10, 10);
-
-        if (client->container != NULL) {
-                /* Get the client which was last focused in this particular container, it may be a different
-                   one than old_client */
-                Client *last_focused = get_last_focused_client(conn, client->container, NULL);
-
-                /* In stacking containers, raise the client in respect to the one which was focused before */
-                if ((client->container->mode == MODE_STACK || client->container->mode == MODE_TABBED) &&
-                    client->container->workspace->fullscreen_client == NULL) {
-                        /* We need to get the client again, this time excluding the current client, because
-                         * we might have just gone into stacking mode and need to raise */
-                        Client *last_focused = get_last_focused_client(conn, client->container, client);
-
-                        if (last_focused != NULL) {
-                                DLOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child);
-                                uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE };
-                                xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
-                        }
-                }
-
-                /* If it is the same one as old_client, we save us the unnecessary redecorate */
-                if ((last_focused != NULL) && (last_focused != old_client))
-                        redecorate_window(conn, last_focused);
-        }
-
-        /* If the last client was a floating client, we need to go to the next
-         * tiling client in stack and re-decorate it. */
-        if (old_client != NULL && client_is_floating(old_client)) {
-                DLOG("Coming from floating client, searching next tiling...\n");
-                Client *current;
-                SLIST_FOREACH(current, &(client->workspace->focus_stack), focus_clients) {
-                        if (client_is_floating(current))
-                                continue;
-
-                        DLOG("Found window: %p / child %p\n", current->frame, current->child);
-                        redecorate_window(conn, current);
-                        break;
-                }
-        }
-
-        SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
-        SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients);
-
-        /* Clear the urgency flag if set (necessary when i3 sets the flag, for
-         * example when automatically putting windows on the workspace of their
-         * leader) */
-        client->urgent = false;
-        workspace_update_urgent_flag(client->workspace);
-
-        /* If we’re in stacking mode, this renders the container to update changes in the title
-           bars and to raise the focused client */
-        if ((old_client != NULL) && (old_client != client) && !old_client->dock)
-                redecorate_window(conn, old_client);
-
-        /* redecorate_window flushes, so we don’t need to */
-        redecorate_window(conn, client);
+bool path_exists(const char *path) {
+        struct stat buf;
+        return (stat(path, &buf) == 0);
 }
 
 /*
- * Called when the user switches to another mode or when the container is
- * destroyed and thus needs to be cleaned up.
+ * Goes through the list of arguments (for exec()) and checks if the given argument
+ * is present. If not, it copies the arguments (because we cannot realloc it) and
+ * appends the given argument.
  *
  */
-void leave_stack_mode(xcb_connection_t *conn, Container *container) {
-        /* When going out of stacking mode, we need to close the window */
-        struct Stack_Window *stack_win = &(container->stack_win);
-
-        SLIST_REMOVE(&stack_wins, stack_win, Stack_Window, stack_windows);
-
-        xcb_free_gc(conn, stack_win->pixmap.gc);
-        xcb_free_pixmap(conn, stack_win->pixmap.id);
-        xcb_destroy_window(conn, stack_win->window);
-
-        stack_win->rect.width = -1;
-        stack_win->rect.height = -1;
+static char **append_argument(char **original, char *argument) {
+    int num_args;
+    for (num_args = 0; original[num_args] != NULL; num_args++) {
+        DLOG("original argument: \"%s\"\n", original[num_args]);
+        /* If the argument is already present we return the original pointer */
+        if (strcmp(original[num_args], argument) == 0)
+            return original;
+    }
+    /* Copy the original array */
+    char **result = smalloc((num_args+2) * sizeof(char*));
+    memcpy(result, original, num_args * sizeof(char*));
+    result[num_args] = argument;
+    result[num_args+1] = NULL;
+
+    return result;
 }
 
 /*
- * Switches the layout of the given container taking care of the necessary house-keeping
+ * Returns the name of a temporary file with the specified prefix.
  *
  */
-void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode) {
-        if (mode == MODE_STACK || mode == MODE_TABBED) {
-                /* When we’re already in stacking mode, nothing has to be done */
-                if ((mode == MODE_STACK && container->mode == MODE_STACK) ||
-                    (mode == MODE_TABBED && container->mode == MODE_TABBED))
-                        return;
-
-                if (container->mode == MODE_STACK || container->mode == MODE_TABBED)
-                        goto after_stackwin;
-
-                /* When entering stacking mode, we need to open a window on
-                 * which we can draw the title bars of the clients, it has
-                 * height 1 because we don’t bother here with calculating the
-                 * correct height - it will be adjusted when rendering anyways.
-                 * Also, we need to use max(width, 1) because windows cannot
-                 * be created with either width == 0 or height == 0. */
-                Rect rect = {container->x, container->y, max(container->width, 1), 1};
-
-                uint32_t mask = 0;
-                uint32_t values[2];
-
-                /* Don’t generate events for our new window, it should *not* be managed */
-                mask |= XCB_CW_OVERRIDE_REDIRECT;
-                values[0] = 1;
-
-                /* We want to know when… */
-                mask |= XCB_CW_EVENT_MASK;
-                values[1] =     XCB_EVENT_MASK_ENTER_WINDOW |   /* …mouse is moved into our window */
-                                XCB_EVENT_MASK_BUTTON_PRESS |   /* …mouse is pressed */
-                                XCB_EVENT_MASK_EXPOSURE;        /* …our window needs to be redrawn */
-
-                struct Stack_Window *stack_win = &(container->stack_win);
-                stack_win->window = create_window(conn, rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values);
-
-                stack_win->rect.height = 0;
-
-                /* Initialize the entry for our cached pixmap. It will be
-                 * created as soon as it’s needed (see cached_pixmap_prepare). */
-                memset(&(stack_win->pixmap), 0, sizeof(struct Cached_Pixmap));
-                stack_win->pixmap.referred_rect = &stack_win->rect;
-                stack_win->pixmap.referred_drawable = stack_win->window;
-
-                stack_win->container = container;
-
-                SLIST_INSERT_HEAD(&stack_wins, stack_win, stack_windows);
-        } else {
-                if (container->mode == MODE_STACK || container->mode == MODE_TABBED)
-                        leave_stack_mode(conn, container);
+char *get_process_filename(const char *prefix) {
+    char *dir = getenv("XDG_RUNTIME_DIR");
+    if (dir == NULL) {
+        struct passwd *pw = getpwuid(getuid());
+        const char *username = pw ? pw->pw_name : "unknown";
+        if (asprintf(&dir, "/tmp/i3-%s", username) == -1) {
+            perror("asprintf()");
+            return NULL;
         }
-after_stackwin:
-        container->mode = mode;
-
-        /* Force reconfiguration of each client */
-        Client *client;
+    } else {
+        char *tmp;
+        if (asprintf(&tmp, "%s/i3", dir) == -1) {
+            perror("asprintf()");
+            return NULL;
+        }
+        dir = tmp;
+    }
+    if (!path_exists(dir)) {
+        if (mkdir(dir, 0700) == -1) {
+            perror("mkdir()");
+            return NULL;
+        }
+    }
+    char *filename;
+    if (asprintf(&filename, "%s/%s.%d", dir, prefix, getpid()) == -1) {
+        perror("asprintf()");
+        filename = NULL;
+    }
+
+    free(dir);
+    return filename;
+}
 
-        CIRCLEQ_FOREACH(client, &(container->clients), clients)
-                client->force_reconfigure = true;
+#define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__)
+#define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str))
 
-        render_layout(conn);
+char *store_restart_layout() {
+    setlocale(LC_NUMERIC, "C");
+#if YAJL_MAJOR >= 2
+    yajl_gen gen = yajl_gen_alloc(NULL);
+#else
+    yajl_gen gen = yajl_gen_alloc(NULL, NULL);
+#endif
 
-        if (container->currently_focused != NULL) {
-                /* We need to make sure that this client is above *each* of the
-                 * other clients in this container */
-                Client *last_focused = get_last_focused_client(conn, container, container->currently_focused);
+    dump_node(gen, croot, true);
 
-                CIRCLEQ_FOREACH(client, &(container->clients), clients) {
-                        if (client == container->currently_focused || client == last_focused)
-                                continue;
+    setlocale(LC_NUMERIC, "");
 
-                        DLOG("setting %08x below %08x / %08x\n", client->frame, container->currently_focused->frame);
-                        uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW };
-                        xcb_configure_window(conn, client->frame,
-                                             XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
-                }
+    const unsigned char *payload;
+#if YAJL_MAJOR >= 2
+    size_t length;
+#else
+    unsigned int length;
+#endif
+    y(get_buf, &payload, &length);
+
+    /* create a temporary file if one hasn't been specified, or just
+     * resolve the tildes in the specified path */
+    char *filename;
+    if (config.restart_state_path == NULL) {
+        filename = get_process_filename("restart-state");
+        if (!filename)
+            return NULL;
+    } else {
+        filename = resolve_tilde(config.restart_state_path);
+    }
+
+    int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+    if (fd == -1) {
+        perror("open()");
+        free(filename);
+        return NULL;
+    }
+
+    int written = 0;
+    while (written < length) {
+        int n = write(fd, payload + written, length - written);
+        /* TODO: correct error-handling */
+        if (n == -1) {
+            perror("write()");
+            free(filename);
+            return NULL;
+        }
+        if (n == 0) {
+            printf("write == 0?\n");
+            free(filename);
+            return NULL;
+        }
+        written += n;
+#if YAJL_MAJOR >= 2
+        printf("written: %d of %zd\n", written, length);
+#else
+        printf("written: %d of %d\n", written, length);
+#endif
+    }
+    close(fd);
 
-                if (last_focused != NULL) {
-                        DLOG("Putting last_focused directly underneath the currently focused\n");
-                        uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW };
-                        xcb_configure_window(conn, last_focused->frame,
-                                             XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
-                }
+    if (length > 0) {
+        printf("layout: %.*s\n", (int)length, payload);
+    }
 
+    y(free);
 
-                set_focus(conn, container->currently_focused, true);
-        }
+    return filename;
 }
 
 /*
- * Gets the first matching client for the given window class/window title.
- * If the paramater specific is set to a specific client, only this one
- * will be checked.
+ * Restart i3 in-place
+ * appends -a to argument list to disable autostart
  *
  */
-Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle,
-                            Client *specific) {
-        char *to_class, *to_title, *to_title_ucs = NULL;
-        int to_title_ucs_len = 0;
-        Client *matching = NULL;
-
-        to_class = sstrdup(window_classtitle);
-
-        /* If a title was specified, split both strings at the slash */
-        if ((to_title = strstr(to_class, "/")) != NULL) {
-                *(to_title++) = '\0';
-                /* Convert to UCS-2 */
-                to_title_ucs = convert_utf8_to_ucs2(to_title, &to_title_ucs_len);
-        }
+void i3_restart(bool forget_layout) {
+    char *restart_filename = forget_layout ? NULL : store_restart_layout();
 
-        /* If we were given a specific client we only check if that one matches */
-        if (specific != NULL) {
-                if (client_matches_class_name(specific, to_class, to_title, to_title_ucs, to_title_ucs_len))
-                        matching = specific;
-                goto done;
-        }
+    kill_configerror_nagbar(true);
 
-        DLOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title);
-        Workspace *ws;
-        TAILQ_FOREACH(ws, workspaces, workspaces) {
-                if (ws->output == NULL)
-                        continue;
-
-                Client *client;
-                SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) {
-                        DLOG("Checking client with class=%s / %s, name=%s\n", client->window_class_instance,
-                             client->window_class_class, client->name);
-                        if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len))
-                                continue;
-
-                        matching = client;
-                        goto done;
-                }
-        }
+    restore_geometry();
 
-done:
-        free(to_class);
-        FREE(to_title_ucs);
-        return matching;
-}
+    ipc_shutdown();
 
-/*
- * Goes through the list of arguments (for exec()) and checks if the given argument
- * is present. If not, it copies the arguments (because we cannot realloc it) and
- * appends the given argument.
- *
- */
-static char **append_argument(char **original, char *argument) {
+    LOG("restarting \"%s\"...\n", start_argv[0]);
+    /* make sure -a is in the argument list or append it */
+    start_argv = append_argument(start_argv, "-a");
+
+    /* replace -r <file> so that the layout is restored */
+    if (restart_filename != NULL) {
+        /* create the new argv */
         int num_args;
-        for (num_args = 0; original[num_args] != NULL; num_args++) {
-                DLOG("original argument: \"%s\"\n", original[num_args]);
-                /* If the argument is already present we return the original pointer */
-                if (strcmp(original[num_args], argument) == 0)
-                        return original;
+        for (num_args = 0; start_argv[num_args] != NULL; num_args++);
+        char **new_argv = scalloc((num_args + 3) * sizeof(char*));
+
+        /* copy the arguments, but skip the ones we'll replace */
+        int write_index = 0;
+        bool skip_next = false;
+        for (int i = 0; i < num_args; ++i) {
+            if (skip_next)
+                skip_next = false;
+            else if (!strcmp(start_argv[i], "-r") ||
+                     !strcmp(start_argv[i], "--restart"))
+                skip_next = true;
+            else
+                new_argv[write_index++] = start_argv[i];
         }
-        /* Copy the original array */
-        char **result = smalloc((num_args+2) * sizeof(char*));
-        memcpy(result, original, num_args * sizeof(char*));
-        result[num_args] = argument;
-        result[num_args+1] = NULL;
-
-        return result;
-}
 
-/*
- * Restart i3 in-place
- * appends -a to argument list to disable autostart
- *
- */
-void i3_restart() {
-        restore_geometry(global_conn);
+        /* add the arguments we'll replace */
+        new_argv[write_index++] = "--restart";
+        new_argv[write_index] = restart_filename;
 
-        ipc_shutdown();
+        /* swap the argvs */
+        start_argv = new_argv;
+    }
 
-        LOG("restarting \"%s\"...\n", start_argv[0]);
-        /* make sure -a is in the argument list or append it */
-        start_argv = append_argument(start_argv, "-a");
-
-        execvp(start_argv[0], start_argv);
-        /* not reached */
+    execvp(start_argv[0], start_argv);
+    /* not reached */
 }
 
-#if defined(__OpenBSD__)
+#if defined(__OpenBSD__) || defined(__APPLE__)
 
 /*
  * Taken from FreeBSD
@@ -532,31 +456,54 @@ void i3_restart() {
  *
  */
 void *memmem(const void *l, size_t l_len, const void *s, size_t s_len) {
-        register char *cur, *last;
-        const char *cl = (const char *)l;
-        const char *cs = (const char *)s;
+    register char *cur, *last;
+    const char *cl = (const char *)l;
+    const char *cs = (const char *)s;
 
-        /* we need something to compare */
-        if (l_len == 0 || s_len == 0)
-                return NULL;
+    /* we need something to compare */
+    if (l_len == 0 || s_len == 0)
+        return NULL;
 
-        /* "s" must be smaller or equal to "l" */
-        if (l_len < s_len)
-                return NULL;
+    /* "s" must be smaller or equal to "l" */
+    if (l_len < s_len)
+        return NULL;
 
-        /* special case where s_len == 1 */
-        if (s_len == 1)
-                return memchr(l, (int)*cs, l_len);
+    /* special case where s_len == 1 */
+    if (s_len == 1)
+        return memchr(l, (int)*cs, l_len);
 
-        /* the last position where its possible to find "s" in "l" */
-        last = (char *)cl + l_len - s_len;
+    /* the last position where its possible to find "s" in "l" */
+    last = (char *)cl + l_len - s_len;
 
-        for (cur = (char *)cl; cur <= last; cur++)
-                if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0)
-                        return cur;
+    for (cur = (char *)cl; cur <= last; cur++)
+        if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0)
+            return cur;
 
-        return NULL;
+    return NULL;
 }
 
 #endif
 
+#if defined(__APPLE__)
+
+/*
+ * Taken from FreeBSD
+ * Returns a pointer to a new string which is a duplicate of the
+ * string, but only copies at most n characters.
+ *
+ */
+char *strndup(const char *str, size_t n) {
+    size_t len;
+    char *copy;
+
+    for (len = 0; len < n && str[len]; len++)
+        continue;
+
+    if ((copy = malloc(len + 1)) == NULL)
+        return (NULL);
+    memcpy(copy, str, len);
+    copy[len] = '\0';
+    return (copy);
+}
+
+#endif
diff --git a/src/window.c b/src/window.c
new file mode 100644 (file)
index 0000000..3dd6645
--- /dev/null
@@ -0,0 +1,217 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include "all.h"
+
+/*
+ * Updates the WM_CLASS (consisting of the class and instance) for the
+ * given window.
+ *
+ */
+void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
+    if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
+        DLOG("empty property, not updating\n");
+        FREE(prop);
+        return;
+    }
+
+    /* We cannot use asprintf here since this property contains two
+     * null-terminated strings (for compatibility reasons). Instead, we
+     * use strdup() on both strings */
+    char *new_class = xcb_get_property_value(prop);
+
+    FREE(win->class_instance);
+    FREE(win->class_class);
+
+    win->class_instance = sstrdup(new_class);
+    if ((strlen(new_class) + 1) < xcb_get_property_value_length(prop))
+        win->class_class = sstrdup(new_class + strlen(new_class) + 1);
+    else win->class_class = NULL;
+    LOG("WM_CLASS changed to %s (instance), %s (class)\n",
+        win->class_instance, win->class_class);
+
+    if (before_mgmt) {
+        free(prop);
+        return;
+    }
+
+    run_assignments(win);
+
+    free(prop);
+}
+
+/*
+ * Updates the name by using _NET_WM_NAME (encoded in UTF-8) for the given
+ * window. Further updates using window_update_name_legacy will be ignored.
+ *
+ */
+void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
+    if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
+        DLOG("_NET_WM_NAME not specified, not changing\n");
+        FREE(prop);
+        return;
+    }
+
+    /* Save the old pointer to make the update atomic */
+    char *new_name;
+    if (asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop),
+                 (char*)xcb_get_property_value(prop)) == -1) {
+        perror("asprintf()");
+        DLOG("Could not get window name\n");
+        free(prop);
+        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);
+    if (ucs2_name == NULL) {
+        LOG("Could not convert _NET_WM_NAME to UCS-2, ignoring new hint\n");
+        FREE(new_name);
+        free(prop);
+        return;
+    }
+    FREE(win->name_x);
+    FREE(win->name_json);
+    win->name_json = new_name;
+    win->name_x = ucs2_name;
+    win->name_len = len;
+    win->name_x_changed = true;
+    LOG("_NET_WM_NAME changed to \"%s\"\n", win->name_json);
+
+    win->uses_net_wm_name = true;
+
+    if (before_mgmt) {
+        free(prop);
+        return;
+    }
+
+    run_assignments(win);
+
+    free(prop);
+}
+
+/*
+ * Updates the name by using WM_NAME (encoded in COMPOUND_TEXT). We do not
+ * touch what the client sends us but pass it to xcb_image_text_8. To get
+ * proper unicode rendering, the application has to use _NET_WM_NAME (see
+ * window_update_name()).
+ *
+ */
+void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
+    if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
+        DLOG("prop == NULL\n");
+        FREE(prop);
+        return;
+    }
+
+    /* ignore update when the window is known to already have a UTF-8 name */
+    if (win->uses_net_wm_name) {
+        free(prop);
+        return;
+    }
+
+    char *new_name;
+    if (asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop),
+                 (char*)xcb_get_property_value(prop)) == -1) {
+        perror("asprintf()");
+        DLOG("Could not get legacy window name\n");
+        free(prop);
+        return;
+    }
+
+    LOG("Using legacy window title. Note that in order to get Unicode window "
+        "titles in i3, the application has to set _NET_WM_NAME (UTF-8)\n");
+
+    FREE(win->name_x);
+    FREE(win->name_json);
+    win->name_x = new_name;
+    win->name_json = sstrdup(new_name);
+    win->name_len = strlen(new_name);
+    win->name_x_changed = true;
+
+    if (before_mgmt) {
+        free(prop);
+        return;
+    }
+
+    run_assignments(win);
+
+    free(prop);
+}
+
+/*
+ * Updates the CLIENT_LEADER (logical parent window).
+ *
+ */
+void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop) {
+    if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
+        DLOG("prop == NULL\n");
+        FREE(prop);
+        return;
+    }
+
+    xcb_window_t *leader = xcb_get_property_value(prop);
+    if (leader == NULL) {
+        free(prop);
+        return;
+    }
+
+    DLOG("Client leader changed to %08x\n", *leader);
+
+    win->leader = *leader;
+
+    free(prop);
+}
+
+/*
+ * Updates the TRANSIENT_FOR (logical parent window).
+ *
+ */
+void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop) {
+    if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
+        DLOG("prop == NULL\n");
+        FREE(prop);
+        return;
+    }
+
+    xcb_window_t transient_for;
+    if (!xcb_icccm_get_wm_transient_for_from_reply(&transient_for, prop)) {
+        free(prop);
+        return;
+    }
+
+    DLOG("Transient for changed to %08x\n", transient_for);
+
+    win->transient_for = transient_for;
+
+    free(prop);
+}
+
+/*
+ * Updates the _NET_WM_STRUT_PARTIAL (reserved pixels at the screen edges)
+ *
+ */
+void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop) {
+    if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
+        DLOG("prop == NULL\n");
+        FREE(prop);
+        return;
+    }
+
+    uint32_t *strut;
+    if (!(strut = xcb_get_property_value(prop))) {
+        free(prop);
+        return;
+    }
+
+    DLOG("Reserved pixels changed to: left = %d, right = %d, top = %d, bottom = %d\n",
+         strut[0], strut[1], strut[2], strut[3]);
+
+    win->reserved = (struct reservedpx){ strut[0], strut[1], strut[2], strut[3] };
+
+    free(prop);
+}
index 40f68bf8332e4b5a67ea58da3584201fdaf63826..4021dd14419abbdf01bf6534ff52638171a18454 100644 (file)
@@ -1,34 +1,15 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * workspace.c: Functions for modifying workspaces
  *
  */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
 #include <limits.h>
-#include <err.h>
-
-#include "util.h"
-#include "data.h"
-#include "i3.h"
-#include "config.h"
-#include "xcb.h"
-#include "table.h"
-#include "randr.h"
-#include "layout.h"
-#include "workspace.h"
-#include "client.h"
-#include "log.h"
-#include "ewmh.h"
-#include "ipc.h"
+
+#include "all.h"
 
 /*
  * Returns a pointer to the workspace with the given number (starting at 0),
  * memory and initializing the data structures correctly).
  *
  */
-Workspace *workspace_get(int number) {
-        Workspace *ws = NULL;
-        TAILQ_FOREACH(ws, workspaces, workspaces)
-                if (ws->num == number)
-                        return ws;
-
-        /* If we are still there, we could not find the requested workspace. */
-        int last_ws = TAILQ_LAST(workspaces, workspaces_head)->num;
-
-        DLOG("We need to initialize that one, last ws = %d\n", last_ws);
-
-        for (int c = last_ws; c < number; c++) {
-                DLOG("Creating new ws\n");
-
-                ws = scalloc(sizeof(Workspace));
-                ws->num = c+1;
-                TAILQ_INIT(&(ws->floating_clients));
-                expand_table_cols(ws);
-                expand_table_rows(ws);
-                workspace_set_name(ws, NULL);
-
-                TAILQ_INSERT_TAIL(workspaces, ws, workspaces);
-
-                ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"create\"}");
+Con *workspace_get(const char *num, bool *created) {
+    Con *output, *workspace = NULL;
+
+    TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
+        GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, num));
+
+    if (workspace == NULL) {
+        LOG("Creating new workspace \"%s\"\n", num);
+        /* unless an assignment is found, we will create this workspace on the current output */
+        output = con_get_output(focused);
+        /* look for assignments */
+        struct Workspace_Assignment *assignment;
+        TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
+            if (strcmp(assignment->name, num) != 0)
+                continue;
+
+            LOG("Found workspace assignment to output \"%s\"\n", assignment->output);
+            GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
+            break;
+        }
+        Con *content = output_get_content(output);
+        LOG("got output %p with content %p\n", output, content);
+        /* We need to attach this container after setting its type. con_attach
+         * will handle CT_WORKSPACEs differently */
+        workspace = con_new(NULL, NULL);
+        char *name;
+        asprintf(&name, "[i3 con] workspace %s", num);
+        x_set_name(workspace, name);
+        free(name);
+        workspace->type = CT_WORKSPACE;
+        FREE(workspace->name);
+        workspace->name = sstrdup(num);
+        /* We set ->num to the number if this workspace’s name consists only of
+         * a positive number. Otherwise it’s a named ws and num will be -1. */
+        char *end;
+        long parsed_num = strtol(num, &end, 10);
+        if (parsed_num == LONG_MIN ||
+            parsed_num == LONG_MAX ||
+            parsed_num < 0 ||
+            (end && *end != '\0'))
+            workspace->num = -1;
+        else workspace->num = parsed_num;
+        LOG("num = %d\n", workspace->num);
+
+        /* If default_orientation is set to NO_ORIENTATION we
+         * determine workspace orientation from workspace size.
+         * Otherwise we just set the orientation to default_orientation. */
+        if (config.default_orientation == NO_ORIENTATION) {
+            workspace->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ;
+            DLOG("Auto orientation. Output resolution set to (%d,%d), setting orientation to %d.\n",
+                 workspace->rect.width, workspace->rect.height, workspace->orientation);
+        } else {
+            workspace->orientation = config.default_orientation;
         }
-        DLOG("done\n");
-
-        ewmh_update_workarea();
-
-        return ws;
-}
-
-/*
- * Sets the name (or just its number) for the given workspace. This has to
- * be called for every workspace as the rendering function
- * (render_internal_bar) relies on workspace->name and workspace->name_len
- * being ready-to-use.
- *
- */
-void workspace_set_name(Workspace *ws, const char *name) {
-        char *label;
-        int ret;
-
-        if (name != NULL)
-                ret = asprintf(&label, "%d: %s", ws->num + 1, name);
-        else ret = asprintf(&label, "%d", ws->num + 1);
-
-        if (ret == -1)
-                errx(1, "asprintf() failed");
 
-        FREE(ws->name);
-        FREE(ws->utf8_name);
+        con_attach(workspace, content, false);
 
-        ws->name = convert_utf8_to_ucs2(label, &(ws->name_len));
-        if (config.font != NULL)
-                ws->text_width = predict_text_width(global_conn, config.font, ws->name, ws->name_len);
-        else ws->text_width = 0;
-        ws->utf8_name = label;
+        ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
+        if (created != NULL)
+            *created = true;
+    }
+    else if (created != NULL) {
+        *created = false;
+    }
+
+    return workspace;
 }
 
 /*
@@ -102,378 +91,276 @@ void workspace_set_name(Workspace *ws, const char *name) {
  * workspaces.
  *
  */
-bool workspace_is_visible(Workspace *ws) {
-        return (ws->output != NULL && ws->output->current_workspace == ws);
+bool workspace_is_visible(Con *ws) {
+    Con *output = con_get_output(ws);
+    if (output == NULL)
+        return false;
+    Con *fs = con_get_fullscreen_con(output, CF_OUTPUT);
+    LOG("workspace visible? fs = %p, ws = %p\n", fs, ws);
+    return (fs == ws);
 }
 
 /*
- * Switches to the given workspace
+ * XXX: we need to clean up all this recursive walking code.
  *
  */
-void workspace_show(xcb_connection_t *conn, int workspace) {
-        bool need_warp = false;
-        /* t_ws (to workspace) is just a convenience pointer to the workspace we’re switching to */
-        Workspace *t_ws = workspace_get(workspace-1);
-
-        DLOG("show_workspace(%d)\n", workspace);
-
-        /* Store current_row/current_col */
-        c_ws->current_row = current_row;
-        c_ws->current_col = current_col;
-
-        /* Check if the workspace has not been used yet */
-        workspace_initialize(t_ws, c_ws->output, false);
-
-        if (c_ws->output != t_ws->output) {
-                /* We need to switch to the other output first */
-                DLOG("moving over to other output.\n");
-
-                /* Store the old client */
-                Client *old_client = CUR_CELL->currently_focused;
-
-                c_ws = t_ws->output->current_workspace;
-                current_col = c_ws->current_col;
-                current_row = c_ws->current_row;
-                if (CUR_CELL->currently_focused != NULL)
-                        need_warp = true;
-                else {
-                        Rect *dims = &(c_ws->output->rect);
-                        xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0,
-                                         dims->x + (dims->width / 2), dims->y + (dims->height / 2));
-                }
-
-                /* Re-decorate the old client, it’s not focused anymore */
-                if ((old_client != NULL) && !old_client->dock)
-                        redecorate_window(conn, old_client);
-                else xcb_flush(conn);
-
-                /* We need to check if a global fullscreen-client is blocking
-                 * the t_ws and if necessary switch that to local fullscreen */
-                Client* client = c_ws->fullscreen_client;
-                if (client != NULL && client->workspace != c_ws) {
-                        if (c_ws->fullscreen_client->workspace != c_ws)
-                                c_ws->fullscreen_client = NULL;
-                        client_enter_fullscreen(conn, client, false);
-                }
-        }
-
-        /* Check if we need to change something or if we’re already there */
-        if (c_ws->output->current_workspace->num == (workspace-1)) {
-                Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
-                if (last_focused != SLIST_END(&(c_ws->focus_stack)))
-                        set_focus(conn, last_focused, true);
-                if (need_warp) {
-                        client_warp_pointer_into(conn, last_focused);
-                        xcb_flush(conn);
-                }
-
-                ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
-
-                return;
-        }
-
-        Workspace *old_workspace = c_ws;
-        c_ws = t_ws->output->current_workspace = workspace_get(workspace-1);
-
-        current_row = c_ws->current_row;
-        current_col = c_ws->current_col;
-        DLOG("new current row = %d, current col = %d\n", current_row, current_col);
-
-        /* Map new clients before unmapping old clients to prevent wallpaper flickering */
-        workspace_map_clients(conn, c_ws);
-
-        /* Unmap all clients of the old workspace */
-        workspace_unmap_clients(conn, old_workspace);
-
-        ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
-
-        /* POTENTIAL TO IMPROVE HERE: due to the call to _map_clients first and
-         * render_layout afterwards, there is a short flickering on the source
-         * workspace (assign ws 3 to output 0, ws 4 to output 1, create single
-         * client on ws 4, move it to ws 3, switch to ws 3, you’ll see the
-         * flickering). */
-
-        /* Restore focus on the new workspace */
-        Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
-        if (last_focused != SLIST_END(&(c_ws->focus_stack)))
-                set_focus(conn, last_focused, true);
-        else xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
-
-        render_layout(conn);
-
-        /* We can warp the pointer only after the window has been
-         * reconfigured in render_layout, otherwise the pointer will
-         * be warped to the old position, which will not work when we
-         * moved it to another output. */
-        if (last_focused != SLIST_END(&(c_ws->focus_stack)) && need_warp) {
-                client_warp_pointer_into(conn, last_focused);
-                xcb_flush(conn);
-        }
+Con *_get_sticky(Con *con, const char *sticky_group, Con *exclude) {
+    Con *current;
+
+    TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
+        if (current != exclude &&
+            current->sticky_group != NULL &&
+            current->window != NULL &&
+            strcmp(current->sticky_group, sticky_group) == 0)
+            return current;
+
+        Con *recurse = _get_sticky(current, sticky_group, exclude);
+        if (recurse != NULL)
+            return recurse;
+    }
+
+    TAILQ_FOREACH(current, &(con->floating_head), floating_windows) {
+        if (current != exclude &&
+            current->sticky_group != NULL &&
+            current->window != NULL &&
+            strcmp(current->sticky_group, sticky_group) == 0)
+            return current;
+
+        Con *recurse = _get_sticky(current, sticky_group, exclude);
+        if (recurse != NULL)
+            return recurse;
+    }
+
+    return NULL;
 }
 
 /*
- * Assigns the given workspace to the given output by correctly updating its
- * state and reconfiguring all the clients on this workspace.
+ * Reassigns all child windows in sticky containers. Called when the user
+ * changes workspaces.
  *
- * This is called when initializing a output and when re-assigning it to a
- * different output which just got available (if you configured it to be on
- * output 1 and you just plugged in output 1).
+ * XXX: what about sticky containers which contain containers?
  *
  */
-void workspace_assign_to(Workspace *ws, Output *output, bool hide_it) {
-        Client *client;
-        bool empty = true;
-        bool visible = workspace_is_visible(ws);
-
-        ws->output = output;
+static void workspace_reassign_sticky(Con *con) {
+    Con *current;
+    /* 1: go through all containers */
+
+    /* handle all children and floating windows of this node */
+    TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
+        if (current->sticky_group == NULL) {
+            workspace_reassign_sticky(current);
+            continue;
+        }
 
-        /* Copy the dimensions from the virtual output */
-        memcpy(&(ws->rect), &(ws->output->rect), sizeof(Rect));
+        LOG("Ah, this one is sticky: %s / %p\n", current->name, current);
+        /* 2: find a window which we can re-assign */
+        Con *output = con_get_output(current);
+        Con *src = _get_sticky(output, current->sticky_group, current);
 
-        ewmh_update_workarea();
-
-        /* Force reconfiguration for each client on that workspace */
-        SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) {
-                client->force_reconfigure = true;
-                empty = false;
+        if (src == NULL) {
+            LOG("No window found for this sticky group\n");
+            workspace_reassign_sticky(current);
+            continue;
         }
 
-        if (empty)
-                return;
-
-        /* Render the workspace to reconfigure the clients. However, they will be visible now, so… */
-        render_workspace(global_conn, output, ws);
+        x_move_win(src, current);
+        current->window = src->window;
+        current->mapped = true;
+        src->window = NULL;
+        src->mapped = false;
 
-        /* …unless we want to see them at the moment, we should hide that workspace */
-        if (visible && !hide_it)
-                return;
+        x_reparent_child(current, src);
 
-        /* however, if this is the current workspace, we only need to adjust
-         * the output’s current_workspace pointer (and must not unmap the
-         * windows) */
-        if (c_ws == ws) {
-                DLOG("Need to adjust output->current_workspace...\n");
-                output->current_workspace = c_ws;
-                ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
-                return;
-        }
+        LOG("re-assigned window from src %p to dest %p\n", src, current);
+    }
 
-        workspace_unmap_clients(global_conn, ws);
+    TAILQ_FOREACH(current, &(con->floating_head), floating_windows)
+        workspace_reassign_sticky(current);
 }
 
 /*
- * Initializes the given workspace if it is not already initialized. The given
- * screen is to be understood as a fallback, if the workspace itself either
- * was not assigned to a particular screen or cannot be placed there because
- * the screen is not attached at the moment.
+ * Switches to the given workspace
  *
  */
-void workspace_initialize(Workspace *ws, Output *output, bool recheck) {
-        Output *old_output;
-
-        if (ws->output != NULL && !recheck) {
-                DLOG("Workspace already initialized\n");
-                return;
+void workspace_show(const char *num) {
+    Con *workspace, *current, *old = NULL;
+
+    bool changed_num_workspaces;
+    workspace = workspace_get(num, &changed_num_workspaces);
+
+    /* disable fullscreen for the other workspaces and get the workspace we are
+     * currently on. */
+    TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) {
+        if (current->fullscreen_mode == CF_OUTPUT)
+            old = current;
+        current->fullscreen_mode = CF_NONE;
+    }
+
+    /* enable fullscreen for the target workspace. If it happens to be the
+     * same one we are currently on anyways, we can stop here. */
+    workspace->fullscreen_mode = CF_OUTPUT;
+    if (workspace == con_get_workspace(focused)) {
+        DLOG("Not switching, already there.\n");
+        return;
+    }
+
+    workspace_reassign_sticky(workspace);
+
+    LOG("switching to %p\n", workspace);
+    Con *next = con_descend_focused(workspace);
+
+    if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) {
+        /* check if this workspace is currently visible */
+        if (!workspace_is_visible(old)) {
+            LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name);
+            tree_close(old, DONT_KILL_WINDOW, false);
+            ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
+            changed_num_workspaces = true;
         }
+    }
 
-        old_output = ws->output;
+    con_focus(next);
+    workspace->fullscreen_mode = CF_OUTPUT;
+    LOG("focused now = %p / %s\n", focused, focused->name);
 
-        /* If this workspace has no preferred output or if the output it wants
-         * to be on is not available at the moment, we initialize it with
-         * the output which was given */
-        if (ws->preferred_output == NULL ||
-            (ws->output = get_output_by_name(ws->preferred_output)) == NULL)
-                ws->output = output;
-
-        DLOG("old_output = %p, ws->output = %p\n", old_output, ws->output);
-        /* If the assignment did not change, we do not need to update anything */
-        if (old_output != NULL && ws->output == old_output)
-                return;
-
-        workspace_assign_to(ws, ws->output, false);
+    /* Update the EWMH hints */
+    if (changed_num_workspaces)
+        ewmh_update_workarea();
+    ewmh_update_current_desktop();
 
-        ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
+    ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
 }
 
 /*
- * Gets the first unused workspace for the given screen, taking into account
- * the preferred_output setting of every workspace (workspace assignments).
+ * Focuses the next workspace.
  *
  */
-Workspace *get_first_workspace_for_output(Output *output) {
-        Workspace *result = NULL;
-
-        Workspace *ws;
-        TAILQ_FOREACH(ws, workspaces, workspaces) {
-                if (ws->preferred_output == NULL ||
-                    get_output_by_name(ws->preferred_output) != output)
-                        continue;
+void workspace_next() {
+    Con *ws = con_get_workspace(focused);
+    Con *next = TAILQ_NEXT(ws, nodes);
+    if (!next)
+        next = TAILQ_FIRST(&(ws->parent->nodes_head));
 
-                result = ws;
-                break;
-        }
-
-        if (result == NULL) {
-                /* No assignment found, returning first unused workspace */
-                TAILQ_FOREACH(ws, workspaces, workspaces) {
-                        if (ws->output != NULL)
-                                continue;
-
-                        result = ws;
-                        break;
-                }
-        }
-
-        if (result == NULL) {
-                DLOG("No existing free workspace found to assign, creating a new one\n");
-
-                int last_ws = 0;
-                TAILQ_FOREACH(ws, workspaces, workspaces)
-                        last_ws = ws->num;
-                result = workspace_get(last_ws + 1);
-        }
-
-        workspace_initialize(result, output, false);
-        return result;
+    workspace_show(next->name);
 }
 
 /*
- * Maps all clients (and stack windows) of the given workspace.
+ * Focuses the previous workspace.
  *
  */
-void workspace_map_clients(xcb_connection_t *conn, Workspace *ws) {
-        Client *client;
+void workspace_prev() {
+    Con *ws = con_get_workspace(focused);
+    Con *prev = TAILQ_PREV(ws, nodes_head, nodes);
+    if (!prev)
+        prev = TAILQ_LAST(&(ws->parent->nodes_head), nodes_head);
 
-        ignore_enter_notify_forall(conn, ws, true);
-
-        /* Map all clients on the new workspace */
-        FOR_TABLE(ws)
-                CIRCLEQ_FOREACH(client, &(ws->table[cols][rows]->clients), clients)
-                        client_map(conn, client);
+    workspace_show(prev->name);
+}
 
-        /* Map all floating clients */
-        if (!ws->floating_hidden)
-                TAILQ_FOREACH(client, &(ws->floating_clients), floating_clients)
-                        client_map(conn, client);
+static bool get_urgency_flag(Con *con) {
+    Con *child;
+    TAILQ_FOREACH(child, &(con->nodes_head), nodes)
+        if (child->urgent || get_urgency_flag(child))
+            return true;
 
-        /* Map all stack windows, if any */
-        struct Stack_Window *stack_win;
-        SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
-                if (stack_win->container->workspace == ws && stack_win->rect.height > 0)
-                        xcb_map_window(conn, stack_win->window);
+    TAILQ_FOREACH(child, &(con->floating_head), floating_windows)
+        if (child->urgent || get_urgency_flag(child))
+            return true;
 
-        ignore_enter_notify_forall(conn, ws, false);
+    return false;
 }
 
 /*
- * Unmaps all clients (and stack windows) of the given workspace.
- *
- * This needs to be called separately when temporarily rendering
- * a workspace which is not the active workspace to force
- * reconfiguration of all clients, like in src/xinerama.c when
- * re-assigning a workspace to another screen.
+ * Goes through all clients on the given workspace and updates the workspace’s
+ * urgent flag accordingly.
  *
  */
-void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) {
-        Client *client;
-        struct Stack_Window *stack_win;
-
-        /* Ignore notify events because they would cause focus to be changed */
-        ignore_enter_notify_forall(conn, u_ws, true);
-
-        /* Unmap all clients of the given workspace */
-        int unmapped_clients = 0;
-        FOR_TABLE(u_ws)
-                CIRCLEQ_FOREACH(client, &(u_ws->table[cols][rows]->clients), clients) {
-                        DLOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child);
-                        client_unmap(conn, client);
-                        unmapped_clients++;
-                }
-
-        /* To find floating clients, we traverse the focus stack */
-        SLIST_FOREACH(client, &(u_ws->focus_stack), focus_clients) {
-                if (!client_is_floating(client))
-                        continue;
-
-                DLOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child);
-
-                client_unmap(conn, client);
-                unmapped_clients++;
-        }
-
-        /* If we did not unmap any clients, the workspace is empty and we can destroy it, at least
-         * if it is not the current workspace. */
-        if (unmapped_clients == 0 && u_ws != c_ws) {
-                /* Re-assign the workspace of all dock clients which use this workspace */
-                Client *dock;
-                DLOG("workspace %p is empty\n", u_ws);
-                SLIST_FOREACH(dock, &(u_ws->output->dock_clients), dock_clients) {
-                        if (dock->workspace != u_ws)
-                                continue;
-
-                        DLOG("Re-assigning dock client to c_ws (%p)\n", c_ws);
-                        dock->workspace = c_ws;
-                }
-                u_ws->output = NULL;
-        }
-
-        /* Unmap the stack windows on the given workspace, if any */
-        SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
-                if (stack_win->container->workspace == u_ws)
-                        xcb_unmap_window(conn, stack_win->window);
+void workspace_update_urgent_flag(Con *ws) {
+    bool old_flag = ws->urgent;
+    ws->urgent = get_urgency_flag(ws);
+    DLOG("Workspace urgency flag changed from %d to %d\n", old_flag, ws->urgent);
 
-        ignore_enter_notify_forall(conn, u_ws, false);
+    if (old_flag != ws->urgent)
+        ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}");
 }
 
 /*
- * Goes through all clients on the given workspace and updates the workspace’s
- * urgent flag accordingly.
+ * 'Forces' workspace orientation by moving all cons into a new split-con with
+ * the same orientation as the workspace and then changing the workspace
+ * orientation.
  *
  */
-void workspace_update_urgent_flag(Workspace *ws) {
-        Client *current;
-        bool old_flag = ws->urgent;
-        bool urgent = false;
+void ws_force_orientation(Con *ws, orientation_t orientation) {
+    /* 1: create a new split container */
+    Con *split = con_new(NULL, NULL);
+    split->parent = ws;
 
-        SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) {
-                if (!current->urgent)
-                        continue;
+    /* 2: copy layout and orientation from workspace */
+    split->layout = ws->layout;
+    split->orientation = ws->orientation;
 
-                urgent = true;
-                break;
-        }
+    Con *old_focused = TAILQ_FIRST(&(ws->focus_head));
 
-        ws->urgent = urgent;
+    /* 3: move the existing cons of this workspace below the new con */
+    DLOG("Moving cons\n");
+    while (!TAILQ_EMPTY(&(ws->nodes_head))) {
+        Con *child = TAILQ_FIRST(&(ws->nodes_head));
+        con_detach(child);
+        con_attach(child, split, true);
+    }
 
-        if (old_flag != urgent)
-                ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}");
-}
+    /* 4: switch workspace orientation */
+    ws->orientation = orientation;
 
-/*
- * Returns the width of the workspace.
- *
- */
-int workspace_width(Workspace *ws) {
-        return ws->rect.width;
+    /* 5: attach the new split container to the workspace */
+    DLOG("Attaching new split to ws\n");
+    con_attach(split, ws, false);
+
+    /* 6: fix the percentages */
+    con_fix_percent(ws);
+
+    if (old_focused)
+        con_focus(old_focused);
 }
 
 /*
- * Returns the effective height of the workspace (without the internal bar and
- * without dock clients).
+ * Called when a new con (with a window, not an empty or split con) should be
+ * attached to the workspace (for example when managing a new window or when
+ * moving an existing window to the workspace level).
+ *
+ * Depending on the workspace_layout setting, this function either returns the
+ * workspace itself (default layout) or creates a new stacked/tabbed con and
+ * returns that.
  *
  */
-int workspace_height(Workspace *ws) {
-        int height = ws->rect.height;
-        i3Font *font = load_font(global_conn, config.font);
-
-        /* Reserve space for dock clients */
-        Client *client;
-        SLIST_FOREACH(client, &(ws->output->dock_clients), dock_clients)
-                height -= client->desired_height;
+Con *workspace_attach_to(Con *ws) {
+    DLOG("Attaching a window to workspace %p / %s\n", ws, ws->name);
 
-        /* Space for the internal bar */
-        if (!config.disable_workspace_bar)
-                height -= (font->height + 6);
-
-        return height;
+    if (config.default_layout == L_DEFAULT) {
+        DLOG("Default layout, just attaching it to the workspace itself.\n");
+        return ws;
+    }
+
+    DLOG("Non-default layout, creating a new split container\n");
+    /* 1: create a new split container */
+    Con *new = con_new(NULL, NULL);
+    new->parent = ws;
+
+    /* 2: set the requested layout on the split con */
+    new->layout = config.default_layout;
+
+    /* 3: While the layout is irrelevant in stacked/tabbed mode, it needs
+     * to be set. Otherwise, this con will not be interpreted as a split
+     * container. */
+    if (config.default_orientation == NO_ORIENTATION) {
+        new->orientation = (ws->rect.height > ws->rect.width) ? VERT : HORIZ;
+    } else {
+        new->orientation = config.default_orientation;
+    }
+
+    /* 4: attach the new split container to the workspace */
+    DLOG("Attaching new split %p to workspace %p\n", new, ws);
+    con_attach(new, ws, false);
+
+    return new;
 }
diff --git a/src/x.c b/src/x.c
new file mode 100644 (file)
index 0000000..e93e90e
--- /dev/null
+++ b/src/x.c
@@ -0,0 +1,901 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ */
+
+#include "all.h"
+
+/* Stores the X11 window ID of the currently focused window */
+xcb_window_t focused_id = XCB_NONE;
+
+/*
+ * Describes the X11 state we may modify (map state, position, window stack).
+ * There is one entry per container. The state represents the current situation
+ * as X11 sees it (with the exception of the order in the state_head CIRCLEQ,
+ * which represents the order that will be pushed to X11, while old_state_head
+ * represents the current order). It will be updated in x_push_changes().
+ *
+ */
+typedef struct con_state {
+    xcb_window_t id;
+    bool mapped;
+    bool unmap_now;
+    bool child_mapped;
+
+    /* For reparenting, we have a flag (need_reparent) and the X ID of the old
+     * frame this window was in. The latter is necessary because we need to
+     * ignore UnmapNotify events (by changing the window event mask). */
+    bool need_reparent;
+    xcb_window_t old_frame;
+
+    Rect rect;
+    Rect window_rect;
+
+    bool initial;
+
+    char *name;
+
+    CIRCLEQ_ENTRY(con_state) state;
+    CIRCLEQ_ENTRY(con_state) old_state;
+} con_state;
+
+CIRCLEQ_HEAD(state_head, con_state) state_head =
+    CIRCLEQ_HEAD_INITIALIZER(state_head);
+
+CIRCLEQ_HEAD(old_state_head, con_state) old_state_head =
+    CIRCLEQ_HEAD_INITIALIZER(old_state_head);
+
+/*
+ * Returns the container state for the given frame. This function always
+ * returns a container state (otherwise, there is a bug in the code and the
+ * container state of a container for which x_con_init() was not called was
+ * requested).
+ *
+ */
+static con_state *state_for_frame(xcb_window_t window) {
+    con_state *state;
+    CIRCLEQ_FOREACH(state, &state_head, state)
+        if (state->id == window)
+            return state;
+
+    /* TODO: better error handling? */
+    ELOG("No state found\n");
+    assert(false);
+    return NULL;
+}
+
+/*
+ * Initializes the X11 part for the given container. Called exactly once for
+ * every container from con_new().
+ *
+ */
+void x_con_init(Con *con) {
+    /* TODO: maybe create the window when rendering first? we could then even
+     * get the initial geometry right */
+
+    uint32_t mask = 0;
+    uint32_t values[2];
+
+    /* our own frames should not be managed */
+    mask |= XCB_CW_OVERRIDE_REDIRECT;
+    values[0] = 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;
+
+    Rect dims = { -15, -15, 10, 10 };
+    con->frame = create_window(conn, dims, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_POINTER, false, mask, values);
+
+    struct con_state *state = scalloc(sizeof(struct con_state));
+    state->id = con->frame;
+    state->mapped = false;
+    state->initial = true;
+    CIRCLEQ_INSERT_HEAD(&state_head, state, state);
+    CIRCLEQ_INSERT_HEAD(&old_state_head, state, old_state);
+    DLOG("adding new state for window id 0x%08x\n", state->id);
+}
+
+/*
+ * Re-initializes the associated X window state for this container. You have
+ * to call this when you assign a client to an empty container to ensure that
+ * its state gets updated correctly.
+ *
+ */
+void x_reinit(Con *con) {
+    struct con_state *state;
+
+    if ((state = state_for_frame(con->frame)) == NULL) {
+        ELOG("window state not found\n");
+        return;
+    }
+
+    DLOG("resetting state %p to initial\n", state);
+    state->initial = true;
+    state->child_mapped = false;
+    memset(&(state->window_rect), 0, sizeof(Rect));
+}
+
+/*
+ * Reparents the child window of the given container (necessary for sticky
+ * containers). The reparenting happens in the next call of x_push_changes().
+ *
+ */
+void x_reparent_child(Con *con, Con *old) {
+    struct con_state *state;
+    if ((state = state_for_frame(con->frame)) == NULL) {
+        ELOG("window state for con not found\n");
+        return;
+    }
+
+    state->need_reparent = true;
+    state->old_frame = old->frame;
+}
+
+/*
+ * Moves a child window from Container src to Container dest.
+ *
+ */
+void x_move_win(Con *src, Con *dest) {
+    struct con_state *state_src, *state_dest;
+
+    if ((state_src = state_for_frame(src->frame)) == NULL) {
+        ELOG("window state for src not found\n");
+        return;
+    }
+
+    if ((state_dest = state_for_frame(dest->frame)) == NULL) {
+        ELOG("window state for dest not found\n");
+        return;
+    }
+
+    Rect zero = { 0, 0, 0, 0 };
+    if (memcmp(&(state_dest->window_rect), &(zero), sizeof(Rect)) == 0) {
+        memcpy(&(state_dest->window_rect), &(state_src->window_rect), sizeof(Rect));
+        DLOG("COPYING RECT\n");
+    }
+}
+
+/*
+ * Kills the window decoration associated with the given container.
+ *
+ */
+void x_con_kill(Con *con) {
+    con_state *state;
+
+    xcb_destroy_window(conn, con->frame);
+    xcb_free_pixmap(conn, con->pixmap);
+    xcb_free_gc(conn, con->pm_gc);
+    state = state_for_frame(con->frame);
+    CIRCLEQ_REMOVE(&state_head, state, state);
+    CIRCLEQ_REMOVE(&old_state_head, state, old_state);
+    FREE(state->name);
+    free(state);
+
+    /* Invalidate focused_id to correctly focus new windows with the same ID */
+    focused_id = XCB_NONE;
+}
+
+/*
+ * Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
+ *
+ */
+bool window_supports_protocol(xcb_window_t window, xcb_atom_t atom) {
+    xcb_get_property_cookie_t cookie;
+    xcb_icccm_get_wm_protocols_reply_t protocols;
+    bool result = false;
+
+    cookie = xcb_icccm_get_wm_protocols(conn, window, A_WM_PROTOCOLS);
+    if (xcb_icccm_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1)
+        return false;
+
+    /* Check if the client’s protocols have the requested atom set */
+    for (uint32_t i = 0; i < protocols.atoms_len; i++)
+        if (protocols.atoms[i] == atom)
+            result = true;
+
+    xcb_icccm_get_wm_protocols_reply_wipe(&protocols);
+
+    return result;
+}
+
+/*
+ * Kills the given X11 window using WM_DELETE_WINDOW (if supported).
+ *
+ */
+void x_window_kill(xcb_window_t window, kill_window_t kill_window) {
+    /* if this window does not support WM_DELETE_WINDOW, we kill it the hard way */
+    if (!window_supports_protocol(window, A_WM_DELETE_WINDOW)) {
+        if (kill_window == KILL_WINDOW) {
+            LOG("Killing specific window 0x%08x\n", window);
+            xcb_destroy_window(conn, window);
+        } else {
+            LOG("Killing the X11 client which owns window 0x%08x\n", window);
+            xcb_kill_client(conn, window);
+        }
+        return;
+    }
+
+    /* Every X11 event is 32 bytes long. Therefore, XCB will copy 32 bytes.
+     * In order to properly initialize these bytes, we allocate 32 bytes even
+     * though we only need less for an xcb_configure_notify_event_t */
+    void *event = scalloc(32);
+    xcb_client_message_event_t *ev = event;
+
+    ev->response_type = XCB_CLIENT_MESSAGE;
+    ev->window = window;
+    ev->type = A_WM_PROTOCOLS;
+    ev->format = 32;
+    ev->data.data32[0] = A_WM_DELETE_WINDOW;
+    ev->data.data32[1] = XCB_CURRENT_TIME;
+
+    LOG("Sending WM_DELETE to the client\n");
+    xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char*)ev);
+    xcb_flush(conn);
+    free(event);
+}
+
+/*
+ * Draws the decoration of the given container onto its parent.
+ *
+ */
+void x_draw_decoration(Con *con) {
+    Con *parent = con->parent;
+    /* This code needs to run for:
+     *  • leaf containers
+     *  • non-leaf containers which are in a stacked/tabbed container
+     *
+     * It does not need to run for:
+     *  • floating containers (they don’t have a decoration)
+     */
+    if ((!con_is_leaf(con) &&
+         parent->layout != L_STACKED &&
+         parent->layout != L_TABBED) ||
+        con->type == CT_FLOATING_CON)
+        return;
+    DLOG("decoration should be rendered for con %p\n", con);
+
+    /* Skip containers whose height is 0 (for example empty dockareas) */
+    if (con->rect.height == 0) {
+        DLOG("height == 0, not rendering\n");
+        return;
+    }
+
+    /* Skip containers whose pixmap has not yet been created (can happen when
+     * decoration rendering happens recursively for a window for which
+     * x_push_node() was not yet called) */
+    if (con->pixmap == XCB_NONE) {
+        DLOG("pixmap not yet created, not rendering\n");
+        return;
+    }
+
+    /* 1: build deco_params and compare with cache */
+    struct deco_render_params *p = scalloc(sizeof(struct deco_render_params));
+
+    /* find out which colors to use */
+    if (con->urgent)
+        p->color = &config.client.urgent;
+    else if (con == focused)
+        p->color = &config.client.focused;
+    else if (con == TAILQ_FIRST(&(parent->focus_head)))
+        p->color = &config.client.focused_inactive;
+    else
+        p->color = &config.client.unfocused;
+
+    p->border_style = con_border_style(con);
+
+    Rect *r = &(con->rect);
+    Rect *w = &(con->window_rect);
+    p->con_rect = (struct width_height){ r->width, r->height };
+    p->con_window_rect = (struct width_height){ w->width, w->height };
+    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) &&
+        !parent->pixmap_recreated &&
+        !con->pixmap_recreated &&
+        memcmp(p, con->deco_render_params, sizeof(struct deco_render_params)) == 0) {
+        DLOG("CACHE HIT, copying existing pixmaps\n");
+        free(p);
+        goto copy_pixmaps;
+    }
+
+    DLOG("CACHE MISS\n");
+    Con *next = con;
+    while ((next = TAILQ_NEXT(next, nodes))) {
+        DLOG("Also invalidating cache of %p\n", next);
+        FREE(next->deco_render_params);
+    }
+
+    FREE(con->deco_render_params);
+    con->deco_render_params = p;
+
+    if (con->window != NULL && con->window->name_x_changed)
+        con->window->name_x_changed = false;
+
+    parent->pixmap_recreated = false;
+    con->pixmap_recreated = false;
+
+    /* 2: draw the client.background, but only for the parts around the client_rect */
+    if (con->window != NULL) {
+        xcb_rectangle_t background[] = {
+            /* top area */
+            { 0, 0, r->width, w->y },
+            /* bottom area */
+            { 0, (w->y + w->height), r->width, r->height - (w->y + w->height) },
+            /* left area */
+            { 0, 0, w->x, r->height },
+            /* right area */
+            { w->x + w->width, 0, r->width - (w->x + w->width), r->height }
+        };
+#if 0
+        for (int i = 0; i < 4; i++)
+            DLOG("rect is (%d, %d) with %d x %d\n",
+                    background[i].x,
+                    background[i].y,
+                    background[i].width,
+                    background[i].height
+                );
+#endif
+
+        xcb_change_gc_single(conn, con->pm_gc, XCB_GC_FOREGROUND, config.client.background);
+        xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, sizeof(background) / sizeof(xcb_rectangle_t), background);
+    }
+
+    /* 3: draw a rectangle in border color around the client */
+    if (p->border_style != BS_NONE && p->con_is_leaf) {
+        Rect br = con_border_style_rect(con);
+#if 0
+        DLOG("con->rect spans %d x %d\n", con->rect.width, con->rect.height);
+        DLOG("border_rect spans (%d, %d) with %d x %d\n", br.x, br.y, br.width, br.height);
+        DLOG("window_rect spans (%d, %d) with %d x %d\n", con->window_rect.x, con->window_rect.y, con->window_rect.width, con->window_rect.height);
+#endif
+
+        /* These rectangles represents the border around the child window
+         * (left, bottom and right part). We don’t just fill the whole
+         * rectangle because some childs are not freely resizable and we want
+         * their background color to "shine through". */
+        xcb_change_gc_single(conn, con->pm_gc, XCB_GC_FOREGROUND, p->color->background);
+        xcb_rectangle_t borders[] = {
+            { 0, 0, br.x, r->height },
+            { 0, r->height + br.height + br.y, r->width, r->height },
+            { r->width + br.width + br.x, 0, r->width, r->height }
+        };
+        xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 3, borders);
+        /* 1pixel border needs an additional line at the top */
+        if (p->border_style == BS_1PIXEL) {
+            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);
+        }
+    }
+
+    /* if this is a borderless/1pixel window, we don’t * need to render the
+     * decoration. */
+    if (p->border_style != BS_NORMAL) {
+        DLOG("border style not BS_NORMAL, aborting rendering of decoration\n");
+        goto copy_pixmaps;
+    }
+
+    /* 4: paint the bar */
+    xcb_change_gc_single(conn, parent->pm_gc, XCB_GC_FOREGROUND, p->color->background);
+    xcb_rectangle_t drect = { con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height };
+    xcb_poly_fill_rectangle(conn, parent->pixmap, parent->pm_gc, 1, &drect);
+
+    /* 5: draw two unconnected lines in border color */
+    xcb_change_gc_single(conn, parent->pm_gc, XCB_GC_FOREGROUND, p->color->border);
+    Rect *dr = &(con->deco_rect);
+    xcb_segment_t segments[] = {
+        { dr->x,             dr->y,
+          dr->x + dr->width, dr->y },
+
+        { dr->x,             dr->y + dr->height - 1,
+          dr->x + dr->width, dr->y + dr->height - 1 }
+    };
+    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;
+
+    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"
+        );
+
+        goto copy_pixmaps;
+    }
+
+    int indent_level = 0,
+        indent_mult = 0;
+    Con *il_parent = parent;
+    if (il_parent->layout != L_STACKED) {
+        while (1) {
+            DLOG("il_parent = %p, layout = %d\n", il_parent, il_parent->layout);
+            if (il_parent->layout == L_STACKED)
+                indent_level++;
+            if (il_parent->type == CT_WORKSPACE || il_parent->type == CT_DOCKAREA || il_parent->type == CT_OUTPUT)
+                break;
+            il_parent = il_parent->parent;
+            indent_mult++;
+        }
+    }
+    DLOG("indent_level = %d, indent_mult = %d\n", indent_level, indent_mult);
+    int indent_px = (indent_level * 5) * indent_mult;
+
+    if (win->uses_net_wm_name)
+        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
+        );
+
+copy_pixmaps:
+    xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height);
+}
+
+/*
+ * Recursively calls x_draw_decoration. This cannot be done in x_push_node
+ * because x_push_node uses focus order to recurse (see the comment above)
+ * while drawing the decoration needs to happen in the actual order.
+ *
+ */
+void x_deco_recurse(Con *con) {
+    Con *current;
+    bool leaf = TAILQ_EMPTY(&(con->nodes_head)) &&
+                TAILQ_EMPTY(&(con->floating_head));
+    con_state *state = state_for_frame(con->frame);
+
+    if (!leaf) {
+        TAILQ_FOREACH(current, &(con->nodes_head), nodes)
+            x_deco_recurse(current);
+
+        TAILQ_FOREACH(current, &(con->floating_head), floating_windows)
+            x_deco_recurse(current);
+
+        if (state->mapped)
+            xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height);
+    }
+
+    if ((con->type != CT_ROOT && con->type != CT_OUTPUT) &&
+        con->mapped)
+        x_draw_decoration(con);
+}
+
+/*
+ * This function pushes the properties of each node of the layout tree to
+ * X11 if they have changed (like the map state, position of the window, …).
+ * It recursively traverses all children of the given node.
+ *
+ */
+void x_push_node(Con *con) {
+    Con *current;
+    con_state *state;
+    Rect rect = con->rect;
+
+    //DLOG("Pushing changes for node %p / %s\n", con, con->name);
+    state = state_for_frame(con->frame);
+
+    if (state->name != NULL) {
+        DLOG("pushing name %s for con %p\n", state->name, con);
+
+        xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->frame,
+                            A_WM_NAME, A_STRING, 8, strlen(state->name), state->name);
+        FREE(state->name);
+    }
+
+    if (con->window == NULL) {
+        /* Calculate the height of all window decorations which will be drawn on to
+         * this frame. */
+        uint32_t max_y = 0, max_height = 0;
+        TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
+            Rect *dr = &(current->deco_rect);
+            if (dr->y >= max_y && dr->height >= max_height) {
+                max_y = dr->y;
+                max_height = dr->height;
+            }
+        }
+        rect.height = max_y + max_height;
+        if (rect.height == 0) {
+            DLOG("Unmapping container %p because it does not contain anything.\n", con);
+            con->mapped = false;
+        }
+    }
+
+    /* reparent the child window (when the window was moved due to a sticky
+     * container) */
+    if (state->need_reparent && con->window != NULL) {
+        DLOG("Reparenting child window\n");
+
+        /* Temporarily set the event masks to XCB_NONE so that we won’t get
+         * UnmapNotify events (otherwise the handler would close the container).
+         * These events are generated automatically when reparenting. */
+        uint32_t values[] = { XCB_NONE };
+        xcb_change_window_attributes(conn, state->old_frame, XCB_CW_EVENT_MASK, values);
+        xcb_change_window_attributes(conn, con->window->id, XCB_CW_EVENT_MASK, values);
+
+        xcb_reparent_window(conn, con->window->id, con->frame, 0, 0);
+
+        values[0] = FRAME_EVENT_MASK;
+        xcb_change_window_attributes(conn, state->old_frame, XCB_CW_EVENT_MASK, values);
+        values[0] = CHILD_EVENT_MASK;
+        xcb_change_window_attributes(conn, con->window->id, XCB_CW_EVENT_MASK, values);
+
+        state->old_frame = XCB_NONE;
+        state->need_reparent = false;
+
+        con->ignore_unmap++;
+        DLOG("ignore_unmap for reparenting of con %p (win 0x%08x) is now %d\n",
+                con, con->window->id, con->ignore_unmap);
+    }
+
+    bool fake_notify = false;
+    /* Set new position if rect changed (and if height > 0) */
+    if (memcmp(&(state->rect), &rect, sizeof(Rect)) != 0 &&
+        rect.height > 0) {
+        /* We first create the new pixmap, then render to it, set it as the
+         * background and only afterwards change the window size. This reduces
+         * flickering. */
+
+        /* As the pixmap only depends on the size and not on the position, it
+         * is enough to check if width/height have changed. Also, we don’t
+         * create a pixmap at all when the window is actually not visible
+         * (height == 0). */
+        if ((state->rect.width != rect.width ||
+            state->rect.height != rect.height)) {
+            DLOG("CACHE: creating new pixmap for con %p (old: %d x %d, new: %d x %d)\n",
+                    con, state->rect.width, state->rect.height,
+                    rect.width, rect.height);
+            if (con->pixmap == 0) {
+                con->pixmap = xcb_generate_id(conn);
+                con->pm_gc = xcb_generate_id(conn);
+            } else {
+                xcb_free_pixmap(conn, con->pixmap);
+                xcb_free_gc(conn, con->pm_gc);
+            }
+            xcb_create_pixmap(conn, root_depth, con->pixmap, con->frame, rect.width, rect.height);
+            /* For the graphics context, we disable GraphicsExposure events.
+             * Those will be sent when a CopyArea request cannot be fulfilled
+             * properly due to parts of the source being unmapped or otherwise
+             * unavailable. Since we always copy from pixmaps to windows, this
+             * is not a concern for us. */
+            uint32_t values[] = { 0 };
+            xcb_create_gc(conn, con->pm_gc, con->pixmap, XCB_GC_GRAPHICS_EXPOSURES, values);
+
+            con->pixmap_recreated = true;
+
+            /* Don’t render the decoration for windows inside a stack which are
+             * not visible right now */
+            if (!con->parent ||
+                con->parent->layout != L_STACKED ||
+                TAILQ_FIRST(&(con->parent->focus_head)) == con)
+                /* Render the decoration now to make the correct decoration visible
+                 * from the very first moment. Later calls will be cached, so this
+                 * doesn’t hurt performance. */
+                x_deco_recurse(con);
+        }
+
+        DLOG("setting rect (%d, %d, %d, %d)\n", rect.x, rect.y, rect.width, rect.height);
+        /* flush to ensure that the following commands are sent in a single
+         * buffer and will be processed directly afterwards (the contents of a
+         * window get lost when resizing it, therefore we want to provide it as
+         * fast as possible) */
+        xcb_flush(conn);
+        xcb_set_window_rect(conn, con->frame, rect);
+        if (con->pixmap != XCB_NONE)
+            xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height);
+        xcb_flush(conn);
+
+        memcpy(&(state->rect), &rect, sizeof(Rect));
+        fake_notify = true;
+    }
+
+    /* dito, but for child windows */
+    if (con->window != NULL &&
+        memcmp(&(state->window_rect), &(con->window_rect), sizeof(Rect)) != 0) {
+        DLOG("setting window rect (%d, %d, %d, %d)\n",
+            con->window_rect.x, con->window_rect.y, con->window_rect.width, con->window_rect.height);
+        xcb_set_window_rect(conn, con->window->id, con->window_rect);
+        memcpy(&(state->window_rect), &(con->window_rect), sizeof(Rect));
+        fake_notify = true;
+    }
+
+    /* Map if map state changed, also ensure that the child window
+     * is changed if we are mapped *and* in initial state (meaning the
+     * container was empty before, but now got a child). Unmaps are handled in
+     * x_push_node_unmaps(). */
+    if ((state->mapped != con->mapped || (con->mapped && state->initial)) &&
+        con->mapped) {
+        xcb_void_cookie_t cookie;
+
+        if (con->window != NULL) {
+            /* Set WM_STATE_NORMAL because GTK applications don’t want to
+             * drag & drop if we don’t. Also, xprop(1) needs it. */
+            long data[] = { XCB_ICCCM_WM_STATE_NORMAL, XCB_NONE };
+            xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id,
+                                A_WM_STATE, A_WM_STATE, 32, 2, data);
+        }
+
+        uint32_t values[1];
+        if (!state->child_mapped && con->window != NULL) {
+            cookie = xcb_map_window(conn, con->window->id);
+
+            /* We are interested in EnterNotifys as soon as the window is
+             * mapped */
+            values[0] = CHILD_EVENT_MASK;
+            xcb_change_window_attributes(conn, con->window->id, XCB_CW_EVENT_MASK, values);
+            DLOG("mapping child window (serial %d)\n", cookie.sequence);
+            state->child_mapped = true;
+        }
+
+        cookie = xcb_map_window(conn, con->frame);
+
+        values[0] = FRAME_EVENT_MASK;
+        xcb_change_window_attributes(conn, con->frame, XCB_CW_EVENT_MASK, values);
+
+        /* copy the pixmap contents to the frame window immediately after mapping */
+        if (con->pixmap != XCB_NONE)
+            xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height);
+        xcb_flush(conn);
+
+        DLOG("mapping container %08x (serial %d)\n", con->frame, cookie.sequence);
+        state->mapped = con->mapped;
+    }
+
+    state->unmap_now = (state->mapped != con->mapped) && !con->mapped;
+
+    if (fake_notify) {
+        DLOG("Sending fake configure notify\n");
+        fake_absolute_configure_notify(con);
+    }
+
+    /* Handle all children and floating windows of this node. We recurse
+     * in focus order to display the focused client in a stack first when
+     * switching workspaces (reduces flickering). */
+    TAILQ_FOREACH(current, &(con->focus_head), focused)
+        x_push_node(current);
+}
+
+/*
+ * Same idea as in x_push_node(), but this function only unmaps windows. It is
+ * necessary to split this up to handle new fullscreen clients properly: The
+ * new window needs to be mapped and focus needs to be set *before* the
+ * underlying windows are unmapped. Otherwise, focus will revert to the
+ * PointerRoot and will then be set to the new window, generating unnecessary
+ * FocusIn/FocusOut events.
+ *
+ */
+static void x_push_node_unmaps(Con *con) {
+    Con *current;
+    con_state *state;
+
+    //DLOG("Pushing changes (with unmaps) for node %p / %s\n", con, con->name);
+    state = state_for_frame(con->frame);
+
+    /* map/unmap if map state changed, also ensure that the child window
+     * is changed if we are mapped *and* in initial state (meaning the
+     * container was empty before, but now got a child) */
+    if (state->unmap_now) {
+        xcb_void_cookie_t cookie;
+        if (con->window != NULL) {
+            /* Set WM_STATE_WITHDRAWN, it seems like Java apps need it */
+            long data[] = { XCB_ICCCM_WM_STATE_WITHDRAWN, XCB_NONE };
+            xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id,
+                                A_WM_STATE, A_WM_STATE, 32, 2, data);
+        }
+
+        cookie = xcb_unmap_window(conn, con->frame);
+        DLOG("unmapping container (serial %d)\n", cookie.sequence);
+        /* we need to increase ignore_unmap for this container (if it
+         * contains a window) and for every window "under" this one which
+         * contains a window */
+        if (con->window != NULL) {
+            con->ignore_unmap++;
+            DLOG("ignore_unmap for con %p (frame 0x%08x) now %d\n", con, con->frame, con->ignore_unmap);
+        }
+        state->mapped = con->mapped;
+    }
+
+    /* handle all children and floating windows of this node */
+    TAILQ_FOREACH(current, &(con->nodes_head), nodes)
+        x_push_node_unmaps(current);
+
+    TAILQ_FOREACH(current, &(con->floating_head), floating_windows)
+        x_push_node_unmaps(current);
+}
+
+/*
+ * Pushes all changes (state of each node, see x_push_node() and the window
+ * stack) to X11.
+ *
+ * NOTE: We need to push the stack first so that the windows have the correct
+ * stacking order. This is relevant for workspace switching where we map the
+ * windows because mapping may generate EnterNotify events. When they are
+ * generated in the wrong order, this will cause focus problems when switching
+ * workspaces.
+ *
+ */
+void x_push_changes(Con *con) {
+    con_state *state;
+
+    DLOG("-- PUSHING WINDOW STACK --\n");
+    //DLOG("Disabling EnterNotify\n");
+    uint32_t values[1] = { XCB_NONE };
+    CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) {
+        if (state->mapped)
+            xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values);
+    }
+    //DLOG("Done, EnterNotify disabled\n");
+    bool order_changed = false;
+    /* X11 correctly represents the stack if we push it from bottom to top */
+    CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) {
+        //DLOG("stack: 0x%08x\n", state->id);
+        con_state *prev = CIRCLEQ_PREV(state, state);
+        con_state *old_prev = CIRCLEQ_PREV(state, old_state);
+        if (prev != old_prev)
+            order_changed = true;
+        if ((state->initial || order_changed) && prev != CIRCLEQ_END(&state_head)) {
+            DLOG("Stacking 0x%08x above 0x%08x\n", prev->id, state->id);
+            uint32_t mask = 0;
+            mask |= XCB_CONFIG_WINDOW_SIBLING;
+            mask |= XCB_CONFIG_WINDOW_STACK_MODE;
+            uint32_t values[] = {state->id, XCB_STACK_MODE_ABOVE};
+
+            xcb_configure_window(conn, prev->id, mask, values);
+        }
+        state->initial = false;
+    }
+    //DLOG("Re-enabling EnterNotify\n");
+    values[0] = FRAME_EVENT_MASK;
+    CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) {
+        if (state->mapped)
+            xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values);
+    }
+    //DLOG("Done, EnterNotify re-enabled\n");
+
+    DLOG("\n\n PUSHING CHANGES\n\n");
+    x_push_node(con);
+    x_deco_recurse(con);
+
+    xcb_window_t to_focus = focused->frame;
+    if (focused->window != NULL)
+        to_focus = focused->window->id;
+
+    DLOG("focused_id = 0x%08x, to_focus = 0x%08x\n", focused_id, to_focus);
+    if (focused_id != to_focus) {
+        if (!focused->mapped) {
+            DLOG("Not updating focus (to %p / %s), focused window is not mapped.\n", focused, focused->name);
+            /* Invalidate focused_id to correctly focus new windows with the same ID */
+            focused_id = XCB_NONE;
+        } else {
+            DLOG("Updating focus (focused: %p / %s)\n", focused, focused->name);
+            /* We remove XCB_EVENT_MASK_FOCUS_CHANGE from the event mask to get
+             * no focus change events for our own focus changes. We only want
+             * these generated by the clients. */
+            if (focused->window != NULL) {
+                values[0] = CHILD_EVENT_MASK & ~(XCB_EVENT_MASK_FOCUS_CHANGE);
+                xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values);
+            }
+            xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, to_focus, XCB_CURRENT_TIME);
+            if (focused->window != NULL) {
+                values[0] = CHILD_EVENT_MASK;
+                xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values);
+            }
+
+            if (focused->window != NULL &&
+                focused->window->needs_take_focus) {
+                send_take_focus(to_focus);
+            }
+
+            ewmh_update_active_window(to_focus);
+            focused_id = to_focus;
+        }
+    }
+
+    if (focused_id == XCB_NONE) {
+        DLOG("Still no window focused, better set focus to the root window\n");
+        xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
+        focused_id = root;
+    }
+
+    xcb_flush(conn);
+    DLOG("\n\n ENDING CHANGES\n\n");
+
+    /* Disable EnterWindow events for windows which will be unmapped in
+     * x_push_node_unmaps() now. Unmapping windows happens when switching
+     * workspaces. We want to avoid getting EnterNotifies during that phase
+     * because they would screw up our focus. One of these cases is having a
+     * stack with two windows. If the first window is focused and gets
+     * unmapped, the second one appears under the cursor and therefore gets an
+     * EnterNotify event. */
+    values[0] = FRAME_EVENT_MASK & ~XCB_EVENT_MASK_ENTER_WINDOW;
+    CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) {
+        if (!state->unmap_now)
+            continue;
+        xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values);
+    }
+
+    /* Push all pending unmaps */
+    x_push_node_unmaps(con);
+
+    /* save the current stack as old stack */
+    CIRCLEQ_FOREACH(state, &state_head, state) {
+        CIRCLEQ_REMOVE(&old_state_head, state, old_state);
+        CIRCLEQ_INSERT_TAIL(&old_state_head, state, old_state);
+    }
+    //CIRCLEQ_FOREACH(state, &old_state_head, old_state) {
+    //    DLOG("old stack: 0x%08x\n", state->id);
+    //}
+
+    xcb_flush(conn);
+}
+
+/*
+ * Raises the specified container in the internal stack of X windows. The
+ * next call to x_push_changes() will make the change visible in X11.
+ *
+ */
+void x_raise_con(Con *con) {
+    con_state *state;
+    state = state_for_frame(con->frame);
+    //DLOG("raising in new stack: %p / %s / %s / xid %08x\n", con, con->name, con->window ? con->window->name_json : "", state->id);
+
+    CIRCLEQ_REMOVE(&state_head, state, state);
+    CIRCLEQ_INSERT_HEAD(&state_head, state, state);
+}
+
+/*
+ * Sets the WM_NAME property (so, no UTF8, but used only for debugging anyways)
+ * of the given name. Used for properly tagging the windows for easily spotting
+ * i3 windows in xwininfo -root -all.
+ *
+ */
+void x_set_name(Con *con, const char *name) {
+    struct con_state *state;
+
+    if ((state = state_for_frame(con->frame)) == NULL) {
+        ELOG("window state not found\n");
+        return;
+    }
+
+    FREE(state->name);
+    state->name = sstrdup(name);
+}
+
+/*
+ * Sets up i3 specific atoms (I3_SOCKET_PATH and I3_CONFIG_PATH)
+ *
+ */
+void x_set_i3_atoms() {
+    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_SOCKET_PATH, A_UTF8_STRING, 8,
+                        (current_socketpath == NULL ? 0 : strlen(current_socketpath)),
+                        current_socketpath);
+    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_CONFIG_PATH, A_UTF8_STRING, 8,
+                        strlen(current_configpath), current_configpath);
+}
index 38b1d5a848fcc36f260f9deb049d6f697beb5a7f..2c194013b09172d2fc784c11dfc3362569e053fe 100644 (file)
--- a/src/xcb.c
+++ b/src/xcb.c
@@ -1,5 +1,5 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
  *
  * xcb.c: Helper functions for easier usage of XCB
  *
  */
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
 
-#include <xcb/xcb.h>
-#include <xcb/xcb_keysyms.h>
-
-#include "i3.h"
-#include "util.h"
-#include "xcb.h"
-#include "log.h"
+#include "all.h"
 
 TAILQ_HEAD(cached_fonts_head, Font) cached_fonts = TAILQ_HEAD_INITIALIZER(cached_fonts);
 unsigned int xcb_numlock_mask;
 
 /*
- * Loads a font for usage, getting its height. This function is used very often, so it
- * maintains a cache.
+ * Loads a font for usage, also getting its height. If fallback is true,
+ * i3 loads 'fixed' or '-misc-*' if the font cannot be found instead of
+ * exiting.
  *
  */
-i3Font *load_font(xcb_connection_t *conn, const char *pattern) {
-        /* Check if we got the font cached */
-        i3Font *font;
-        TAILQ_FOREACH(font, &cached_fonts, fonts)
-                if (strcmp(font->pattern, pattern) == 0)
-                        return font;
-
-        i3Font *new = smalloc(sizeof(i3Font));
-        xcb_void_cookie_t font_cookie;
-        xcb_list_fonts_with_info_cookie_t info_cookie;
-
-        /* Send all our requests first */
-        new->id = xcb_generate_id(conn);
-        font_cookie = xcb_open_font_checked(conn, new->id, strlen(pattern), pattern);
+i3Font load_font(const char *pattern, bool fallback) {
+    i3Font new;
+    xcb_void_cookie_t font_cookie;
+    xcb_list_fonts_with_info_cookie_t info_cookie;
+
+    /* Send all our requests first */
+    new.id = xcb_generate_id(conn);
+    font_cookie = xcb_open_font_checked(conn, new.id, strlen(pattern), pattern);
+    info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
+
+    /* Check for errors. If errors, fall back to default font. */
+    xcb_generic_error_t *error = xcb_request_check(conn, font_cookie);
+
+    /* If we fail to open font, fall back to 'fixed'. If opening 'fixed' fails fall back to '-misc-*' */
+    if (error != NULL) {
+        ELOG("Could not open font %s (X error %d). Reverting to backup font.\n", pattern, error->error_code);
+        pattern = "fixed";
+        font_cookie = xcb_open_font_checked(conn, new.id, strlen(pattern), pattern);
         info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
 
-        check_error(conn, font_cookie, "Could not open font");
+        /* Check if we managed to open 'fixed' */
+        xcb_generic_error_t *error = xcb_request_check(conn, font_cookie);
+
+        /* Fall back to '-misc-*' if opening 'fixed' fails. */
+        if (error != NULL) {
+            ELOG("Could not open fallback font '%s', trying with '-misc-*'\n",pattern);
+            pattern = "-misc-*";
+            font_cookie = xcb_open_font_checked(conn, new.id, strlen(pattern), pattern);
+            info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
 
-        /* Get information (height/name) for this font */
-        xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL);
-        exit_if_null(reply, "Could not load font \"%s\"\n", pattern);
+            check_error(conn, font_cookie, "Could open neither requested font nor fallback (fixed or -misc-*");
+        }
+    }
 
-        if (asprintf(&(new->name), "%.*s", xcb_list_fonts_with_info_name_length(reply),
-                                           xcb_list_fonts_with_info_name(reply)) == -1)
-                die("asprintf() failed\n");
-        new->pattern = sstrdup(pattern);
-        new->height = reply->font_ascent + reply->font_descent;
+    /* Get information (height/name) for this font */
+    xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL);
+    exit_if_null(reply, "Could not load font \"%s\"\n", pattern);
 
-        /* Insert into cache */
-        TAILQ_INSERT_TAIL(&cached_fonts, new, fonts);
+    new.height = reply->font_ascent + reply->font_descent;
 
-        return new;
+    free(reply);
+
+    return new;
 }
 
 /*
@@ -74,15 +76,15 @@ i3Font *load_font(xcb_connection_t *conn, const char *pattern) {
  * This has to be done by the caller.
  *
  */
-uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
-        char strgroups[3][3] = {{hex[1], hex[2], '\0'},
-                                {hex[3], hex[4], '\0'},
-                                {hex[5], hex[6], '\0'}};
-        uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
-                             (strtol(strgroups[1], NULL, 16)),
-                             (strtol(strgroups[2], NULL, 16))};
-
-        return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
+uint32_t get_colorpixel(char *hex) {
+    char strgroups[3][3] = {{hex[1], hex[2], '\0'},
+                            {hex[3], hex[4], '\0'},
+                            {hex[5], hex[6], '\0'}};
+    uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
+                         (strtol(strgroups[1], NULL, 16)),
+                         (strtol(strgroups[2], NULL, 16))};
+
+    return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
 }
 
 /*
@@ -90,39 +92,44 @@ uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
  * for errors.
  *
  */
-xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_class, int cursor,
-                           bool map, uint32_t mask, uint32_t *values) {
-        xcb_window_t result = xcb_generate_id(conn);
+xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_class,
+        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);
+
+    xcb_create_window(conn,
+            depth,
+            result, /* the window id */
+            root, /* parent == root */
+            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 */
+            mask,
+            values);
+
+    /* Set the cursor */
+    if (xcursor_supported) {
+        mask = XCB_CW_CURSOR;
+        values[0] = xcursor_get_cursor(cursor);
+        xcb_change_window_attributes(conn, result, mask, values);
+    } else {
         xcb_cursor_t cursor_id = 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);
-
-        xcb_create_window(conn,
-                          depth,
-                          result, /* the window id */
-                          root, /* parent == root */
-                          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 */
-                          mask,
-                          values);
-
-        /* Set the cursor */
-        i3Font *cursor_font = load_font(conn, "cursor");
-        xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id,
-                        (cursor == -1 ? XCB_CURSOR_LEFT_PTR : cursor),
-                        (cursor == -1 ? XCB_CURSOR_LEFT_PTR : cursor) + 1,
-                        0, 0, 0, 65535, 65535, 65535);
+        i3Font cursor_font = load_font("cursor", false);
+        int xcb_cursor = xcursor_get_xcb_cursor(cursor);
+        xcb_create_glyph_cursor(conn, cursor_id, cursor_font.id, cursor_font.id,
+                xcb_cursor, xcb_cursor + 1, 0, 0, 0, 65535, 65535, 65535);
         xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
         xcb_free_cursor(conn, cursor_id);
+    }
 
-        /* Map the window (= make it visible) */
-        if (map)
-                xcb_map_window(conn, result);
+    /* Map the window (= make it visible) */
+    if (map)
+        xcb_map_window(conn, result);
 
-        return result;
+    return result;
 }
 
 /*
@@ -130,7 +137,7 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl
  *
  */
 void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value) {
-        xcb_change_gc(conn, gc, mask, &value);
+    xcb_change_gc(conn, gc, mask, &value);
 }
 
 /*
@@ -139,9 +146,9 @@ void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t ma
  */
 void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc,
                    uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t to_x, uint32_t to_y) {
-        xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, colorpixel);
-        xcb_point_t points[] = {{x, y}, {to_x, to_y}};
-        xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, drawable, gc, 2, points);
+    xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, colorpixel);
+    xcb_point_t points[] = {{x, y}, {to_x, to_y}};
+    xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, drawable, gc, 2, points);
 }
 
 /*
@@ -150,9 +157,9 @@ void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext
  */
 void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc,
                    uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
-        xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, colorpixel);
-        xcb_rectangle_t rect = {x, y, width, height};
-        xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
+    xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, colorpixel);
+    xcb_rectangle_t rect = {x, y, width, height};
+    xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
 }
 
 /*
@@ -162,23 +169,29 @@ void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext
  *
  */
 void fake_configure_notify(xcb_connection_t *conn, Rect r, xcb_window_t window) {
-        xcb_configure_notify_event_t generated_event;
+    /* Every X11 event is 32 bytes long. Therefore, XCB will copy 32 bytes.
+     * In order to properly initialize these bytes, we allocate 32 bytes even
+     * though we only need less for an xcb_configure_notify_event_t */
+    void *event = scalloc(32);
+    xcb_configure_notify_event_t *generated_event = event;
 
-        generated_event.event = window;
-        generated_event.window = window;
-        generated_event.response_type = XCB_CONFIGURE_NOTIFY;
+    generated_event->event = window;
+    generated_event->window = window;
+    generated_event->response_type = XCB_CONFIGURE_NOTIFY;
 
-        generated_event.x = r.x;
-        generated_event.y = r.y;
-        generated_event.width = r.width;
-        generated_event.height = r.height;
+    generated_event->x = r.x;
+    generated_event->y = r.y;
+    generated_event->width = r.width;
+    generated_event->height = r.height;
 
-        generated_event.border_width = 0;
-        generated_event.above_sibling = XCB_NONE;
-        generated_event.override_redirect = false;
+    generated_event->border_width = 0;
+    generated_event->above_sibling = XCB_NONE;
+    generated_event->override_redirect = false;
 
-        xcb_send_event(conn, false, window, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)&generated_event);
-        xcb_flush(conn);
+    xcb_send_event(conn, false, window, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)generated_event);
+    xcb_flush(conn);
+
+    free(event);
 }
 
 /*
@@ -186,15 +199,42 @@ void fake_configure_notify(xcb_connection_t *conn, Rect r, xcb_window_t window)
  * window, not to the client’s frame) for the given client.
  *
  */
-void fake_absolute_configure_notify(xcb_connection_t *conn, Client *client) {
-        Rect absolute;
+void fake_absolute_configure_notify(Con *con) {
+    Rect absolute;
+    if (con->window == NULL)
+        return;
+
+    absolute.x = con->rect.x + con->window_rect.x;
+    absolute.y = con->rect.y + con->window_rect.y;
+    absolute.width = con->window_rect.width;
+    absolute.height = con->window_rect.height;
 
-        absolute.x = client->rect.x + client->child_rect.x;
-        absolute.y = client->rect.y + client->child_rect.y;
-        absolute.width = client->child_rect.width;
-        absolute.height = client->child_rect.height;
+    DLOG("fake rect = (%d, %d, %d, %d)\n", absolute.x, absolute.y, absolute.width, absolute.height);
 
-        fake_configure_notify(conn, absolute, client->child);
+    fake_configure_notify(conn, absolute, con->window->id);
+}
+
+/*
+ * Sends the WM_TAKE_FOCUS ClientMessage to the given window
+ *
+ */
+void send_take_focus(xcb_window_t window) {
+    /* Every X11 event is 32 bytes long. Therefore, XCB will copy 32 bytes.
+     * In order to properly initialize these bytes, we allocate 32 bytes even
+     * though we only need less for an xcb_configure_notify_event_t */
+    void *event = scalloc(32);
+    xcb_client_message_event_t *ev = event;
+
+    ev->response_type = XCB_CLIENT_MESSAGE;
+    ev->window = window;
+    ev->type = A_WM_PROTOCOLS;
+    ev->format = 32;
+    ev->data.data32[0] = A_WM_TAKE_FOCUS;
+    ev->data.data32[1] = XCB_CURRENT_TIME;
+
+    DLOG("Sending WM_TAKE_FOCUS to the client\n");
+    xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char*)ev);
+    free(event);
 }
 
 /*
@@ -202,53 +242,53 @@ void fake_absolute_configure_notify(xcb_connection_t *conn, Client *client) {
  *
  */
 void xcb_get_numlock_mask(xcb_connection_t *conn) {
-        xcb_key_symbols_t *keysyms;
-        xcb_get_modifier_mapping_cookie_t cookie;
-        xcb_get_modifier_mapping_reply_t *reply;
-        xcb_keycode_t *modmap;
-        int mask, i;
-        const int masks[8] = { XCB_MOD_MASK_SHIFT,
-                               XCB_MOD_MASK_LOCK,
-                               XCB_MOD_MASK_CONTROL,
-                               XCB_MOD_MASK_1,
-                               XCB_MOD_MASK_2,
-                               XCB_MOD_MASK_3,
-                               XCB_MOD_MASK_4,
-                               XCB_MOD_MASK_5 };
-
-        /* Request the modifier map */
-        cookie = xcb_get_modifier_mapping_unchecked(conn);
-
-        /* Get the keysymbols */
-        keysyms = xcb_key_symbols_alloc(conn);
-
-        if ((reply = xcb_get_modifier_mapping_reply(conn, cookie, NULL)) == NULL) {
-                xcb_key_symbols_free(keysyms);
-                return;
-        }
+    xcb_key_symbols_t *keysyms;
+    xcb_get_modifier_mapping_cookie_t cookie;
+    xcb_get_modifier_mapping_reply_t *reply;
+    xcb_keycode_t *modmap;
+    int mask, i;
+    const int masks[8] = { XCB_MOD_MASK_SHIFT,
+                           XCB_MOD_MASK_LOCK,
+                           XCB_MOD_MASK_CONTROL,
+                           XCB_MOD_MASK_1,
+                           XCB_MOD_MASK_2,
+                           XCB_MOD_MASK_3,
+                           XCB_MOD_MASK_4,
+                           XCB_MOD_MASK_5 };
+
+    /* Request the modifier map */
+    cookie = xcb_get_modifier_mapping(conn);
+
+    /* Get the keysymbols */
+    keysyms = xcb_key_symbols_alloc(conn);
+
+    if ((reply = xcb_get_modifier_mapping_reply(conn, cookie, NULL)) == NULL) {
+        xcb_key_symbols_free(keysyms);
+        return;
+    }
 
-        modmap = xcb_get_modifier_mapping_keycodes(reply);
+    modmap = xcb_get_modifier_mapping_keycodes(reply);
 
-        /* Get the keycode for numlock */
+    /* Get the keycode for numlock */
 #ifdef OLD_XCB_KEYSYMS_API
-        xcb_keycode_t numlock = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
+    xcb_keycode_t numlock = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
 #else
-        /* For now, we only use the first keysymbol. */
-        xcb_keycode_t *numlock_syms = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
-        if (numlock_syms == NULL)
-                return;
-        xcb_keycode_t numlock = *numlock_syms;
-        free(numlock_syms);
+    /* For now, we only use the first keysymbol. */
+    xcb_keycode_t *numlock_syms = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
+    if (numlock_syms == NULL)
+        return;
+    xcb_keycode_t numlock = *numlock_syms;
+    free(numlock_syms);
 #endif
 
-        /* Check all modifiers (Mod1-Mod5, Shift, Control, Lock) */
-        for (mask = 0; mask < 8; mask++)
-                for (i = 0; i < reply->keycodes_per_modifier; i++)
-                        if (modmap[(mask * reply->keycodes_per_modifier) + i] == numlock)
-                                xcb_numlock_mask = masks[mask];
+    /* Check all modifiers (Mod1-Mod5, Shift, Control, Lock) */
+    for (mask = 0; mask < 8; mask++)
+        for (i = 0; i < reply->keycodes_per_modifier; i++)
+            if (modmap[(mask * reply->keycodes_per_modifier) + i] == numlock)
+                xcb_numlock_mask = masks[mask];
 
-        xcb_key_symbols_free(keysyms);
-        free(reply);
+    xcb_key_symbols_free(keysyms);
+    free(reply);
 }
 
 /*
@@ -256,79 +296,68 @@ void xcb_get_numlock_mask(xcb_connection_t *conn) {
  *
  */
 void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) {
-        uint32_t values[] = { XCB_STACK_MODE_ABOVE };
-        xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_STACK_MODE, values);
+    uint32_t values[] = { XCB_STACK_MODE_ABOVE };
+    xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_STACK_MODE, values);
 }
 
 /*
- *
- * Prepares the given Cached_Pixmap for usage (checks whether the size of the
- * object this pixmap is related to (e.g. a window) has changed and re-creates
- * the pixmap if so).
+ * Query the width of the given text (16-bit characters, UCS) with given real
+ * length (amount of glyphs) using the given font.
  *
  */
-void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap) {
-        DLOG("preparing pixmap\n");
-
-        /* If the Rect did not change, the pixmap does not need to be recreated */
-        if (memcmp(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect)) == 0)
-                return;
-
-        memcpy(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect));
-
-        if (pixmap->id == 0 || pixmap->gc == 0) {
-                DLOG("Creating new pixmap...\n");
-                pixmap->id = xcb_generate_id(conn);
-                pixmap->gc = xcb_generate_id(conn);
-        } else {
-                DLOG("Re-creating this pixmap...\n");
-                xcb_free_gc(conn, pixmap->gc);
-                xcb_free_pixmap(conn, pixmap->id);
-        }
-
-        xcb_create_pixmap(conn, root_depth, pixmap->id,
-                          pixmap->referred_drawable, pixmap->rect.width, pixmap->rect.height);
-
-        xcb_create_gc(conn, pixmap->gc, pixmap->id, 0, 0);
+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;
 }
 
 /*
- * Query the width of the given text (16-bit characters, UCS) with given real
- * length (amount of glyphs) using the given font.
+ * Configures the given window to have the size/position specified by given rect
  *
  */
-int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *text, int length) {
-        i3Font *font = load_font(conn, font_pattern);
-
-        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, 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;
+void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r) {
+    xcb_void_cookie_t cookie;
+    cookie = xcb_configure_window(conn, window,
+                         XCB_CONFIG_WINDOW_X |
+                         XCB_CONFIG_WINDOW_Y |
+                         XCB_CONFIG_WINDOW_WIDTH |
+                         XCB_CONFIG_WINDOW_HEIGHT,
+                         &(r.x));
+    /* ignore events which are generated because we configured a window */
+    add_ignore_event(cookie.sequence, -1);
 }
 
 /*
- * Configures the given window to have the size/position specified by given rect
+ * Returns true if the given reply contains the given atom.
  *
  */
-void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r) {
-        xcb_configure_window(conn, window,
-                             XCB_CONFIG_WINDOW_X |
-                             XCB_CONFIG_WINDOW_Y |
-                             XCB_CONFIG_WINDOW_WIDTH |
-                             XCB_CONFIG_WINDOW_HEIGHT,
-                             &(r.x));
+bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom) {
+    if (prop == NULL || xcb_get_property_value_length(prop) == 0)
+        return false;
+
+    xcb_atom_t *atoms;
+    if ((atoms = xcb_get_property_value(prop)) == NULL)
+        return false;
+
+    for (int i = 0; i < xcb_get_property_value_length(prop) / (prop->format / 8); i++)
+        if (atoms[i] == atom)
+            return true;
+
+    return false;
+
 }
diff --git a/src/xcursor.c b/src/xcursor.c
new file mode 100644 (file)
index 0000000..69518c3
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ */
+#include <assert.h>
+#include <X11/Xcursor/Xcursor.h>
+#include <X11/cursorfont.h>
+
+#include "i3.h"
+#include "xcb.h"
+#include "xcursor.h"
+
+static Cursor cursors[XCURSOR_CURSOR_MAX];
+
+static const int xcb_cursors[XCURSOR_CURSOR_MAX] = {
+    XCB_CURSOR_LEFT_PTR,
+    XCB_CURSOR_SB_H_DOUBLE_ARROW,
+    XCB_CURSOR_SB_V_DOUBLE_ARROW
+};
+
+static Cursor load_cursor(const char *name) {
+    Cursor c = XcursorLibraryLoadCursor(xlibdpy, name);
+    if (c == None)
+        xcursor_supported = false;
+    return c;
+}
+
+void xcursor_load_cursors() {
+    cursors[XCURSOR_CURSOR_POINTER] = load_cursor("left_ptr");
+    cursors[XCURSOR_CURSOR_RESIZE_HORIZONTAL] = load_cursor("sb_h_double_arrow");
+    cursors[XCURSOR_CURSOR_RESIZE_VERTICAL] = load_cursor("sb_v_double_arrow");
+}
+
+/*
+ * Sets the cursor of the root window to the 'pointer' cursor.
+ *
+ * This function is called when i3 is initialized, because with some login
+ * managers, the root window will not have a cursor otherwise.
+ *
+ * We have a separate xcursor function to use the same X11 connection as the
+ * xcursor_load_cursors() function. If we mix the Xlib and the XCB connection,
+ * races might occur (even though we flush the Xlib connection).
+ *
+ */
+void xcursor_set_root_cursor() {
+    XSetWindowAttributes attributes;
+    attributes.cursor = xcursor_get_cursor(XCURSOR_CURSOR_POINTER);
+    XChangeWindowAttributes(xlibdpy, DefaultRootWindow(xlibdpy), CWCursor, &attributes);
+    XFlush(xlibdpy);
+}
+
+Cursor xcursor_get_cursor(enum xcursor_cursor_t c) {
+    assert(c >= 0 && c < XCURSOR_CURSOR_MAX);
+    return cursors[c];
+}
+
+int xcursor_get_xcb_cursor(enum xcursor_cursor_t c) {
+    assert(c >= 0 && c < XCURSOR_CURSOR_MAX);
+    return xcb_cursors[c];
+}
index d7efff0d6c6199f096a1730bd477ef40b8780aa6..c116deaa954fb0bd709a1e5ec44a23c0f1b5e7a9 100644 (file)
@@ -1,9 +1,9 @@
 /*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
  *
- * © 2009-2010 Michael Stapelberg and contributors
+ * © 2009-2011 Michael Stapelberg and contributors
  *
  * See file LICENSE for license information.
  *
  * driver which does not support RandR in 2010 *sigh*.
  *
  */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
 
-#include <xcb/xcb.h>
 #include <xcb/xinerama.h>
 
-#include "queue.h"
-#include "data.h"
-#include "util.h"
-#include "xinerama.h"
-#include "workspace.h"
-#include "log.h"
-#include "randr.h"
+#include "all.h"
 
 static int num_screens;
 
@@ -34,12 +24,12 @@ static int num_screens;
  *
  */
 static Output *get_screen_at(int x, int y) {
-        Output *output;
-        TAILQ_FOREACH(output, &outputs, outputs)
-                if (output->rect.x == x && output->rect.y == y)
-                        return output;
+    Output *output;
+    TAILQ_FOREACH(output, &outputs, outputs)
+        if (output->rect.x == x && output->rect.y == y)
+            return output;
 
-        return NULL;
+    return NULL;
 }
 
 /*
@@ -48,52 +38,54 @@ static Output *get_screen_at(int x, int y) {
  *
  */
 static void query_screens(xcb_connection_t *conn) {
-        xcb_xinerama_query_screens_reply_t *reply;
-        xcb_xinerama_screen_info_t *screen_info;
+    xcb_xinerama_query_screens_reply_t *reply;
+    xcb_xinerama_screen_info_t *screen_info;
 
-        reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL);
-        if (!reply) {
-                ELOG("Couldn't get Xinerama screens\n");
-                return;
-        }
-        screen_info = xcb_xinerama_query_screens_screen_info(reply);
-        int screens = xcb_xinerama_query_screens_screen_info_length(reply);
-
-        for (int screen = 0; screen < screens; screen++) {
-                Output *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org);
-                if (s != NULL) {
-                        DLOG("Re-used old Xinerama screen %p\n", s);
-                        /* This screen already exists. We use the littlest screen so that the user
-                           can always see the complete workspace */
-                        s->rect.width = min(s->rect.width, screen_info[screen].width);
-                        s->rect.height = min(s->rect.height, screen_info[screen].height);
-                } else {
-                        s = scalloc(sizeof(Output));
-                        asprintf(&(s->name), "xinerama-%d", num_screens);
-                        DLOG("Created new Xinerama screen %s (%p)\n", s->name, s);
-                        s->active = true;
-                        s->rect.x = screen_info[screen].x_org;
-                        s->rect.y = screen_info[screen].y_org;
-                        s->rect.width = screen_info[screen].width;
-                        s->rect.height = screen_info[screen].height;
-                        /* We always treat the screen at 0x0 as the primary screen */
-                        if (s->rect.x == 0 && s->rect.y == 0)
-                                TAILQ_INSERT_HEAD(&outputs, s, outputs);
-                        else TAILQ_INSERT_TAIL(&outputs, s, outputs);
-                        num_screens++;
-                }
+    reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL);
+    if (!reply) {
+        ELOG("Couldn't get Xinerama screens\n");
+        return;
+    }
+    screen_info = xcb_xinerama_query_screens_screen_info(reply);
+    int screens = xcb_xinerama_query_screens_screen_info_length(reply);
 
-                DLOG("found Xinerama screen: %d x %d at %d x %d\n",
-                                screen_info[screen].width, screen_info[screen].height,
-                                screen_info[screen].x_org, screen_info[screen].y_org);
+    for (int screen = 0; screen < screens; screen++) {
+        Output *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org);
+        if (s != NULL) {
+            DLOG("Re-used old Xinerama screen %p\n", s);
+            /* This screen already exists. We use the littlest screen so that the user
+               can always see the complete workspace */
+            s->rect.width = min(s->rect.width, screen_info[screen].width);
+            s->rect.height = min(s->rect.height, screen_info[screen].height);
+        } else {
+            s = scalloc(sizeof(Output));
+            asprintf(&(s->name), "xinerama-%d", num_screens);
+            DLOG("Created new Xinerama screen %s (%p)\n", s->name, s);
+            s->active = true;
+            s->rect.x = screen_info[screen].x_org;
+            s->rect.y = screen_info[screen].y_org;
+            s->rect.width = screen_info[screen].width;
+            s->rect.height = screen_info[screen].height;
+            /* We always treat the screen at 0x0 as the primary screen */
+            if (s->rect.x == 0 && s->rect.y == 0)
+                    TAILQ_INSERT_HEAD(&outputs, s, outputs);
+            else TAILQ_INSERT_TAIL(&outputs, s, outputs);
+            output_init_con(s);
+            init_ws_for_output(s, output_get_content(s->con));
+            num_screens++;
         }
 
-        free(reply);
+        DLOG("found Xinerama screen: %d x %d at %d x %d\n",
+                        screen_info[screen].width, screen_info[screen].height,
+                        screen_info[screen].x_org, screen_info[screen].y_org);
+    }
 
-        if (num_screens == 0) {
-                ELOG("No screens found. Please fix your setup. i3 will exit now.\n");
-                exit(0);
-        }
+    free(reply);
+
+    if (num_screens == 0) {
+        ELOG("No screens found. Please fix your setup. i3 will exit now.\n");
+        exit(0);
+    }
 }
 
 /*
@@ -101,28 +93,30 @@ static void query_screens(xcb_connection_t *conn) {
  * information to setup workspaces for each screen.
  *
  */
-void initialize_xinerama(xcb_connection_t *conn) {
-        if (!xcb_get_extension_data(conn, &xcb_xinerama_id)->present) {
-                DLOG("Xinerama extension not found, disabling.\n");
-                disable_randr(conn);
-        } else {
-                xcb_xinerama_is_active_reply_t *reply;
-                reply = xcb_xinerama_is_active_reply(conn, xcb_xinerama_is_active(conn), NULL);
+void xinerama_init() {
+    if (!xcb_get_extension_data(conn, &xcb_xinerama_id)->present) {
+        DLOG("Xinerama extension not found, disabling.\n");
+        disable_randr(conn);
+    } else {
+        xcb_xinerama_is_active_reply_t *reply;
+        reply = xcb_xinerama_is_active_reply(conn, xcb_xinerama_is_active(conn), NULL);
 
-                if (reply == NULL || !reply->state) {
-                        DLOG("Xinerama is not active (in your X-Server), disabling.\n");
-                        disable_randr(conn);
-                } else
-                        query_screens(conn);
+        if (reply == NULL || !reply->state) {
+            DLOG("Xinerama is not active (in your X-Server), disabling.\n");
+            disable_randr(conn);
+        } else
+            query_screens(conn);
 
-                FREE(reply);
-        }
+        FREE(reply);
+    }
 
-        Output *output;
-        Workspace *ws;
-        /* Just go through each active output and associate one workspace */
-        TAILQ_FOREACH(output, &outputs, outputs) {
-                ws = get_first_workspace_for_output(output);
-                initialize_output(conn, output, ws);
-        }
+#if 0
+    Output *output;
+    Workspace *ws;
+    /* Just go through each active output and associate one workspace */
+    TAILQ_FOREACH(output, &outputs, outputs) {
+        ws = get_first_workspace_for_output(output);
+        initialize_output(conn, output, ws);
+    }
+#endif
 }
index d37450ab320bbbd445cfa00dd942bc9eed0ad239..edf5ee0d0322742b91f4637e2fc9857ccd8c2d5d 100644 (file)
@@ -1,4 +1,10 @@
 test:
-       PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" t/*.t
+       PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" -It/lib t/12*.t
+
+clean:
+       rm -rf testsuite-* latest
 
 all: test
+
+testfull:
+       PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" -It/lib t/01* t/02* t/03* t/05* t/17* t/18* t/19* t/20*
diff --git a/testcases/Xdummy b/testcases/Xdummy
new file mode 100755 (executable)
index 0000000..638a7b3
--- /dev/null
@@ -0,0 +1,1930 @@
+#!/bin/sh
+# ----------------------------------------------------------------------
+#    Copyright (C) 2005-2010 Karl J. Runge <runge@karlrunge.com> 
+#    All rights reserved.
+# 
+# This file is part of Xdummy.
+# 
+# Xdummy is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or (at
+# your option) any later version.
+# 
+# Xdummy is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with Xdummy; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA
+# or see <http://www.gnu.org/licenses/>.
+# ----------------------------------------------------------------------
+# 
+# 
+# Xdummy: an LD_PRELOAD hack to run a stock Xorg(1) or XFree86(1) server
+# with the "dummy" video driver to make it avoid Linux VT switching, etc.
+#
+# Run "Xdummy -help" for more info.
+#
+install=""
+uninstall=""
+runit=1
+prconf=""
+notweak=""
+root=""
+nosudo=""
+xserver=""
+geom=""
+nomodelines=""
+depth=""
+debug=""
+strace=""
+cmdline_config=""
+
+PATH=$PATH:/bin:/usr/bin
+export PATH
+
+program=`basename "$0"`
+
+help () {
+       ${PAGER:-more} << END
+$program:
+
+    A hack to run a stock Xorg(1) or XFree86(1) X server with the "dummy"
+    (RAM-only framebuffer) video driver such that it AVOIDS the Linux VT
+    switching, opening device files in /dev, keyboard and mouse conflicts,
+    and other problems associated with the normal use of "dummy".
+
+    In other words, it tries to make Xorg/XFree86 with the "dummy"
+    device driver act more like Xvfb(1).
+
+    The primary motivation for the Xdummy script is to provide a virtual X
+    server for x11vnc but with more features than Xvfb (or Xvnc); however
+    it could be used for other reasons (e.g. better automated testing
+    than with Xvfb.)  One nice thing is the dummy server supports RANDR
+    dynamic resizing while Xvfb does not.
+
+    So, for example, x11vnc+Xdummy terminal services are a little better
+    than x11vnc+Xvfb.
+
+    To achieve this, while running the real Xserver $program intercepts
+    system and library calls via the LD_PRELOAD method and modifies
+    the behavior to make it work correctly (e.g. avoid the VT stuff.)
+    LD_PRELOAD tricks are usually "clever hacks" and so might not work
+    in all situations or break when something changes.
+
+    WARNING: Take care in using Xdummy, although it never has it is
+    possible that it could damage hardware.  One can use the -prconf
+    option to have it print out the xorg.conf config that it would use
+    and then inspect it carefully before actually using it.
+
+    This program no longer needs to be run as root as of 12/2009.
+    However, if there are problems for certain situations (usually older
+    servers) it may perform better if run as root (use the -root option.)
+    When running as root remember the previous paragraph and that Xdummy
+    comes without any warranty.
+
+    gcc/cc and other build tools are required for this script to be able
+    to compile the LD_PRELOAD shared object.  Be sure they are installed
+    on the system.  See -install and -uninstall described below.
+
+    Your Linux distribution may not install the dummy driver by default,
+    e.g:
+
+        /usr/lib/xorg/modules/drivers/dummy_drv.so
+    
+    some have it in a package named xserver-xorg-video-dummy you that
+    need to install.
+
+Usage:
+
+       $program <${program}-args> <Xserver-args>
+
+       (actually, the arguments can be supplied in any order.)
+
+Examples:
+
+       $program -install
+
+       $program :1
+
+       $program -debug :1
+
+       $program -tmpdir ~/mytmp :1 -nolisten tcp
+
+startx example:
+
+       startx -e bash -- $program :2 -depth 16
+
+       (if startx needs to be run as root, you can su(1) to a normal
+       user in the bash shell and then launch ~/.xinitrc or ~/.xsession,
+       gnome-session, startkde, startxfce4, etc.)
+
+xdm example:
+
+       xdm -config /usr/local/dummy/xdm-config -nodaemon
+
+       where the xdm-config file has line:
+
+            DisplayManager.servers:         /usr/local/dummy/Xservers
+
+       and /usr/local/dummy/Xservers has lines:
+
+            :1 local /usr/local/dummy/Xdummy :1 -debug
+            :2 local /usr/local/dummy/Xdummy :2 -debug
+
+        (-debug is optional)
+
+gdm/kdm example:
+
+       TBD.
+
+Root permission and x11vnc:
+
+       Update: as of 12/2009 this program no longer must be run as root.
+       So try it as non-root before running it as root and/or the
+       following schemes.
+
+       In some circumstances X server program may need to be run as root.
+       If so, one could run x11vnc as root with -unixpw (it switches
+       to the user that logs in) and that may be OK, some other ideas:
+
+       - add this to sudo via visudo:
+
+               ALL ALL = NOPASSWD: /usr/local/bin/Xdummy
+
+       - use this little suid wrapper:
+/* 
+ * xdummy.c
+ *
+   cc -o ./xdummy xdummy.c
+   sudo cp ./xdummy /usr/local/bin/xdummy
+   sudo chown root:root /usr/local/bin/xdummy
+   sudo chmod u+s /usr/local/bin/xdummy
+ *
+ */
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <stdio.h>
+
+int main (int argc, char *argv[]) {
+       extern char **environ;
+       char str[100];
+       sprintf(str, "XDUMMY_UID=%d", (int) getuid());
+       putenv(str);
+       setuid(0);  
+       setgid(0);
+       execv("/usr/local/bin/Xdummy", argv); 
+       exit(1);
+       return 1;
+}
+
+
+Options:
+
+    ${program}-args:
+
+       -install        Compile the LD_PRELOAD shared object and install it
+                       next to the $program script file as:
+
+                         $0.so
+
+                       When that file exists it is used as the LD_PRELOAD
+                       shared object without recompiling.  Otherwise,
+                       each time $program is run the LD_PRELOAD shared
+                       object is compiled as a file in /tmp (or -tmpdir)
+
+                       If you set the environment variable
+                       INTERPOSE_GETUID=1 when building, then when
+                       $program is run as an ordinary user, the shared
+                       object will interpose getuid() calls and pretend
+                       to be root.  Otherwise it doesn't pretend to
+                       be root.
+
+                       You can also set the CFLAGS environment variable
+                       to anything else you want on the compile cmdline.
+
+       -uninstall      Remove the file:
+
+                         $0.so
+
+                       The LD_PRELOAD shared object will then be compiled
+                       each time this program is run.
+
+       The X server is not started under -install, -uninstall, or -prconf.
+
+
+       :N              The DISPLAY (e.g. :15) is often the first
+                       argument.  It is passed to the real X server and
+                       also used by the Xdummy script as an identifier.
+
+       -geom geom1[,geom2...]  Take the geometry (e.g. 1024x768) or list
+                       of geometries and insert them into the Screen
+                       section of the tweaked X server config file.
+                       Use this to have a different geometry than the
+                       one(s) in the system config file.
+
+                       The option -geometry can be used instead of -geom;
+                       x11vnc calls Xdummy and Xvfb this way.
+
+       -nomodelines    When you specify -geom/-geometry, $program will
+                       create Modelines for each geometry and put them
+                       in the Monitor section.  If you do not want this
+                       then supply -nomodelines.
+
+       -depth n        Use pixel color depth n (e.g. 8, 16, or 24). This
+                       makes sure the X config file has a Screen.Display
+                       subsection of this depth.  Note this option is
+                       ALSO passed to the X server.
+
+       -DEPTH n        Same as -depth, except not passed to X server.
+
+       -tmpdir dir     Specify a temporary directory, owned by you and
+                       only writable by you.  This is used in place of
+                       /tmp/Xdummy.\$USER/..  to place the $program.so
+                       shared object, tweaked config files, etc.
+
+       -nonroot        Run in non-root mode (working 12/2009, now default)
+
+       -root           Run as root (may still be needed in some
+                       environments.)  Same as XDUMMY_RUN_AS_ROOT=1.
+
+       -nosudo         Do not try to use sudo(1) when re-running as root,
+                       use su(1) instead.
+
+       -xserver path   Specify the path to the Xserver to use.  Default
+                       is to try "Xorg" first and then "XFree86".  If
+                       those are not in \$PATH, it tries these locations:
+                               /usr/bin/Xorg
+                               /usr/X11R6/bin/Xorg
+                               /usr/X11R6/bin/XFree86
+
+       -n              Do not run the command to start the X server,
+                       just show the command that $program would run.
+                       The LD_PRELOAD shared object will be built,
+                       if needed.  Also note any XDUMMY* environment
+                       variables that need to be set.
+
+       -prconf         Print, to stdout, the tweaked Xorg/XFree86
+                       config file (-config and -xf86config server
+                       options, respectively.)  The Xserver is not
+                       started.
+
+       -notweak        Do not tweak (modify) the Xorg/XFree86 config file
+                       (system or server command line) at all.  The -geom
+                       and similar config file modifications are ignored.
+
+                       It is up to you to make sure it is a working
+                       config file (e.g. "dummy" driver, etc.)
+                       Perhaps you want to use a file based on the
+                       -prconf output.
+
+       -debug          Extra debugging output.
+
+       -strace         strace(1) the Xserver process (for troubleshooting.)
+       -ltrace         ltrace(1) instead of strace (can be slow.)
+
+       -h, -help       Print out this help.
+
+
+    Xserver-args:
+
+       Most of the Xorg and XFree86 options will work and are simply
+       passed along if you supply them.  Important ones that may be
+       supplied if missing:
+
+       :N              X Display number for server to use.
+
+       vtNN            Linux virtual terminal (VT) to use (a VT is currently
+                       still used, just not switched to and from.)
+
+       -config file            Driver "dummy" tweaked config file, a
+       -xf86config file        number of settings are tweaked besides Driver.
+
+       If -config/-xf86config is not given, the system one
+       (e.g. /etc/X11/xorg.conf) is used.  If the system one cannot be
+       found, a built-in one is used.  Any settings in the config file
+       that are not consistent with "dummy" mode will be overwritten
+       (unless -notweak is specified.)
+
+       Use -config xdummy-builtin to force usage of the builtin config.
+
+       If "file" is only a basename (e.g. "xorg.dummy.conf") with no /'s,
+       then no tweaking of it is done: the X server will look for that
+       basename via its normal search algorithm.  If the found file does
+       not refer to the "dummy" driver, etc, then the X server will fail.
+
+Notes:
+
+    The Xorg/XFree86 "dummy" driver is currently undocumented.  It works
+    well in this mode, but it is evidently not intended for end-users.
+    So it could be removed or broken at any time.
+
+    If the display Xserver-arg (e.g. :1) is not given, or ":" is given
+    that indicates $program should try to find a free one (based on
+    tcp ports.)
+
+    If the display virtual terminal, VT, (e.g. vt9) is not given that
+    indicates $program should try to find a free one (or guess a high one.) 
+    
+    This program is not completely secure WRT files in /tmp (but it tries
+    to a good degree.)  Better is to use the -tmpdir option to supply a
+    directory only writable by you.  Even better is to get rid of users
+    on the local machine you do not trust :-)
+
+    Set XDUMMY_SET_XV=1 to turn on debugging output for this script.
+
+END
+}
+
+warn() {
+       echo "$*" 1>&2
+}
+
+if [ "X$XDUMMY_SET_XV" != "X" ]; then
+       set -xv
+fi
+
+if [ "X$XDUMMY_UID" = "X" ]; then
+       XDUMMY_UID=`id -u`
+       export XDUMMY_UID
+fi
+if [ "X$XDUMMY_UID" = "X0" ]; then
+       if [ "X$SUDO_UID" != "X" ]; then
+               XDUMMY_UID=$SUDO_UID
+               export XDUMMY_UID
+       fi
+fi
+
+# check if root=1 first:
+#
+if [ "X$XDUMMY_RUN_AS_ROOT" = "X1" ]; then
+       root=1
+fi
+for arg in $*
+do
+       if [ "X$arg" = "X-nonroot" ]; then
+               root=""
+       elif [ "X$arg" = "X-root" ]; then
+               root=1
+       fi
+done
+
+# See if it really needs to be run as root:
+#
+if [ "X$XDUMMY_SU_EXEC" = "X" -a "X$root" = "X1" -a "X`id -u`" != "X0"  ]; then
+       # this is to prevent infinite loop in case su/sudo doesn't work:
+       XDUMMY_SU_EXEC=1
+       export XDUMMY_SU_EXEC
+
+       dosu=1
+       nosudo=""
+
+       for arg in $*
+       do
+               if [ "X$arg" = "X-nonroot" ]; then
+                       dosu=""
+               elif [ "X$arg" = "X-nosudo" ]; then
+                       nosudo="1"
+               elif [ "X$arg" = "X-help" ]; then
+                       dosu=""
+               elif [ "X$arg" = "X-h" ]; then
+                       dosu=""
+               elif [ "X$arg" = "X-install" ]; then
+                       dosu=""
+               elif [ "X$arg" = "X-uninstall" ]; then
+                       dosu=""
+               elif [ "X$arg" = "X-n" ]; then
+                       dosu=""
+               elif [ "X$arg" = "X-prconf" ]; then
+                       dosu=""
+               fi
+       done
+       if [ $dosu ]; then
+               # we need to restart it with su/sudo:
+               if type sudo > /dev/null 2>&1; then
+                       :
+               else
+                       nosudo=1
+               fi
+               if [ "X$nosudo" = "X" ]; then
+                       warn "$program: supply the sudo password to restart as root:"
+                       if [ "X$XDUMMY_UID" != "X" ]; then
+                               exec sudo $0 -uid $XDUMMY_UID "$@"
+                       else
+                               exec sudo $0 "$@"
+                       fi
+               else
+                       warn "$program: supply the root password to restart as root:"
+                       if [ "X$XDUMMY_UID" != "X" ]; then
+                               exec su -c "$0 -uid $XDUMMY_UID $*"
+                       else
+                               exec su -c "$0 $*"
+                       fi
+               fi
+               # DONE:
+               exit
+       fi
+fi
+
+# This will hold the X display, e.g. :20
+#
+disp=""
+args=""
+cmdline_config=""
+
+# Process Xdummy args:
+#
+while [ "X$1" != "X" ]
+do
+    if [ "X$1" = "X-config" -o "X$1" = "X-xf86config" ]; then
+       cmdline_config="$2"
+    fi
+    case $1 in 
+       ":"*)   disp=$1
+                ;;
+       "-install") install=1; runit=""
+                ;;
+       "-uninstall") uninstall=1; runit=""
+                ;;
+       "-n")  runit=""
+                ;;
+       "-no") runit=""
+                ;;
+       "-norun") runit=""
+                ;;
+       "-prconf") prconf=1; runit=""
+                ;;
+       "-notweak") notweak=1
+                ;;
+       "-noconf")  notweak=1
+                ;;
+       "-nonroot") root=""
+                ;;
+       "-root")    root=1
+                ;;
+       "-nosudo") nosudo=1
+                ;;
+       "-xserver") xserver="$2"; shift
+                ;;
+       "-uid") XDUMMY_UID="$2"; shift
+               export XDUMMY_UID
+                ;;
+       "-geom")     geom="$2"; shift
+                ;;
+       "-geometry") geom="$2"; shift
+                ;;
+       "-nomodelines") nomodelines=1
+                ;;
+       "-depth") depth="$2"; args="$args -depth $2";
+                 shift
+                ;;
+       "-DEPTH") depth="$2"; shift
+                ;;
+       "-tmpdir") XDUMMY_TMPDIR="$2"; shift
+                ;;
+       "-debug")   debug=1
+                ;;
+       "-nodebug") debug=""
+                ;;
+       "-strace") strace=1
+                ;;
+       "-ltrace") strace=2
+                ;;
+       "-h")    help; exit 0
+                ;;
+       "-help") help; exit 0
+                ;;
+       *)      args="$args $1"
+                ;;
+    esac
+    shift
+done
+
+# Try to get a username for use in our tmp directory, etc.
+#
+user=""
+if [ X`id -u` = "X0"  ]; then
+       user=root       # this will also be used below for id=0
+elif [ "X$USER" != "X" ]; then
+       user=$USER
+elif [ "X$LOGNAME" != "X" ]; then
+       user=$LOGNAME
+fi
+
+# Keep trying...
+#
+if [ "X$user" = "X" ]; then
+       user=`whoami 2>/dev/null`
+fi
+if [ "X$user" = "X" ]; then
+       user=`basename "$HOME"`
+fi
+if [ "X$user" = "X" -o "X$user" = "X." ]; then
+       user="u$$"
+fi
+
+if [ "X$debug" = "X1" -a "X$runit" != "X" ]; then
+       echo ""
+       echo "/usr/bin/env:"
+       env | egrep -v '^(LS_COLORS|TERMCAP)' | sort
+       echo ""
+fi
+
+# Function to compile the LD_PRELOAD shared object:
+#
+make_so() {
+       # extract code embedded in this script into a tmp C file: 
+       n1=`grep -n '^#code_begin' $0 | head -1 | awk -F: '{print $1}'`
+       n2=`grep -n '^#code_end'   $0 | head -1 | awk -F: '{print $1}'`
+       n1=`expr $n1 + 1`
+       dn=`expr $n2 - $n1`
+
+       tmp=$tdir/Xdummy.$RANDOM$$.c
+       rm -f $tmp
+       if [ -e $tmp -o -h $tmp ]; then
+               warn "$tmp still exists."
+               exit 1
+       fi
+       touch $tmp || exit 1
+       tail -n +$n1 $0 | head -n $dn > $tmp
+
+       # compile it to Xdummy.so:
+       if [ -f "$SO" ]; then
+               mv $SO $SO.$$
+               rm -f $SO.$$
+       fi
+       rm -f $SO
+       touch $SO
+       if [ ! -f "$SO" ]; then
+               SO=$tdir/Xdummy.$user.so
+               warn "warning switching LD_PRELOAD shared object to: $SO"
+       fi
+
+       if [ -f "$SO" ]; then
+               mv $SO $SO.$$
+               rm -f $SO.$$
+       fi
+       rm -f $SO
+
+       # we assume gcc:
+       if [ "X$INTERPOSE_GETUID" = "X1" ]; then
+               CFLAGS="$CFLAGS -DINTERPOSE_GETUID"
+       fi
+       echo "$program:" cc -shared -fPIC $CFLAGS -o $SO $tmp
+                         cc -shared -fPIC $CFLAGS -o $SO $tmp
+       rc=$?
+       rm -f $tmp
+       if [ $rc != 0 ]; then
+               warn "$program: cannot build $SO"
+               exit 1
+       fi
+       if [ "X$debug" != "X" -o "X$install" != "X" ]; then
+               warn "$program: created  $SO"
+               ls -l "$SO"
+       fi
+}
+
+# Set tdir to tmp dir for make_so():
+if [ "X$XDUMMY_TMPDIR" != "X" ]; then
+       tdir=$XDUMMY_TMPDIR
+       mkdir -p $tdir
+else
+       tdir="/tmp"
+fi
+
+# Handle -install/-uninstall case:
+SO=$0.so
+if [ "X$install" != "X" -o "X$uninstall" != "X" ]; then
+       if [ -e "$SO" -o -h "$SO" ]; then
+               warn "$program: removing $SO"
+       fi
+       if [ -f "$SO" ]; then
+               mv $SO $SO.$$
+               rm -f $SO.$$
+       fi
+       rm -f $SO
+       if [ -e "$SO" -o -h "$SO" ]; then
+               warn "warning: $SO still exists."
+               exit 1
+       fi
+       if [ $install ]; then
+               make_so
+               if [ ! -f "$SO" ]; then
+                       exit 1
+               fi
+       fi
+       exit 0
+fi
+
+# We need a tmp directory for the .so, tweaked config file, and for
+# redirecting filenames we cannot create (under -nonroot)
+#
+tack=""
+if [ "X$XDUMMY_TMPDIR" = "X" ]; then
+       XDUMMY_TMPDIR="/tmp/Xdummy.$user"
+
+       # try to tack on a unique subdir (display number or pid)
+       # to allow multiple instances
+       #
+       if [ "X$disp" != "X" ]; then
+               t0=$disp
+       else
+               t0=$1
+       fi
+       tack=`echo "$t0" | sed -e 's/^.*://'`
+       if echo "$tack" | grep '^[0-9][0-9]*$' > /dev/null; then
+               :
+       else
+               tack=$$
+       fi
+       if [ "X$tack" != "X" ]; then
+               XDUMMY_TMPDIR="$XDUMMY_TMPDIR/$tack"
+       fi
+fi
+
+tmp=$XDUMMY_TMPDIR
+if echo "$tmp" | grep '^/tmp' > /dev/null; then
+       if [ "X$tmp" != "X/tmp" -a "X$tmp" != "X/tmp/" ]; then
+               # clean this subdir of /tmp out, otherwise leave it...
+               rm -rf $XDUMMY_TMPDIR
+               if [ -e $XDUMMY_TMPDIR ]; then
+                       warn "$XDUMMY_TMPDIR still exists"
+                       exit 1
+               fi
+       fi
+fi
+
+mkdir -p $XDUMMY_TMPDIR
+chmod 700 $XDUMMY_TMPDIR
+if [ "X$tack" != "X" ]; then
+       chmod 700 `dirname "$XDUMMY_TMPDIR"` 2>/dev/null
+fi
+
+# See if we can write something there:
+#
+tfile="$XDUMMY_TMPDIR/test.file"
+touch $tfile
+if [ ! -f "$tfile" ]; then
+       XDUMMY_TMPDIR="/tmp/Xdummy.$$.$USER"
+       warn "warning: setting tmpdir to $XDUMMY_TMPDIR ..."
+       rm -rf $XDUMMY_TMPDIR || exit 1
+       mkdir -p $XDUMMY_TMPDIR || exit 1
+fi
+rm -f $tfile
+
+export XDUMMY_TMPDIR
+
+# Compile the LD_PRELOAD shared object if needed (needs XDUMMY_TMPDIR)
+#
+if [ ! -f "$SO" ]; then
+       SO="$XDUMMY_TMPDIR/Xdummy.so"
+       make_so
+fi
+
+# Decide which X server to use:
+#
+if [ "X$xserver" = "X" ]; then
+       if type Xorg >/dev/null 2>&1; then
+               xserver="Xorg"
+       elif type XFree86 >/dev/null 2>&1; then
+               xserver="XFree86"
+       elif -x /usr/bin/Xorg; then
+               xserver="/usr/bin/Xorg"
+       elif -x /usr/X11R6/bin/Xorg; then
+               xserver="/usr/X11R6/bin/Xorg"
+       elif -x /usr/X11R6/bin/XFree86; then
+               xserver="/usr/X11R6/bin/XFree86"
+       fi
+       if [ "X$xserver" = "X" ]; then
+               # just let it fail below.
+               xserver="/usr/bin/Xorg"
+               warn "$program: cannot locate a stock Xserver... assuming $xserver"
+       fi
+fi
+
+# See if the binary is suid or not readable under -nonroot mode:
+#
+if [ "X$BASH_VERSION" != "X" ]; then
+       xserver_path=`type -p $xserver 2>/dev/null`
+else
+       xserver_path=`type $xserver 2>/dev/null | awk '{print $NF}'`
+fi
+if [ -e "$xserver_path" -a "X$root" = "X" -a "X$runit" != "X" ]; then
+       if [ ! -r $xserver_path -o -u $xserver_path -o -g $xserver_path ]; then
+               # XXX not quite correct with rm -rf $XDUMMY_TMPDIR ...
+               # we keep on a filesystem we know root can write to.
+               base=`basename "$xserver_path"`
+               new="/tmp/$base.$user.bin"
+               if [ -e $new ]; then
+                       snew=`ls -l $new          | awk '{print $5}' | grep '^[0-9][0-9]*$'`
+                       sold=`ls -l $xserver_path | awk '{print $5}' | grep '^[0-9][0-9]*$'`
+                       if [ "X$snew" != "X" -a "X$sold" != "X" -a "X$sold" != "X$snew" ]; then
+                               warn "removing different sized copy:"
+                               ls -l $new $xserver_path
+                               rm -f $new
+                       fi
+               fi
+               if [ ! -e $new -o ! -s $new ]; then
+                       rm -f $new
+                       touch $new || exit 1
+                       chmod 700 $new || exit 1
+                       if [ ! -r $xserver_path ]; then
+                               warn ""
+                               warn "NEED TO COPY UNREADABLE $xserver_path to $new as root:"
+                               warn ""
+                               ls -l $xserver_path 1>&2
+                               warn ""
+                               warn "This only needs to be done once:"
+                               warn "    cat $xserver_path > $new"
+                               warn ""
+                               nos=$nosudo
+                               if type sudo > /dev/null 2>&1; then
+                                       :
+                               else
+                                       nos=1
+                               fi
+                               if [ "X$nos" = "X1" ]; then
+                                       warn "Please supply root passwd to 'su -c'"
+                                       su -c "cat $xserver_path > $new"
+                               else
+                                       warn "Please supply the sudo passwd if asked:"
+                                       sudo /bin/sh -c "cat $xserver_path > $new"
+                               fi
+                       else
+                               warn ""
+                               warn "COPYING SETUID $xserver_path to $new"
+                               warn ""
+                               ls -l $xserver_path 1>&2
+                               warn ""
+                               cat $xserver_path > $new
+                       fi
+                       ls -l $new
+                       if [ -s $new ]; then
+                               :
+                       else
+                               rm -f $new
+                               ls -l $new
+                               exit 1
+                       fi
+                       warn ""
+                       warn "Please restart Xdummy now."
+                       exit 0
+               fi
+               if [ ! -O $new ]; then
+                       warn "file \"$new\" not owned by us!"
+                       ls -l $new
+                       exit 1
+               fi
+               xserver=$new
+       fi 
+fi
+
+# Work out display:
+#
+if [ "X$disp" != "X" ]; then
+       :
+elif [ "X$1" != "X" ]; then
+       if echo "$1" | grep '^:[0-9]' > /dev/null; then
+               disp=$1
+               shift
+       elif [ "X$1" = "X:" ]; then
+               # ":" means for us to find one.
+               shift
+       fi
+fi
+if [ "X$disp" = "X" -o "X$disp" = "X:" ]; then
+       # try to find an open display port:
+       # (tcp outdated...)
+       ports=`netstat -ant | grep LISTEN | awk '{print $4}' | sed -e 's/^.*://'`
+       n=0
+       while [ $n -le 20 ]
+       do
+               port=`printf "60%02d" $n`
+               if echo "$ports" | grep "^${port}\$" > /dev/null; then
+                       :
+               else
+                       disp=":$n"
+                       warn "$program: auto-selected DISPLAY $disp"
+                       break   
+               fi
+               n=`expr $n + 1`
+       done
+fi
+
+# Work out which vt to use, try to find/guess an open one if necessary.
+#
+vt=""
+for arg in $*
+do
+       if echo "$arg" | grep '^vt' > /dev/null; then
+               vt=$arg
+               break
+       fi
+done
+if [ "X$vt" = "X" ]; then
+       if [ "X$user" = "Xroot" ]; then
+               # root can user fuser(1) to see if it is in use:
+               if type fuser >/dev/null 2>&1; then
+                       # try /dev/tty17 thru /dev/tty32
+                       n=17
+                       while [ $n -le 32 ]
+                       do
+                               dev="/dev/tty$n"
+                               if fuser $dev >/dev/null 2>&1; then
+                                       :
+                               else
+                                       vt="vt$n"
+                                       warn "$program: auto-selected VT $vt => $dev"
+                                       break
+                               fi
+                               n=`expr $n + 1`
+                       done
+               fi
+       fi
+       if [ "X$vt" = "X" ]; then
+               # take a wild guess...
+               vt=vt16
+               warn "$program: selected fallback VT $vt"
+       fi
+else
+       vt=""
+fi
+
+# Decide flavor of Xserver:
+#
+stype=`basename "$xserver"`
+if echo "$stype" | grep -i xfree86 > /dev/null; then
+       stype=xfree86
+else
+       stype=xorg
+fi
+
+tweak_config() {
+    in="$1"
+    config2="$XDUMMY_TMPDIR/xdummy_modified_xconfig.conf"
+    if [ "X$disp" != "X" ]; then
+       d=`echo "$disp" | sed -e 's,/,,g' -e 's/:/_/g'`
+       config2="$config2$d"
+    fi
+    
+    # perl script to tweak the config file... add/delete options, etc.
+    #
+    env XDUMMY_GEOM=$geom \
+        XDUMMY_DEPTH=$depth \
+        XDUMMY_NOMODELINES=$nomodelines \
+        perl > $config2 < $in -e '
+    $n = 0;
+    $geom  = $ENV{XDUMMY_GEOM};
+    $depth = $ENV{XDUMMY_DEPTH};
+    $nomodelines = $ENV{XDUMMY_NOMODELINES};
+    $mode_str = "";
+    $videoram = "24000";
+    $HorizSync   = "30.0 - 130.0";
+    $VertRefresh = "50.0 - 250.0";
+    if ($geom ne "") {
+       my $tmp = "";
+       foreach $g (split(/,/, $geom)) {
+               $tmp .= "\"$g\" ";
+               if (!$nomodelines && $g =~ /(\d+)x(\d+)/) {
+                       my $w = $1;
+                       my $h = $2;
+                       $mode_str .= "  Modeline \"$g\" ";
+                       my $dot = sprintf("%.2f", $w * $h * 70 * 1.e-6);
+                       $mode_str .= $dot;
+                       $mode_str .= " " . $w;
+                       $mode_str .= " " . int(1.02 * $w);
+                       $mode_str .= " " . int(1.10 * $w);
+                       $mode_str .= " " . int(1.20 * $w);
+                       $mode_str .= " " . $h;
+                       $mode_str .= " " . int($h + 1);
+                       $mode_str .= " " . int($h + 3);
+                       $mode_str .= " " . int($h + 20);
+                       $mode_str .= "\n";
+               }
+       }
+       $tmp =~ s/\s*$//;
+       $geom = $tmp;
+    }
+    while (<>) {
+       if ($ENV{XDUMMY_NOTWEAK}) {
+               print $_;
+               next;
+       }
+       $n++;
+       if (/^\s*#/) {
+               # pass comments straight thru
+               print;
+               next;
+       }
+       if (/^\s*Section\s+(\S+)/i) {
+               # start of Section
+               $sect = $1;
+               $sect =~ s/\W//g;
+               $sect =~ y/A-Z/a-z/;
+               $sects{$sect} = 1;
+               print;
+               next;
+       }
+       if (/^\s*EndSection/i) {
+               # end of Section
+               if ($sect eq "serverflags") {
+                       if (!$got_DontVTSwitch) {
+                               print "  ##Xdummy:##\n";
+                               print "  Option \"DontVTSwitch\" \"true\"\n";
+                       }
+                       if (!$got_AllowMouseOpenFail) {
+                               print "  ##Xdummy:##\n";
+                               print "  Option \"AllowMouseOpenFail\" \"true\"\n";
+                       }
+                       if (!$got_PciForceNone) {
+                               print "  ##Xdummy:##\n";
+                               print "  Option \"PciForceNone\" \"true\"\n";
+                       }
+               } elsif ($sect eq "device") {
+                       if (!$got_Driver) {
+                               print "  ##Xdummy:##\n";
+                               print "  Driver \"dummy\"\n";
+                       }
+                       if (!$got_VideoRam) {
+                               print "  ##Xdummy:##\n";
+                               print "  VideoRam $videoram\n";
+                       }
+               } elsif ($sect eq "screen") {
+                       if ($depth ne "" && !got_DefaultDepth) {
+                               print "  ##Xdummy:##\n";
+                               print "  DefaultDepth $depth\n";
+                       }
+                       if ($got_Monitor eq "") {
+                               print "  ##Xdummy:##\n";
+                               print "  Monitor \"Monitor0\"\n";
+                       }
+               } elsif ($sect eq "monitor") {
+                       if (!got_HorizSync) {
+                               print "  ##Xdummy:##\n";
+                               print "  HorizSync   $HorizSync\n";
+                       }
+                       if (!got_VertRefresh) {
+                               print "  ##Xdummy:##\n";
+                               print "  VertRefresh $VertRefresh\n";
+                       }
+                       if (!$nomodelines) {
+                               print "  ##Xdummy:##\n";
+                               print $mode_str;
+                       }
+               }
+               $sect = "";
+               print;
+               next;
+       }
+
+       if (/^\s*SubSection\s+(\S+)/i) {
+               # start of Section
+               $subsect = $1;
+               $subsect =~ s/\W//g;
+               $subsect =~ y/A-Z/a-z/;
+               $subsects{$subsect} = 1;
+               if ($sect eq "screen" && $subsect eq "display") {
+                       $got_Modes = 0;
+               }
+               print;
+               next;
+       }
+       if (/^\s*EndSubSection/i) {
+               # end of SubSection
+               if ($sect eq "screen") {
+                       if ($subsect eq "display") {
+                               if ($depth ne "" && !$set_Depth) {
+                                       print "          ##Xdummy:##\n";
+                                       print "          Depth\t$depth\n";
+                               }
+                               if ($geom ne "" && ! $got_Modes) {
+                                       print "          ##Xdummy:##\n";
+                                       print "          Modes\t$geom\n";
+                               }
+                       }
+               }
+               $subsect = "";
+               print;
+               next;
+       }
+
+       $l = $_;
+       $l =~ s/#.*$//;
+       if ($sect eq "serverflags") {
+               if ($l =~ /^\s*Option.*DontVTSwitch/i) {
+                       $_ =~ s/false/true/ig;
+                       $got_DontVTSwitch = 1;
+               }
+               if ($l =~ /^\s*Option.*AllowMouseOpenFail/i) {
+                       $_ =~ s/false/true/ig;
+                       $got_AllowMouseOpenFail = 1;
+               }
+               if ($l =~ /^\s*Option.*PciForceNone/i) {
+                       $_ =~ s/false/true/ig;
+                       $got_PciForceNone= 1;
+               }
+       }
+       if ($sect eq "module") {
+               if ($l =~ /^\s*Load.*\b(dri|fbdevhw)\b/i) {
+                       $_ = "##Xdummy## $_";
+               }
+       }
+       if ($sect eq "monitor") {
+               if ($l =~ /^\s*HorizSync/i) {
+                       $got_HorizSync = 1;
+               }
+               if ($l =~ /^\s*VertRefresh/i) {
+                       $got_VertRefresh = 1;
+               }
+       }
+       if ($sect eq "device") {
+               if ($l =~ /^(\s*Driver)\b/i) {
+                       $_ = "$1 \"dummy\"\n";
+                       $got_Driver = 1;
+               }
+               if ($l =~ /^\s*VideoRam/i) {
+                       $got_VideoRam= 1;
+               }
+       }
+       if ($sect eq "inputdevice") {
+               if ($l =~ /^\s*Option.*\bDevice\b/i) {
+                       print "  ##Xdummy:##\n";
+                       $_ = "  Option \"Device\" \"/dev/dilbert$n\"\n";
+               }
+       }
+       if ($sect eq "screen") {
+               if ($l =~ /^\s*DefaultDepth\s+(\d+)/i) {
+                       if ($depth ne "") {
+                               print "  ##Xdummy:##\n";
+                               $_ = "  DefaultDepth\t$depth\n";
+                       }
+                       $got_DefaultDepth = 1;
+               }
+               if ($l =~ /^\s*Monitor\s+(\S+)/i) {
+                       $got_Monitor = $1;
+                       $got_Monitor =~ s/"//g;
+               }
+               if ($subsect eq "display") {
+                       if ($geom ne "") {
+                               if ($l =~ /^(\s*Modes)\b/i) {
+                                       print "          ##Xdummy:##\n";
+                                       $_ = "$1 $geom\n";
+                                       $got_Modes = 1;
+                               }
+                       }
+                       if ($l =~ /^\s*Depth\s+(\d+)/i) {
+                               my $d = $1;
+                               if (!$set_Depth && $depth ne "") {
+                                       $set_Depth = 1;
+                                       if ($depth != $d) {
+                                               print "          ##Xdummy:##\n";
+                                               $_ =  "          Depth\t$depth\n";
+                                       }
+                               }
+                       }
+               }
+       }
+       print;
+    }
+    if ($ENV{XDUMMY_NOTWEAK}) {
+       exit;
+    }
+    # create any crucial sections that are missing:
+    if (! exists($sects{serverflags})) {
+       print "\n##Xdummy:##\n";
+       print "Section \"ServerFlags\"\n";
+       print "  Option \"DontVTSwitch\" \"true\"\n";
+       print "  Option \"AllowMouseOpenFail\" \"true\"\n";
+       print "  Option \"PciForceNone\" \"true\"\n";
+       print "EndSection\n";
+    }
+    if (! exists($sects{device})) {
+       print "\n##Xdummy:##\n";
+       print "Section \"Device\"\n";
+       print "  Identifier \"Videocard0\"\n";
+       print "  Driver \"dummy\"\n";
+       print "  VideoRam $videoram\n";
+       print "EndSection\n";
+    }
+    if (! exists($sects{monitor})) {
+       print "\n##Xdummy:##\n";
+       print "Section \"Monitor\"\n";
+       print "  Identifier \"Monitor0\"\n";
+       print "  HorizSync   $HorizSync\n";
+       print "  VertRefresh $VertRefresh\n";
+       print "EndSection\n";
+    }
+    if (! exists($sects{screen})) {
+       print "\n##Xdummy:##\n";
+       print "Section \"Screen\"\n";
+       print "  Identifier \"Screen0\"\n";
+       print "  Device \"Videocard0\"\n";
+       if ($got_Monitor ne "") {
+               print "  Monitor \"$got_Monitor\"\n";
+       } else {
+               print "  Monitor \"Monitor0\"\n";
+       }
+       if ($depth ne "") {
+               print "  DefaultDepth $depth\n";
+       } else {
+               print "  DefaultDepth 24\n";
+       }
+       print "  SubSection \"Display\"\n";
+       print "    Viewport 0 0\n";
+       print "    Depth 24\n";
+       if ($got_Modes) {
+               ;
+       } elsif ($geom ne "") {
+               print "    Modes $geom\n";
+       } else {
+               print "    Modes \"1280x1024\" \"1024x768\" \"800x600\"\n";
+       }
+       print "  EndSubSection\n";
+       print "EndSection\n";
+    }
+';
+}
+
+# Work out config file and tweak it.
+#
+if [ "X$cmdline_config" = "X" ]; then
+       :
+elif [ "X$cmdline_config" = "Xxdummy-builtin" ]; then
+       :
+elif echo "$cmdline_config" | grep '/' > /dev/null; then
+       :
+else
+       # ignore basename only case (let server handle it)
+       cmdline_config=""
+       notweak=1
+fi
+
+config=$cmdline_config
+
+if [ "X$notweak" = "X1" -a "X$root" = "X" -a  -f "$cmdline_config" ]; then
+       # if not root we need to copy (but not tweak) the specified config.
+       XDUMMY_NOTWEAK=1
+       export XDUMMY_NOTWEAK
+       notweak=""
+fi
+
+if [ ! $notweak ]; then
+       # tweaked config will be put in $config2:
+       config2=""
+       if [ "X$config" = "X" ]; then
+               # use the default one:
+               if [ "X$stype" = "Xxorg" ]; then
+                       config=/etc/X11/xorg.conf
+               else
+                       if [ -f "/etc/X11/XF86Config-4" ]; then
+                               config="/etc/X11/XF86Config-4"
+                       else
+                               config="/etc/X11/XF86Config"
+                       fi
+               fi
+               if [ ! -f "$config" ]; then
+                       for c in /etc/X11/xorg.conf /etc/X11/XF86Config-4 /etc/X11/XF86Config
+                       do
+                               if [ -f $c ]; then
+                                       config=$c
+                                       break
+                               fi
+                       done
+               fi
+       fi
+
+       if [ "X$config" = "Xxdummy-builtin" ]; then
+               config=""
+       fi
+
+       if [ ! -f "$config" ]; then
+               config="$XDUMMY_TMPDIR/xorg.conf"
+               warn "$program: using minimal built-in xorg.conf settings."
+               cat > $config <<END
+
+Section "ServerLayout"
+    Identifier     "Layout0"
+    Screen      0  "Screen0"
+    InputDevice    "Keyboard0" "CoreKeyboard"
+    InputDevice    "Mouse0" "CorePointer"
+EndSection
+
+Section "Files"
+EndSection
+
+Section "Module"
+    Load           "dbe"
+    Load           "extmod"
+    Load           "freetype"
+    Load           "glx"
+EndSection
+
+Section "InputDevice"
+    Identifier     "Mouse0"
+    Driver         "mouse"
+    Option         "Protocol" "auto"
+    Option         "Device" "/dev/psaux"
+    Option         "Emulate3Buttons" "no"
+    Option         "ZAxisMapping" "4 5"
+EndSection
+
+Section "InputDevice"
+    Identifier     "Keyboard0"
+    Driver         "kbd"
+EndSection
+
+Section "Monitor"
+    Identifier     "Monitor0"
+    VendorName     "Unknown"
+    ModelName      "Unknown"
+    HorizSync       30.0 - 130.0
+    VertRefresh     50.0 - 250.0
+    Option         "DPMS"
+EndSection
+
+Section "Device"
+    Identifier     "Device0"
+    Driver         "foovideo"
+    VendorName     "foovideo Corporation"
+EndSection
+
+Section "Screen"
+    Identifier     "Screen0"
+    Device         "Device0"
+    Monitor        "Monitor0"
+    DefaultDepth    24
+    SubSection     "Display"
+        Depth       24
+        Modes           "1280x1024"
+    EndSubSection
+EndSection
+
+END
+       fi
+
+       if [ -f "$config" ]; then
+               tweak_config $config
+       fi
+
+       # now we need to get our tweaked config file onto the command line:
+       if [ "X$cmdline_config" = "X" ]; then
+               # append to cmdline (FUBAR will be substituted below.)
+               if [ "X$stype" = "Xxorg" ]; then
+                       args="$args -config FUBAR"
+               else
+                       args="$args -xf86config FUBAR"
+               fi
+       fi
+       if [ "X$config2" != "X" ]; then
+               # or modify $args:
+               c2=$config2
+               if [ "X$root" = "X" ]; then
+                       # ordinary user cannot use absolute path.
+                       c2=`basename $config2`
+               fi
+               args=`echo "$args" | sed \
+                       -e "s,-config  *[^ ][^ ]*,-config $c2,g" \
+                       -e "s,-xf86config  *[^ ][^ ]*,-xf86config $c2,g"`
+       fi
+fi
+
+if [ $prconf ]; then
+       warn ""
+       warn "Printing out the Xorg/XFree86 server config file:"
+       warn ""
+       if [ "X$config2" = "X" ]; then
+               warn "NO CONFIG GENERATED."
+               exit 1
+       else
+               cat "$config2"
+       fi
+       exit 0
+fi
+
+if [ $debug ]; then
+       XDUMMY_DEBUG=1
+       export XDUMMY_DEBUG
+fi
+if [ $root ]; then
+       XDUMMY_ROOT=1
+       export XDUMMY_ROOT
+fi
+
+# Finally, run it:
+#
+if [ "X$debug" != "X" -o "X$runit" = "X" ]; then
+       if [ ! $runit ]; then
+               echo ""
+               echo "/usr/bin/env:"
+               env | egrep -v '^(LS_COLORS|TERMCAP)' | sort
+               echo ""
+               echo "XDUMMY*:"
+               env | grep '^XDUMMY' | sort
+               echo ""
+       fi
+       warn ""
+       warn "The command to run is:"
+       warn ""
+       so=$SO
+       pwd=`pwd`
+       if echo "$so" | grep '^\./' > /dev/null; then
+               so=`echo "$so" | sed -e "s,^\.,$pwd,"`
+       fi
+       if echo "$so" | grep '/' > /dev/null; then
+               :
+       else
+               so="$pwd/$so"
+       fi
+       warn "env LD_PRELOAD=$so $xserver $disp $args $vt"
+       warn ""
+       if [ ! $runit ]; then
+               exit 0
+       fi
+fi
+
+if [ $strace ]; then
+       if [ "X$strace" = "X2" ]; then
+               ltrace -f env LD_PRELOAD=$SO $xserver $disp $args $vt
+       else
+               strace -f env LD_PRELOAD=$SO $xserver $disp $args $vt
+       fi
+else
+       exec env LD_PRELOAD=$SO $xserver $disp $args $vt
+fi
+
+exit $?
+
+#########################################################################
+
+code() {
+#code_begin
+#include <stdio.h>
+#define O_ACCMODE          0003
+#define O_RDONLY             00
+#define O_WRONLY             01
+#define O_RDWR               02
+#define O_CREAT            0100 /* not fcntl */
+#define O_EXCL             0200 /* not fcntl */
+#define O_NOCTTY           0400 /* not fcntl */
+#define O_TRUNC           01000 /* not fcntl */
+#define O_APPEND          02000
+#define O_NONBLOCK        04000
+#define O_NDELAY        O_NONBLOCK
+#define O_SYNC           010000
+#define O_FSYNC          O_SYNC
+#define O_ASYNC          020000
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <linux/vt.h>
+#include <linux/kd.h>
+
+#define __USE_GNU
+#include <dlfcn.h>
+
+static char tmpdir[4096];
+static char str1[4096];
+static char str2[4096];
+
+static char devs[256][1024];
+static int debug = -1;
+static int root = -1;
+static int changed_uid = 0;
+static int saw_fonts = 0;
+static int saw_lib_modules = 0;
+
+static time_t start = 0; 
+
+void check_debug(void) {
+       if (debug < 0) {
+               if (getenv("XDUMMY_DEBUG") != NULL) {
+                       debug = 1;
+               } else {
+                       debug = 0;
+               }
+               /* prevent other processes using the preload: */
+               putenv("LD_PRELOAD=");
+       }
+}
+void check_root(void) {
+       if (root < 0) {
+               /* script tells us if we are root */
+               if (getenv("XDUMMY_ROOT") != NULL) {
+                       root = 1;
+               } else {
+                       root = 0;
+               }
+       }
+}
+
+void check_uid(void) {
+       if (start == 0) {
+               start = time(NULL);
+               if (debug) fprintf(stderr, "START: %u\n", (unsigned int) start);
+               return;
+       } else if (changed_uid == 0) {
+               if (saw_fonts || time(NULL) > start + 20) {
+                       if (getenv("XDUMMY_UID")) {
+                               int uid = atoi(getenv("XDUMMY_UID"));
+                               if (debug) fprintf(stderr, "SETREUID: %d saw_fonts=%d\n", uid, saw_fonts);
+                               if (uid >= 0) {
+                                       /* this will simply fail in -nonroot mode: */
+                                       setreuid(uid, -1);
+                               }
+                       }
+                       changed_uid = 1;
+               }
+       }
+}
+
+#define CHECKIT if (debug < 0) check_debug(); \
+               if (root  < 0) check_root(); \
+               check_uid();
+
+static void set_tmpdir(void) {
+       char *s;
+       static int didset = 0;
+       if (didset) {
+               return;
+       }
+       s = getenv("XDUMMY_TMPDIR");
+       if (! s) {
+               s = "/tmp";
+       }
+       tmpdir[0] = '\0';
+       strcat(tmpdir, s);
+       strcat(tmpdir, "/");
+       didset = 1;
+}
+
+static char *tmpdir_path(const char *path) {
+       char *str;
+       set_tmpdir();
+       strcpy(str2, path);
+       str = str2;
+       while (*str) {
+               if (*str == '/') {
+                       *str = '_';
+               }
+               str++;
+       }
+       strcpy(str1, tmpdir);
+       strcat(str1, str2);
+       return str1;
+}
+
+int open(const char *pathname, int flags, unsigned short mode) {
+       int fd;
+       char *store_dev = NULL;
+       static int (*real_open)(const char *, int , unsigned short) = NULL;
+
+       CHECKIT
+       if (! real_open) {
+               real_open = (int (*)(const char *, int , unsigned short))
+                       dlsym(RTLD_NEXT, "open");
+       }
+
+       if (strstr(pathname, "lib/modules/")) {
+               /* not currently used. */
+               saw_lib_modules = 1;
+       }
+
+       if (!root) {
+               if (strstr(pathname, "/dev/") == pathname) {
+                       store_dev = strdup(pathname);
+               }
+               if (strstr(pathname, "/dev/tty") == pathname && strcmp(pathname, "/dev/tty")) {
+                       pathname = tmpdir_path(pathname);
+                       if (debug) fprintf(stderr, "OPEN: %s -> %s (as FIFO)\n", store_dev, pathname);
+                       /* we make it a FIFO so ioctl on it does not fail */
+                       unlink(pathname);
+                       mkfifo(pathname, 0666);
+               } else if (0) {
+                       /* we used to handle more /dev files ... */
+                       fd = real_open(pathname, O_WRONLY|O_CREAT, 0777);
+                       close(fd);
+               }
+       }
+
+       fd = real_open(pathname, flags, mode);
+
+       if (debug) fprintf(stderr, "OPEN: %s %d %d fd=%d\n", pathname, flags, mode, fd);
+
+       if (! root) {
+               if (store_dev) {
+                       if (fd < 256) {
+                               strcpy(devs[fd], store_dev);
+                       }
+                       free(store_dev);
+               }
+       }
+
+       return(fd);
+}
+
+int open64(const char *pathname, int flags, unsigned short mode) {
+       int fd;
+
+       CHECKIT
+       if (debug) fprintf(stderr, "OPEN64: %s %d %d\n", pathname, flags, mode);
+
+       fd = open(pathname, flags, mode);
+       return(fd);
+}
+
+int rename(const char *oldpath, const char *newpath) {
+       static int (*real_rename)(const char *, const char *) = NULL;
+
+       CHECKIT
+       if (! real_rename) {
+               real_rename = (int (*)(const char *, const char *))
+                       dlsym(RTLD_NEXT, "rename");
+       }
+
+       if (debug) fprintf(stderr, "RENAME: %s %s\n", oldpath, newpath);
+
+       if (root) {
+               return(real_rename(oldpath, newpath));
+       }
+
+       if (strstr(oldpath, "/var/log") == oldpath) {
+               if (debug) fprintf(stderr, "RENAME: returning 0\n");
+               return 0;
+       }
+       return(real_rename(oldpath, newpath));
+}
+
+FILE *fopen(const char *pathname, const char *mode) {
+       static FILE* (*real_fopen)(const char *, const char *) = NULL;
+       char *str;
+
+       if (! saw_fonts) {
+               if (strstr(pathname, "/fonts/")) {
+                       if (strstr(pathname, "fonts.dir")) {
+                               saw_fonts = 1;
+                       } else if (strstr(pathname, "fonts.alias")) {
+                               saw_fonts = 1;
+                       }
+               }
+       }
+
+       CHECKIT
+       if (! real_fopen) {
+               real_fopen = (FILE* (*)(const char *, const char *))
+                       dlsym(RTLD_NEXT, "fopen");
+       }
+
+       if (debug) fprintf(stderr, "FOPEN: %s %s\n", pathname, mode);
+
+       if (strstr(pathname, "xdummy_modified_xconfig.conf")) {
+               /* make our config appear to be in /etc/X11, etc. */
+               char *q = strrchr(pathname, '/');
+               if (q != NULL && getenv("XDUMMY_TMPDIR") != NULL) {
+                       strcpy(str1, getenv("XDUMMY_TMPDIR"));
+                       strcat(str1, q);
+                       if (debug) fprintf(stderr, "FOPEN: %s -> %s\n", pathname, str1);
+                       pathname = str1;
+               }
+       }
+
+       if (root) {
+               return(real_fopen(pathname, mode));
+       }
+
+       str = (char *) pathname;
+       if (strstr(pathname, "/var/log") == pathname) {
+               str = tmpdir_path(pathname);
+               if (debug) fprintf(stderr, "FOPEN: %s -> %s\n", pathname, str);
+       }
+       return(real_fopen(str, mode));
+}
+
+
+#define RETURN0 if (debug) \
+       {fprintf(stderr, "IOCTL: covered %d 0x%x\n", fd, req);} return 0;
+#define RETURN1 if (debug) \
+       {fprintf(stderr, "IOCTL: covered %d 0x%x\n", fd, req);} return -1;
+
+int ioctl(int fd, int req, void *ptr) {
+       static int closed_xf86Info_consoleFd = 0;
+       static int (*real_ioctl)(int, int , void *) = NULL;
+
+       CHECKIT
+       if (! real_ioctl) {
+               real_ioctl = (int (*)(int, int , void *))
+                       dlsym(RTLD_NEXT, "open");
+       }
+       if (debug) fprintf(stderr, "IOCTL: %d 0x%x %p\n", fd, req, ptr);
+
+       /* based on xorg-x11-6.8.1-dualhead.patch */
+       if (req == VT_GETMODE) {
+               /* close(xf86Info.consoleFd) */
+               if (0 && ! closed_xf86Info_consoleFd) {
+                       /* I think better not to close it... */
+                       close(fd);
+                       closed_xf86Info_consoleFd = 1;
+               }
+               RETURN0
+       } else if (req == VT_SETMODE) {
+               RETURN0
+       } else if (req == VT_GETSTATE) {
+               RETURN0
+       } else if (req == KDSETMODE) {
+               RETURN0
+       } else if (req == KDSETLED) {
+               RETURN0
+       } else if (req == KDGKBMODE) {
+               RETURN0
+       } else if (req == KDSKBMODE) {
+               RETURN0
+       } else if (req == VT_ACTIVATE) {
+               RETURN0
+       } else if (req == VT_WAITACTIVE) {
+               RETURN0
+       } else if (req == VT_RELDISP) {
+               if (ptr == (void *) 1) {
+                       RETURN1
+               } else if (ptr == (void *) VT_ACKACQ) {
+                       RETURN0
+               }
+       }
+
+       return(real_ioctl(fd, req, ptr));
+}
+
+typedef void (*sighandler_t)(int);
+#define SIGUSR1       10
+#define SIG_DFL       ((sighandler_t)0)
+
+sighandler_t signal(int signum, sighandler_t handler) {
+       static sighandler_t (*real_signal)(int, sighandler_t) = NULL;
+
+       CHECKIT
+       if (! real_signal) {
+               real_signal = (sighandler_t (*)(int, sighandler_t))
+                       dlsym(RTLD_NEXT, "signal");
+       }
+
+       if (debug) fprintf(stderr, "SIGNAL: %d %p\n", signum, handler);
+
+       if (signum == SIGUSR1) {
+               if (debug) fprintf(stderr, "SIGNAL: skip SIGUSR1\n");
+               return SIG_DFL;
+       }
+       
+       return(real_signal(signum, handler));
+}
+
+int close(int fd) {
+       static int (*real_close)(int) = NULL;
+
+       CHECKIT
+       if (! real_close) {
+               real_close = (int (*)(int)) dlsym(RTLD_NEXT, "close");
+       }
+
+       if (debug) fprintf(stderr, "CLOSE: %d\n", fd);
+       if (!root) {
+               if (fd < 256) {
+                       devs[fd][0] = '\0';
+               }
+       }
+       return(real_close(fd));
+}
+
+struct stat {
+       int foo;
+};
+
+int stat(const char *path, struct stat *buf) {
+       static int (*real_stat)(const char *, struct stat *) = NULL;
+
+       CHECKIT
+       if (! real_stat) {
+               real_stat = (int (*)(const char *, struct stat *))
+                       dlsym(RTLD_NEXT, "stat");
+       }
+
+       if (debug) fprintf(stderr, "STAT: %s\n", path);
+
+       return(real_stat(path, buf));
+}
+
+int stat64(const char *path, struct stat *buf) {
+       static int (*real_stat64)(const char *, struct stat *) = NULL;
+
+       CHECKIT
+       if (! real_stat64) {
+               real_stat64 = (int (*)(const char *, struct stat *))
+                       dlsym(RTLD_NEXT, "stat64");
+       }
+
+       if (debug) fprintf(stderr, "STAT64: %s\n", path);
+
+       return(real_stat64(path, buf));
+}
+
+int chown(const char *path, uid_t owner, gid_t group) {
+       static int (*real_chown)(const char *, uid_t, gid_t) = NULL;
+
+       CHECKIT
+       if (! real_chown) {
+               real_chown = (int (*)(const char *, uid_t, gid_t))
+                       dlsym(RTLD_NEXT, "chown");
+       }
+
+       if (root) {
+               return(real_chown(path, owner, group));
+       }
+
+       if (debug) fprintf(stderr, "CHOWN: %s %d %d\n", path, owner, group);
+
+       if (strstr(path, "/dev") == path) {
+               if (debug) fprintf(stderr, "CHOWN: return 0\n");
+               return 0;
+       }
+
+       return(real_chown(path, owner, group));
+}
+
+extern int *__errno_location (void);
+#ifndef ENODEV
+#define ENODEV 19
+#endif
+
+int ioperm(unsigned long from, unsigned long num, int turn_on) {
+       static int (*real_ioperm)(unsigned long, unsigned long, int) = NULL;
+
+       CHECKIT
+       if (! real_ioperm) {
+               real_ioperm = (int (*)(unsigned long, unsigned long, int))
+                       dlsym(RTLD_NEXT, "ioperm");
+       }
+       if (debug) fprintf(stderr, "IOPERM: %d %d %d\n", (int) from, (int) num, turn_on);
+       if (root) {
+               return(real_ioperm(from, num, turn_on));
+       }
+       if (from == 0 && num == 1024 && turn_on == 1) {
+               /* we want xf86EnableIO to fail */
+               if (debug) fprintf(stderr, "IOPERM: setting ENODEV.\n");
+               *__errno_location() = ENODEV;
+               return -1;
+       }
+       return 0;
+}
+
+int iopl(int level) {
+       static int (*real_iopl)(int) = NULL;
+
+       CHECKIT
+       if (! real_iopl) {
+               real_iopl = (int (*)(int)) dlsym(RTLD_NEXT, "iopl");
+       }
+       if (debug) fprintf(stderr, "IOPL: %d\n", level);
+       if (root) {
+               return(real_iopl(level));
+       }
+       return 0;
+}
+
+#ifdef INTERPOSE_GETUID 
+
+/*
+ * we got things to work w/o pretending to be root.
+ * so we no longer interpose getuid(), etc.
+ */
+
+uid_t getuid(void) {
+       static uid_t (*real_getuid)(void) = NULL;
+       CHECKIT
+       if (! real_getuid) {
+               real_getuid = (uid_t (*)(void)) dlsym(RTLD_NEXT, "getuid");
+       }
+       if (root) {
+               return(real_getuid());
+       }
+       if (debug) fprintf(stderr, "GETUID: 0\n");
+       return 0;
+}
+uid_t geteuid(void) {
+       static uid_t (*real_geteuid)(void) = NULL;
+       CHECKIT
+       if (! real_geteuid) {
+               real_geteuid = (uid_t (*)(void)) dlsym(RTLD_NEXT, "geteuid");
+       }
+       if (root) {
+               return(real_geteuid());
+       }
+       if (debug) fprintf(stderr, "GETEUID: 0\n");
+       return 0;
+}
+uid_t geteuid_kludge1(void) {
+       static uid_t (*real_geteuid)(void) = NULL;
+       CHECKIT
+       if (! real_geteuid) {
+               real_geteuid = (uid_t (*)(void)) dlsym(RTLD_NEXT, "geteuid");
+       }
+       if (debug) fprintf(stderr, "GETEUID: 0 saw_libmodules=%d\n", saw_lib_modules);
+       if (root && !saw_lib_modules) {
+               return(real_geteuid());
+       } else {
+               saw_lib_modules = 0;
+               return 0;
+       }
+}
+
+uid_t getuid32(void) {
+       static uid_t (*real_getuid32)(void) = NULL;
+       CHECKIT
+       if (! real_getuid32) {
+               real_getuid32 = (uid_t (*)(void)) dlsym(RTLD_NEXT, "getuid32");
+       }
+       if (root) {
+               return(real_getuid32());
+       }
+       if (debug) fprintf(stderr, "GETUID32: 0\n");
+       return 0;
+}
+uid_t geteuid32(void) {
+       static uid_t (*real_geteuid32)(void) = NULL;
+       CHECKIT
+       if (! real_geteuid32) {
+               real_geteuid32 = (uid_t (*)(void)) dlsym(RTLD_NEXT, "geteuid32");
+       }
+       if (root) {
+               return(real_geteuid32());
+       }
+       if (debug) fprintf(stderr, "GETEUID32: 0\n");
+       return 0;
+}
+
+gid_t getgid(void) {
+       static gid_t (*real_getgid)(void) = NULL;
+       CHECKIT
+       if (! real_getgid) {
+               real_getgid = (gid_t (*)(void)) dlsym(RTLD_NEXT, "getgid");
+       }
+       if (root) {
+               return(real_getgid());
+       }
+       if (debug) fprintf(stderr, "GETGID: 0\n");
+       return 0;
+}
+gid_t getegid(void) {
+       static gid_t (*real_getegid)(void) = NULL;
+       CHECKIT
+       if (! real_getegid) {
+               real_getegid = (gid_t (*)(void)) dlsym(RTLD_NEXT, "getegid");
+       }
+       if (root) {
+               return(real_getegid());
+       }
+       if (debug) fprintf(stderr, "GETEGID: 0\n");
+       return 0;
+}
+gid_t getgid32(void) {
+       static gid_t (*real_getgid32)(void) = NULL;
+       CHECKIT
+       if (! real_getgid32) {
+               real_getgid32 = (gid_t (*)(void)) dlsym(RTLD_NEXT, "getgid32");
+       }
+       if (root) {
+               return(real_getgid32());
+       }
+       if (debug) fprintf(stderr, "GETGID32: 0\n");
+       return 0;
+}
+gid_t getegid32(void) {
+       static gid_t (*real_getegid32)(void) = NULL;
+       CHECKIT
+       if (! real_getegid32) {
+               real_getegid32 = (gid_t (*)(void)) dlsym(RTLD_NEXT, "getegid32");
+       }
+       if (root) {
+               return(real_getegid32());
+       }
+       if (debug) fprintf(stderr, "GETEGID32: 0\n");
+       return 0;
+}
+#endif
+
+#if 0
+/* maybe we need to interpose on strcmp someday... here is the template */
+int strcmp(const char *s1, const char *s2) {
+       static int (*real_strcmp)(const char *, const char *) = NULL;
+       CHECKIT
+       if (! real_strcmp) {
+               real_strcmp = (int (*)(const char *, const char *)) dlsym(RTLD_NEXT, "strcmp");
+       }
+       if (debug) fprintf(stderr, "STRCMP: '%s' '%s'\n", s1, s2);
+       return(real_strcmp(s1, s2));
+}
+#endif
+
+#code_end
+}
diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl
new file mode 100755 (executable)
index 0000000..c05d6e5
--- /dev/null
@@ -0,0 +1,211 @@
+#!/usr/bin/env perl
+# vim:ts=4:sw=4:expandtab
+#
+# © 2010-2011 Michael Stapelberg and contributors
+#
+# syntax: ./complete-run.pl --display :1 --display :2
+# to run the test suite on the X11 displays :1 and :2
+# use 'Xdummy :1' and 'Xdummy :2' before to start two
+# headless X11 servers
+#
+
+use strict;
+use warnings;
+use EV;
+use AnyEvent;
+use IO::Scalar; # not in core :\
+use File::Temp qw(tempfile tempdir);
+use v5.10;
+use DateTime;
+use Data::Dumper;
+use Cwd qw(abs_path);
+use Proc::Background;
+use TAP::Harness;
+use TAP::Parser;
+use TAP::Parser::Aggregator;
+use File::Basename qw(basename);
+use AnyEvent::I3 qw(:all);
+use Try::Tiny;
+use Getopt::Long;
+use Time::HiRes qw(sleep);
+use X11::XCB::Connection;
+
+# install a dummy CHLD handler to overwrite the CHLD handler of AnyEvent / EV
+# XXX: we could maybe also use a different loop than the default loop in EV?
+$SIG{CHLD} = sub {
+};
+
+# reads in a whole file
+sub slurp {
+    open my $fh, '<', shift;
+    local $/;
+    <$fh>;
+}
+
+my $coverage_testing = 0;
+my @displays = ();
+
+my $result = GetOptions(
+    "coverage-testing" => \$coverage_testing,
+    "display=s" => \@displays,
+);
+
+@displays = split(/,/, join(',', @displays));
+@displays = map { s/ //g; $_ } @displays;
+
+@displays = qw(:1) if @displays == 0;
+
+# 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;
+for my $display (@displays) {
+    try {
+        my $x = X11::XCB::Connection->new(display => $display);
+        push @conns, $x;
+        push @wdisplays, $display;
+    } catch {
+        say STDERR "WARNING: Not using X11 display $display, could not connect";
+    };
+}
+
+my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler";
+my $config = slurp('i3-test.config');
+
+# 1: 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;
+
+# 2: create an output directory for this test-run
+my $outdir = "testsuite-";
+$outdir .= DateTime->now->strftime("%Y-%m-%d-%H-%M-%S-");
+$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";
+
+# 3: run all tests
+my @done;
+my $num = @testfiles;
+my $harness = TAP::Harness->new({ });
+
+my $aggregator = TAP::Parser::Aggregator->new();
+$aggregator->start();
+
+my $cv = AnyEvent->condvar;
+
+# We start tests concurrently: For each display, one test gets started. Every
+# test starts another test after completing.
+take_job($_) for @wdisplays;
+
+#
+# Takes a test from the beginning of @testfiles and runs it.
+#
+# The TAP::Parser (which reads the test output) will get called as soon as
+# there is some activity on the stdout file descriptor of the test process
+# (using an AnyEvent->io watcher).
+#
+# When a test completes and @done contains $num entries, the $cv condvar gets
+# triggered to finish testing.
+#
+sub take_job {
+    my ($display) = @_;
+    my ($fh, $tmpfile) = tempfile();
+    say $fh $config;
+    say $fh "ipc-socket /tmp/nested-$display";
+    close($fh);
+
+    my $test = shift @testfiles;
+    return unless $test;
+    my $logpath = "$outdir/i3-log-for-" . basename($test);
+    my $cmd = "export DISPLAY=$display; exec $i3cmd -c $tmpfile >$logpath 2>&1";
+    my $dont_start = (slurp($test) =~ /# !NO_I3_INSTANCE!/);
+
+    my $process = Proc::Background->new($cmd) unless $dont_start;
+    say "[$display] Running $test with logfile $logpath";
+
+    sleep 0.5;
+    my $kill_i3 = sub {
+        # Don’t bother killing i3 when we haven’t started it
+        return if $dont_start;
+
+        # When measuring code coverage, try to exit i3 cleanly (otherwise, .gcda
+        # files are not written) and fallback to killing it
+        if ($coverage_testing) {
+            my $exited = 0;
+            try {
+                say "Exiting i3 cleanly...";
+                i3("/tmp/nested-$display")->command('exit')->recv;
+                $exited = 1;
+            };
+            return if $exited;
+        }
+
+        say "[$display] killing i3";
+        kill(9, $process->pid) or die "could not kill i3";
+    };
+
+    my $output;
+    my $parser = TAP::Parser->new({
+        exec => [ 'sh', '-c', "DISPLAY=$display /usr/bin/perl -It/lib $test" ],
+        spool => IO::Scalar->new(\$output),
+        merge => 1,
+    });
+
+    my @watchers;
+    my ($stdout, $stderr) = $parser->get_select_handles;
+    for my $handle ($parser->get_select_handles) {
+        my $w;
+        $w = AnyEvent->io(
+            fh => $handle,
+            poll => 'r',
+            cb => sub {
+                # Ignore activity on stderr (unnecessary with merge => 1,
+                # but let’s keep it in here if we want to use merge => 0
+                # for some reason in the future).
+                return if defined($stderr) and $handle == $stderr;
+
+                my $result = $parser->next;
+                if (defined($result)) {
+                    # TODO: check if we should bail out
+                    return;
+                }
+
+                # $result is not defined, we are done parsing
+                say "[$display] $test finished";
+                close($parser->delete_spool);
+                $aggregator->add($test, $parser);
+                push @done, [ $test, $output ];
+
+                $kill_i3->();
+
+                undef $_ for @watchers;
+                if (@done == $num) {
+                    $cv->send;
+                } else {
+                    take_job($display);
+                }
+            }
+        );
+        push @watchers, $w;
+    }
+}
+
+$cv->recv;
+
+$aggregator->stop();
+
+for (@done) {
+    my ($test, $output) = @$_;
+    say "output for $test:";
+    say $output;
+}
+
+# 4: print summary
+$harness->summary($aggregator);
diff --git a/testcases/i3-test.config b/testcases/i3-test.config
new file mode 100644 (file)
index 0000000..4f59205
--- /dev/null
@@ -0,0 +1,103 @@
+for_window [class="borderless"] border none
+for_window [title="special borderless title"] border none
+for_window [title="special mark title"] border none, mark bleh
+
+# ISO 10646 = Unicode
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+# Use Mouse+Mod1 to drag floating windows to their wanted position
+floating_modifier Mod1
+
+# Open empty container
+bindsym Mod1+Shift+Return open
+
+# Start terminal (Mod1+Enter)
+bindsym Mod1+Return exec /usr/bin/urxvt
+
+# Start dmenu (Mod1+p)
+bindsym Mod1+p exec /usr/bin/dmenu_run
+
+# Horizontal orientation
+bindsym Mod1+h split h
+
+# Vertical orientation
+bindsym Mod1+v split v
+
+# Fullscreen (Mod1+f)
+bindsym Mod1+f fullscreen
+
+# Stacking (Mod1+s)
+bindsym Mod1+s layout stacking
+
+# Tabbed (Mod1+w)
+bindsym Mod1+w layout tabbed
+
+# Default (Mod1+l)
+bindsym Mod1+l layout default
+
+# toggle tiling / floating
+bindsym Mod1+Shift+space mode toggle
+
+bindsym Mod1+u level up
+#bindsym Mod1+d level down
+
+# Kill current client (Mod1+c)
+bindsym Mod1+c kill
+
+# Restore saved JSON layout
+bindsym Mod1+y restore /home/michael/i3/layout.json
+
+# Restart i3
+bindsym Mod1+Shift+c restart
+# Reload i3
+bindsym Mod1+Shift+j reload
+# Exit i3
+bindsym Mod1+Shift+l exit
+
+# Focus (Mod1+n/r/t/d)
+bindsym Mod1+n prev h
+bindsym Mod1+r next v
+bindsym Mod1+t prev v
+bindsym Mod1+d next h
+
+# alternatively, you can use the cursor keys:
+bindsym Mod1+Left prev h
+bindsym Mod1+Right next h
+bindsym Mod1+Down next v
+bindsym Mod1+Up prev v
+
+# Move
+bindsym Mod1+Shift+n move left
+bindsym Mod1+Shift+r move down
+bindsym Mod1+Shift+t move up
+bindsym Mod1+Shift+d move right
+
+# alternatively, you can use the cursor keys:
+bindsym Mod1+Shift+Left move left
+bindsym Mod1+Shift+Right move right
+bindsym Mod1+Shift+Down move down
+bindsym Mod1+Shift+Up move up
+
+# Workspaces (Mod1+1/2/…)
+bindsym Mod1+1 workspace 1
+bindsym Mod1+2 workspace 2
+bindsym Mod1+3 workspace 3
+bindsym Mod1+4 workspace 4
+bindsym Mod1+5 workspace 5
+bindsym Mod1+6 workspace 6
+bindsym Mod1+7 workspace 7
+bindsym Mod1+8 workspace 8
+bindsym Mod1+9 workspace 9
+bindsym Mod1+0 workspace 10
+
+# Move to Workspaces
+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
index a5aa143d0175f50989cca60a3252296896879a2b..0db3b83b3e824cb4bb241c304b02b6690c37a7ed 100644 (file)
@@ -1,13 +1,11 @@
 #!perl
+# vim:ts=4:sw=4:expandtab
 
-use Test::More tests => 4;
-use Test::Deep;
+use i3test;
 use X11::XCB qw(:all);
-use Data::Dumper;
-use Time::HiRes qw(sleep);
 
 BEGIN {
-       use_ok('X11::XCB::Window');
+    use_ok('X11::XCB::Window');
 }
 
 my $x = X11::XCB::Connection->new;
@@ -15,9 +13,9 @@ 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',
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => $original_rect,
+    background_color => '#C0C0C0',
 );
 
 isa_ok($window, 'X11::XCB::Window');
@@ -31,4 +29,4 @@ sleep(0.25);
 my $new_rect = $window->rect;
 ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
 
-diag( "Testing i3, Perl $], $^X" );
+done_testing;
index 40a7d983b63c2a6793adf1b96f9c794756c29766..34e5364e495c821255937cf82d6360b980be96d4 100644 (file)
@@ -1,26 +1,47 @@
 #!perl
+# vim:ts=4:sw=4:expandtab
 
-use Test::More tests => 8;
-use Test::Deep;
+use i3test;
 use X11::XCB qw(:all);
-use Data::Dumper;
+use List::Util qw(first);
 
-# We use relatively long sleeps (1/4 second) to make sure the window manager
-# reacted.
-use Time::HiRes qw(sleep);
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+sub fullscreen_windows {
+    scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($tmp)}
+}
+
+# get the output of this workspace
+my $tree = $i3->get_tree->recv;
+my @outputs = @{$tree->{nodes}};
+my $output;
+for my $o (@outputs) {
+    # get the first CT_CON of each output
+    my $content = first { $_->{type} == 2 } @{$o->{nodes}};
+    if (defined(first { $_->{name} eq $tmp } @{$content->{nodes}})) {
+        $output = $o;
+        last;
+    }
+}
 
 BEGIN {
-       use_ok('X11::XCB::Window');
+    use_ok('X11::XCB::Window');
 }
 
 my $x = X11::XCB::Connection->new;
 
+##################################
+# map a window, then fullscreen it
+##################################
+
 my $original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
 
 my $window = $x->root->create_child(
-                class => WINDOW_CLASS_INPUT_OUTPUT,
-               rect => $original_rect,
-               background_color => '#C0C0C0',
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => $original_rect,
+    background_color => '#C0C0C0',
 );
 
 isa_ok($window, 'X11::XCB::Window');
@@ -29,27 +50,50 @@ is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
 
 $window->map;
 
-sleep(0.25);
+sleep 0.25;
+
+# open another container to make the window get only half of the screen
+cmd 'open';
 
 my $new_rect = $window->rect;
 ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
 $original_rect = $new_rect;
 
-sleep(0.25);
+sleep 0.25;
 
 $window->fullscreen(1);
 
-sleep(0.25);
+sleep 0.25;
 
 $new_rect = $window->rect;
 ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen");
 
+my $orect = $output->{rect};
+my $wrect = $new_rect;
+
+# see if the window really is fullscreen. 20 px for borders are allowed
+my $threshold = 20;
+ok(($wrect->{x} - $orect->{x}) < $threshold, 'x coordinate fullscreen');
+ok(($wrect->{y} - $orect->{y}) < $threshold, 'y coordinate fullscreen');
+ok(abs($wrect->{width} - $orect->{width}) < $threshold, 'width coordinate fullscreen');
+ok(abs($wrect->{height} - $orect->{height}) < $threshold, 'height coordinate fullscreen');
+
+
 $window->unmap;
 
+#########################################################
+# test with a window which is fullscreened before mapping
+#########################################################
+
+# open another container because the empty one will swallow the window we
+# map in a second
+cmd 'open';
+
+$original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
 $window = $x->root->create_child(
-       class => WINDOW_CLASS_INPUT_OUTPUT,
-       rect => $original_rect,
-       background_color => 61440,
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => $original_rect,
+    background_color => 61440,
 );
 
 is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
@@ -59,7 +103,68 @@ $window->map;
 
 sleep(0.25);
 
+$new_rect = $window->rect;
 ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen");
 ok($window->mapped, "Window is mapped after opening it in fullscreen mode");
 
-diag( "Testing i3, Perl $], $^X" );
+$wrect = $new_rect;
+
+# see if the window really is fullscreen. 20 px for borders are allowed
+ok(($wrect->{x} - $orect->{x}) < $threshold, 'x coordinate fullscreen');
+ok(($wrect->{y} - $orect->{y}) < $threshold, 'y coordinate fullscreen');
+ok(abs($wrect->{width} - $orect->{width}) < $threshold, 'width coordinate fullscreen');
+ok(abs($wrect->{height} - $orect->{height}) < $threshold, 'height coordinate fullscreen');
+
+###############################################################################
+# test if setting two windows in fullscreen mode at the same time does not work
+###############################################################################
+
+$original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
+my $swindow = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => $original_rect,
+    background_color => '#C0C0C0',
+);
+
+$swindow->map;
+sleep 0.25;
+
+ok(!$swindow->mapped, 'window not mapped while fullscreen window active');
+
+$new_rect = $swindow->rect;
+ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
+
+$swindow->fullscreen(1);
+sleep 0.25;
+
+is(fullscreen_windows(), 1, 'amount of fullscreen windows');
+
+$window->fullscreen(0);
+sleep 0.25;
+is(fullscreen_windows(), 0, 'amount of fullscreen windows');
+
+ok($swindow->mapped, 'window mapped after other fullscreen ended');
+
+###########################################################################
+# as $swindow is out of state at the moment (it requested to be fullscreen,
+# but the WM denied), we check what happens if we go out of fullscreen now
+# (nothing should happen)
+###########################################################################
+
+$swindow->fullscreen(0);
+sleep 0.25;
+
+is(fullscreen_windows(), 0, 'amount of fullscreen windows after disabling');
+
+cmd 'fullscreen';
+
+is(fullscreen_windows(), 1, 'amount of fullscreen windows after fullscreen command');
+
+cmd 'fullscreen';
+
+is(fullscreen_windows(), 0, 'amount of fullscreen windows after fullscreen command');
+
+# clean up the workspace so that it will be cleaned when switching away
+cmd 'kill' for (@{get_ws_content($tmp)});
+
+done_testing;
index 3cd9833a4fa1afdd359468b51683f6005a5ef4a7..1ef934ee5f99f3f6bdddf18e1a66c2d9050187c7 100644 (file)
@@ -1,10 +1,8 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 
-use Test::More tests => 5;
-use Test::Deep;
+use i3test;
 use X11::XCB qw(:all);
-use Data::Dumper;
 
 BEGIN {
     use_ok('X11::XCB::Window');
@@ -32,4 +30,4 @@ isa_ok($new_rect, 'X11::XCB::Rect');
 
 is_deeply($new_rect, $original_rect, "window untouched");
 
-diag( "Testing i3, Perl $], $^X" );
+done_testing;
index 65bd09ad9e1fc9d9ac97459b0899828a64294a5a..d4aea8280771652a6173adc08a0274a2494cbaa2 100644 (file)
@@ -1,14 +1,8 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 
-use Test::More tests => 10;
-use Test::Deep;
-use X11::XCB qw(:all);
-use Data::Dumper;
-use Time::HiRes qw(sleep);
-use FindBin;
-use lib "$FindBin::Bin/lib";
 use i3test;
+use X11::XCB qw(:all);
 
 BEGIN {
     use_ok('X11::XCB::Window');
@@ -29,13 +23,13 @@ isa_ok($window, 'X11::XCB::Window');
 
 $window->map;
 
-sleep(0.25);
+sleep 0.25;
 
 my ($absolute, $top) = $window->rect;
 
 ok($window->mapped, 'Window is mapped');
-ok($absolute->{width} >= 75, 'i3 raised the width to 75');
-ok($absolute->{height} >= 50, 'i3 raised the height to 50');
+cmp_ok($absolute->{width}, '>=', 75, 'i3 raised the width to 75');
+cmp_ok($absolute->{height}, '>=', 50, 'i3 raised the height to 50');
 
 ok($absolute->{x} != 0 && $absolute->{y} != 0, 'i3 did not map it to (0x0)');
 
@@ -52,15 +46,18 @@ isa_ok($window, 'X11::XCB::Window');
 
 $window->map;
 
-sleep(0.25);
+sleep 0.25;
 
 ($absolute, $top) = $window->rect;
 
-ok($absolute->{width} == 80, "i3 let the width at 80");
-ok($absolute->{height} == 90, "i3 let the height at 90");
+cmp_ok($absolute->{width}, '==', 80, "i3 let the width at 80");
+cmp_ok($absolute->{height}, '==', 92, "i3 let the height at 90");
 
-ok($top->{x} == 1 && $top->{y} == 1, "i3 mapped it to (1,1)");
+# We need to compare the position with decorations due to the way
+# we do decoration rendering (on the parent frame) in the tree branch
+cmp_ok($top->{x}, '==', 1, 'i3 mapped it to x=1');
+cmp_ok($top->{y}, '==', 19, 'i3 mapped it to y=18');
 
 $window->unmap;
 
-diag( "Testing i3, Perl $], $^X" );
+done_testing;
index 8f4279389be7c4e90663fa6b0b909b372795d0ba..a910c930b19a87e377e1ffa2c57b05ff4de43231 100644 (file)
@@ -1,45 +1,34 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 
-use Test::More tests => 3;
-use Test::Deep;
-use X11::XCB qw(:all);
-use Data::Dumper;
-use Time::HiRes qw(sleep);
-use FindBin;
-use lib "$FindBin::Bin/lib";
 use i3test;
-use AnyEvent::I3;
+use X11::XCB qw(:all);
 
 BEGIN {
-    use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
     use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
 }
 
 my $x = X11::XCB::Connection->new;
 
-my $i3 = i3;
+fresh_workspace;
 
 #####################################################################
 # Ensure IPC works by switching workspaces
 #####################################################################
 
-# Switch to the first workspace to get a clean testing environment
-$i3->command('1')->recv;
-
 # Create a window so we can get a focus different from NULL
-my $window = i3test::open_standard_window($x);
+my $window = open_standard_window($x);
 diag("window->id = " . $window->id);
 
-sleep(0.25);
+sleep 0.25;
 
 my $focus = $x->input_focus;
 diag("old focus = $focus");
 
-# Switch to the nineth workspace
-$i3->command('9')->recv;
+# Switch to another workspace
+fresh_workspace;
 
 my $new_focus = $x->input_focus;
 isnt($focus, $new_focus, "Focus changed");
 
-diag( "Testing i3, Perl $], $^X" );
+done_testing;
index a95e0e40b959c2f47b31f94f4a7b4455c4137fd7..d357c8a9de2d5009d20c93cc7d37b8793e90ff75 100644 (file)
@@ -1,18 +1,8 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
-# Beware that this test uses workspace 9 to perform some tests (it expects
-# the workspace to be empty).
-# TODO: skip it by default?
 
-use Test::More tests => 13;
-use Test::Deep;
-use X11::XCB qw(:all);
-use Data::Dumper;
-use Time::HiRes qw(sleep);
-use FindBin;
-use lib "$FindBin::Bin/lib";
 use i3test;
-use AnyEvent::I3;
+use X11::XCB qw(:all);
 
 BEGIN {
     use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
@@ -20,22 +10,21 @@ BEGIN {
 
 my $x = X11::XCB::Connection->new;
 
-my $i3 = i3;
-
-# Switch to the nineth workspace
-$i3->command('9')->recv;
+my $i3 = i3(get_socket_path());
+my $tmp = fresh_workspace;
 
 #####################################################################
 # Create two windows and make sure focus switching works
 #####################################################################
 
 # Change mode of the container to "default" for following tests
-$i3->command('d')->recv;
+cmd 'layout default';
+cmd 'split v';
 
-my $top = i3test::open_standard_window($x);
-my $mid = i3test::open_standard_window($x);
-my $bottom = i3test::open_standard_window($x);
-sleep(0.25);
+my $top = open_standard_window($x);
+my $mid = open_standard_window($x);
+my $bottom = open_standard_window($x);
+sleep 0.25;
 
 diag("top id = " . $top->id);
 diag("mid id = " . $mid->id);
@@ -55,64 +44,64 @@ sub focus_after {
 $focus = $x->input_focus;
 is($focus, $bottom->id, "Latest window focused");
 
-$focus = focus_after("k");
+$focus = focus_after('focus up');
 is($focus, $mid->id, "Middle window focused");
 
-$focus = focus_after("k");
+$focus = focus_after('focus up');
 is($focus, $top->id, "Top window focused");
 
 #####################################################################
 # Test focus wrapping
 #####################################################################
 
-$focus = focus_after("k");
+$focus = focus_after('focus up');
 is($focus, $bottom->id, "Bottom window focused (wrapping to the top works)");
 
-$focus = focus_after("j");
+$focus = focus_after('focus down');
 is($focus, $top->id, "Top window focused (wrapping to the bottom works)");
 
 ###############################################
 # Test focus with empty containers and colspan
 ###############################################
 
-# Switch to the 10. workspace
-$i3->command('10')->recv;
-
-$top = i3test::open_standard_window($x);
-$bottom = i3test::open_standard_window($x);
-sleep 0.25;
-
-$focus = focus_after("mj");
-$focus = focus_after("mh");
-$focus = focus_after("k");
-is($focus, $bottom->id, "Selecting top window without snapping doesn't work");
-
-$focus = focus_after("sl");
-is($focus, $bottom->id, "Bottom window focused");
-
-$focus = focus_after("k");
-is($focus, $top->id, "Top window focused");
-
-# Same thing, but left/right instead of top/bottom
-
-# Switch to the 11. workspace
-$i3->command('11')->recv;
-
-my $left = i3test::open_standard_window($x);
-my $right = i3test::open_standard_window($x);
-sleep 0.25;
-
-$focus = focus_after("ml");
-$focus = focus_after("h");
-$focus = focus_after("mk");
-$focus = focus_after("l");
-is($focus, $left->id, "Selecting right window without snapping doesn't work");
-
-$focus = focus_after("sj");
-is($focus, $left->id, "left window focused");
-
-$focus = focus_after("l");
-is($focus, $right->id, "right window focused");
+#my $otmp = get_unused_workspace();
+#$i3->command("workspace $otmp")->recv;
+#
+#$top = i3test::open_standard_window($x);
+#$bottom = i3test::open_standard_window($x);
+#sleep 0.25;
+#
+#$focus = focus_after("mj");
+#$focus = focus_after("mh");
+#$focus = focus_after("k");
+#is($focus, $bottom->id, "Selecting top window without snapping doesn't work");
+#
+#$focus = focus_after("sl");
+#is($focus, $bottom->id, "Bottom window focused");
+#
+#$focus = focus_after("k");
+#is($focus, $top->id, "Top window focused");
+#
+## Same thing, but left/right instead of top/bottom
+#
+#my $o2tmp = get_unused_workspace();
+#$i3->command("workspace $o2tmp")->recv;
+#
+#my $left = i3test::open_standard_window($x);
+#my $right = i3test::open_standard_window($x);
+#sleep 0.25;
+#
+#$focus = focus_after("ml");
+#$focus = focus_after("h");
+#$focus = focus_after("mk");
+#$focus = focus_after("l");
+#is($focus, $left->id, "Selecting right window without snapping doesn't work");
+#
+#$focus = focus_after("sj");
+#is($focus, $left->id, "left window focused");
+#
+#$focus = focus_after("l");
+#is($focus, $right->id, "right window focused");
 
 
-diag( "Testing i3, Perl $], $^X" );
+done_testing;
index efd3df15da29ab703908563362142e4218368048..6e35ebe49ee7c0ba5847b4077bc4bf2b0d81e701 100644 (file)
@@ -4,20 +4,17 @@
 # the workspace to be empty).
 # TODO: skip it by default?
 
-use Test::More tests => 8;
-use Test::Deep;
+use i3test tests => 8;
 use X11::XCB qw(:all);
-use Data::Dumper;
 use Time::HiRes qw(sleep);
-use FindBin;
-use lib "$FindBin::Bin/lib";
-use i3test;
-use AnyEvent::I3;
 
 BEGIN {
     use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
 }
 
+SKIP: {
+    skip "Testcase not yet modified for new move concept", 7;
+
 my $x = X11::XCB::Connection->new;
 
 my $i3 = i3;
@@ -82,3 +79,4 @@ for my $cmd (qw(m12 t m13 12 13)) {
     $i3->command($cmd)->recv;
 }
 ok(1, "Still living");
+}
index 4ae924070e4c7dca84c33e1297a21dac8ee6141d..f8143979164e777cc19b5e6f9b5ce3d370eb6652 100644 (file)
@@ -3,15 +3,8 @@
 # Checks if the focus is correctly restored, when creating a floating client
 # over an unfocused tiling client and destroying the floating one again.
 
-use Test::More tests => 4;
-use Test::Deep;
-use X11::XCB qw(:all);
-use Data::Dumper;
-use Time::HiRes qw(sleep);
-use FindBin;
-use lib "$FindBin::Bin/lib";
 use i3test;
-use AnyEvent::I3;
+use X11::XCB qw(:all);
 
 BEGIN {
     use_ok('X11::XCB::Window') or BAIL_OUT('Could not load X11::XCB::Window');
@@ -19,17 +12,14 @@ BEGIN {
 
 my $x = X11::XCB::Connection->new;
 
-my $i3 = i3;
-
-# Switch to the nineth workspace
-$i3->command('9')->recv;
-
-my $tiled_left = i3test::open_standard_window($x);
-my $tiled_right = i3test::open_standard_window($x);
+my $i3 = i3(get_socket_path());
+fresh_workspace;
 
-sleep(0.25);
+cmd 'split h';
+my $tiled_left = open_standard_window($x);
+my $tiled_right = open_standard_window($x);
 
-$i3->command('ml')->recv;
+sleep 0.25;
 
 # Get input focus before creating the floating window
 my $focus = $x->input_focus;
@@ -46,13 +36,14 @@ isa_ok($window, 'X11::XCB::Window');
 
 $window->map;
 
-sleep(0.25);
+sleep 1;
+sleep 0.25;
 is($x->input_focus, $window->id, 'floating window focused');
 
 $window->unmap;
 
-sleep(0.25);
+sleep 0.25;
 
 is($x->input_focus, $focus, 'Focus correctly restored');
 
-diag( "Testing i3, Perl $], $^X" );
+done_testing;
index 59d2e6f467119308f61875c21bae592b734e044a..1cb205ed9a192188206303973a90bedee600fcc0 100644 (file)
@@ -4,20 +4,17 @@
 # the workspace to be empty).
 # TODO: skip it by default?
 
-use Test::More tests => 22;
-use Test::Deep;
+use i3test tests => 22;
 use X11::XCB qw(:all);
-use Data::Dumper;
 use Time::HiRes qw(sleep);
-use FindBin;
-use lib "$FindBin::Bin/lib";
-use i3test;
-use AnyEvent::I3;
 
 BEGIN {
     use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
 }
 
+SKIP: {
+    skip "stacking test not yet updated", 21;
+
 my $x = X11::XCB::Connection->new;
 
 my $i3 = i3;
@@ -132,3 +129,4 @@ is($focus, $bottom->id, "Window above is bottom");
 $focus = focus_after("k");
 is($focus, $mid->id, "Window above is mid");
 
+}
index 5206313133a4ee0453f1ea6b87567e216891771b..d4da82451560fcb843f09e36e08eb0ba9bbe661e 100644 (file)
@@ -1,14 +1,8 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 
-use Test::More tests => 2;
-use Test::Deep;
-use X11::XCB qw(:all);
-use Data::Dumper;
-use Time::HiRes qw(sleep);
-use FindBin;
-use lib "$FindBin::Bin/lib";
 use i3test;
+use X11::XCB qw(:all);
 use List::Util qw(first);
 
 BEGIN {
@@ -16,6 +10,15 @@ BEGIN {
 }
 
 my $x = X11::XCB::Connection->new;
+my $i3 = i3(get_socket_path());
+
+#####################################################################
+# verify that there is no dock window yet
+#####################################################################
+
+# Children of all dockareas
+my @docked = get_dock_clients;
+is(@docked, 0, 'no dock clients yet');
 
 #####################################################################
 # Create a dock window and see if it gets managed
@@ -32,7 +35,7 @@ my $window = $x->root->create_child(
     class => WINDOW_CLASS_INPUT_OUTPUT,
     rect => [ 0, 0, 30, 30],
     background_color => '#FF0000',
-    type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
 );
 
 $window->map;
@@ -41,12 +44,142 @@ sleep 0.25;
 
 my $rect = $window->rect;
 is($rect->width, $primary->rect->width, 'dock client is as wide as the screen');
+is($rect->height, 30, 'height is unchanged');
+
+#####################################################################
+# check that we can find it in the layout tree at the expected position
+#####################################################################
+
+@docked = get_dock_clients('top');
+is(@docked, 1, 'one dock client found');
+
+# verify the position/size
+my $docknode = $docked[0];
+
+is($docknode->{rect}->{x}, 0, 'dock node placed at x=0');
+is($docknode->{rect}->{y}, 0, 'dock node placed at y=0');
+is($docknode->{rect}->{width}, $primary->rect->width, 'dock node as wide as the screen');
+is($docknode->{rect}->{height}, 30, 'dock node has unchanged height');
+
+$window->destroy;
+
+sleep 0.25;
+
+@docked = get_dock_clients();
+is(@docked, 0, 'no more dock clients');
+
+#####################################################################
+# check if it gets placed on bottom (by coordinates)
+#####################################################################
+
+$window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 1000, 30, 30],
+    background_color => '#FF0000',
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+);
+
+$window->map;
+
+sleep 0.25;
+
+my $rect = $window->rect;
+is($rect->width, $primary->rect->width, 'dock client is as wide as the screen');
+is($rect->height, 30, 'height is unchanged');
+
+@docked = get_dock_clients('bottom');
+is(@docked, 1, 'dock client on bottom');
+
+$window->destroy;
+
+sleep 0.25;
+
+@docked = get_dock_clients();
+is(@docked, 0, 'no more dock clients');
+
+#####################################################################
+# check if it gets placed on bottom (by hint)
+#####################################################################
+
+$window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 1000, 30, 30],
+    background_color => '#FF0000',
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+);
+
+$window->_create();
+
+# Add a _NET_WM_STRUT_PARTIAL hint
+my $atomname = $x->atom(name => '_NET_WM_STRUT_PARTIAL');
+my $atomtype = $x->atom(name => 'CARDINAL');
+
+$x->change_property(
+    PROP_MODE_REPLACE,
+    $window->id,
+    $atomname->id,
+    $atomtype->id,
+    32,         # 32 bit integer
+    12,
+    pack('L12', 0, 0, 16, 0, 0, 0, 0, 0, 0, 1280, 0, 0)
+);
+
+$window->map;
+
+sleep 0.25;
+
+@docked = get_dock_clients('top');
+is(@docked, 1, 'dock client on top');
+
+$window->destroy;
+
+sleep 0.25;
+
+@docked = get_dock_clients();
+is(@docked, 0, 'no more dock clients');
+
+$window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 1000, 30, 30],
+    background_color => '#FF0000',
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+);
+
+$window->_create();
+
+# Add a _NET_WM_STRUT_PARTIAL hint
+my $atomname = $x->atom(name => '_NET_WM_STRUT_PARTIAL');
+my $atomtype = $x->atom(name => 'CARDINAL');
+
+$x->change_property(
+    PROP_MODE_REPLACE,
+    $window->id,
+    $atomname->id,
+    $atomtype->id,
+    32,         # 32 bit integer
+    12,
+    pack('L12', 0, 0, 0, 16, 0, 0, 0, 0, 0, 1280, 0, 0)
+);
+
+$window->map;
+
+sleep 0.25;
+
+@docked = get_dock_clients('bottom');
+is(@docked, 1, 'dock client on bottom');
+
+$window->destroy;
+
+
+#####################################################################
+# regression test: transient dock client
+#####################################################################
 
 my $fwindow = $x->root->create_child(
     class => WINDOW_CLASS_INPUT_OUTPUT,
     rect => [ 0, 0, 30, 30],
     background_color => '#FF0000',
-    type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
 );
 
 $fwindow->transient_for($window);
@@ -54,5 +187,6 @@ $fwindow->map;
 
 sleep 0.25;
 
+does_i3_live;
 
-diag( "Testing i3, Perl $], $^X" );
+done_testing;
index 9b06112b172832af6e18bad9f214f6c9a959d04e..542dc828bd146fa316fc881493f603152ada0249 100644 (file)
@@ -1,19 +1,9 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
-# Beware that this test uses workspace 9 to perform some tests (it expects
-# the workspace to be empty).
-# TODO: skip it by default?
 
-use Test::More tests => 7;
-use Test::Deep;
+use i3test;
 use X11::XCB qw(:all);
-use Data::Dumper;
-use Time::HiRes qw(sleep);
-use FindBin;
 use Digest::SHA1 qw(sha1_base64);
-use lib "$FindBin::Bin/lib";
-use i3test;
-use AnyEvent::I3;
 
 BEGIN {
     use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
@@ -21,21 +11,21 @@ BEGIN {
 
 my $x = X11::XCB::Connection->new;
 
-my $i3 = i3;
+my $i3 = i3(get_socket_path());
+my $tmp = fresh_workspace;
 
-# Switch to the nineth workspace
-$i3->command('9')->recv;
+cmd 'split h';
 
 #####################################################################
 # Create two windows and make sure focus switching works
 #####################################################################
 
-my $top = i3test::open_standard_window($x);
-sleep(0.25);
-my $mid = i3test::open_standard_window($x);
-sleep(0.25);
-my $bottom = i3test::open_standard_window($x);
-sleep(0.25);
+my $top = open_standard_window($x);
+sleep 0.25;
+my $mid = open_standard_window($x);
+sleep 0.25;
+my $bottom = open_standard_window($x);
+sleep 0.25;
 
 diag("top id = " . $top->id);
 diag("mid id = " . $mid->id);
@@ -48,17 +38,14 @@ diag("bottom id = " . $bottom->id);
 sub focus_after {
     my $msg = shift;
 
-    $i3->command($msg)->recv;
+    cmd $msg;
     return $x->input_focus;
 }
 
 $focus = $x->input_focus;
 is($focus, $bottom->id, "Latest window focused");
 
-$focus = focus_after("ml");
-is($focus, $bottom->id, "Right window still focused");
-
-$focus = focus_after("h");
+$focus = focus_after('focus left');
 is($focus, $mid->id, "Middle window focused");
 
 #####################################################################
@@ -67,14 +54,36 @@ is($focus, $mid->id, "Middle window focused");
 
 my $random_mark = sha1_base64(rand());
 
-$focus = focus_after("goto $random_mark");
+$focus = focus_after(qq|[con_mark="$random_mark"] focus|);
 is($focus, $mid->id, "focus unchanged");
 
 $i3->command("mark $random_mark")->recv;
 
-$focus = focus_after("k");
+$focus = focus_after('focus left');
+is($focus, $top->id, "Top window focused");
+
+$focus = focus_after(qq|[con_mark="$random_mark"] focus|);
+is($focus, $mid->id, "goto worked");
+
+# check that we can specify multiple criteria
+
+$focus = focus_after('focus left');
 is($focus, $top->id, "Top window focused");
 
-$focus = focus_after("goto $random_mark");
+$focus = focus_after(qq|[con_mark="$random_mark" con_mark="$random_mark"] focus|);
 is($focus, $mid->id, "goto worked");
 
+#####################################################################
+# Check whether the focus command will switch to a different
+# workspace if necessary
+#####################################################################
+
+my $tmp2 = fresh_workspace;
+
+is(focused_ws(), $tmp2, 'tmp2 now focused');
+
+cmd qq|[con_mark="$random_mark"] focus|;
+
+is(focused_ws(), $tmp, 'tmp now focused');
+
+done_testing;
index 74f66535013c12e11a5ac5f816467dd0bf080dbb..09297df0d6015eb6551900f3801215ba3329b10a 100644 (file)
@@ -4,16 +4,8 @@
 # the workspace to be empty).
 # TODO: skip it by default?
 
-use Test::More tests => 15;
-use Test::Deep;
-use X11::XCB qw(:all);
-use Data::Dumper;
-use Time::HiRes qw(sleep);
-use FindBin;
-use Digest::SHA1 qw(sha1_base64);
-use lib "$FindBin::Bin/lib";
 use i3test;
-use AnyEvent::I3;
+use X11::XCB qw(:all);
 
 BEGIN {
     use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
@@ -21,10 +13,7 @@ BEGIN {
 
 my $x = X11::XCB::Connection->new;
 
-my $i3 = i3;
-
-# Switch to the nineth workspace
-$i3->command('9')->recv;
+fresh_workspace;
 
 #####################################################################
 # Create a floating window and see if resizing works
@@ -36,7 +25,7 @@ my $window = $x->root->create_child(
     rect => [ 0, 0, 30, 30],
     background_color => '#C0C0C0',
     # replace the type with 'utility' as soon as the coercion works again in X11::XCB
-    type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
 );
 
 isa_ok($window, 'X11::XCB::Window');
@@ -75,11 +64,13 @@ sub test_resize {
 test_resize;
 
 # Test borderless
-$i3->command('bb')->recv;
+cmd 'border none';
 
 test_resize;
 
 # Test with 1-px-border
-$i3->command('bp')->recv;
+cmd 'border 1pixel';
 
 test_resize;
+
+done_testing;
index 5fce6aeeccc08a4690d9b89007eb6e519e3c96b3..f40b72fb9c7691b9894b1da2d0d02317db9cfd85 100644 (file)
@@ -1,19 +1,9 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
-# Beware that this test uses workspace 9 to perform some tests (it expects
-# the workspace to be empty).
-# TODO: skip it by default?
 
-use Test::More tests => 7;
-use Test::Deep;
-use X11::XCB qw(:all);
-use Data::Dumper;
-use Time::HiRes qw(sleep);
-use FindBin;
-use Digest::SHA1 qw(sha1_base64);
-use lib "$FindBin::Bin/lib";
 use i3test;
-use AnyEvent::I3;
+use X11::XCB qw(:all);
+use List::Util qw(first);
 
 BEGIN {
     use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
@@ -21,33 +11,65 @@ BEGIN {
 
 my $x = X11::XCB::Connection->new;
 
-my $i3 = i3;
-
-# Switch to the nineth workspace
-$i3->command('9')->recv;
+my $tmp = fresh_workspace;
 
 #####################################################################
 # Create two windows and put them in stacking mode
 #####################################################################
 
-my $top = i3test::open_standard_window($x);
-sleep 0.25;
-my $bottom = i3test::open_standard_window($x);
-sleep 0.25;
+cmd 'split v';
+
+my $top = open_standard_window($x);
+my $bottom = open_standard_window($x);
+
+my @urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
+is(@urgent, 0, 'no window got the urgent flag');
 
-$i3->command('s')->recv;
+# cmd 'layout stacking';
 
 #####################################################################
 # Add the urgency hint, switch to a different workspace and back again
 #####################################################################
 $top->add_hint('urgency');
-sleep 1;
+sleep 0.5;
+
+@content = @{get_ws_content($tmp)};
+@urgent = grep { $_->{urgent} } @content;
+$top_info = first { $_->{window} == $top->id } @content;
+$bottom_info = first { $_->{window} == $bottom->id } @content;
+
+ok($top_info->{urgent}, 'top window is marked urgent');
+ok(!$bottom_info->{urgent}, 'bottom window is not marked urgent');
+is(@urgent, 1, 'exactly one window got the urgent flag');
+
+cmd '[id="' . $top->id . '"] focus';
+
+@urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
+is(@urgent, 0, 'no window got the urgent flag after focusing');
+
+$top->add_hint('urgency');
+sleep 0.5;
+
+@urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
+is(@urgent, 0, 'no window got the urgent flag after re-setting urgency hint');
+
+#####################################################################
+# Check if the workspace urgency hint gets set/cleared correctly
+#####################################################################
+my $ws = get_ws($tmp);
+ok(!$ws->{urgent}, 'urgent flag not set on workspace');
+
+my $otmp = fresh_workspace;
+
+$top->add_hint('urgency');
+sleep 0.5;
+
+$ws = get_ws($tmp);
+ok($ws->{urgent}, 'urgent flag set on workspace');
+
+cmd "workspace $tmp";
 
-$i3->command('1')->recv;
-$i3->command('9')->recv;
-$i3->command('1')->recv;
+$ws = get_ws($tmp);
+ok(!$ws->{urgent}, 'urgent flag not set on workspace after switching');
 
-my $std = i3test::open_standard_window($x);
-sleep 0.25;
-$std->add_hint('urgency');
-sleep 1;
+done_testing;
index b9160131e76afb88e7eba48020ee97dc7ab41241..98978eb3a76ed50343883d66104ca99d09e4cb50 100644 (file)
 #!perl
 # vim:ts=4:sw=4:expandtab
-# Beware that this test uses workspace 9 and 10 to perform some tests (it expects
-# the workspace to be empty).
-# TODO: skip it by default?
 
-use Test::More tests => 3;
-use Test::Deep;
-use X11::XCB qw(:all);
-use Data::Dumper;
-use Time::HiRes qw(sleep);
-use FindBin;
-use Digest::SHA1 qw(sha1_base64);
-use lib "$FindBin::Bin/lib";
 use i3test;
-use AnyEvent::I3;
+use X11::XCB qw(:all);
 
 BEGIN {
     use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
 }
 
 my $x = X11::XCB::Connection->new;
-my $i3 = i3;
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+####################################################################################
+# first part: test if a floating window will be correctly positioned above its leader
+#
+# This is verified by opening two windows, then opening a floating window above the
+# right one, then above the left one. If the floating windows are all positioned alike,
+# one of both (depending on your screen resolution) will be positioned wrong.
+####################################################################################
+
+my $left = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [0, 0, 30, 30],
+    background_color => '#FF0000',
+);
+
+$left->name('Left');
+$left->map;
+
+my $right = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [0, 0, 30, 30],
+    background_color => '#FF0000',
+);
+
+$right->name('Right');
+$right->map;
 
-# Switch to the nineth workspace
-$i3->command('9')->recv;
+sleep 0.25;
+
+my ($abs, $rgeom) = $right->rect;
+
+my $child = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#C0C0C0',
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
+);
+
+$child->name('Child window');
+$child->client_leader($right);
+$child->map;
+
+sleep 0.25;
+
+my $cgeom;
+($abs, $cgeom) = $child->rect;
+cmp_ok($cgeom->x, '>=', $rgeom->x, 'Child X >= right container X');
+
+my $child2 = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#C0C0C0',
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
+);
+
+$child2->name('Child window 2');
+$child2->client_leader($left);
+$child2->map;
+
+sleep 0.25;
+
+($abs, $cgeom) = $child2->rect;
+cmp_ok(($cgeom->x + $cgeom->width), '<', $rgeom->x, 'child above left window');
+
+# check wm_transient_for
+
+
+my $fwindow = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30],
+    background_color => '#FF0000',
+);
+
+$fwindow->transient_for($right);
+$fwindow->map;
+
+sleep 0.25;
+
+my ($absolute, $top) = $fwindow->rect;
+ok($absolute->{x} != 0 && $absolute->{y} != 0, 'i3 did not map it to (0x0)');
+
+SKIP: {
+    skip "(workspace placement by client_leader not yet implemented)", 3;
 
 #####################################################################
 # Create a parent window
@@ -41,10 +112,10 @@ $window->map;
 sleep 0.25;
 
 #########################################################################
-# Switch workspace to 10 and open a child window. It should be positioned
-# on workspace 9.
+# Switch to a different workspace and open a child window. It should be opened
+# on the old workspace.
 #########################################################################
-$i3->command('10')->recv;
+fresh_workspace;
 
 my $child = $x->root->create_child(
 class => WINDOW_CLASS_INPUT_OUTPUT,
@@ -61,6 +132,10 @@ sleep 0.25;
 isnt($x->input_focus, $child->id, "Child window focused");
 
 # Switch back
-$i3->command('9')->recv;
+cmd "workspace $tmp";
 
 is($x->input_focus, $child->id, "Child window focused");
+
+}
+
+done_testing;
index 4e2c0e8daf5e5b2b96f50bf9b8f4cfc02350004e..085163b29419cc17bdf8c5f282aec9761969894e 100644 (file)
@@ -1,20 +1,18 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 
-use Test::More tests => 3;
-use Test::Exception;
-use List::MoreUtils qw(all);
-use FindBin;
-use lib "$FindBin::Bin/lib";
 use i3test;
-use AnyEvent::I3;
+use List::MoreUtils qw(all);
 
-my $i3 = i3;
+my $i3 = i3(get_socket_path());
 
 ####################
 # Request workspaces
 ####################
 
+SKIP: {
+    skip "IPC API not yet stabilized", 2;
+
 my $workspaces = $i3->get_workspaces->recv;
 
 ok(@{$workspaces} > 0, "More than zero workspaces found");
@@ -22,4 +20,6 @@ ok(@{$workspaces} > 0, "More than zero workspaces found");
 my $name_exists = all { defined($_->{name}) } @{$workspaces};
 ok($name_exists, "All workspaces have a name");
 
-diag( "Testing i3, Perl $], $^X" );
+}
+
+done_testing;
diff --git a/testcases/t/16-nestedcons.t b/testcases/t/16-nestedcons.t
new file mode 100644 (file)
index 0000000..f9d2726
--- /dev/null
@@ -0,0 +1,68 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use List::MoreUtils qw(all none);
+use List::Util qw(first);
+
+my $i3 = i3(get_socket_path());
+
+####################
+# Request tree
+####################
+
+my $tree = $i3->get_tree->recv;
+
+my $expected = {
+    fullscreen_mode => 0,
+    nodes => ignore(),
+    window => undef,
+    name => 'root',
+    orientation => ignore(),
+    type => 0,
+    id => ignore(),
+    rect => ignore(),
+    window_rect => ignore(),
+    geometry => ignore(),
+    swallows => ignore(),
+    percent => undef,
+    layout => 'default',
+    focus => ignore(),
+    focused => JSON::XS::false,
+    urgent => JSON::XS::false,
+    border => 'normal',
+    'floating_nodes' => ignore(),
+};
+
+cmp_deeply($tree, $expected, 'root node OK');
+
+my @nodes = @{$tree->{nodes}};
+
+ok(@nodes > 0, 'root node has at least one leaf');
+
+ok((all { $_->{type} == 1 } @nodes), 'all nodes are of type CT_OUTPUT');
+ok((none { defined($_->{window}) } @nodes), 'no CT_OUTPUT contains a window');
+ok((all { @{$_->{nodes}} > 0 } @nodes), 'all nodes have at least one leaf (workspace)');
+my @workspaces;
+for my $ws (@nodes) {
+    my $content = first { $_->{type} == 2 } @{$ws->{nodes}};
+    @workspaces = (@workspaces, @{$content->{nodes}});
+}
+
+ok((all { $_->{type} == 4 } @workspaces), 'all workspaces are of type CT_WORKSPACE');
+#ok((all { @{$_->{nodes}} == 0 } @workspaces), 'all workspaces are empty yet');
+ok((none { defined($_->{window}) } @workspaces), 'no CT_OUTPUT contains a window');
+
+# TODO: get the focused container
+
+$i3->command('open')->recv;
+
+# TODO: get the focused container, check if it changed.
+# TODO: get the old focused container, check if there is a new child
+
+#diag(Dumper(\@workspaces));
+
+#diag(Dumper($tree));
+
+
+done_testing;
diff --git a/testcases/t/17-workspace.t b/testcases/t/17-workspace.t
new file mode 100644 (file)
index 0000000..19e2df3
--- /dev/null
@@ -0,0 +1,101 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests whether we can switch to a non-existant workspace
+# (necessary for further tests)
+#
+use List::Util qw(first);
+use i3test;
+
+# to ensure that workspace 1 stays open
+cmd 'open';
+
+my $tmp = fresh_workspace;
+ok(workspace_exists($tmp), 'workspace created');
+# if the workspace could not be created, we cannot run any other test
+# (every test starts by creating its workspace)
+if (!workspace_exists($tmp)) {
+    BAIL_OUT('Cannot create workspace, further tests make no sense');
+}
+
+my $otmp = fresh_workspace;
+diag("Other temporary workspace name: $otmp\n");
+
+# As the old workspace was empty, it should get
+# cleaned up as we switch away from it
+cmd "workspace $otmp";
+ok(!workspace_exists($tmp), 'old workspace cleaned up');
+
+# Switch to the same workspace again to make sure it doesn’t get cleaned up
+cmd "workspace $otmp";
+cmd "workspace $otmp";
+ok(workspace_exists($otmp), 'other workspace still exists');
+
+
+#####################################################################
+# check if the workspace next / prev commands work
+#####################################################################
+
+cmd 'workspace next';
+
+ok(!workspace_exists('next'), 'workspace "next" does not exist');
+
+cmd "workspace $tmp";
+cmd 'open';
+
+ok(workspace_exists($tmp), 'workspace created');
+
+cmd "workspace $otmp";
+cmd 'open';
+
+ok(workspace_exists($tmp), 'workspace tmp still exists');
+ok(workspace_exists($otmp), 'workspace otmp created');
+
+is(focused_ws(), $otmp, 'focused workspace is otmp');
+
+cmd 'workspace prev';
+is(focused_ws(), $tmp, 'focused workspace is tmp after workspace prev');
+
+cmd 'workspace next';
+is(focused_ws(), $otmp, 'focused workspace is otmp after workspace next');
+
+
+#####################################################################
+# check that wrapping works
+#####################################################################
+
+cmd 'workspace next';
+is(focused_ws(), '1', 'focused workspace is 1 after workspace next');
+
+cmd 'workspace next';
+is(focused_ws(), $tmp, 'focused workspace is tmp after workspace next');
+
+cmd 'workspace next';
+is(focused_ws(), $otmp, 'focused workspace is otmp after workspace next');
+
+
+cmd 'workspace prev';
+is(focused_ws(), $tmp, 'focused workspace is tmp after workspace prev');
+
+cmd 'workspace prev';
+is(focused_ws(), '1', 'focused workspace is tmp after workspace prev');
+
+cmd 'workspace prev';
+is(focused_ws(), $otmp, 'focused workspace is otmp after workspace prev');
+
+
+#####################################################################
+# check if we can change to "next" / "prev"
+#####################################################################
+
+cmd 'workspace "next"';
+
+ok(workspace_exists('next'), 'workspace "next" exists');
+is(focused_ws(), 'next', 'now on workspace next');
+
+cmd 'workspace "prev"';
+
+ok(workspace_exists('prev'), 'workspace "prev" exists');
+is(focused_ws(), 'prev', 'now on workspace prev');
+
+done_testing;
diff --git a/testcases/t/18-openkill.t b/testcases/t/18-openkill.t
new file mode 100644 (file)
index 0000000..e2a729c
--- /dev/null
@@ -0,0 +1,40 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests whether opening an empty container and killing it again works
+#
+use List::Util qw(first);
+use i3test;
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+# Open a new container
+cmd 'open';
+
+ok(@{get_ws_content($tmp)} == 1, 'container opened');
+
+cmd 'kill';
+ok(@{get_ws_content($tmp)} == 0, 'container killed');
+
+##############################################################
+# open two containers and kill the one which is not focused
+# by its ID to test if the parser correctly matches the window
+##############################################################
+
+cmd 'open';
+cmd 'open';
+ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
+
+my $content = get_ws_content($tmp);
+my $not_focused = first { !$_->{focused} } @{$content};
+my $id = $not_focused->{id};
+
+cmd "[con_id=\"$id\"] kill";
+
+$content = get_ws_content($tmp);
+ok(@{$content} == 1, 'one container killed');
+ok($content->[0]->{id} != $id, 'correct window killed');
+
+done_testing;
diff --git a/testcases/t/19-match.t b/testcases/t/19-match.t
new file mode 100644 (file)
index 0000000..2332bc7
--- /dev/null
@@ -0,0 +1,121 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests all kinds of matching methods
+#
+use i3test;
+use X11::XCB qw(:all);
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+# Open a new window
+my $x = X11::XCB::Connection->new;
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#C0C0C0',
+);
+
+$window->map;
+# give it some time to be picked up by the window manager
+# TODO: better check for $window->mapped or something like that?
+# maybe we can even wait for getting mapped?
+my $c = 0;
+while (@{get_ws_content($tmp)} == 0 and $c++ < 5) {
+    sleep 0.25;
+}
+my $content = get_ws_content($tmp);
+ok(@{$content} == 1, 'window mapped');
+my $win = $content->[0];
+
+######################################################################
+# first test that matches which should not match this window really do
+# not match it
+######################################################################
+# TODO: use PCRE expressions
+# TODO: specify more match types
+cmd q|[class="*"] kill|;
+cmd q|[con_id="99999"] kill|;
+
+$content = get_ws_content($tmp);
+ok(@{$content} == 1, 'window still there');
+
+# now kill the window
+cmd 'nop now killing the window';
+my $id = $win->{id};
+cmd qq|[con_id="$id"] kill|;
+
+# give i3 some time to pick up the UnmapNotify event
+sleep 0.25;
+
+cmd 'nop checking if its gone';
+$content = get_ws_content($tmp);
+ok(@{$content} == 0, 'window killed');
+
+# TODO: same test, but with pcre expressions
+
+######################################################################
+# check that multiple criteria work are checked with a logical AND,
+# not a logical OR (that is, matching is not cancelled after the first
+# criterion matches).
+######################################################################
+
+$tmp = fresh_workspace;
+
+# TODO: move to X11::XCB
+sub set_wm_class {
+    my ($id, $class, $instance) = @_;
+
+    # Add a _NET_WM_STRUT_PARTIAL hint
+    my $atomname = $x->atom(name => 'WM_CLASS');
+    my $atomtype = $x->atom(name => 'STRING');
+
+    $x->change_property(
+        PROP_MODE_REPLACE,
+        $id,
+        $atomname->id,
+        $atomtype->id,
+        8,
+        length($class) + length($instance) + 2,
+        "$instance\x00$class\x00"
+    );
+}
+
+my $left = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#0000ff',
+);
+
+$left->_create;
+set_wm_class($left->id, 'special', 'special');
+$left->name('left');
+$left->map;
+sleep 0.25;
+
+my $right = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#0000ff',
+);
+
+$right->_create;
+set_wm_class($right->id, 'special', 'special');
+$right->name('right');
+$right->map;
+sleep 0.25;
+
+# two windows should be here
+$content = get_ws_content($tmp);
+ok(@{$content} == 2, 'two windows opened');
+
+cmd '[class="special" title="left"] kill';
+
+sleep 0.25;
+
+$content = get_ws_content($tmp);
+is(@{$content}, 1, 'one window still there');
+
+done_testing;
diff --git a/testcases/t/20-multiple-cmds.t b/testcases/t/20-multiple-cmds.t
new file mode 100644 (file)
index 0000000..784329f
--- /dev/null
@@ -0,0 +1,56 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests multiple commands (using ';') and multiple operations (using ',')
+#
+use i3test;
+
+my $tmp = fresh_workspace;
+
+sub multiple_cmds {
+    my ($cmd) = @_;
+
+    cmd 'open';
+    cmd 'open';
+    ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
+
+    cmd $cmd;
+    ok(@{get_ws_content($tmp)} == 0, "both containers killed (cmd = $cmd)");
+}
+multiple_cmds('kill;kill');
+multiple_cmds('kill; kill');
+multiple_cmds('kill ; kill');
+multiple_cmds('kill ;kill');
+multiple_cmds('kill  ;kill');
+multiple_cmds('kill  ;  kill');
+multiple_cmds("kill;\tkill");
+multiple_cmds("kill\t;kill");
+multiple_cmds("kill\t;\tkill");
+multiple_cmds("kill\t ;\tkill");
+multiple_cmds("kill\t ;\t kill");
+multiple_cmds("kill \t ; \t kill");
+
+#####################################################################
+# test if un-quoted strings are handled correctly
+#####################################################################
+
+$tmp = fresh_workspace;
+cmd 'open';
+my $unused = get_unused_workspace;
+ok(!($unused ~~ @{get_workspace_names()}), 'workspace does not exist yet');
+cmd "move workspace $unused; nop parser test";
+ok(($unused ~~ @{get_workspace_names()}), 'workspace exists after moving');
+
+#####################################################################
+# quote the workspace name and use a ; (command separator) in its name
+#####################################################################
+
+$unused = get_unused_workspace;
+$unused .= ';a';
+ok(!($unused ~~ @{get_workspace_names()}), 'workspace does not exist yet');
+cmd qq|move workspace "$unused"; nop parser test|;
+ok(($unused ~~ @{get_workspace_names()}), 'workspace exists after moving');
+
+# TODO: need a non-invasive command before implementing a test which uses ','
+
+done_testing;
diff --git a/testcases/t/21-next-prev.t b/testcases/t/21-next-prev.t
new file mode 100644 (file)
index 0000000..447be31
--- /dev/null
@@ -0,0 +1,76 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests focus switching (next/prev)
+#
+use i3test;
+
+my $tmp = fresh_workspace;
+
+######################################################################
+# Open one container, verify that 'focus down' and 'focus right' do nothing
+######################################################################
+cmd 'open';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+my $old_focused = $focus->[0];
+
+cmd 'focus down';
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $old_focused, 'focus did not change with only one con');
+
+cmd 'focus right';
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $old_focused, 'focus did not change with only one con');
+
+######################################################################
+# Open another container, verify that 'focus right' switches
+######################################################################
+my $left = $old_focused;
+
+cmd 'open';
+($nodes, $focus) = get_ws_content($tmp);
+isnt($old_focused, $focus->[0], 'new container is focused');
+my $mid = $focus->[0];
+
+cmd 'open';
+($nodes, $focus) = get_ws_content($tmp);
+isnt($old_focused, $focus->[0], 'new container is focused');
+my $right = $focus->[0];
+
+cmd 'focus right';
+($nodes, $focus) = get_ws_content($tmp);
+isnt($focus->[0], $right, 'focus did change');
+is($focus->[0], $left, 'left container focused (wrapping)');
+
+cmd 'focus right';
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $mid, 'middle container focused');
+
+cmd 'focus right';
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $right, 'right container focused');
+
+cmd 'focus left';
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $mid, 'middle container focused');
+
+cmd 'focus left';
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $left, 'left container focused');
+
+cmd 'focus left';
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $right, 'right container focused');
+
+
+######################################################################
+# Test focus command
+######################################################################
+
+cmd qq|[con_id="$mid"] focus|;
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $mid, 'middle container focused');
+
+
+done_testing;
diff --git a/testcases/t/22-split.t b/testcases/t/22-split.t
new file mode 100644 (file)
index 0000000..3484c7f
--- /dev/null
@@ -0,0 +1,92 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests splitting
+#
+use i3test;
+use X11::XCB qw(:all);
+
+my $tmp = fresh_workspace;
+
+my $ws = get_ws($tmp);
+is($ws->{orientation}, 'horizontal', 'orientation horizontal by default');
+cmd 'split v';
+$ws = get_ws($tmp);
+is($ws->{orientation}, 'vertical', 'split v changes workspace orientation');
+
+######################################################################
+# Open two containers, split, open another container. Then verify
+# the layout is like we expect it to be
+######################################################################
+cmd 'open';
+cmd 'open';
+my $content = get_ws_content($tmp);
+
+is(@{$content}, 2, 'two containers on workspace level');
+my $first = $content->[0];
+my $second = $content->[1];
+
+is(@{$first->{nodes}}, 0, 'first container has no children');
+is(@{$second->{nodes}}, 0, 'second container has no children (yet)');
+my $old_name = $second->{name};
+
+
+cmd 'split h';
+cmd 'open';
+
+$content = get_ws_content($tmp);
+
+is(@{$content}, 2, 'two containers on workspace level');
+$first = $content->[0];
+$second = $content->[1];
+
+is(@{$first->{nodes}}, 0, 'first container has no children');
+isnt($second->{name}, $old_name, 'second container was replaced');
+is($second->{orientation}, 'horizontal', 'orientation is horizontal');
+is(@{$second->{nodes}}, 2, 'second container has 2 children');
+is($second->{nodes}->[0]->{name}, $old_name, 'found old second container');
+
+# TODO: extend this test-case (test next/prev)
+# - wrapping (no horizontal switch possible, goes level-up)
+# - going level-up "manually"
+
+######################################################################
+# Test splitting multiple times without actually creating windows
+######################################################################
+
+$tmp = fresh_workspace;
+
+$ws = get_ws($tmp);
+is($ws->{orientation}, 'horizontal', 'orientation horizontal by default');
+cmd 'split v';
+$ws = get_ws($tmp);
+is($ws->{orientation}, 'vertical', 'split v changes workspace orientation');
+
+cmd 'open';
+my @content = @{get_ws_content($tmp)};
+
+# recursively sums up all nodes and their children
+sub sum_nodes {
+    my ($nodes) = @_;
+
+    return 0 if !@{$nodes};
+
+    my @children = (map { @{$_->{nodes}} } @{$nodes},
+                    map { @{$_->{'floating_nodes'}} } @{$nodes});
+
+    return @{$nodes} + sum_nodes(\@children);
+}
+
+my $old_count = sum_nodes(\@content);
+cmd 'split v';
+
+@content = @{get_ws_content($tmp)};
+$old_count = sum_nodes(\@content);
+
+cmd 'split v';
+
+@content = @{get_ws_content($tmp)};
+my $count = sum_nodes(\@content);
+is($count, $old_count, 'not more windows after splitting again');
+
+done_testing;
diff --git a/testcases/t/24-move.t b/testcases/t/24-move.t
new file mode 100644 (file)
index 0000000..4df3a2a
--- /dev/null
@@ -0,0 +1,134 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests moving. Basically, there are four different code-paths:
+# 1) move a container which cannot be moved (single container on a workspace)
+# 2) move a container before another single container
+# 3) move a container inside another container
+# 4) move a container in a different direction so that we need to go up in tree
+#
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+######################################################################
+# 1) move a container which cannot be moved
+######################################################################
+
+cmd 'open';
+
+my $old_content = get_ws_content($tmp);
+is(@{$old_content}, 1, 'one container on this workspace');
+
+my $first = $old_content->[0]->{id};
+
+#cmd 'move before h';
+#cmd 'move before v';
+#cmd 'move after v';
+#cmd 'move after h';
+
+my $content = get_ws_content($tmp);
+#is_deeply($old_content, $content, 'workspace unmodified after useless moves');
+
+######################################################################
+# 2) move a container before another single container
+######################################################################
+
+cmd 'open';
+$content = get_ws_content($tmp);
+is(@{$content}, 2, 'two containers on this workspace');
+my $second = $content->[1]->{id};
+
+is($content->[0]->{id}, $first, 'first container unmodified');
+
+# Move the second container before the first one (→ swap them)
+cmd 'move left';
+$content = get_ws_content($tmp);
+is($content->[0]->{id}, $second, 'first container modified');
+
+# We should not be able to move any further
+cmd 'move left';
+$content = get_ws_content($tmp);
+is($content->[0]->{id}, $second, 'first container unmodified');
+
+# Now move in the other direction
+cmd 'move right';
+$content = get_ws_content($tmp);
+is($content->[0]->{id}, $first, 'first container modified');
+
+# We should not be able to move any further
+cmd 'move right';
+$content = get_ws_content($tmp);
+is($content->[0]->{id}, $first, 'first container unmodified');
+
+######################################################################
+# 3) move a container inside another container
+######################################################################
+
+# Split the current (second) container and create a new container on workspace
+# level. Our layout looks like this now:
+# --------------------------
+# |       | second |       |
+# | first | ------ | third |
+# |       |        |       |
+# --------------------------
+cmd 'split v';
+cmd 'focus parent';
+cmd 'open';
+
+$content = get_ws_content($tmp);
+is(@{$content}, 3, 'three containers on this workspace');
+my $third = $content->[2]->{id};
+
+cmd 'move left';
+$content = get_ws_content($tmp);
+is(@{$content}, 2, 'only two containers on this workspace');
+my $nodes = $content->[1]->{nodes};
+is($nodes->[0]->{id}, $second, 'second container on top');
+is($nodes->[1]->{id}, $third, 'third container on bottom');
+
+######################################################################
+# move it inside the split container
+######################################################################
+
+cmd 'move up';
+$nodes = get_ws_content($tmp)->[1]->{nodes};
+is($nodes->[0]->{id}, $third, 'third container on top');
+is($nodes->[1]->{id}, $second, 'second container on bottom');
+
+# move it outside again
+cmd 'move left';
+$content = get_ws_content($tmp);
+is(@{$content}, 3, 'three nodes on this workspace');
+
+# due to automatic flattening/cleanup, the remaining split container
+# will be replaced by the con itself, so we will still have 3 nodes
+cmd 'move right';
+$content = get_ws_content($tmp);
+is(@{$content}, 2, 'two nodes on this workspace');
+
+######################################################################
+# 4) We create two v-split containers on the workspace, then we move
+#    all Cons from the left v-split to the right one. The old vsplit
+#    container needs to be closed. Verify that it will be closed.
+######################################################################
+
+my $otmp = fresh_workspace;
+
+cmd "open";
+cmd "open";
+cmd "split v";
+cmd "open";
+cmd 'focus left';
+cmd "split v";
+cmd "open";
+cmd "move right";
+cmd 'focus left';
+cmd "move right";
+
+$content = get_ws_content($otmp);
+is(@{$content}, 1, 'only one nodes on this workspace');
+
+done_testing;
diff --git a/testcases/t/26-regress-close.t b/testcases/t/26-regress-close.t
new file mode 100644 (file)
index 0000000..8aec87d
--- /dev/null
@@ -0,0 +1,18 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression: closing of floating clients did crash i3 when closing the
+# container which contained this client.
+#
+use i3test;
+
+fresh_workspace;
+
+cmd 'open';
+cmd 'mode toggle';
+cmd 'kill';
+cmd 'kill';
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/27-regress-floating-parent.t b/testcases/t/27-regress-floating-parent.t
new file mode 100644 (file)
index 0000000..52b8b9c
--- /dev/null
@@ -0,0 +1,41 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression: make a container floating, kill its parent, make it tiling again
+#
+use i3test;
+
+my $tmp = fresh_workspace;
+
+cmd 'open';
+my $left = get_focused($tmp);
+cmd 'open';
+my $old = get_focused($tmp);
+cmd 'split v';
+cmd 'open';
+my $floating = get_focused($tmp);
+diag("focused floating: " . get_focused($tmp));
+cmd 'mode toggle';
+# TODO: eliminate this race conditition
+sleep 1;
+
+# kill old container
+cmd qq|[con_id="$old"] focus|;
+is(get_focused($tmp), $old, 'old container focused');
+cmd 'kill';
+
+# kill left container
+cmd qq|[con_id="$left"] focus|;
+is(get_focused($tmp), $left, 'old container focused');
+cmd 'kill';
+
+# focus floating window, make it tiling again
+cmd qq|[con_id="$floating"] focus|;
+is(get_focused($tmp), $floating, 'floating window focused');
+
+sleep 1;
+cmd 'mode toggle';
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/28-open-order.t b/testcases/t/28-open-order.t
new file mode 100644 (file)
index 0000000..b638e70
--- /dev/null
@@ -0,0 +1,37 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Check if new containers are opened after the currently focused one instead
+# of always at the end
+use List::Util qw(first);
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+# Open two new container
+my $first = open_empty_con($i3);
+
+ok(@{get_ws_content($tmp)} == 1, 'containers opened');
+
+my $second = open_empty_con($i3);
+
+isnt($first, $second, 'different container focused');
+
+##############################################################
+# see if new containers open after the currently focused
+##############################################################
+
+cmd qq|[con_id="$first"] focus|;
+cmd 'open';
+$content = get_ws_content($tmp);
+ok(@{$content} == 3, 'three containers opened');
+
+is($content->[0]->{id}, $first, 'first container unmodified');
+isnt($content->[1]->{id}, $second, 'second container replaced');
+is($content->[2]->{id}, $second, 'third container unmodified');
+
+done_testing;
diff --git a/testcases/t/29-focus-after-close.t b/testcases/t/29-focus-after-close.t
new file mode 100644 (file)
index 0000000..ac029eb
--- /dev/null
@@ -0,0 +1,122 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Check if the focus is correctly restored after closing windows.
+#
+use i3test;
+use X11::XCB qw(:all);
+use List::Util qw(first);
+
+my $x = X11::XCB::Connection->new;
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $first = open_empty_con($i3);
+my $second = open_empty_con($i3);
+
+cmd 'split v';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+
+ok(!$nodes->[1]->{focused}, 'split container not focused');
+cmd 'focus parent';
+($nodes, $focus) = get_ws_content($tmp);
+ok($nodes->[1]->{focused}, 'split container focused after focus parent');
+
+my $third = open_empty_con($i3);
+
+isnt(get_focused($tmp), $second, 'different container focused');
+
+# We have the following layout now (con is focused):
+# .----------------.
+# | split  |       |
+# | .----. |  con  |
+# | | cn | |       |
+# | `----' |       |
+# `----------------'
+
+##############################################################
+# see if the focus goes down to $first (not to its split parent)
+# when closing $second
+##############################################################
+
+cmd 'kill';
+# TODO: this testcase sometimes has different outcomes when the
+# sleep is missing. why?
+sleep 0.25;
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{nodes}->[0]->{id}, $second, 'second container found');
+ok($nodes->[1]->{nodes}->[0]->{focused}, 'second container focused');
+
+##############################################################
+# another case, using a slightly different layout (regression)
+##############################################################
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+cmd 'split v';
+$first = open_empty_con($i3);
+my $bottom = open_empty_con($i3);
+
+cmd 'focus up';
+cmd 'split h';
+my $middle = open_empty_con($i3);
+my $right = open_empty_con($i3);
+cmd 'focus down';
+
+# We have the following layout now (second is focused):
+# .----------------------------.
+# | .------------------------. |
+# | | first | middle | right | |
+# | `------------------------' |
+# |----------------------------|
+# |                            |
+# |          second            |
+# |                            |
+# `----------------------------'
+
+# Check if the focus is restored to $right when we close $second
+cmd 'kill';
+
+is(get_focused($tmp), $right, 'top right container focused (in focus stack)');
+
+($nodes, $focus) = get_ws_content($tmp);
+my $tr = first { $_->{id} eq $right } @{$nodes->[0]->{nodes}};
+is($tr->{focused}, 1, 'top right container really has focus');
+
+##############################################################
+# check if focus is correct after closing an unfocused window
+##############################################################
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_empty_con($i3);
+$middle = open_empty_con($i3);
+# XXX: the $right empty con will be filled with the x11 window we are creating afterwards
+$right = open_empty_con($i3);
+my $win = open_standard_window($x, '#00ff00');
+
+cmd qq|[con_id="$middle"] focus|;
+$win->destroy;
+
+sleep 0.25;
+
+is(get_focused($tmp), $middle, 'middle container focused');
+
+##############################################################
+# and now for something completely different:
+# check if the pointer position is relevant when restoring focus
+# (it should not be relevant, of course)
+##############################################################
+
+# TODO: add test code as soon as I can reproduce it
+
+done_testing;
diff --git a/testcases/t/30-close-empty-split.t b/testcases/t/30-close-empty-split.t
new file mode 100644 (file)
index 0000000..57855cd
--- /dev/null
@@ -0,0 +1,82 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Check if empty split containers are automatically closed.
+#
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $first = open_empty_con($i3);
+my $second = open_empty_con($i3);
+cmd qq|[con_id="$first"] focus|;
+
+cmd 'split v';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+is($nodes->[0]->{focused}, 0, 'split container not focused');
+
+# focus the split container
+cmd 'level up';
+($nodes, $focus) = get_ws_content($tmp);
+my $split = $focus->[0];
+cmd 'level down';
+
+my $second = open_empty_con($i3);
+
+isnt($first, $second, 'different container focused');
+
+##############################################################
+# close both windows and see if the split container still exists
+##############################################################
+
+cmd 'kill';
+cmd 'kill';
+($nodes, $focus) = get_ws_content($tmp);
+isnt($nodes->[0]->{id}, $split, 'split container closed');
+
+##############################################################
+# same thing but this time we are moving the cons away instead
+# of killing them
+##############################################################
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_empty_con($i3);
+$second = open_empty_con($i3);
+cmd qq|[con_id="$first"] focus|;
+
+cmd 'split v';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+is($nodes->[0]->{focused}, 0, 'split container not focused');
+
+# focus the split container
+cmd 'level up';
+($nodes, $focus) = get_ws_content($tmp);
+my $split = $focus->[0];
+cmd 'level down';
+
+my $second = open_empty_con($i3);
+
+isnt($first, $second, 'different container focused');
+
+##############################################################
+# close both windows and see if the split container still exists
+##############################################################
+
+my $otmp = get_unused_workspace();
+cmd "move workspace $otmp";
+cmd "move workspace $otmp";
+($nodes, $focus) = get_ws_content($tmp);
+isnt($nodes->[0]->{id}, $split, 'split container closed');
+
+done_testing;
diff --git a/testcases/t/31-stacking-order.t b/testcases/t/31-stacking-order.t
new file mode 100644 (file)
index 0000000..9c1e74c
--- /dev/null
@@ -0,0 +1,52 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Check if stacking containers can be used independantly of
+# the split mode (horizontal/vertical) of the underlying
+# container.
+#
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+# Enforce vertical split mode
+cmd 'split v';
+
+my $first = open_empty_con($i3);
+my $second = open_empty_con($i3);
+
+isnt($first, $second, 'two different containers opened');
+
+##############################################################
+# change mode to stacking and cycle through the containers
+##############################################################
+
+cmd 'layout stacking';
+is(get_focused($tmp), $second, 'second container still focused');
+
+cmd 'focus down';
+is(get_focused($tmp), $first, 'first container focused');
+
+cmd 'focus up';
+is(get_focused($tmp), $second, 'second container focused again');
+
+##############################################################
+# now change the orientation to horizontal and cycle
+##############################################################
+
+cmd 'focus parent';
+cmd 'split h';
+cmd 'focus child';
+
+cmd 'focus down';
+is(get_focused($tmp), $first, 'first container focused');
+
+cmd 'focus up';
+is(get_focused($tmp), $second, 'second container focused again');
+
+
+done_testing;
diff --git a/testcases/t/32-move-workspace.t b/testcases/t/32-move-workspace.t
new file mode 100644 (file)
index 0000000..3b50e39
--- /dev/null
@@ -0,0 +1,62 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Checks if the 'move workspace' command works correctly
+#
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+# We move the pointer out of our way to avoid a bug where the focus will
+# be set to the window under the cursor
+my $x = X11::XCB::Connection->new;
+$x->root->warp_pointer(0, 0);
+
+my $tmp = get_unused_workspace();
+my $tmp2 = get_unused_workspace();
+cmd "workspace $tmp";
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $first = open_empty_con($i3);
+my $second = open_empty_con($i3);
+ok(@{get_ws_content($tmp)} == 2, 'two containers on first ws');
+
+cmd "workspace $tmp2";
+ok(@{get_ws_content($tmp2)} == 0, 'no containers on second ws yet');
+
+cmd "workspace $tmp";
+
+cmd "move workspace $tmp2";
+ok(@{get_ws_content($tmp)} == 1, 'one container on first ws anymore');
+ok(@{get_ws_content($tmp2)} == 1, 'one container on second ws');
+my ($nodes, $focus) = get_ws_content($tmp2);
+
+is($focus->[0], $second, 'same container on different ws');
+
+($nodes, $focus) = get_ws_content($tmp);
+ok($nodes->[0]->{focused}, 'first container focused on first ws');
+
+###################################################################
+# check if floating cons are moved to new workspaces properly
+# (that is, if they are floating on the target ws, too)
+###################################################################
+
+$tmp = get_unused_workspace();
+$tmp2 = get_unused_workspace();
+cmd "workspace $tmp";
+
+cmd "open";
+cmd "floating toggle";
+
+my $ws = get_ws($tmp);
+is(@{$ws->{nodes}}, 0, 'no nodes on workspace');
+is(@{$ws->{floating_nodes}}, 1, 'one floating node on workspace');
+
+cmd "move workspace $tmp2";
+
+$ws = get_ws($tmp2);
+is(@{$ws->{nodes}}, 0, 'no nodes on workspace');
+is(@{$ws->{floating_nodes}}, 1, 'one floating node on workspace');
+
+done_testing;
diff --git a/testcases/t/33-size-hints.t b/testcases/t/33-size-hints.t
new file mode 100644 (file)
index 0000000..05897c8
--- /dev/null
@@ -0,0 +1,42 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Checks if size hints are interpreted correctly.
+#
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $win = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30),
+    background_color => '#C0C0C0',
+);
+
+# XXX: we should check screen size. in screens with an AR of 2.0,
+# this is not a good idea.
+my $aspect = X11::XCB::Sizehints::Aspect->new;
+$aspect->min_num(600);
+$aspect->min_den(300);
+$aspect->max_num(600);
+$aspect->max_den(300);
+$win->_create;
+$win->map;
+sleep 0.25;
+$win->hints->aspect($aspect);
+$x->flush;
+
+sleep 0.25;
+
+my $rect = $win->rect;
+my $ar = $rect->width / $rect->height;
+diag("Aspect ratio = $ar");
+ok(($ar > 1.90) && ($ar < 2.10), 'Aspect ratio about 2.0');
+
+done_testing;
diff --git a/testcases/t/34-invalid-command.t b/testcases/t/34-invalid-command.t
new file mode 100644 (file)
index 0000000..d58985e
--- /dev/null
@@ -0,0 +1,12 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# 
+#
+use i3test;
+
+cmd 'blargh!';
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/35-floating-focus.t b/testcases/t/35-floating-focus.t
new file mode 100644 (file)
index 0000000..3f820ea
--- /dev/null
@@ -0,0 +1,174 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+#############################################################################
+# 1: see if focus stays the same when toggling tiling/floating mode
+#############################################################################
+
+my $first = open_standard_window($x);
+my $second = open_standard_window($x);
+
+is($x->input_focus, $second->id, 'second window focused');
+
+cmd 'floating enable';
+cmd 'floating disable';
+
+is($x->input_focus, $second->id, 'second window still focused after mode toggle');
+
+#############################################################################
+# 2: see if focus stays on the current floating window if killing another
+# floating window
+#############################################################################
+
+$tmp = fresh_workspace;
+
+$first = open_standard_window($x);    # window 2
+$second = open_standard_window($x);   # window 3
+my $third = open_standard_window($x); # window 4
+
+is($x->input_focus, $third->id, 'last container focused');
+
+cmd 'floating enable';
+
+cmd '[id="' . $second->id . '"] focus';
+
+is($x->input_focus, $second->id, 'second con focused');
+
+cmd 'floating enable';
+
+# now kill the third one (it's floating). focus should stay unchanged
+cmd '[id="' . $third->id . '"] kill';
+
+sleep 0.25;
+
+is($x->input_focus, $second->id, 'second con still focused after killing third');
+
+
+#############################################################################
+# 3: see if the focus gets reverted correctly when closing floating clients
+# (first to the next floating client, then to the last focused tiling client)
+#############################################################################
+
+$tmp = fresh_workspace;
+
+$first = open_standard_window($x, '#ff0000');    # window 5
+$second = open_standard_window($x, '#00ff00');   # window 6
+my $third = open_standard_window($x, '#0000ff'); # window 7
+
+is($x->input_focus, $third->id, 'last container focused');
+
+cmd 'floating enable';
+
+cmd '[id="' . $second->id . '"] focus';
+
+is($x->input_focus, $second->id, 'second con focused');
+
+cmd 'floating enable';
+
+# now kill the second one. focus should fall back to the third one, which is
+# also floating
+cmd 'kill';
+
+sleep 0.25;
+
+is($x->input_focus, $third->id, 'third con focused');
+
+cmd 'kill';
+
+sleep 0.25;
+
+is($x->input_focus, $first->id, 'first con focused after killing all floating cons');
+
+#############################################################################
+# 4: same test as 3, but with another split con
+#############################################################################
+
+$tmp = fresh_workspace;
+
+$first = open_standard_window($x, '#ff0000');    # window 5
+cmd 'split v';
+cmd 'layout stacked';
+$second = open_standard_window($x, '#00ff00');   # window 6
+$third = open_standard_window($x, '#0000ff'); # window 7
+
+is($x->input_focus, $third->id, 'last container focused');
+
+cmd 'floating enable';
+
+cmd '[id="' . $second->id . '"] focus';
+
+is($x->input_focus, $second->id, 'second con focused');
+
+cmd 'floating enable';
+
+sleep 0.5;
+
+# now kill the second one. focus should fall back to the third one, which is
+# also floating
+cmd 'kill';
+
+sleep 0.25;
+
+is($x->input_focus, $third->id, 'second con focused');
+
+cmd 'kill';
+
+sleep 0.25;
+
+is($x->input_focus, $first->id, 'first con focused after killing all floating cons');
+
+#############################################################################
+# 5: see if the 'focus tiling' and 'focus floating' commands work
+#############################################################################
+
+$tmp = fresh_workspace;
+
+$first = open_standard_window($x, '#ff0000');    # window 8
+$second = open_standard_window($x, '#00ff00');   # window 9
+
+is($x->input_focus, $second->id, 'second container focused');
+
+cmd 'floating enable';
+
+is($x->input_focus, $second->id, 'second container focused');
+
+cmd 'focus tiling';
+
+sleep 0.25;
+
+is($x->input_focus, $first->id, 'first (tiling) container focused');
+
+cmd 'focus floating';
+
+sleep 0.25;
+
+is($x->input_focus, $second->id, 'second (floating) container focused');
+
+cmd 'focus floating';
+
+sleep 0.25;
+
+is($x->input_focus, $second->id, 'second (floating) container still focused');
+
+cmd 'focus mode_toggle';
+
+sleep 0.25;
+
+is($x->input_focus, $first->id, 'first (tiling) container focused');
+
+cmd 'focus mode_toggle';
+
+sleep 0.25;
+
+is($x->input_focus, $second->id, 'second (floating) container focused');
+
+
+done_testing;
diff --git a/testcases/t/36-floating-ws-empty.t b/testcases/t/36-floating-ws-empty.t
new file mode 100644 (file)
index 0000000..f33d04d
--- /dev/null
@@ -0,0 +1,48 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Regression test: when only having a floating window on a workspace, it should not be deleted.
+
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+#############################################################################
+# 1: open a floating window, get it mapped
+#############################################################################
+
+ok(workspace_exists($tmp), "workspace $tmp exists");
+
+my $x = X11::XCB::Connection->new;
+
+# Create a floating window which is smaller than the minimum enforced size of i3
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30],
+    background_color => '#C0C0C0',
+    # replace the type with 'utility' as soon as the coercion works again in X11::XCB
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
+);
+
+isa_ok($window, 'X11::XCB::Window');
+
+$window->map;
+
+sleep 0.25;
+
+ok($window->mapped, 'Window is mapped');
+
+# switch to a different workspace, see if the window is still mapped?
+
+my $otmp = fresh_workspace;
+
+ok(workspace_exists($otmp), "new workspace $otmp exists");
+ok(workspace_exists($tmp), "old workspace $tmp still exists");
+
+done_testing;
diff --git a/testcases/t/37-floating-unmap.t b/testcases/t/37-floating-unmap.t
new file mode 100644 (file)
index 0000000..3ae4b12
--- /dev/null
@@ -0,0 +1,50 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Regression test: Floating windows were not correctly unmapped when switching
+# to a different workspace.
+
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+#############################################################################
+# 1: open a floating window, get it mapped
+#############################################################################
+
+my $x = X11::XCB::Connection->new;
+
+# Create a floating window which is smaller than the minimum enforced size of i3
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30],
+    background_color => '#C0C0C0',
+    # replace the type with 'utility' as soon as the coercion works again in X11::XCB
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
+);
+
+isa_ok($window, 'X11::XCB::Window');
+
+$window->map;
+
+sleep 0.25;
+
+ok($window->mapped, 'Window is mapped');
+
+# switch to a different workspace, see if the window is still mapped?
+
+my $otmp = fresh_workspace;
+
+sleep 0.25;
+
+ok(!$window->mapped, 'Window is not mapped after switching ws');
+
+cmd "nop testcase done";
+
+done_testing;
diff --git a/testcases/t/38-floating-attach.t b/testcases/t/38-floating-attach.t
new file mode 100644 (file)
index 0000000..31bddaf
--- /dev/null
@@ -0,0 +1,108 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Regression test: New windows were attached to the container of a floating window
+# if only a floating window is present on the workspace.
+
+use i3test;
+use X11::XCB qw(:all);
+use Time::HiRes qw(sleep);
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+#############################################################################
+# 1: open a floating window, get it mapped
+#############################################################################
+
+my $x = X11::XCB::Connection->new;
+
+# Create a floating window
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30],
+    background_color => '#C0C0C0',
+    # replace the type with 'utility' as soon as the coercion works again in X11::XCB
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
+);
+
+isa_ok($window, 'X11::XCB::Window');
+
+$window->map;
+
+sleep 0.25;
+
+ok($window->mapped, 'Window is mapped');
+
+my $ws = get_ws($tmp);
+my ($nodes, $focus) = get_ws_content($tmp);
+
+is(@{$ws->{floating_nodes}}, 1, 'one floating node');
+is(@{$nodes}, 0, 'no tiling nodes');
+
+# Create a tiling window
+my $twindow = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30],
+    background_color => '#C0C0C0',
+);
+
+isa_ok($twindow, 'X11::XCB::Window');
+
+$twindow->map;
+
+sleep 0.25;
+
+($nodes, $focus) = get_ws_content($tmp);
+
+is(@{$nodes}, 1, 'one tiling node');
+
+#############################################################################
+# 2: similar case: floating windows should be attached at the currently focused
+# position in the workspace (for example a stack), not just at workspace level.
+#############################################################################
+
+$tmp = fresh_workspace;
+
+my $first = open_standard_window($x);
+my $second = open_standard_window($x);
+
+cmd 'layout stacked';
+
+$ws = get_ws($tmp);
+is(@{$ws->{floating_nodes}}, 0, 'no floating nodes so far');
+is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)');
+
+# Create a floating window
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30],
+    background_color => '#C0C0C0',
+    # replace the type with 'utility' as soon as the coercion works again in X11::XCB
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
+);
+
+isa_ok($window, 'X11::XCB::Window');
+
+$window->map;
+
+sleep 0.25;
+
+ok($window->mapped, 'Window is mapped');
+
+$ws = get_ws($tmp);
+is(@{$ws->{floating_nodes}}, 1, 'one floating nodes');
+is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)');
+
+my $third = open_standard_window($x);
+
+
+$ws = get_ws($tmp);
+is(@{$ws->{floating_nodes}}, 1, 'one floating nodes');
+is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)');
+
+done_testing;
diff --git a/testcases/t/39-ws-numbers.t b/testcases/t/39-ws-numbers.t
new file mode 100644 (file)
index 0000000..3afd281
--- /dev/null
@@ -0,0 +1,60 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Check if numbered workspaces and named workspaces are sorted in the right way
+# in get_workspaces IPC output (necessary for i3bar etc.).
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $i3 = i3(get_socket_path());
+my $x = X11::XCB::Connection->new;
+
+sub check_order {
+    my ($msg) = @_;
+
+    my @ws = @{$i3->get_workspaces->recv};
+    my @nums = map { $_->{num} } grep { defined($_->{num}) } @ws;
+    my @sorted = sort @nums;
+
+    cmp_deeply(\@nums, \@sorted, $msg);
+}
+
+check_order('workspace order alright before testing');
+
+#############################################################################
+# open a window to keep this ws open
+#############################################################################
+
+cmd "workspace 93";
+
+open_standard_window($x);
+
+my @ws = @{$i3->get_workspaces->recv};
+my @f = grep { defined($_->{num}) && $_->{num} == 93 } @ws;
+is(@f, 1, 'ws 93 found by num');
+check_order('workspace order alright after opening 93');
+
+cmd "workspace 92";
+open_standard_window($x);
+check_order('workspace order alright after opening 92');
+
+cmd "workspace 94";
+open_standard_window($x);
+check_order('workspace order alright after opening 94');
+
+cmd "workspace 96";
+open_standard_window($x);
+check_order('workspace order alright after opening 96');
+
+cmd "workspace foo";
+open_standard_window($x);
+check_order('workspace order alright after opening foo');
+
+cmd "workspace 91";
+open_standard_window($x);
+check_order('workspace order alright after opening 91');
+
+done_testing;
diff --git a/testcases/t/40-focus-lost.t b/testcases/t/40-focus-lost.t
new file mode 100644 (file)
index 0000000..9df220d
--- /dev/null
@@ -0,0 +1,47 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Regression: Check if the focus stays the same when switching the layout
+# bug introduced by 77d0d42ed2d7ac8cafe267c92b35a81c1b9491eb
+use i3test;
+use X11::XCB qw(:all);
+use Time::HiRes qw(sleep);
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $i3 = i3(get_socket_path());
+my $x = X11::XCB::Connection->new;
+
+sub check_order {
+    my ($msg) = @_;
+
+    my @ws = @{$i3->get_workspaces->recv};
+    my @nums = map { $_->{num} } grep { defined($_->{num}) } @ws;
+    my @sorted = sort @nums;
+
+    cmp_deeply(\@nums, \@sorted, $msg);
+}
+
+my $tmp = fresh_workspace;
+
+my $left = open_standard_window($x);
+sleep 0.25;
+my $mid = open_standard_window($x);
+sleep 0.25;
+my $right = open_standard_window($x);
+sleep 0.25;
+
+diag("left = " . $left->id . ", mid = " . $mid->id . ", right = " . $right->id);
+
+is($x->input_focus, $right->id, 'Right window focused');
+
+cmd 'focus left';
+
+is($x->input_focus, $mid->id, 'Mid window focused');
+
+cmd 'layout stacked';
+
+is($x->input_focus, $mid->id, 'Mid window focused');
+
+done_testing;
diff --git a/testcases/t/41-resize.t b/testcases/t/41-resize.t
new file mode 100644 (file)
index 0000000..1717e8f
--- /dev/null
@@ -0,0 +1,49 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Tests resizing tiling containers
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+cmd 'split v';
+
+my $top = open_standard_window($x);
+sleep 0.25;
+my $bottom = open_standard_window($x);
+sleep 0.25;
+
+diag("top = " . $top->id . ", bottom = " . $bottom->id);
+
+is($x->input_focus, $bottom->id, 'Bottom window focused');
+
+############################################################
+# resize
+############################################################
+
+cmd 'resize grow up 10 px or 25 ppt';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+
+is($nodes->[0]->{percent}, 0.25, 'top window got only 25%');
+is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
+
+
+############################################################
+# split and check if the 'percent' factor is still correct
+############################################################
+
+cmd 'split h';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+is($nodes->[0]->{percent}, 0.25, 'top window got only 25%');
+is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
+
+done_testing;
diff --git a/testcases/t/42-regress-move-floating.t b/testcases/t/42-regress-move-floating.t
new file mode 100644 (file)
index 0000000..6b2df80
--- /dev/null
@@ -0,0 +1,17 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression: move a floating window to a different workspace crashes i3
+#
+use i3test;
+
+my $tmp = fresh_workspace;
+my $otmp = get_unused_workspace();
+
+cmd 'open';
+cmd 'mode toggle';
+cmd "move workspace $otmp";
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/43-regress-floating-restart.t b/testcases/t/43-regress-floating-restart.t
new file mode 100644 (file)
index 0000000..babbb57
--- /dev/null
@@ -0,0 +1,23 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression: floating windows are tiling after restarting, closing them crashes i3
+#
+use i3test;
+
+my $tmp = fresh_workspace;
+
+cmd 'open';
+cmd 'mode toggle';
+cmd 'restart';
+
+sleep 0.5;
+
+diag('Checking if i3 still lives');
+
+does_i3_live;
+
+my $ws = get_ws($tmp);
+diag('ws = ' . Dumper($ws));
+
+done_testing;
diff --git a/testcases/t/44-regress-floating-resize.t b/testcases/t/44-regress-floating-resize.t
new file mode 100644 (file)
index 0000000..de33eeb
--- /dev/null
@@ -0,0 +1,37 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression: when resizing two containers on a workspace, opening a floating
+# client, then closing it again, i3 will re-distribute the space on the
+# workspace as if a tiling container was closed, leading to the containers
+# taking much more space than they possibly could.
+#
+use i3test;
+use List::Util qw(sum);
+
+my $tmp = fresh_workspace;
+
+cmd 'exec /usr/bin/urxvt';
+sleep 0.5;
+cmd 'exec /usr/bin/urxvt';
+sleep 0.5;
+my ($nodes, $focus) = get_ws_content($tmp);
+my $old_sum = sum map { $_->{rect}->{width} } @{$nodes};
+#cmd 'open';
+cmd 'resize grow left 10 px or 25 ppt';
+cmd 'split v';
+#cmd 'open';
+cmd 'exec /usr/bin/urxvt';
+sleep 0.5;
+cmd 'mode toggle';
+sleep 0.5;
+cmd 'kill';
+
+sleep 0.5;
+
+($nodes, $focus) = get_ws_content($tmp);
+my $new_sum = sum map { $_->{rect}->{width} } @{$nodes};
+
+is($old_sum, $new_sum, 'combined container width is still equal');
+
+done_testing;
diff --git a/testcases/t/45-flattening.t b/testcases/t/45-flattening.t
new file mode 100644 (file)
index 0000000..98b93ef
--- /dev/null
@@ -0,0 +1,34 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# by moving the window in the opposite orientation that its parent has, we
+# force i3 to create a new split container with the appropriate orientation.
+# However, when doing that two times in a row, we end up with two split
+# containers which are then redundant (workspace is horizontal, then v-split,
+# then h-split – we could just append the children of the latest h-split to the
+# workspace itself).
+#
+# This testcase checks that the tree is properly flattened after moving.
+#
+use X11::XCB qw(:all);
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+my $left = open_standard_window($x);
+sleep 0.25;
+my $mid = open_standard_window($x);
+sleep 0.25;
+my $right = open_standard_window($x);
+sleep 0.25;
+
+cmd 'move before v';
+cmd 'move after h';
+my $ws = get_ws($tmp);
+
+is($ws->{orientation}, 'horizontal', 'workspace orientation is horizontal');
+is(@{$ws->{nodes}}, 3, 'all three windows on workspace level');
+
+done_testing;
diff --git a/testcases/t/46-floating-reinsert.t b/testcases/t/46-floating-reinsert.t
new file mode 100644 (file)
index 0000000..a4e90d4
--- /dev/null
@@ -0,0 +1,63 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+use X11::XCB qw(:all);
+use Time::HiRes qw(sleep);
+use i3test;
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+my $left = open_standard_window($x);
+sleep 0.25;
+my $mid = open_standard_window($x);
+sleep 0.25;
+
+cmd 'split v';
+my $bottom = open_standard_window($x);
+sleep 0.25;
+
+my ($nodes, $focus) = get_ws_content($tmp);
+
+#############################################################################
+# 1: open a floating window, get it mapped
+#############################################################################
+
+my $x = X11::XCB::Connection->new;
+
+# Create a floating window
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30],
+    background_color => '#C0C0C0',
+    # replace the type with 'utility' as soon as the coercion works again in X11::XCB
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
+);
+
+isa_ok($window, 'X11::XCB::Window');
+
+$window->map;
+
+sleep 0.25;
+
+ok($window->mapped, 'Window is mapped');
+
+($nodes, $focus) = get_ws_content($tmp);
+is(@{$nodes->[1]->{nodes}}, 2, 'two windows in split con');
+
+#############################################################################
+# 2: make it tiling, see where it ends up
+#############################################################################
+
+cmd 'floating toggle';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+
+is(@{$nodes->[1]->{nodes}}, 3, 'three windows in split con after floating toggle');
+
+done_testing;
diff --git a/testcases/t/47-regress-floatingmove.t b/testcases/t/47-regress-floatingmove.t
new file mode 100644 (file)
index 0000000..6e04916
--- /dev/null
@@ -0,0 +1,47 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test for moving a con outside of a floating con when there are no
+# tiling cons on a workspace
+#
+use X11::XCB qw(:all);
+use Time::HiRes qw(sleep);
+use i3test;
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+my $left = open_standard_window($x);
+sleep 0.25;
+my $mid = open_standard_window($x);
+sleep 0.25;
+my $right = open_standard_window($x);
+sleep 0.25;
+
+# go to workspace level
+cmd 'level up';
+sleep 0.25;
+
+# make it floating
+cmd 'mode toggle';
+sleep 0.25;
+
+# move the con outside the floating con
+cmd 'move before v';
+sleep 0.25;
+
+does_i3_live;
+
+# move another con outside
+cmd '[id="' . $mid->id . '"] focus';
+cmd 'move before v';
+sleep 0.25;
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/48-regress-floatingmovews.t b/testcases/t/48-regress-floatingmovews.t
new file mode 100644 (file)
index 0000000..0bec541
--- /dev/null
@@ -0,0 +1,38 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test for correct focus behaviour when moving a floating con to
+# another workspace.
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+# open a tiling window on the first workspace
+open_standard_window($x);
+sleep 0.25;
+my $first = get_focused($tmp);
+
+# on a different ws, open a floating window
+my $otmp = fresh_workspace;
+open_standard_window($x);
+sleep 0.25;
+my $float = get_focused($otmp);
+cmd 'mode toggle';
+sleep 0.25;
+
+# move the floating con to first workspace
+cmd "move workspace $tmp";
+sleep 0.25;
+
+# switch to the first ws and check focus
+is(get_focused($tmp), $float, 'floating client correctly focused');
+
+done_testing;
diff --git a/testcases/t/50-regress-dock-restart.t b/testcases/t/50-regress-dock-restart.t
new file mode 100644 (file)
index 0000000..a4f7beb
--- /dev/null
@@ -0,0 +1,109 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test for inplace restarting with dock clients
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# verify that there is no dock window yet
+#####################################################################
+
+# Children of all dockareas
+my @docked = get_dock_clients;
+
+is(@docked, 0, 'no dock clients yet');
+
+# open a dock client
+
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30],
+    background_color => '#FF0000',
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+);
+
+$window->map;
+
+sleep 0.25;
+
+#####################################################################
+# check that we can find it in the layout tree at the expected position
+#####################################################################
+
+@docked = get_dock_clients;
+is(@docked, 1, 'one dock client found');
+
+# verify the height
+my $docknode = $docked[0];
+
+is($docknode->{rect}->{height}, 30, 'dock node has unchanged height');
+
+# perform an inplace-restart
+cmd 'restart';
+
+sleep 0.25;
+
+does_i3_live;
+
+
+#####################################################################
+# check that we can still find the dock client
+#####################################################################
+
+@docked = get_dock_clients;
+is(@docked, 1, 'one dock client found');
+$docknode = $docked[0];
+
+is($docknode->{rect}->{height}, 30, 'dock node has unchanged height after restart');
+
+$window->destroy;
+
+sleep 0.25;
+
+@docked = get_dock_clients;
+is(@docked, 0, 'no dock clients found');
+
+#####################################################################
+# create a dock client with a 1px border
+#####################################################################
+
+$window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    border => 1,
+    rect => [ 0, 0, 30, 20],
+    background_color => '#00FF00',
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+);
+
+$window->map;
+
+sleep 0.25;
+
+@docked = get_dock_clients;
+is(@docked, 1, 'one dock client found');
+$docknode = $docked[0];
+
+is($docknode->{rect}->{height}, 20, 'dock node has unchanged height');
+
+cmd 'restart';
+sleep 0.25;
+
+@docked = get_dock_clients;
+is(@docked, 1, 'one dock client found');
+$docknode = $docked[0];
+
+is($docknode->{rect}->{height}, 20, 'dock node has unchanged height');
+
+
+done_testing;
diff --git a/testcases/t/51-regress-float-size.t b/testcases/t/51-regress-float-size.t
new file mode 100644 (file)
index 0000000..881ef8c
--- /dev/null
@@ -0,0 +1,17 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test for setting a window to floating, tiling and opening a new window
+#
+use i3test;
+
+fresh_workspace;
+
+cmd 'open';
+cmd 'mode toggle';
+cmd 'mode toggle';
+cmd 'open';
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/52-regress-level-up.t b/testcases/t/52-regress-level-up.t
new file mode 100644 (file)
index 0000000..678cdd9
--- /dev/null
@@ -0,0 +1,18 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test for using level-up to get to the 'content'-container and
+# toggle floating
+#
+use i3test;
+
+fresh_workspace;
+
+cmd 'open';
+cmd 'level up';
+cmd 'level up';
+cmd 'mode toggle';
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/53-floating-originalsize.t b/testcases/t/53-floating-originalsize.t
new file mode 100644 (file)
index 0000000..16e62c2
--- /dev/null
@@ -0,0 +1,50 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Test if the requested width/height is set after making the window floating.
+#
+use X11::XCB qw(:all);
+use i3test;
+
+my $tmp = fresh_workspace;
+
+my $x = X11::XCB::Connection->new;
+
+# Create a floating window which is smaller than the minimum enforced size of i3
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 400, 150],
+    background_color => '#C0C0C0',
+);
+
+isa_ok($window, 'X11::XCB::Window');
+
+$window->map;
+
+sleep 0.25;
+
+my ($absolute, $top) = $window->rect;
+
+ok($window->mapped, 'Window is mapped');
+cmp_ok($absolute->{width}, '>', 400, 'i3 raised the width');
+cmp_ok($absolute->{height}, '>', 150, 'i3 raised the height');
+
+cmd 'floating toggle';
+sleep 0.25;
+
+($absolute, $top) = $window->rect;
+
+diag('new width: ' . $absolute->{width});
+diag('new height: ' . $absolute->{height});
+
+# we compare with a tolerance of ± 20 pixels for borders in each direction
+# (overkill, but hey)
+cmp_ok($absolute->{width}, '>', 400-20, 'width now > 380');
+cmp_ok($absolute->{width}, '<', 400+20, 'width now < 420');
+cmp_ok($absolute->{height}, '>', 150-20, 'height now > 130');
+cmp_ok($absolute->{height}, '<', 150+20, 'height now < 170');
+
+#cmp_ok($absolute->{width}, '>=', 75, 'i3 raised the width to 75');
+#cmp_ok($absolute->{height}, '>=', 50, 'i3 raised the height to 50');
+
+done_testing;
diff --git a/testcases/t/54-regress-multiple-dock.t b/testcases/t/54-regress-multiple-dock.t
new file mode 100644 (file)
index 0000000..36070db
--- /dev/null
@@ -0,0 +1,70 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test for closing one of multiple dock clients
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# verify that there is no dock window yet
+#####################################################################
+
+# Children of all dockareas
+my @docked = get_dock_clients;
+
+is(@docked, 0, 'no dock clients yet');
+
+#####################################################################
+# open a dock client
+#####################################################################
+
+my $first = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30],
+    background_color => '#FF0000',
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+);
+
+$first->map;
+
+sleep 0.25;
+
+#####################################################################
+# Open a second dock client
+#####################################################################
+
+my $second = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30],
+    background_color => '#FF0000',
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+);
+
+$second->map;
+
+sleep 0.25;
+
+#####################################################################
+# Kill the second dock client
+#####################################################################
+cmd "nop destroying dock client";
+$second->destroy;
+
+#####################################################################
+# Now issue a focus command
+#####################################################################
+cmd 'next v';
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/55-floating-split-size.t b/testcases/t/55-floating-split-size.t
new file mode 100644 (file)
index 0000000..ecffbb1
--- /dev/null
@@ -0,0 +1,68 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Test to see if i3 combines the geometry of all children in a split container
+# when setting the split container to floating
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# open a window with 200x80
+#####################################################################
+
+my $first = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 200, 80],
+    background_color => '#FF0000',
+);
+
+$first->map;
+
+sleep 0.25;
+
+#####################################################################
+# Open a second window with 300x90
+#####################################################################
+
+my $second = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 300, 90],
+    background_color => '#00FF00',
+);
+
+$second->map;
+
+sleep 0.25;
+
+#####################################################################
+# Set the parent to floating
+#####################################################################
+cmd 'nop setting floating';
+cmd 'focus parent';
+cmd 'floating enable';
+
+#####################################################################
+# Get geometry of the first floating node (the split container)
+#####################################################################
+
+my @nodes = @{get_ws($tmp)->{floating_nodes}};
+my $rect = $nodes[0]->{rect};
+
+# we compare the width with ± 20 pixels for borders
+cmp_ok($rect->{width}, '>', 500-20, 'width now > 480');
+cmp_ok($rect->{width}, '<', 500+20, 'width now < 520');
+# we compare the height with ± 40 pixels for decorations
+cmp_ok($rect->{height}, '>', 90-40, 'width now > 50');
+cmp_ok($rect->{height}, '<', 90+40, 'width now < 130');
+
+done_testing;
diff --git a/testcases/t/56-fullscreen-focus.t b/testcases/t/56-fullscreen-focus.t
new file mode 100644 (file)
index 0000000..ee60dc7
--- /dev/null
@@ -0,0 +1,63 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Test if new containers get focused when there is a fullscreen container at
+# the time of launching the new one.
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# open the left window
+#####################################################################
+
+my $left = open_standard_window($x, '#ff0000');
+
+is($x->input_focus, $left->id, 'left window focused');
+
+diag("left = " . $left->id);
+
+#####################################################################
+# Open the right window
+#####################################################################
+
+my $right = open_standard_window($x, '#00ff00');
+
+diag("right = " . $right->id);
+
+#####################################################################
+# Set the right window to fullscreen
+#####################################################################
+cmd 'nop setting fullscreen';
+cmd 'fullscreen';
+
+#####################################################################
+# Open a third window
+#####################################################################
+
+my $third = open_standard_window($x, '#0000ff');
+
+diag("third = " . $third->id);
+
+# move the fullscreen window to a different ws
+
+my $tmp2 = get_unused_workspace;
+
+cmd "move workspace $tmp2";
+
+# verify that the third window has the focus
+
+sleep 0.25;
+
+is($x->input_focus, $third->id, 'third window focused');
+
+done_testing;
diff --git a/testcases/t/57-regress-fullscreen-level-up.t b/testcases/t/57-regress-fullscreen-level-up.t
new file mode 100644 (file)
index 0000000..3e0b2fe
--- /dev/null
@@ -0,0 +1,49 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test: level up should be a noop during fullscreen mode
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# open a window, verify it’s not in fullscreen mode
+#####################################################################
+
+my $win = open_standard_window($x);
+
+my $nodes = get_ws_content $tmp;
+is(@$nodes, 1, 'exactly one client');
+is($nodes->[0]->{fullscreen_mode}, 0, 'client not fullscreen');
+
+#####################################################################
+# make it fullscreen
+#####################################################################
+
+cmd 'nop making fullscreen';
+cmd 'fullscreen';
+
+my $nodes = get_ws_content $tmp;
+is($nodes->[0]->{fullscreen_mode}, 1, 'client fullscreen now');
+
+#####################################################################
+# send level up, try to un-fullscreen
+#####################################################################
+cmd 'level up';
+cmd 'fullscreen';
+
+my $nodes = get_ws_content $tmp;
+is($nodes->[0]->{fullscreen_mode}, 0, 'client not fullscreen any longer');
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/58-wm_take_focus.t b/testcases/t/58-wm_take_focus.t
new file mode 100644 (file)
index 0000000..04c785a
--- /dev/null
@@ -0,0 +1,115 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests if the WM_TAKE_FOCUS protocol is correctly handled by i3
+#
+use X11::XCB qw(:all);
+use i3test;
+use v5.10;
+
+BEGIN {
+    use_ok('EV');
+    use_ok('AnyEvent');
+    use_ok('X11::XCB::Window');
+    use_ok('X11::XCB::Event::Generic');
+    use_ok('X11::XCB::Event::MapNotify');
+    use_ok('X11::XCB::Event::ClientMessage');
+}
+
+my $x = X11::XCB::Connection->new;
+my $i3 = i3(get_socket_path());
+
+subtest 'Window without WM_TAKE_FOCUS', sub {
+
+    my $tmp = fresh_workspace;
+
+    my $window = $x->root->create_child(
+        class => WINDOW_CLASS_INPUT_OUTPUT,
+        rect => [ 0, 0, 30, 30 ],
+        background_color => '#00ff00',
+        event_mask => [ 'structure_notify' ],
+    );
+
+    $window->name('Window 1');
+    $window->map;
+
+    my $cv = AE::cv;
+
+    my $prep = EV::prepare sub {
+        $x->flush;
+    };
+
+    my $check = EV::check sub {
+        while (defined(my $event = $x->poll_for_event)) {
+            if ($event->response_type == 161) {
+                # clientmessage
+                $cv->send(0);
+            }
+        }
+    };
+
+    my $w = EV::io $x->get_file_descriptor, EV::READ, sub {
+        # do nothing, we only need this watcher so that EV picks up the events
+    };
+
+    # Trigger timeout after 1 second
+    my $t = AE::timer 1, 0, sub {
+        $cv->send(1);
+    };
+
+    my $result = $cv->recv;
+    ok($result, 'cv result');
+
+    done_testing;
+};
+
+subtest 'Window with WM_TAKE_FOCUS', sub {
+
+    my $tmp = fresh_workspace;
+
+    my $window = $x->root->create_child(
+        class => WINDOW_CLASS_INPUT_OUTPUT,
+        rect => [ 0, 0, 30, 30 ],
+        background_color => '#00ff00',
+        event_mask => [ 'structure_notify' ],
+        protocols => [ $x->atom(name => 'WM_TAKE_FOCUS') ],
+    );
+
+    $window->name('Window 1');
+    $window->map;
+
+    my $cv = AE::cv;
+
+    my $prep = EV::prepare sub {
+        $x->flush;
+    };
+
+    my $check = EV::check sub {
+        while (defined(my $event = $x->poll_for_event)) {
+            if ($event->response_type == 161) {
+                $cv->send($event->data);
+            }
+        }
+    };
+
+    my $w = EV::io $x->get_file_descriptor, EV::READ, sub {
+        # do nothing, we only need this watcher so that EV picks up the events
+    };
+
+    my $t = AE::timer 1, 0, sub {
+        say "timer!";
+        $cv->send(undef);
+    };
+
+    my $result = $cv->recv;
+    ok(defined($result), 'got a ClientMessage');
+    if (defined($result)) {
+        my ($data, $time) = unpack("L2", $result);
+        is($data, $x->atom(name => 'WM_TAKE_FOCUS')->id, 'first uint32_t contains WM_TAKE_FOCUS atom');
+    }
+
+    done_testing;
+};
+
+
+done_testing;
diff --git a/testcases/t/59-socketpaths.t b/testcases/t/59-socketpaths.t
new file mode 100644 (file)
index 0000000..3335092
--- /dev/null
@@ -0,0 +1,84 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# Tests if the various ipc_socket_path options are correctly handled
+#
+use i3test;
+use Cwd qw(abs_path);
+use Proc::Background;
+use File::Temp qw(tempfile tempdir);
+use POSIX qw(getuid);
+use v5.10;
+
+# assuming we are run by complete-run.pl
+my $i3_path = abs_path("../i3");
+
+#####################################################################
+# default case: socket will be created in /tmp/i3-<username>/ipc-socket.<pid>
+#####################################################################
+
+my ($fh, $tmpfile) = tempfile();
+say $fh "# i3 config file (v4)";
+say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
+close($fh);
+
+diag("Starting i3");
+my $i3cmd = "unset XDG_RUNTIME_DIR; exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null";
+my $process = Proc::Background->new($i3cmd);
+sleep 1;
+
+diag("pid = " . $process->pid);
+
+my $folder = "/tmp/i3-" . getpwuid(getuid());
+ok(-d $folder, "folder $folder exists");
+my $socketpath = "$folder/ipc-socket." . $process->pid;
+ok(-S $socketpath, "file $socketpath exists and is a socket");
+
+exit_gracefully($process->pid, $socketpath);
+
+sleep 0.25;
+
+#####################################################################
+# XDG_RUNTIME_DIR case: socket gets created in $XDG_RUNTIME_DIR/i3/ipc-socket.<pid>
+#####################################################################
+
+my $rtdir = tempdir(CLEANUP => 1);
+
+ok(! -e "$rtdir/i3", "$rtdir/i3 does not exist yet");
+
+$i3cmd = "export XDG_RUNTIME_DIR=$rtdir; exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null";
+$process = Proc::Background->new($i3cmd);
+sleep 1;
+
+ok(-d "$rtdir/i3", "$rtdir/i3 exists and is a directory");
+$socketpath = "$rtdir/i3/ipc-socket." . $process->pid;
+ok(-S $socketpath, "file $socketpath exists and is a socket");
+
+exit_gracefully($process->pid, $socketpath);
+
+sleep 0.25;
+
+#####################################################################
+# configuration file case: socket gets placed whereever we specify
+#####################################################################
+
+my $tmpdir = tempdir(CLEANUP => 1);
+$socketpath = $tmpdir . "/config.sock";
+ok(! -e $socketpath, "$socketpath does not exist yet");
+
+($fh, $tmpfile) = tempfile();
+say $fh "# i3 config file (v4)";
+say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
+say $fh "ipc-socket $socketpath";
+close($fh);
+
+$i3cmd = "export XDG_RUNTIME_DIR=$rtdir; exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null";
+$process = Proc::Background->new($i3cmd);
+sleep 1;
+
+ok(-S $socketpath, "file $socketpath exists and is a socket");
+
+exit_gracefully($process->pid, $socketpath);
+
+done_testing;
diff --git a/testcases/t/61-regress-borders-restart.t b/testcases/t/61-regress-borders-restart.t
new file mode 100644 (file)
index 0000000..1acf6c6
--- /dev/null
@@ -0,0 +1,39 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test to check if borders are correctly restored after an inplace
+# restart.
+# found in eb8ad348b28e243cba1972e802ca8ee636472fc9
+#
+use X11::XCB qw(:all);
+use List::Util qw(first);
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+my $i3 = i3(get_socket_path());
+my $tmp = fresh_workspace;
+my $window = open_standard_window($x);
+
+sub get_border_style {
+    my @content = @{get_ws_content($tmp)};
+    my $wininfo = first { $_->{window} == $window->id } @content;
+
+    return $wininfo->{border};
+}
+
+is(get_border_style(), 'normal', 'border style normal');
+
+cmd 'border 1pixel';
+
+is(get_border_style(), '1pixel', 'border style 1pixel after changing');
+
+# perform an inplace-restart
+cmd 'restart';
+
+sleep 0.25;
+
+does_i3_live;
+
+is(get_border_style(), '1pixel', 'border style still 1pixel after restart');
+
+done_testing;
diff --git a/testcases/t/62-regress-dock-urgent.t b/testcases/t/62-regress-dock-urgent.t
new file mode 100644 (file)
index 0000000..8d18873
--- /dev/null
@@ -0,0 +1,59 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test for setting the urgent hint on dock clients.
+# found in 4be3178d4d360c2996217d811e61161c84d25898
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# verify that there is no dock window yet
+#####################################################################
+
+# Children of all dockareas
+my @docked = get_dock_clients;
+
+is(@docked, 0, 'no dock clients yet');
+
+# open a dock client
+
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30],
+    background_color => '#FF0000',
+    window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+);
+
+$window->map;
+
+sleep 0.25;
+
+#####################################################################
+# check that we can find it in the layout tree at the expected position
+#####################################################################
+
+@docked = get_dock_clients;
+is(@docked, 1, 'one dock client found');
+
+# verify the height
+my $docknode = $docked[0];
+
+is($docknode->{rect}->{height}, 30, 'dock node has unchanged height');
+
+$window->add_hint('urgency');
+
+sleep 0.25;
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/63-wm-state.t b/testcases/t/63-wm-state.t
new file mode 100644 (file)
index 0000000..7e98328
--- /dev/null
@@ -0,0 +1,41 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when
+# unmapped.
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+    use_ok('X11::XCB::Window');
+    use_ok('X11::XCB::Event::Generic');
+    use_ok('X11::XCB::Event::MapNotify');
+    use_ok('X11::XCB::Event::ClientMessage');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#00ff00',
+    event_mask => [ 'structure_notify' ],
+);
+
+$window->name('Window 1');
+$window->map;
+
+diag('window mapped');
+
+sleep 0.5;
+
+is($window->state, ICCCM_WM_STATE_NORMAL, 'WM_STATE normal');
+
+$window->unmap;
+
+sleep 0.5;
+
+is($window->state, ICCCM_WM_STATE_WITHDRAWN, 'WM_STATE withdrawn');
+
+done_testing;
diff --git a/testcases/t/64-kill-win-vs-client.t b/testcases/t/64-kill-win-vs-client.t
new file mode 100644 (file)
index 0000000..2e0669b
--- /dev/null
@@ -0,0 +1,66 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when
+# unmapped.
+#
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+sub two_windows {
+    my $tmp = fresh_workspace;
+
+    ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+    my $first = open_standard_window($x);
+    my $second = open_standard_window($x);
+
+    is($x->input_focus, $second->id, 'second window focused');
+    ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
+
+    return $tmp;
+}
+
+##############################################################
+# 1: open two windows (in the same client), kill one and see if
+# the other one is still there
+##############################################################
+
+my $tmp = two_windows;
+
+cmd 'kill';
+
+sleep 0.25;
+
+ok(@{get_ws_content($tmp)} == 1, 'one container left after killing');
+
+##############################################################
+# 2: same test case as test 1, but with the explicit variant
+# 'kill window'
+##############################################################
+
+my $tmp = two_windows;
+
+cmd 'kill window';
+
+sleep 0.25;
+
+ok(@{get_ws_content($tmp)} == 1, 'one container left after killing');
+
+##############################################################
+# 3: open two windows (in the same client), use 'kill client'
+# and check if both are gone
+##############################################################
+
+my $tmp = two_windows;
+
+cmd 'kill client';
+
+sleep 0.25;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers left after killing');
+
+done_testing;
diff --git a/testcases/t/65-for_window.t b/testcases/t/65-for_window.t
new file mode 100644 (file)
index 0000000..d295254
--- /dev/null
@@ -0,0 +1,160 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+#
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+##############################################################
+# 1: test the following directive:
+#    for_window [class="borderless"] border none
+# by first creating a window with a different class (should get
+# the normal border), then creating a window with the class
+# "borderless" (should get no border)
+##############################################################
+
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#00ff00',
+);
+
+$window->name('Border window');
+$window->map;
+sleep 0.25;
+
+my @content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'normal', 'normal border');
+
+$window->unmap;
+sleep 0.25;
+
+my @content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 0, 'no more nodes');
+diag('content = '. Dumper(\@content));
+
+$window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#00ff00',
+);
+
+$window->_create;
+
+# TODO: move this to X11::XCB::Window
+sub set_wm_class {
+    my ($id, $class, $instance) = @_;
+
+    # Add a _NET_WM_STRUT_PARTIAL hint
+    my $atomname = $x->atom(name => 'WM_CLASS');
+    my $atomtype = $x->atom(name => 'STRING');
+
+    $x->change_property(
+        PROP_MODE_REPLACE,
+        $id,
+        $atomname->id,
+        $atomtype->id,
+        8,
+        length($class) + length($instance) + 2,
+        "$instance\x00$class\x00"
+    );
+}
+
+set_wm_class($window->id, 'borderless', 'borderless');
+$window->name('Borderless window');
+$window->map;
+sleep 0.25;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'none', 'no border');
+
+$window->unmap;
+sleep 0.25;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 0, 'no more nodes');
+
+##############################################################
+# 2: match on the title, check if for_window is really executed
+# only once
+##############################################################
+
+$window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#00ff00',
+);
+
+$window->name('special title');
+$window->map;
+sleep 0.25;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'normal', 'normal border');
+
+$window->name('special borderless title');
+sleep 0.25;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{border}, 'none', 'no border');
+
+$window->name('special title');
+sleep 0.25;
+
+cmd 'border normal';
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{border}, 'normal', 'border reset to normal');
+
+$window->name('special borderless title');
+sleep 0.25;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{border}, 'normal', 'still normal border');
+
+$window->unmap;
+sleep 0.25;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 0, 'no more nodes');
+
+##############################################################
+# 3: match on the title, set border style *and* a mark
+##############################################################
+
+$window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#00ff00',
+);
+
+$window->name('special mark title');
+$window->map;
+sleep 0.25;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'none', 'no border');
+
+my $other = open_standard_window($x);
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 2, 'two nodes');
+is($content[0]->{border}, 'none', 'no border');
+is($content[1]->{border}, 'normal', 'normal border');
+ok(!$content[0]->{focused}, 'first one not focused');
+
+cmd qq|[con_mark="bleh"] focus|;
+
+@content = @{get_ws_content($tmp)};
+ok($content[0]->{focused}, 'first node focused');
+
+done_testing;
diff --git a/testcases/t/66-assign.t b/testcases/t/66-assign.t
new file mode 100644 (file)
index 0000000..8e70127
--- /dev/null
@@ -0,0 +1,228 @@
+#!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 Cwd qw(abs_path);
+use Proc::Background;
+use File::Temp qw(tempfile tempdir);
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+use v5.10;
+
+my $x = X11::XCB::Connection->new;
+
+# assuming we are run by complete-run.pl
+my $i3_path = abs_path("../i3");
+
+# TODO: move to X11::XCB
+sub set_wm_class {
+    my ($id, $class, $instance) = @_;
+
+    # Add a _NET_WM_STRUT_PARTIAL hint
+    my $atomname = $x->atom(name => 'WM_CLASS');
+    my $atomtype = $x->atom(name => 'STRING');
+
+    $x->change_property(
+        PROP_MODE_REPLACE,
+        $id,
+        $atomname->id,
+        $atomtype->id,
+        8,
+        length($class) + length($instance) + 2,
+        "$instance\x00$class\x00"
+    );
+}
+
+
+#####################################################################
+# start a window and see that it does not get assigned with an empty config
+#####################################################################
+
+my $socketpath = File::Temp::tempnam('/tmp', 'i3-test-socket-');
+
+my ($fh, $tmpfile) = tempfile();
+say $fh "# i3 config file (v4)";
+say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
+say $fh "ipc-socket $socketpath";
+close($fh);
+
+diag("Starting i3");
+my $i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null";
+my $process = Proc::Background->new($i3cmd);
+sleep 1;
+
+# force update of the cached socket path in lib/i3test
+get_socket_path(0);
+
+diag("pid = " . $process->pid);
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#0000ff',
+);
+
+$window->_create;
+set_wm_class($window->id, 'special', 'special');
+$window->name('special window');
+$window->map;
+sleep 0.25;
+
+ok(@{get_ws_content($tmp)} == 1, 'special window got managed to current (random) workspace');
+
+exit_gracefully($process->pid);
+
+$window->destroy;
+
+sleep 0.25;
+
+#####################################################################
+# start a window and see that it gets assigned to a formerly unused
+# workspace
+#####################################################################
+
+($fh, $tmpfile) = tempfile();
+say $fh "# i3 config file (v4)";
+say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
+say $fh "ipc-socket $socketpath";
+say $fh q|assign "special" → targetws|;
+close($fh);
+
+diag("Starting i3");
+$i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null";
+$process = Proc::Background->new($i3cmd);
+sleep 1;
+
+# force update of the cached socket path in lib/i3test
+get_socket_path(0);
+
+diag("pid = " . $process->pid);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+my $workspaces = get_workspace_names;
+ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
+
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#0000ff',
+);
+
+$window->_create;
+set_wm_class($window->id, 'special', 'special');
+$window->name('special window');
+$window->map;
+sleep 0.25;
+
+ok(@{get_ws_content($tmp)} == 0, 'still no containers');
+ok("targetws" ~~ @{get_workspace_names()}, 'targetws exists');
+
+$window->destroy;
+
+exit_gracefully($process->pid);
+
+sleep 0.25;
+
+#####################################################################
+# start a window and see that it gets assigned to a workspace which has content
+# already, next to the existing node.
+#####################################################################
+
+diag("Starting i3");
+$i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null";
+$process = Proc::Background->new($i3cmd);
+sleep 1;
+
+# force update of the cached socket path in lib/i3test
+get_socket_path(0);
+
+diag("pid = " . $process->pid);
+
+# initialize the target workspace, then go to a fresh one
+ok(!("targetws" ~~ @{get_workspace_names()}), 'targetws does not exist yet');
+cmd 'workspace targetws';
+cmp_ok(@{get_ws_content('targetws')}, '==', 0, 'no containers on targetws yet');
+cmd 'open';
+cmp_ok(@{get_ws_content('targetws')}, '==', 1, 'one container on targetws');
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+ok("targetws" ~~ @{get_workspace_names()}, 'targetws does not exist yet');
+
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#0000ff',
+);
+
+$window->_create;
+set_wm_class($window->id, 'special', 'special');
+$window->name('special window');
+$window->map;
+sleep 0.25;
+
+ok(@{get_ws_content($tmp)} == 0, 'still no containers');
+ok(@{get_ws_content('targetws')} == 2, 'two containers on targetws');
+
+exit_gracefully($process->pid);
+
+#####################################################################
+# start a window and see that it gets assigned to a workspace which has content
+# already, next to the existing node.
+#####################################################################
+
+($fh, $tmpfile) = tempfile();
+say $fh "# i3 config file (v4)";
+say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
+say $fh "ipc-socket $socketpath";
+say $fh q|assign "special" → ~|;
+close($fh);
+
+diag("Starting i3");
+$i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null";
+$process = Proc::Background->new($i3cmd);
+sleep 1;
+
+# force update of the cached socket path in lib/i3test
+get_socket_path(0);
+
+diag("pid = " . $process->pid);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+my $workspaces = get_workspace_names;
+ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
+
+my $window = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => [ 0, 0, 30, 30 ],
+    background_color => '#0000ff',
+);
+
+$window->_create;
+set_wm_class($window->id, 'special', 'special');
+$window->name('special window');
+$window->map;
+sleep 0.25;
+
+my $content = get_ws($tmp);
+ok(@{$content->{nodes}} == 0, 'no tiling cons');
+ok(@{$content->{floating_nodes}} == 1, 'one floating con');
+
+$window->destroy;
+
+exit_gracefully($process->pid);
+
+sleep 0.25;
+
+done_testing;
diff --git a/testcases/t/67-workspace_layout.t b/testcases/t/67-workspace_layout.t
new file mode 100644 (file)
index 0000000..cfe8bf6
--- /dev/null
@@ -0,0 +1,149 @@
+#!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 Cwd qw(abs_path);
+use Proc::Background;
+use File::Temp qw(tempfile tempdir);
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+
+my $x = X11::XCB::Connection->new;
+
+# assuming we are run by complete-run.pl
+my $i3_path = abs_path("../i3");
+
+#####################################################################
+# 1: check that with an empty config, cons are place next to each
+# other and no split containers are created
+#####################################################################
+
+my $socketpath = File::Temp::tempnam('/tmp', 'i3-test-socket-');
+
+my ($fh, $tmpfile) = tempfile();
+say $fh "# i3 config file (v4)";
+say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
+say $fh "ipc-socket $socketpath";
+close($fh);
+
+diag("Starting i3");
+my $i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null";
+my $process = Proc::Background->new($i3cmd);
+sleep 1;
+
+# force update of the cached socket path in lib/i3test
+get_socket_path(0);
+
+diag("pid = " . $process->pid);
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $first = open_standard_window($x);
+my $second = open_standard_window($x);
+
+is($x->input_focus, $second->id, 'second window focused');
+ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
+isnt($content[0]->{layout}, 'stacked', 'layout not stacked');
+isnt($content[1]->{layout}, 'stacked', 'layout not stacked');
+
+exit_gracefully($process->pid);
+
+#####################################################################
+# 2: set workspace_layout stacked, check that when opening two cons,
+# they end up in a stacked con
+#####################################################################
+
+($fh, $tmpfile) = tempfile();
+say $fh "# i3 config file (v4)";
+say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
+say $fh "ipc-socket $socketpath";
+say $fh "workspace_layout stacked";
+close($fh);
+
+diag("Starting i3");
+$i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null";
+$process = Proc::Background->new($i3cmd);
+sleep 1;
+
+# force update of the cached socket path in lib/i3test
+get_socket_path(0);
+
+diag("pid = " . $process->pid);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_standard_window($x);
+$second = open_standard_window($x);
+
+is($x->input_focus, $second->id, 'second window focused');
+my @content = @{get_ws_content($tmp)};
+ok(@content == 1, 'one con at workspace level');
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+
+#####################################################################
+# 3: focus parent, open two new cons, check that they end up in a stacked
+# con
+#####################################################################
+
+cmd 'focus parent';
+my $right_top = open_standard_window($x);
+my $right_bot = open_standard_window($x);
+
+@content = @{get_ws_content($tmp)};
+is(@content, 2, 'two cons at workspace level after focus parent');
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+is($content[1]->{layout}, 'stacked', 'layout stacked');
+
+#####################################################################
+# 4: move one of the cons to the right, check that it will end up in
+# a stacked con
+#####################################################################
+
+cmd 'move right';
+
+@content = @{get_ws_content($tmp)};
+is(@content, 3, 'three cons at workspace level after move');
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+is($content[1]->{layout}, 'stacked', 'layout stacked');
+is($content[2]->{layout}, 'stacked', 'layout stacked');
+
+#####################################################################
+# 5: move it to the left again, check that the stacked con is deleted
+#####################################################################
+
+cmd 'move left';
+
+@content = @{get_ws_content($tmp)};
+is(@content, 2, 'two cons at workspace level after moving back');
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+is($content[1]->{layout}, 'stacked', 'layout stacked');
+
+#####################################################################
+# 6: move it to a different workspace, check that it ends up in a
+# stacked con
+#####################################################################
+
+my $otmp = get_unused_workspace;
+
+cmd "move workspace $otmp";
+
+@content = @{get_ws_content($tmp)};
+is(@content, 2, 'still two cons on this workspace');
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+is($content[1]->{layout}, 'stacked', 'layout stacked');
+
+@content = @{get_ws_content($otmp)};
+is(@content, 1, 'one con on target workspace');
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+
+exit_gracefully($process->pid);
+
+done_testing;
diff --git a/testcases/t/68-regress-fullscreen-restart.t b/testcases/t/68-regress-fullscreen-restart.t
new file mode 100644 (file)
index 0000000..fcc9ac7
--- /dev/null
@@ -0,0 +1,28 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Verifies that i3 survives inplace restarts with fullscreen containers
+#
+use i3test;
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+
+my $x = X11::XCB::Connection->new;
+
+fresh_workspace;
+
+open_standard_window($x);
+open_standard_window($x);
+
+cmd 'layout stacking';
+sleep 1;
+
+cmd 'fullscreen';
+sleep 1;
+
+cmd 'restart';
+sleep 1;
+
+does_i3_live;
+
+done_testing;
diff --git a/testcases/t/69-border-toggle.t b/testcases/t/69-border-toggle.t
new file mode 100644 (file)
index 0000000..aec8df6
--- /dev/null
@@ -0,0 +1,40 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests if the 'border toggle' command works correctly
+#
+use i3test;
+
+my $tmp = fresh_workspace;
+
+cmd 'open';
+
+my @nodes = @{get_ws_content($tmp)};
+is(@nodes, 1, 'one container on this workspace');
+is($nodes[0]->{border}, 'normal', 'border style normal');
+
+cmd 'border 1pixel';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, '1pixel', 'border style 1pixel');
+
+cmd 'border none';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, 'none', 'border style none');
+
+cmd 'border normal';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, 'normal', 'border style back to normal');
+
+cmd 'border toggle';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, 'none', 'border style none');
+
+cmd 'border toggle';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, '1pixel', 'border style 1pixel');
+
+cmd 'border toggle';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, 'normal', 'border style back to normal');
+
+done_testing;
diff --git a/testcases/t/70-force_focus_wrapping.t b/testcases/t/70-force_focus_wrapping.t
new file mode 100644 (file)
index 0000000..020a360
--- /dev/null
@@ -0,0 +1,119 @@
+#!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 Cwd qw(abs_path);
+use Proc::Background;
+use File::Temp qw(tempfile tempdir);
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+
+my $x = X11::XCB::Connection->new;
+
+# assuming we are run by complete-run.pl
+my $i3_path = abs_path("../i3");
+my $socketpath = File::Temp::tempnam('/tmp', 'i3-test-socket-');
+
+#####################################################################
+# 1: test the wrapping behaviour without force_focus_wrapping
+#####################################################################
+
+my ($fh, $tmpfile) = tempfile();
+say $fh "# i3 config file (v4)";
+say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
+say $fh "ipc-socket $socketpath";
+close($fh);
+
+diag("Starting i3");
+my $i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null";
+my $process = Proc::Background->new($i3cmd);
+sleep 1;
+
+# force update of the cached socket path in lib/i3test
+get_socket_path(0);
+
+diag("pid = " . $process->pid);
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $first = open_standard_window($x);
+my $second = open_standard_window($x);
+
+cmd 'layout tabbed';
+cmd 'focus parent';
+
+my $third = open_standard_window($x);
+is($x->input_focus, $third->id, 'third window focused');
+
+cmd 'focus left';
+is($x->input_focus, $second->id, 'second window focused');
+
+cmd 'focus left';
+is($x->input_focus, $first->id, 'first window focused');
+
+# now test the wrapping
+cmd 'focus left';
+is($x->input_focus, $second->id, 'second window focused');
+
+# but focusing right should not wrap now, but instead focus the third window
+cmd 'focus right';
+is($x->input_focus, $third->id, 'third window focused');
+
+exit_gracefully($process->pid);
+
+#####################################################################
+# 2: test the wrapping behaviour with force_focus_wrapping
+#####################################################################
+
+($fh, $tmpfile) = tempfile();
+say $fh "# i3 config file (v4)";
+say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
+say $fh "ipc-socket $socketpath";
+say $fh "force_focus_wrapping true";
+close($fh);
+
+diag("Starting i3");
+$i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null";
+$process = Proc::Background->new($i3cmd);
+sleep 1;
+
+# force update of the cached socket path in lib/i3test
+get_socket_path(0);
+
+diag("pid = " . $process->pid);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_standard_window($x);
+$second = open_standard_window($x);
+
+cmd 'layout tabbed';
+cmd 'focus parent';
+
+$third = open_standard_window($x);
+is($x->input_focus, $third->id, 'third window focused');
+
+cmd 'focus left';
+is($x->input_focus, $second->id, 'second window focused');
+
+cmd 'focus left';
+is($x->input_focus, $first->id, 'first window focused');
+
+# now test the wrapping
+cmd 'focus left';
+is($x->input_focus, $second->id, 'second window focused');
+
+# focusing right should now be forced to wrap
+cmd 'focus right';
+is($x->input_focus, $first->id, 'first window focused');
+
+exit_gracefully($process->pid);
+
+done_testing;
diff --git a/testcases/t/71-config-migrate.t b/testcases/t/71-config-migrate.t
new file mode 100644 (file)
index 0000000..e93ca9e
--- /dev/null
@@ -0,0 +1,345 @@
+#!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.pl correctly migrates all config file
+# directives and commands
+#
+use i3test;
+use Cwd qw(abs_path);
+use Proc::Background;
+use File::Temp qw(tempfile tempdir);
+use POSIX qw(getuid);
+use Data::Dumper;
+use v5.10;
+
+# reads in a whole file
+sub slurp {
+    open my $fh, '<', shift;
+    local $/;
+    <$fh>;
+}
+
+sub migrate_config {
+    my ($config) = @_;
+
+    my ($fh, $tmpfile) = tempfile();
+    print $fh $config;
+    close($fh);
+
+    my $cmd = "sh -c 'exec " . abs_path("../i3-migrate-config-to-v4.pl") . " --v3 <$tmpfile'";
+    return [ split /\n/, qx($cmd) ];
+}
+
+sub line_exists {
+    my ($lines, $pattern) = @_;
+
+    for my $line (@$lines) {
+        return 1 if $line =~ $pattern;
+    }
+
+    return 0
+}
+
+#####################################################################
+# check that some directives remain untouched
+#####################################################################
+
+my $input = <<EOT;
+    font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $output = migrate_config($input);
+ok(line_exists($output, qr|font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1|), 'font directive unchanged');
+
+$input = <<EOT;
+    floating_Modifier Mod1
+    focus_follows_mouse true
+    ipc-socket /tmp/i3-ipc.sock
+    ipc_socket /tmp/i3-ipc.sock
+    exec /usr/bin/i3
+    set stuff Mod1
+    assign "XTerm" → 3
+    assign "XTerm" → ~5
+    client.focused #2F343A #900000 #FFFFFF
+    client.focused_inactive #FF0000 #FF0000 #FF0000
+    client.unfocused #00FF00 #00FF00 #00FF00
+    client.urgent #0000FF #0000FF #0000FF
+    client.background #000000
+EOT
+
+$output = migrate_config($input);
+ok(line_exists($output, qr|^floating_Modifier Mod1$|), 'floating_modifier unchanged');
+ok(line_exists($output, qr|^focus_follows_mouse true$|), 'focus_follows_mouse unchanged');
+ok(line_exists($output, qr|^ipc-socket /tmp/i3-ipc.sock$|), 'ipc-socket unchanged');
+ok(line_exists($output, qr|^ipc_socket /tmp/i3-ipc.sock$|), 'ipc_socket unchanged');
+ok(line_exists($output, qr|^exec /usr/bin/i3|), 'exec unchanged');
+ok(line_exists($output, qr|^set stuff Mod1|), 'set unchanged');
+ok(line_exists($output, qr|^assign "XTerm" → 3|), 'assign unchanged');
+ok(line_exists($output, qr|^assign "XTerm" → ~5|), 'assign unchanged');
+ok(line_exists($output, qr|^client\.focused #2F343A #900000 #FFFFFF$|), 'client.focused unchanged');
+ok(line_exists($output, qr|^client\.focused_inactive #FF0000 #FF0000 #FF0000$|), 'client.focused_inactive unchanged');
+ok(line_exists($output, qr|^client\.unfocused #00FF00 #00FF00 #00FF00$|), 'client.unfocused unchanged');
+ok(line_exists($output, qr|^client\.urgent #0000FF #0000FF #0000FF$|), 'client.urgent unchanged');
+ok(line_exists($output, qr|^client\.background #000000$|), 'client.background unchanged');
+
+#####################################################################
+# check whether the bar colors get removed properly
+#####################################################################
+
+$input = <<EOT;
+    bar.focused #FFFF00 #FFFF00 #FFFF00
+    bar.unfocused #FFFF00 #FFFF00 #FFFF00
+    bar.urgent #FFFF00 #FFFF00 #FFFF00
+EOT
+
+$output = migrate_config($input);
+ok(!line_exists($output, qr|^bar\.|), 'no bar. lines');
+ok(line_exists($output, qr|^#.*REMOVED bar|), 'note bar. removed');
+
+
+#####################################################################
+# check whether the other directives get converted correctly
+#####################################################################
+
+$input = <<EOT;
+    new_container stacking
+    workspace_bar no
+    new_window bb
+EOT
+
+$output = migrate_config($input);
+ok(line_exists($output, qr|^workspace_layout stacking$|), 'new_container changed');
+ok(line_exists($output, qr|REMOVED workspace_bar|), 'workspace_bar removed');
+ok(!line_exists($output, qr|^workspace_bar|), 'no workspace_bar in the output');
+ok(line_exists($output, qr|^new_window none$|), 'new_window changed');
+
+#####################################################################
+# check whether new_window's parameters get changed correctly
+#####################################################################
+
+$output = migrate_config('new_window bb');
+ok(line_exists($output, qr|^new_window none$|), 'new_window bb changed');
+
+$output = migrate_config('new_window bn');
+ok(line_exists($output, qr|^new_window normal$|), 'new_window bn changed');
+
+$output = migrate_config('new_window bp');
+ok(line_exists($output, qr|^new_window 1pixel$|), 'new_window bp changed');
+
+#####################################################################
+# check that some commands remain untouched
+#####################################################################
+
+$input = <<EOT;
+    bindsym Mod1+s exec /usr/bin/urxvt
+    bindsym Mod1+s mark foo
+    bindsym Mod1+s restart
+    bindsym Mod1+s reload
+    bindsym Mod1+s exit
+    bindsym Mod1+s stack-limit cols 2
+    bindsym Mod1+s stack-limit rows 3
+    bind Mod1+c exec /usr/bin/urxvt
+    mode "asdf" {
+        bind 36 mode default
+    }
+EOT
+
+$output = migrate_config($input);
+ok(line_exists($output, qr|^bindsym Mod1\+s exec /usr/bin/urxvt$|), 'exec unchanged');
+ok(line_exists($output, qr|^bindsym Mod1\+s mark foo$|), 'mark unchanged');
+ok(line_exists($output, qr|^bindsym Mod1\+s restart$|), 'restart unchanged');
+ok(line_exists($output, qr|^bindsym Mod1\+s reload$|), 'reload unchanged');
+ok(line_exists($output, qr|^bindsym Mod1\+s exit$|), 'exit unchanged');
+ok(line_exists($output, qr|^bindsym Mod1\+s stack-limit cols 2$|), 'stack-limit unchanged');
+ok(line_exists($output, qr|^bindsym Mod1\+s stack-limit rows 3$|), 'stack-limit unchanged');
+ok(line_exists($output, qr|^bindcode Mod1\+c exec /usr/bin/urxvt$|), 'bind changed to bindcode');
+ok(line_exists($output, qr|^mode "asdf" {$|), 'mode asdf unchanged');
+ok(line_exists($output, qr|^bindcode 36 mode \"default\"$|), 'mode default unchanged');
+ok(line_exists($output, qr|^}$|), 'closing mode bracket still there');
+
+#####################################################################
+# check the simple command replacements
+#####################################################################
+
+$input = <<EOT;
+    bindsym Mod1+s s
+    bindsym Mod1+s d
+    bindsym Mod1+s T
+
+    bindsym Mod1+s f
+    bindsym Mod1+s fg
+
+    bindsym Mod1+s t
+
+    bindsym Mod1+s h
+    bindsym Mod1+s j
+    bindsym Mod1+s k
+    bindsym Mod1+s l
+
+    bindsym Mod1+s mh
+    bindsym Mod1+s mj
+    bindsym Mod1+s mk
+    bindsym Mod1+s ml
+
+    bindsym Mod1+s bn
+    bindsym Mod1+s bp
+    bindsym Mod1+s bb
+    bindsym Mod1+s bt
+
+    bindsym Mod1+j wch
+    bindsym Mod1+j wcml
+
+    bindsym Mod1+k kill
+
+    bindsym Mod1+n nw
+    bindsym Mod1+p pw
+EOT
+
+$output = migrate_config($input);
+ok(line_exists($output, qr|^bindsym Mod1\+s layout stacking$|), 's replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s layout default$|), 'd replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s layout tabbed$|), 'T replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s fullscreen$|), 'f replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s fullscreen global$|), 'fg replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s floating toggle$|), 't replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s focus left$|), 'h replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s focus down$|), 'j replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s focus up$|), 'k replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s focus right$|), 'l replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s move left$|), 'mh replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s move down$|), 'mj replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s move up$|), 'mk replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s move right$|), 'ml replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s border normal$|), 'bn replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s border 1pixel$|), 'bp replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s border none$|), 'bb replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s border toggle$|), 'bt replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+j focus parent; focus left$|), 'with container replaced with focus parent; focus left');
+ok(line_exists($output, qr|^bindsym Mod1\+j focus parent; move right$|), 'with container replaced with focus parent; move right');
+ok(line_exists($output, qr|^bindsym Mod1\+k kill$|), 'kill unchanged');
+ok(line_exists($output, qr|^bindsym Mod1\+n workspace next$|), 'nw replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+p workspace prev$|), 'pw replaced');
+
+#####################################################################
+# check more advanced replacements
+#####################################################################
+
+$input = <<EOT;
+    bindsym Mod1+s goto foo
+EOT
+
+$output = migrate_config($input);
+ok(line_exists($output, qr|^bindsym Mod1\+s \[con_mark="foo"\] focus$|), 'goto replaced');
+
+#####################################################################
+# check whether focus's parameters get changed correctly
+#####################################################################
+
+$output = migrate_config('bindsym Mod1+f focus 3');
+ok(line_exists($output, qr|^#.*focus.*obsolete.*focus 3$|), 'focus [number] gone');
+
+$output = migrate_config('bindsym Mod1+f focus floating');
+ok(line_exists($output, qr|^bindsym Mod1\+f focus floating$|), 'focus floating unchanged');
+
+$output = migrate_config('bindsym Mod1+f focus tiling');
+ok(line_exists($output, qr|^bindsym Mod1\+f focus tiling$|), 'focus tiling unchanged');
+
+$output = migrate_config('bindsym Mod1+f focus ft');
+ok(line_exists($output, qr|^bindsym Mod1\+f focus mode_toggle$|), 'focus ft changed');
+
+#####################################################################
+# check whether resize's parameters get changed correctly
+#####################################################################
+
+$output = migrate_config('bindsym Mod1+f resize left +10');
+ok(line_exists($output, qr|^bindsym Mod1\+f resize grow left 10 px$|), 'resize left changed');
+
+$output = migrate_config('bindsym Mod1+f resize top -20');
+ok(line_exists($output, qr|^bindsym Mod1\+f resize shrink top 20 px$|), 'resize top changed');
+
+$output = migrate_config('bindsym Mod1+f resize right -20');
+ok(line_exists($output, qr|^bindsym Mod1\+f resize shrink right 20 px$|), 'resize right changed');
+
+$output = migrate_config('bindsym Mod1+f resize bottom +23');
+ok(line_exists($output, qr|^bindsym Mod1\+f resize grow bottom 23 px$|), 'resize bottom changed');
+
+#####################################################################
+# check whether jump's parameters get changed correctly
+#####################################################################
+
+$output = migrate_config('bindsym Mod1+f jump 3');
+ok(line_exists($output, qr|^#.*obsolete.*jump 3$|), 'jump to workspace removed');
+
+$output = migrate_config('bindsym Mod1+f jump 3 4 5');
+ok(line_exists($output, qr|^#.*obsolete.*jump 3 4 5$|), 'jump to workspace + col/row removed');
+
+$output = migrate_config('bindsym Mod1+f jump "XTerm"');
+ok(line_exists($output, qr|^bindsym Mod1\+f \[class="XTerm"\] focus$|), 'jump changed');
+
+$output = migrate_config('bindsym Mod1+f jump "XTerm/irssi"');
+ok(line_exists($output, qr|^bindsym Mod1\+f \[class="XTerm" title="irssi"\] focus$|), 'jump changed');
+
+#####################################################################
+# check whether workspace commands are handled correctly
+#####################################################################
+
+$output = migrate_config('workspace 3 output VGA-1');
+ok(line_exists($output, qr|^workspace 3 output VGA-1$|), 'workspace assignment unchanged');
+
+$output = migrate_config('workspace 3 work');
+ok(!line_exists($output, qr|^workspace|), 'workspace name not present');
+ok(line_exists($output, qr|#.*workspace name.*bindings|), 'note present');
+
+$input = <<EOT;
+    workspace 3 work
+    bindsym Mod1+3 3
+EOT
+$output = migrate_config($input);
+ok(!line_exists($output, qr|^workspace|), 'workspace name not present');
+ok(line_exists($output, qr|^bindsym Mod1\+3 workspace work|), 'named workspace in bindings');
+
+# The same, but in reverse order
+$input = <<EOT;
+    bindsym Mod1+3 3
+    workspace 3 work
+EOT
+$output = migrate_config($input);
+ok(!line_exists($output, qr|^workspace|), 'workspace name not present');
+ok(line_exists($output, qr|^bindsym Mod1\+3 workspace work|), 'named workspace in bindings');
+
+$output = migrate_config('bindsym Mod1+3 3');
+ok(line_exists($output, qr|^bindsym Mod1\+3 workspace 3|), 'workspace changed');
+
+$output = migrate_config('bindsym Mod1+3 m3');
+ok(line_exists($output, qr|^bindsym Mod1\+3 move workspace 3|), 'move workspace changed');
+
+$input = <<EOT;
+    workspace 3 work
+    bindsym Mod1+3 m3
+EOT
+$output = migrate_config($input);
+ok(!line_exists($output, qr|^workspace|), 'workspace name not present');
+ok(line_exists($output, qr|^bindsym Mod1\+3 move workspace work|), 'move to named workspace in bindings');
+
+#####################################################################
+# check whether an i3bar call is added if the workspace bar bar was enabled
+#####################################################################
+
+$output = migrate_config('');
+ok(line_exists($output, qr|i3bar|), 'i3bar added');
+
+$output = migrate_config('workspace_bar enable');
+ok(line_exists($output, qr|i3bar|), 'i3bar added');
+
+$output = migrate_config('workspace_bar no');
+ok(!line_exists($output, qr|i3bar|), 'no i3bar added');
+
+#####################################################################
+# check whether the mode command gets quotes
+#####################################################################
+
+$output = migrate_config('bindsym Mod1+m mode foobar');
+ok(line_exists($output, qr|^bindsym Mod1\+m mode "foobar"|), 'mode got quotes');
+
+done_testing();
index eb4167a88920dc0cc29e181afe267ff16acd5382..bd40f565813eb9005d8253e5a91f59659f35ed7c 100644 (file)
@@ -1,9 +1,23 @@
 package i3test;
 # vim:ts=4:sw=4:expandtab
 
+use File::Temp qw(tmpnam);
+use Test::Builder;
 use X11::XCB::Rect;
 use X11::XCB::Window;
 use X11::XCB qw(:all);
+use AnyEvent::I3;
+use List::Util qw(first);
+use List::MoreUtils qw(lastval);
+use Time::HiRes qw(sleep);
+use Try::Tiny;
+use v5.10;
+
+use Exporter ();
+our @EXPORT = qw(get_workspace_names get_unused_workspace fresh_workspace get_ws_content get_ws get_focused open_empty_con open_standard_window get_dock_clients cmd does_i3_live exit_gracefully workspace_exists focused_ws get_socket_path);
+
+my $tester = Test::Builder->new();
+my $_cached_socket_path = undef;
 
 BEGIN {
     my $window_count = 0;
@@ -12,13 +26,32 @@ BEGIN {
     }
 }
 
+sub import {
+    my $class = shift;
+    my $pkg = caller;
+    eval "package $pkg;
+use Test::Most" . (@_ > 0 ? " qw(@_)" : "") . ";
+use Data::Dumper;
+use AnyEvent::I3;
+use Time::HiRes qw(sleep);
+use Test::Deep qw(eq_deeply cmp_deeply cmp_set cmp_bag cmp_methods useclass noclass set bag subbagof superbagof subsetof supersetof superhashof subhashof bool str arraylength Isa ignore methods regexprefonly regexpmatches num regexponly scalref reftype hashkeysonly blessed array re hash regexpref hash_each shallow array_each code arrayelementsonly arraylengthonly scalarrefonly listmethods any hashkeys isa);
+use v5.10;
+use strict;
+use warnings;
+";
+    @_ = ($class);
+    goto \&Exporter::import;
+}
+
 sub open_standard_window {
-    my ($x) = @_;
+    my ($x, $color) = @_;
+
+    $color ||= '#c0c0c0';
 
     my $window = $x->root->create_child(
         class => WINDOW_CLASS_INPUT_OUTPUT,
         rect => [ 0, 0, 30, 30 ],
-        background_color => '#C0C0C0',
+        background_color => $color,
     );
 
     $window->name('Window ' . counter_window());
@@ -29,4 +62,171 @@ sub open_standard_window {
     return $window;
 }
 
+sub open_empty_con {
+    my ($i3) = @_;
+
+    my $reply = $i3->command('open')->recv;
+    return $reply->{id};
+}
+
+sub get_workspace_names {
+    my $i3 = i3(get_socket_path());
+    my $tree = $i3->get_tree->recv;
+    my @outputs = @{$tree->{nodes}};
+    my @cons;
+    for my $output (@outputs) {
+        # get the first CT_CON of each output
+        my $content = first { $_->{type} == 2 } @{$output->{nodes}};
+        @cons = (@cons, @{$content->{nodes}});
+    }
+    [ map { $_->{name} } @cons ]
+}
+
+sub get_unused_workspace {
+    my @names = get_workspace_names();
+    my $tmp;
+    do { $tmp = tmpnam() } while ($tmp ~~ @names);
+    $tmp
+}
+
+sub fresh_workspace {
+    my $unused = get_unused_workspace;
+    cmd("workspace $unused");
+    $unused
+}
+
+sub get_ws {
+    my ($name) = @_;
+    my $i3 = i3(get_socket_path());
+    my $tree = $i3->get_tree->recv;
+
+    my @outputs = @{$tree->{nodes}};
+    my @workspaces;
+    for my $output (@outputs) {
+        # get the first CT_CON of each output
+        my $content = first { $_->{type} == 2 } @{$output->{nodes}};
+        @workspaces = (@workspaces, @{$content->{nodes}});
+    }
+
+    # as there can only be one workspace with this name, we can safely
+    # return the first entry
+    return first { $_->{name} eq $name } @workspaces;
+}
+
+#
+# returns the content (== tree, starting from the node of a workspace)
+# of a workspace. If called in array context, also includes the focus
+# stack of the workspace
+#
+sub get_ws_content {
+    my ($name) = @_;
+    my $con = get_ws($name);
+    return wantarray ? ($con->{nodes}, $con->{focus}) : $con->{nodes};
+}
+
+sub get_focused {
+    my ($ws) = @_;
+    my $con = get_ws($ws);
+
+    my @focused = @{$con->{focus}};
+    my $lf;
+    while (@focused > 0) {
+        $lf = $focused[0];
+        last unless defined($con->{focus});
+        @focused = @{$con->{focus}};
+        @cons = grep { $_->{id} == $lf } (@{$con->{nodes}}, @{$con->{'floating_nodes'}});
+        $con = $cons[0];
+    }
+
+    return $lf;
+}
+
+sub get_dock_clients {
+    my $which = shift;
+
+    my $tree = i3(get_socket_path())->get_tree->recv;
+    my @outputs = @{$tree->{nodes}};
+    # Children of all dockareas
+    my @docked;
+    for my $output (@outputs) {
+        if (!defined($which)) {
+            @docked = (@docked, map { @{$_->{nodes}} }
+                                grep { $_->{type} == 5 }
+                                @{$output->{nodes}});
+        } elsif ($which eq 'top') {
+            my $first = first { $_->{type} == 5 } @{$output->{nodes}};
+            @docked = (@docked, @{$first->{nodes}});
+        } elsif ($which eq 'bottom') {
+            my $last = lastval { $_->{type} == 5 } @{$output->{nodes}};
+            @docked = (@docked, @{$last->{nodes}});
+        }
+    }
+    return @docked;
+}
+
+sub cmd {
+    i3(get_socket_path())->command(@_)->recv
+}
+
+sub workspace_exists {
+    my ($name) = @_;
+    ($name ~~ @{get_workspace_names()})
+}
+
+sub focused_ws {
+    my $i3 = i3(get_socket_path());
+    my $tree = $i3->get_tree->recv;
+    my @outputs = @{$tree->{nodes}};
+    my @cons;
+    for my $output (@outputs) {
+        # get the first CT_CON of each output
+        my $content = first { $_->{type} == 2 } @{$output->{nodes}};
+        my $first = first { $_->{fullscreen_mode} == 1 } @{$content->{nodes}};
+        return $first->{name}
+    }
+}
+
+sub does_i3_live {
+    my $tree = i3(get_socket_path())->get_tree->recv;
+    my @nodes = @{$tree->{nodes}};
+    my $ok = (@nodes > 0);
+    $tester->ok($ok, 'i3 still lives');
+    return $ok;
+}
+
+# Tries to exit i3 gracefully (with the 'exit' cmd) or kills the PID if that fails
+sub exit_gracefully {
+    my ($pid, $socketpath) = @_;
+    $socketpath ||= get_socket_path();
+
+    my $exited = 0;
+    try {
+        say "Exiting i3 cleanly...";
+        i3($socketpath)->command('exit')->recv;
+        $exited = 1;
+    };
+
+    if (!$exited) {
+        kill(9, $pid) or die "could not kill i3";
+    }
+}
+
+# Gets the socket path from the I3_SOCKET_PATH atom stored on the X11 root window
+sub get_socket_path {
+    my ($cache) = @_;
+    $cache ||= 1;
+
+    if ($cache && defined($_cached_socket_path)) {
+        return $_cached_socket_path;
+    }
+
+    my $x = X11::XCB::Connection->new;
+    my $atom = $x->atom(name => 'I3_SOCKET_PATH');
+    my $cookie = $x->get_property(0, $x->get_root_window(), $atom->id, GET_PROPERTY_TYPE_ANY, 0, 256);
+    my $reply = $x->get_property_reply($cookie->{sequence});
+    my $socketpath = $reply->{value};
+    $_cached_socket_path = $socketpath;
+    return $socketpath;
+}
+
 1
diff --git a/tests/queue.h b/tests/queue.h
new file mode 100644 (file)
index 0000000..75bb957
--- /dev/null
@@ -0,0 +1,527 @@
+/*     $OpenBSD: queue.h,v 1.1 2007/10/26 03:14:08 niallo Exp $        */
+/*     $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $       */
+
+/*
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)queue.h     8.5 (Berkeley) 8/20/94
+ */
+
+#ifndef        _SYS_QUEUE_H_
+#define        _SYS_QUEUE_H_
+
+/*
+ * This file defines five types of data structures: singly-linked lists,
+ * lists, simple queues, tail queues, and circular queues.
+ *
+ *
+ * A singly-linked list is headed by a single forward pointer. The elements
+ * are singly linked for minimum space and pointer manipulation overhead at
+ * the expense of O(n) removal for arbitrary elements. New elements can be
+ * added to the list after an existing element or at the head of the list.
+ * Elements being removed from the head of the list should use the explicit
+ * macro for this purpose for optimum efficiency. A singly-linked list may
+ * only be traversed in the forward direction.  Singly-linked lists are ideal
+ * for applications with large datasets and few or no removals or for
+ * implementing a LIFO queue.
+ *
+ * A list is headed by a single forward pointer (or an array of forward
+ * pointers for a hash table header). The elements are doubly linked
+ * so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before
+ * or after an existing element or at the head of the list. A list
+ * may only be traversed in the forward direction.
+ *
+ * A simple queue is headed by a pair of pointers, one the head of the
+ * list and the other to the tail of the list. The elements are singly
+ * linked to save space, so elements can only be removed from the
+ * head of the list. New elements can be added to the list before or after
+ * an existing element, at the head of the list, or at the end of the
+ * list. A simple queue may only be traversed in the forward direction.
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may be traversed in either direction.
+ *
+ * A circle queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or after
+ * an existing element, at the head of the list, or at the end of the list.
+ * A circle queue may be traversed in either direction, but has a more
+ * complex end of list detection.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ */
+
+#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC))
+#define _Q_INVALIDATE(a) (a) = ((void *)-1)
+#else
+#define _Q_INVALIDATE(a)
+#endif
+
+/*
+ * Singly-linked List definitions.
+ */
+#define SLIST_HEAD(name, type)                                         \
+struct name {                                                          \
+       struct type *slh_first; /* first element */                     \
+}
+
+#define        SLIST_HEAD_INITIALIZER(head)                                    \
+       { NULL }
+
+#define SLIST_ENTRY(type)                                              \
+struct {                                                               \
+       struct type *sle_next;  /* next element */                      \
+}
+
+/*
+ * Singly-linked List access methods.
+ */
+#define        SLIST_FIRST(head)       ((head)->slh_first)
+#define        SLIST_END(head)         NULL
+#define        SLIST_EMPTY(head)       (SLIST_FIRST(head) == SLIST_END(head))
+#define        SLIST_NEXT(elm, field)  ((elm)->field.sle_next)
+
+#define        SLIST_FOREACH(var, head, field)                                 \
+       for((var) = SLIST_FIRST(head);                                  \
+           (var) != SLIST_END(head);                                   \
+           (var) = SLIST_NEXT(var, field))
+
+#define        SLIST_FOREACH_PREVPTR(var, varp, head, field)                   \
+       for ((varp) = &SLIST_FIRST((head));                             \
+           ((var) = *(varp)) != SLIST_END(head);                       \
+           (varp) = &SLIST_NEXT((var), field))
+
+/*
+ * Singly-linked List functions.
+ */
+#define        SLIST_INIT(head) {                                              \
+       SLIST_FIRST(head) = SLIST_END(head);                            \
+}
+
+#define        SLIST_INSERT_AFTER(slistelm, elm, field) do {                   \
+       (elm)->field.sle_next = (slistelm)->field.sle_next;             \
+       (slistelm)->field.sle_next = (elm);                             \
+} while (0)
+
+#define        SLIST_INSERT_HEAD(head, elm, field) do {                        \
+       (elm)->field.sle_next = (head)->slh_first;                      \
+       (head)->slh_first = (elm);                                      \
+} while (0)
+
+#define        SLIST_REMOVE_NEXT(head, elm, field) do {                        \
+       (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next;  \
+} while (0)
+
+#define        SLIST_REMOVE_HEAD(head, field) do {                             \
+       (head)->slh_first = (head)->slh_first->field.sle_next;          \
+} while (0)
+
+#define SLIST_REMOVE(head, elm, type, field) do {                      \
+       if ((head)->slh_first == (elm)) {                               \
+               SLIST_REMOVE_HEAD((head), field);                       \
+       } else {                                                        \
+               struct type *curelm = (head)->slh_first;                \
+                                                                       \
+               while (curelm->field.sle_next != (elm))                 \
+                       curelm = curelm->field.sle_next;                \
+               curelm->field.sle_next =                                \
+                   curelm->field.sle_next->field.sle_next;             \
+               _Q_INVALIDATE((elm)->field.sle_next);                   \
+       }                                                               \
+} while (0)
+
+/*
+ * List definitions.
+ */
+#define LIST_HEAD(name, type)                                          \
+struct name {                                                          \
+       struct type *lh_first;  /* first element */                     \
+}
+
+#define LIST_HEAD_INITIALIZER(head)                                    \
+       { NULL }
+
+#define LIST_ENTRY(type)                                               \
+struct {                                                               \
+       struct type *le_next;   /* next element */                      \
+       struct type **le_prev;  /* address of previous next element */  \
+}
+
+/*
+ * List access methods
+ */
+#define        LIST_FIRST(head)                ((head)->lh_first)
+#define        LIST_END(head)                  NULL
+#define        LIST_EMPTY(head)                (LIST_FIRST(head) == LIST_END(head))
+#define        LIST_NEXT(elm, field)           ((elm)->field.le_next)
+
+#define LIST_FOREACH(var, head, field)                                 \
+       for((var) = LIST_FIRST(head);                                   \
+           (var)!= LIST_END(head);                                     \
+           (var) = LIST_NEXT(var, field))
+
+/*
+ * List functions.
+ */
+#define        LIST_INIT(head) do {                                            \
+       LIST_FIRST(head) = LIST_END(head);                              \
+} while (0)
+
+#define LIST_INSERT_AFTER(listelm, elm, field) do {                    \
+       if (((elm)->field.le_next = (listelm)->field.le_next) != NULL)  \
+               (listelm)->field.le_next->field.le_prev =               \
+                   &(elm)->field.le_next;                              \
+       (listelm)->field.le_next = (elm);                               \
+       (elm)->field.le_prev = &(listelm)->field.le_next;               \
+} while (0)
+
+#define        LIST_INSERT_BEFORE(listelm, elm, field) do {                    \
+       (elm)->field.le_prev = (listelm)->field.le_prev;                \
+       (elm)->field.le_next = (listelm);                               \
+       *(listelm)->field.le_prev = (elm);                              \
+       (listelm)->field.le_prev = &(elm)->field.le_next;               \
+} while (0)
+
+#define LIST_INSERT_HEAD(head, elm, field) do {                                \
+       if (((elm)->field.le_next = (head)->lh_first) != NULL)          \
+               (head)->lh_first->field.le_prev = &(elm)->field.le_next;\
+       (head)->lh_first = (elm);                                       \
+       (elm)->field.le_prev = &(head)->lh_first;                       \
+} while (0)
+
+#define LIST_REMOVE(elm, field) do {                                   \
+       if ((elm)->field.le_next != NULL)                               \
+               (elm)->field.le_next->field.le_prev =                   \
+                   (elm)->field.le_prev;                               \
+       *(elm)->field.le_prev = (elm)->field.le_next;                   \
+       _Q_INVALIDATE((elm)->field.le_prev);                            \
+       _Q_INVALIDATE((elm)->field.le_next);                            \
+} while (0)
+
+#define LIST_REPLACE(elm, elm2, field) do {                            \
+       if (((elm2)->field.le_next = (elm)->field.le_next) != NULL)     \
+               (elm2)->field.le_next->field.le_prev =                  \
+                   &(elm2)->field.le_next;                             \
+       (elm2)->field.le_prev = (elm)->field.le_prev;                   \
+       *(elm2)->field.le_prev = (elm2);                                \
+       _Q_INVALIDATE((elm)->field.le_prev);                            \
+       _Q_INVALIDATE((elm)->field.le_next);                            \
+} while (0)
+
+/*
+ * Simple queue definitions.
+ */
+#define SIMPLEQ_HEAD(name, type)                                       \
+struct name {                                                          \
+       struct type *sqh_first; /* first element */                     \
+       struct type **sqh_last; /* addr of last next element */         \
+}
+
+#define SIMPLEQ_HEAD_INITIALIZER(head)                                 \
+       { NULL, &(head).sqh_first }
+
+#define SIMPLEQ_ENTRY(type)                                            \
+struct {                                                               \
+       struct type *sqe_next;  /* next element */                      \
+}
+
+/*
+ * Simple queue access methods.
+ */
+#define        SIMPLEQ_FIRST(head)         ((head)->sqh_first)
+#define        SIMPLEQ_END(head)           NULL
+#define        SIMPLEQ_EMPTY(head)         (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head))
+#define        SIMPLEQ_NEXT(elm, field)    ((elm)->field.sqe_next)
+
+#define SIMPLEQ_FOREACH(var, head, field)                              \
+       for((var) = SIMPLEQ_FIRST(head);                                \
+           (var) != SIMPLEQ_END(head);                                 \
+           (var) = SIMPLEQ_NEXT(var, field))
+
+/*
+ * Simple queue functions.
+ */
+#define        SIMPLEQ_INIT(head) do {                                         \
+       (head)->sqh_first = NULL;                                       \
+       (head)->sqh_last = &(head)->sqh_first;                          \
+} while (0)
+
+#define SIMPLEQ_INSERT_HEAD(head, elm, field) do {                     \
+       if (((elm)->field.sqe_next = (head)->sqh_first) == NULL)        \
+               (head)->sqh_last = &(elm)->field.sqe_next;              \
+       (head)->sqh_first = (elm);                                      \
+} while (0)
+
+#define SIMPLEQ_INSERT_TAIL(head, elm, field) do {                     \
+       (elm)->field.sqe_next = NULL;                                   \
+       *(head)->sqh_last = (elm);                                      \
+       (head)->sqh_last = &(elm)->field.sqe_next;                      \
+} while (0)
+
+#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do {           \
+       if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\
+               (head)->sqh_last = &(elm)->field.sqe_next;              \
+       (listelm)->field.sqe_next = (elm);                              \
+} while (0)
+
+#define SIMPLEQ_REMOVE_HEAD(head, field) do {                  \
+       if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \
+               (head)->sqh_last = &(head)->sqh_first;                  \
+} while (0)
+
+/*
+ * Tail queue definitions.
+ */
+#define TAILQ_HEAD(name, type)                                         \
+struct name {                                                          \
+       struct type *tqh_first; /* first element */                     \
+       struct type **tqh_last; /* addr of last next element */         \
+}
+
+#define TAILQ_HEAD_INITIALIZER(head)                                   \
+       { NULL, &(head).tqh_first }
+
+#define TAILQ_ENTRY(type)                                              \
+struct {                                                               \
+       struct type *tqe_next;  /* next element */                      \
+       struct type **tqe_prev; /* address of previous next element */  \
+}
+
+/*
+ * tail queue access methods
+ */
+#define        TAILQ_FIRST(head)               ((head)->tqh_first)
+#define        TAILQ_END(head)                 NULL
+#define        TAILQ_NEXT(elm, field)          ((elm)->field.tqe_next)
+#define TAILQ_LAST(head, headname)                                     \
+       (*(((struct headname *)((head)->tqh_last))->tqh_last))
+/* XXX */
+#define TAILQ_PREV(elm, headname, field)                               \
+       (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+#define        TAILQ_EMPTY(head)                                               \
+       (TAILQ_FIRST(head) == TAILQ_END(head))
+
+#define TAILQ_FOREACH(var, head, field)                                        \
+       for((var) = TAILQ_FIRST(head);                                  \
+           (var) != TAILQ_END(head);                                   \
+           (var) = TAILQ_NEXT(var, field))
+
+#define TAILQ_FOREACH_REVERSE(var, head, headname, field)              \
+       for((var) = TAILQ_LAST(head, headname);                         \
+           (var) != TAILQ_END(head);                                   \
+           (var) = TAILQ_PREV(var, headname, field))
+
+/*
+ * Tail queue functions.
+ */
+#define        TAILQ_INIT(head) do {                                           \
+       (head)->tqh_first = NULL;                                       \
+       (head)->tqh_last = &(head)->tqh_first;                          \
+} while (0)
+
+#define TAILQ_INSERT_HEAD(head, elm, field) do {                       \
+       if (((elm)->field.tqe_next = (head)->tqh_first) != NULL)        \
+               (head)->tqh_first->field.tqe_prev =                     \
+                   &(elm)->field.tqe_next;                             \
+       else                                                            \
+               (head)->tqh_last = &(elm)->field.tqe_next;              \
+       (head)->tqh_first = (elm);                                      \
+       (elm)->field.tqe_prev = &(head)->tqh_first;                     \
+} while (0)
+
+#define TAILQ_INSERT_TAIL(head, elm, field) do {                       \
+       (elm)->field.tqe_next = NULL;                                   \
+       (elm)->field.tqe_prev = (head)->tqh_last;                       \
+       *(head)->tqh_last = (elm);                                      \
+       (head)->tqh_last = &(elm)->field.tqe_next;                      \
+} while (0)
+
+#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do {             \
+       if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\
+               (elm)->field.tqe_next->field.tqe_prev =                 \
+                   &(elm)->field.tqe_next;                             \
+       else                                                            \
+               (head)->tqh_last = &(elm)->field.tqe_next;              \
+       (listelm)->field.tqe_next = (elm);                              \
+       (elm)->field.tqe_prev = &(listelm)->field.tqe_next;             \
+} while (0)
+
+#define        TAILQ_INSERT_BEFORE(listelm, elm, field) do {                   \
+       (elm)->field.tqe_prev = (listelm)->field.tqe_prev;              \
+       (elm)->field.tqe_next = (listelm);                              \
+       *(listelm)->field.tqe_prev = (elm);                             \
+       (listelm)->field.tqe_prev = &(elm)->field.tqe_next;             \
+} while (0)
+
+#define TAILQ_REMOVE(head, elm, field) do {                            \
+       if (((elm)->field.tqe_next) != NULL)                            \
+               (elm)->field.tqe_next->field.tqe_prev =                 \
+                   (elm)->field.tqe_prev;                              \
+       else                                                            \
+               (head)->tqh_last = (elm)->field.tqe_prev;               \
+       *(elm)->field.tqe_prev = (elm)->field.tqe_next;                 \
+       _Q_INVALIDATE((elm)->field.tqe_prev);                           \
+       _Q_INVALIDATE((elm)->field.tqe_next);                           \
+} while (0)
+
+#define TAILQ_REPLACE(head, elm, elm2, field) do {                     \
+       if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL)   \
+               (elm2)->field.tqe_next->field.tqe_prev =                \
+                   &(elm2)->field.tqe_next;                            \
+       else                                                            \
+               (head)->tqh_last = &(elm2)->field.tqe_next;             \
+       (elm2)->field.tqe_prev = (elm)->field.tqe_prev;                 \
+       *(elm2)->field.tqe_prev = (elm2);                               \
+       _Q_INVALIDATE((elm)->field.tqe_prev);                           \
+       _Q_INVALIDATE((elm)->field.tqe_next);                           \
+} while (0)
+
+/*
+ * Circular queue definitions.
+ */
+#define CIRCLEQ_HEAD(name, type)                                       \
+struct name {                                                          \
+       struct type *cqh_first;         /* first element */             \
+       struct type *cqh_last;          /* last element */              \
+}
+
+#define CIRCLEQ_HEAD_INITIALIZER(head)                                 \
+       { CIRCLEQ_END(&head), CIRCLEQ_END(&head) }
+
+#define CIRCLEQ_ENTRY(type)                                            \
+struct {                                                               \
+       struct type *cqe_next;          /* next element */              \
+       struct type *cqe_prev;          /* previous element */          \
+}
+
+/*
+ * Circular queue access methods
+ */
+#define        CIRCLEQ_FIRST(head)             ((head)->cqh_first)
+#define        CIRCLEQ_LAST(head)              ((head)->cqh_last)
+#define        CIRCLEQ_END(head)               ((void *)(head))
+#define        CIRCLEQ_NEXT(elm, field)        ((elm)->field.cqe_next)
+#define        CIRCLEQ_PREV(elm, field)        ((elm)->field.cqe_prev)
+#define        CIRCLEQ_EMPTY(head)                                             \
+       (CIRCLEQ_FIRST(head) == CIRCLEQ_END(head))
+
+#define CIRCLEQ_FOREACH(var, head, field)                              \
+       for((var) = CIRCLEQ_FIRST(head);                                \
+           (var) != CIRCLEQ_END(head);                                 \
+           (var) = CIRCLEQ_NEXT(var, field))
+
+#define CIRCLEQ_FOREACH_REVERSE(var, head, field)                      \
+       for((var) = CIRCLEQ_LAST(head);                                 \
+           (var) != CIRCLEQ_END(head);                                 \
+           (var) = CIRCLEQ_PREV(var, field))
+
+/*
+ * Circular queue functions.
+ */
+#define        CIRCLEQ_INIT(head) do {                                         \
+       (head)->cqh_first = CIRCLEQ_END(head);                          \
+       (head)->cqh_last = CIRCLEQ_END(head);                           \
+} while (0)
+
+#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do {           \
+       (elm)->field.cqe_next = (listelm)->field.cqe_next;              \
+       (elm)->field.cqe_prev = (listelm);                              \
+       if ((listelm)->field.cqe_next == CIRCLEQ_END(head))             \
+               (head)->cqh_last = (elm);                               \
+       else                                                            \
+               (listelm)->field.cqe_next->field.cqe_prev = (elm);      \
+       (listelm)->field.cqe_next = (elm);                              \
+} while (0)
+
+#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do {          \
+       (elm)->field.cqe_next = (listelm);                              \
+       (elm)->field.cqe_prev = (listelm)->field.cqe_prev;              \
+       if ((listelm)->field.cqe_prev == CIRCLEQ_END(head))             \
+               (head)->cqh_first = (elm);                              \
+       else                                                            \
+               (listelm)->field.cqe_prev->field.cqe_next = (elm);      \
+       (listelm)->field.cqe_prev = (elm);                              \
+} while (0)
+
+#define CIRCLEQ_INSERT_HEAD(head, elm, field) do {                     \
+       (elm)->field.cqe_next = (head)->cqh_first;                      \
+       (elm)->field.cqe_prev = CIRCLEQ_END(head);                      \
+       if ((head)->cqh_last == CIRCLEQ_END(head))                      \
+               (head)->cqh_last = (elm);                               \
+       else                                                            \
+               (head)->cqh_first->field.cqe_prev = (elm);              \
+       (head)->cqh_first = (elm);                                      \
+} while (0)
+
+#define CIRCLEQ_INSERT_TAIL(head, elm, field) do {                     \
+       (elm)->field.cqe_next = CIRCLEQ_END(head);                      \
+       (elm)->field.cqe_prev = (head)->cqh_last;                       \
+       if ((head)->cqh_first == CIRCLEQ_END(head))                     \
+               (head)->cqh_first = (elm);                              \
+       else                                                            \
+               (head)->cqh_last->field.cqe_next = (elm);               \
+       (head)->cqh_last = (elm);                                       \
+} while (0)
+
+#define        CIRCLEQ_REMOVE(head, elm, field) do {                           \
+       if ((elm)->field.cqe_next == CIRCLEQ_END(head))                 \
+               (head)->cqh_last = (elm)->field.cqe_prev;               \
+       else                                                            \
+               (elm)->field.cqe_next->field.cqe_prev =                 \
+                   (elm)->field.cqe_prev;                              \
+       if ((elm)->field.cqe_prev == CIRCLEQ_END(head))                 \
+               (head)->cqh_first = (elm)->field.cqe_next;              \
+       else                                                            \
+               (elm)->field.cqe_prev->field.cqe_next =                 \
+                   (elm)->field.cqe_next;                              \
+       _Q_INVALIDATE((elm)->field.cqe_prev);                           \
+       _Q_INVALIDATE((elm)->field.cqe_next);                           \
+} while (0)
+
+#define CIRCLEQ_REPLACE(head, elm, elm2, field) do {                   \
+       if (((elm2)->field.cqe_next = (elm)->field.cqe_next) ==         \
+           CIRCLEQ_END(head))                                          \
+               (head)->cqh_last = (elm2);                              \
+       else                                                            \
+               (elm2)->field.cqe_next->field.cqe_prev = (elm2);        \
+       if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) ==         \
+           CIRCLEQ_END(head))                                          \
+               (head)->cqh_first = (elm2);                             \
+       else                                                            \
+               (elm2)->field.cqe_prev->field.cqe_next = (elm2);        \
+       _Q_INVALIDATE((elm)->field.cqe_prev);                           \
+       _Q_INVALIDATE((elm)->field.cqe_next);                           \
+} while (0)
+
+#endif /* !_SYS_QUEUE_H_ */
diff --git a/tests/swap.c b/tests/swap.c
new file mode 100644 (file)
index 0000000..606b0f7
--- /dev/null
@@ -0,0 +1,229 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "queue.h"
+
+struct obj {
+    int abc;
+    TAILQ_ENTRY(obj) entry;
+};
+
+TAILQ_HEAD(objhead, obj) head;
+
+void dump() {
+    struct obj *e;
+    printf("dump:\n");
+    e = TAILQ_FIRST(&head);
+    printf("first: %d\n", e->abc);
+    e = TAILQ_LAST(&head, objhead);
+    printf("last: %d\n", e->abc);
+    TAILQ_FOREACH(e, &head, entry) {
+        printf("  %d\n", e->abc);
+    }
+    printf("again, but reverse:\n");
+    TAILQ_FOREACH_REVERSE(e, &head, objhead, entry) {
+        printf("  %d\n", e->abc);
+    }
+    printf("done\n\n");
+}
+
+#define TAILQ_SWAP(first, second, head, field) do { \
+    *((first)->field.tqe_prev) = (second); \
+    (second)->field.tqe_prev = (first)->field.tqe_prev; \
+    (first)->field.tqe_prev = &((second)->field.tqe_next); \
+    (first)->field.tqe_next = (second)->field.tqe_next; \
+    if ((second)->field.tqe_next) \
+        (second)->field.tqe_next->field.tqe_prev = &((first)->field.tqe_next); \
+    (second)->field.tqe_next = first; \
+    if ((head)->tqh_last == &((second)->field.tqe_next)) \
+        (head)->tqh_last = &((first)->field.tqe_next); \
+} while (0)
+
+void _TAILQ_SWAP(struct obj *first, struct obj *second, struct objhead *head) {
+    struct obj **tqe_prev = first->entry.tqe_prev;
+    *tqe_prev = second;
+
+    second->entry.tqe_prev = first->entry.tqe_prev;
+
+    first->entry.tqe_prev = &(second->entry.tqe_next);
+
+    first->entry.tqe_next = second->entry.tqe_next;
+
+    if (second->entry.tqe_next) {
+        struct obj *tqe_next = second->entry.tqe_next;
+        tqe_next->entry.tqe_prev = &(first->entry.tqe_next);
+    }
+
+    second->entry.tqe_next = first;
+
+    if (head->tqh_last == &(second->entry.tqe_next))
+        head->tqh_last = &(first->entry.tqe_next);
+
+}
+
+int main() {
+    printf("hello\n");
+
+    TAILQ_INIT(&head);
+
+    struct obj first;
+    first.abc = 123;
+
+    struct obj second;
+    second.abc = 456;
+
+    struct obj third;
+    third.abc = 789;
+
+    struct obj fourth;
+    fourth.abc = 999;
+
+
+    struct obj fifth;
+    fifth.abc = 5555;
+
+    /*
+     * ************************************************
+     */
+    printf("swapping first two elements:\n");
+
+    TAILQ_INSERT_TAIL(&head, &first, entry);
+    TAILQ_INSERT_TAIL(&head, &second, entry);
+    TAILQ_INSERT_TAIL(&head, &third, entry);
+
+    dump();
+
+    TAILQ_SWAP(&first, &second, &head, entry);
+
+    dump();
+
+    /*
+     * ************************************************
+     */
+    printf("swapping last two elements:\n");
+
+    TAILQ_INIT(&head);
+
+    TAILQ_INSERT_TAIL(&head, &first, entry);
+    TAILQ_INSERT_TAIL(&head, &second, entry);
+    TAILQ_INSERT_TAIL(&head, &third, entry);
+
+    dump();
+
+    TAILQ_SWAP(&second, &third, &head, entry);
+
+    dump();
+
+    /*
+     * ************************************************
+     */
+    printf("longer list:\n");
+
+    TAILQ_INIT(&head);
+
+    TAILQ_INSERT_TAIL(&head, &first, entry);
+    TAILQ_INSERT_TAIL(&head, &second, entry);
+    TAILQ_INSERT_TAIL(&head, &third, entry);
+    TAILQ_INSERT_TAIL(&head, &fourth, entry);
+
+    dump();
+
+    TAILQ_SWAP(&first, &second, &head, entry);
+
+    dump();
+
+    /*
+     * ************************************************
+     */
+    printf("longer list 2:\n");
+
+    TAILQ_INIT(&head);
+
+    TAILQ_INSERT_TAIL(&head, &first, entry);
+    TAILQ_INSERT_TAIL(&head, &second, entry);
+    TAILQ_INSERT_TAIL(&head, &third, entry);
+    TAILQ_INSERT_TAIL(&head, &fourth, entry);
+
+    dump();
+
+    TAILQ_SWAP(&second, &third, &head, entry);
+
+    dump();
+
+
+    /*
+     * ************************************************
+     */
+    printf("longer list, swap, then insert:\n");
+
+    TAILQ_INIT(&head);
+
+    TAILQ_INSERT_TAIL(&head, &first, entry);
+    TAILQ_INSERT_TAIL(&head, &second, entry);
+    TAILQ_INSERT_TAIL(&head, &third, entry);
+    TAILQ_INSERT_TAIL(&head, &fourth, entry);
+
+    dump();
+
+    TAILQ_SWAP(&second, &third, &head, entry);
+
+    dump();
+
+    TAILQ_INSERT_AFTER(&head, &third, &fifth, entry);
+
+    dump();
+
+
+    /*
+     * ************************************************
+     */
+    printf("longer list, swap, then append:\n");
+
+    TAILQ_INIT(&head);
+
+    TAILQ_INSERT_TAIL(&head, &first, entry);
+    TAILQ_INSERT_TAIL(&head, &second, entry);
+    TAILQ_INSERT_TAIL(&head, &third, entry);
+    TAILQ_INSERT_TAIL(&head, &fourth, entry);
+
+    dump();
+
+    TAILQ_SWAP(&second, &third, &head, entry);
+
+    dump();
+
+    TAILQ_INSERT_TAIL(&head, &fifth, entry);
+
+    dump();
+
+
+    /*
+     * ************************************************
+     */
+    printf("longer list, swap, then remove:\n");
+
+    TAILQ_INIT(&head);
+
+    TAILQ_INSERT_TAIL(&head, &first, entry);
+    TAILQ_INSERT_TAIL(&head, &second, entry);
+    TAILQ_INSERT_TAIL(&head, &third, entry);
+    TAILQ_INSERT_TAIL(&head, &fourth, entry);
+
+    dump();
+
+    TAILQ_SWAP(&second, &third, &head, entry);
+
+    dump();
+
+    TAILQ_REMOVE(&head, &second, entry);
+
+    dump();
+
+}
index d1aabbabc3cd7ef9100e5bc32d318b15181ae1bd..583d4fe09ece79224b4b4253b9ba0ce89e6c7da0 100644 (file)
@@ -1,79 +1,61 @@
-<!DOCTYPE html>
-<html>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
 <head>
 <title>i3 - an improved dynamic tiling window manager</title>
-<link rel="icon" type="image/png" href="/favicon.png">
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-<meta name="description" content="i3 is a dynamic tiling window manager with clean, readable and documented code, featuring extended XRandR support, usage of libxcb instead of xlib and several improvements over wmii">
-<meta name="keywords" content="i3, window, manager, tiling, keyboard, wmii, x11, xcb, xinerama, utf8">
-<meta name="author" content="i3 developers">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta name="description" content="i3 is a dynamic tiling window manager with clean, readable and documented code, featuring extended Xinerama support, usage of libxcb instead of xlib and several improvements over wmii" />
+<meta name="keywords" content="i3, window, manager, tiling, keyboard, wmii, x11, xcb, xinerama, utf8" />
+<meta name="author" content="i3 developers" />
 <style type="text/css">
 body {
        text-align: justify;
-       max-width: 900px;
-       background-color: #1e1e1e;
-       color: #c0c0c0;
+       max-width: 800px;
 }
 
-ol li {
+li {
        margin-bottom: 10px;
 }
-
 #menu {
-       margin-left: -15px;
        overflow: hidden;
        list-style-type: none;
 }
-
 #menu li {
        margin-left: 30px;
        margin-right: 30px;
        float: left;
 }
 
-a {
-        color: #c0c0c0;
-       text-decoration: underline;
-}
-
 </style>
 </head>
 <body>
 
-<img width="131" height="125" src="http://i3.zekjur.net/logo.png" style="margin-right: 15px" alt="logo" id="logo" align="left">
-
-<h1 style="color: red; font-family: georgia; font-size: 3em;margin-bottom: 0;">i3</h1>
-<h1 style="font-family: georgia; font-size: 2em;margin-bottom: 0;">improved tiling wm</h1>
-
-<br style="height: 0; clear: both;">
-<hr style="border: 1px solid black; clear: both; margin-top: 5px;">
+<h1>i3 - an improved dynamic tiling window manager</h1>
 
 <ul id="menu">
   <li>
     <a href="/">Goals</a>
   </li>
   <li>
-    <a href="/docs/">Docs</a>
+    <a href="/docs/">Documentation</a>
   </li>
   <li>
     <a href="/downloads/">Downloads</a>
   </li>
   <li>
-    <a href="/screenshots/">Screens</a>
+    <a href="/screenshots/">Screenshots</a>
   </li>
   <li>
     <a href="/bugs">Bugtracker</a>
   </li>
   <li>
-    <a href="/contact/">Contact</a>
+    <strong><a href="/contact/">Contact</a></strong>
   </li>
   <li>
-    <a href="/impress.html">Impressum</a>
+    <a href="/impress.html">Impressum/Imprint</a>
   </li>
 </ul>
 
-<hr style="border: 1px solid black; clear: both; margin-top: 15px;">
-
 <p>
 
 <h2>Contact</h2>
@@ -94,18 +76,5 @@ a {
   think before you ask :-).
 </p>
 
-<h2>Mailing list</h2>
-
-<p>
-  If you prefer to discuss via mail rather than on IRC, you can also post to our
-  mailing list. Subscribe by sending a mail to <code>i3-discuss-subscribe@i3.zekjur.net</code>
-</p>
-
-<p>
-  All discussion on this mailing list is archived and accessible for the public.
-  You can browse the archives at <a href="http://i3.zekjur.net/archives/i3-discuss/"
-  title="i3-discuss archives">http://i3.zekjur.net/archives/i3-discuss/</a>.
-</p>
-
 </body>
 </html>
index c60f29a6fb110a6babe0697e04f2240e9c8b24db..151108a18b61e82cea9d859f29cb3cc6221284d6 100644 (file)
@@ -33,14 +33,6 @@ li {
 $ git clone http://code.stapelberg.de/git/i3
 </pre>
 
-<p>
-  Now install the build-dependencies for i3 which are included in ubuntu:
-</p>
-
-<pre>
-$ sudo apt-get install libev-dev
-</pre>
-
 <p>
   The Ubuntu repositories do not contain the required versions of several packages. Hence we need
   to get them from Debian. Create a new directory for the .debs
index e2573ddd722e110167e24e02c4465290ced7bd99..d64949027ff6b7e85643dac4e2f102f3b79b3cc8 100644 (file)
@@ -1,65 +1,49 @@
-<!DOCTYPE html>
-<html>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
 <head>
 <title>i3 - an improved dynamic tiling window manager</title>
-<link rel="icon" type="image/png" href="/favicon.png">
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-<meta name="description" content="i3 is a dynamic tiling window manager with clean, readable and documented code, featuring extended XRandR support, usage of libxcb instead of xlib and several improvements over wmii">
-<meta name="keywords" content="i3, window, manager, tiling, keyboard, wmii, x11, xcb, xinerama, utf8">
-<meta name="author" content="i3 developers">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta name="description" content="i3 is a dynamic tiling window manager with clean, readable and documented code, featuring extended Xinerama support, usage of libxcb instead of xlib and several improvements over wmii" />
+<meta name="keywords" content="i3, window, manager, tiling, keyboard, wmii, x11, xcb, xinerama, utf8" />
+<meta name="author" content="i3 developers" />
 <style type="text/css">
 body {
        text-align: justify;
-       max-width: 900px;
-       background-color: #1e1e1e;
-       color: #c0c0c0;
+       max-width: 800px;
 }
 
-ol li {
+li {
        margin-bottom: 10px;
 }
-
 #menu {
-       margin-left: -15px;
        overflow: hidden;
        list-style-type: none;
 }
-
 #menu li {
        margin-left: 30px;
        margin-right: 30px;
        float: left;
 }
 
-a {
-        color: #c0c0c0;
-       text-decoration: underline;
-}
-
 </style>
 </head>
 <body>
 
-<img width="131" height="125" src="http://i3.zekjur.net/logo.png" style="margin-right: 15px" alt="logo" id="logo" align="left">
-
-<h1 style="color: red; font-family: georgia; font-size: 3em;margin-bottom: 0;">i3</h1>
-<h1 style="font-family: georgia; font-size: 2em;margin-bottom: 0;">improved tiling wm</h1>
-
-<br style="height: 0; clear: both;">
-<hr style="border: 1px solid black; clear: both; margin-top: 5px;">
+<h1>i3 - an improved dynamic tiling window manager</h1>
 
 <ul id="menu">
   <li>
     <a href="/">Goals</a>
   </li>
   <li>
-    <a href="/docs/">Docs</a>
+    <strong><a href="/docs/">Documentation</a></strong>
   </li>
   <li>
     <a href="/downloads/">Downloads</a>
   </li>
   <li>
-    <a href="/screenshots/">Screens</a>
+    <a href="/screenshots/">Screenshots</a>
   </li>
   <li>
     <a href="/bugs">Bugtracker</a>
@@ -68,13 +52,10 @@ a {
     <a href="/contact/">Contact</a>
   </li>
   <li>
-    <a href="/impress.html">Impressum</a>
+    <a href="/impress.html">Impressum/Imprint</a>
   </li>
 </ul>
 
-<hr style="border: 1px solid black; clear: both; margin-top: 15px;">
-
-
 <h2>Documentation</h2>
 
 <p>
@@ -100,11 +81,8 @@ a {
 <h2>Howtos</h2>
 
 <ul>
-  <li><a href="/docs/multi-monitor.html">Important explanation about
-  multi-monitor setups with the nVidia binary driver</a></li>
   <li><a href="/docs/userguide.html">User’s guide</a></li>
   <li><a href="/docs/debugging.html">Debugging i3</a> (Read this before sending bugreports)</li>
-  <li><a href="/docs/ipc.html">IPC interface</a></li>
   <li><a href="/docs/building_ubuntu_9.04.html">Building i3 from git on Ubuntu 9.04</a></li>
 </ul>
 
index 749ae0a6d4a4dd88bfe68f52cb75bf5d65f4ba72..5e1f2045b3980e5aae440638b83b6193ae1cd679 100644 (file)
@@ -1,65 +1,49 @@
-<!DOCTYPE html>
-<html>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
 <head>
 <title>i3 - an improved dynamic tiling window manager</title>
-<link rel="icon" type="image/png" href="/favicon.png">
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-<meta name="description" content="i3 is a dynamic tiling window manager with clean, readable and documented code, featuring extended XRandR support, usage of libxcb instead of xlib and several improvements over wmii">
-<meta name="keywords" content="i3, window, manager, tiling, keyboard, wmii, x11, xcb, xinerama, utf8">
-<meta name="author" content="i3 developers">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta name="description" content="i3 is a dynamic tiling window manager with clean, readable and documented code, featuring extended Xinerama support, usage of libxcb instead of xlib and several improvements over wmii" />
+<meta name="keywords" content="i3, window, manager, tiling, keyboard, wmii, x11, xcb, xinerama, utf8" />
+<meta name="author" content="i3 developers" />
 <style type="text/css">
 body {
        text-align: justify;
-       max-width: 900px;
-       background-color: #1e1e1e;
-       color: #c0c0c0;
+       max-width: 800px;
 }
 
-ol li {
+li {
        margin-bottom: 10px;
 }
-
 #menu {
-       margin-left: -15px;
        overflow: hidden;
        list-style-type: none;
 }
-
 #menu li {
        margin-left: 30px;
        margin-right: 30px;
        float: left;
 }
 
-a {
-        color: #c0c0c0;
-       text-decoration: underline;
-}
-
 </style>
 </head>
 <body>
 
-<img width="131" height="125" src="http://i3.zekjur.net/logo.png" style="margin-right: 15px" alt="logo" id="logo" align="left">
-
-<h1 style="color: red; font-family: georgia; font-size: 3em;margin-bottom: 0;">i3</h1>
-<h1 style="font-family: georgia; font-size: 2em;margin-bottom: 0;">improved tiling wm</h1>
-
-<br style="height: 0; clear: both;">
-<hr style="border: 1px solid black; clear: both; margin-top: 5px;">
+<h1>i3 - an improved dynamic tiling window manager</h1>
 
 <ul id="menu">
   <li>
     <a href="/">Goals</a>
   </li>
   <li>
-    <a href="/docs/">Docs</a>
+    <a href="/docs/">Documentation</a>
   </li>
   <li>
-    <a href="/downloads/">Downloads</a>
+    <strong><a href="/downloads/">Downloads</a></strong>
   </li>
   <li>
-    <a href="/screenshots/">Screens</a>
+    <a href="/screenshots/">Screenshots</a>
   </li>
   <li>
     <a href="/bugs">Bugtracker</a>
@@ -68,13 +52,10 @@ a {
     <a href="/contact/">Contact</a>
   </li>
   <li>
-    <a href="/impress.html">Impressum</a>
+    <a href="/impress.html">Impressum/Imprint</a>
   </li>
 </ul>
 
-<hr style="border: 1px solid black; clear: both; margin-top: 15px;">
-
-
 <h2>Distribution</h2>
 
 <p>
@@ -83,7 +64,7 @@ a {
 
 <ul>
   <li>
-    <a href="http://packages.debian.org/sid/i3">Debian GNU/Linux</a>
+    <a href="http://packages.debian.org/sid/i3">Debian GNU/Linux</a> (in unstable currently)
   </li>
   <li>
     <a href="http://aur.archlinux.org/packages.php?ID=24720">Arch Linux</a>
@@ -91,7 +72,7 @@ a {
   </li>
   <li>
     <a href="http://overlays.gentoo.org/proj/sunrise/browser/sunrise/x11-wm/i3">Gentoo Linux</a>
-    (in <a href="http://www.gentoo.org/proj/en/sunrise">sunrise</a>), maintained by mimi_vx / Moredread<br>
+    (in <a href="http://www.gentoo.org/proj/en/sunrise">sunrise</a>), maintained by Moredread<br>
     See also: <a href="http://gist.github.com/89277">i3-9999.ebuild</a>
   </li>
   <li>
@@ -100,12 +81,6 @@ a {
   <li>
     <a href="http://pkgsrc.se/wip/i3">NetBSD</a> (wip/i3 in <a href="http://pkgsrc-wip.sourceforge.net/">pkgsrc-wip</a>)
   </li>
-  <li>
-    <a href="http://www.openbsd.org/cgi-bin/cvsweb/ports/x11/i3/">OpenBSD</a>, maintained by bapt
-  </li>
-  <li>
-    <a href="https://admin.fedoraproject.org/pkgdb/acls/name/i3">Fedora</a>, maintained by cassmodiah
-  </li>
   <li>
     <a href="http://packages.ubuntu.com/karmic/i3">Ubuntu Linux</a> (in karmic/universe)
   </li>
@@ -114,42 +89,11 @@ a {
 <h2>Downloads</h2>
 
 <p>
-  The current stable version is 3.ε (transcribed 3.e because many systems still can’t
+  The current stable version is 3.β (transcribed 3.b because many systems still can’t
   handle UTF-8 in version numbers).
 </p>
 
-<p>
-  <strong>IMPORTANT:</strong> If you use the nVidia binary driver (which does
-  not support XRandR at the moment), read <a
-  href="/docs/multi-monitor.html">this document</a> for an explanation and how
-  to enable the work-around!
-</p>
-
 <ul>
-  <li>
-    <a href="/downloads/i3-3.e.tar.bz2">i3-3.e.tar.bz2</a>
-    (<a href="/downloads/i3-3.e.tar.bz2.asc">GPG signature</a>), Version 3.ε, 271 KiB, 2010-03-30,
-    <a href="/downloads/RELEASE-NOTES-3.e.txt">release notes</a>
-
-  </li>
-  <li>
-    <a href="/downloads/i3-3.d-bf1.tar.bz2">i3-3.d-bf1.tar.bz2</a>
-    (<a href="/downloads/i3-3.d-bf1.tar.bz2.asc">GPG signature</a>), Version 3.δ-bf1, 153 KiB, 2009-12-21,
-    <a href="/downloads/RELEASE-NOTES-3.d-bf1.txt">release notes</a>
-
-  </li>
-  <li>
-    <a href="/downloads/i3-3.d.tar.bz2">i3-3.d.tar.bz2</a>
-    (<a href="/downloads/i3-3.d.tar.bz2.asc">GPG signature</a>), Version 3.δ, 153 KiB, 2009-11-09,
-    <a href="/downloads/RELEASE-NOTES-3.d.txt">release notes</a>
-
-  </li>
-  <li>
-    <a href="/downloads/i3-3.c.tar.bz2">i3-3.c.tar.bz2</a>
-    (<a href="/downloads/i3-3.c.tar.bz2.asc">GPG signature</a>), Version 3.γ, 107 KiB, 2009-08-19,
-    <a href="/downloads/RELEASE-NOTES-3.c.txt">release notes</a>
-
-  </li>
   <li>
     <a href="/downloads/i3-3.b.tar.bz2">i3-3.b.tar.bz2</a>
     (<a href="/downloads/i3-3.b.tar.bz2.asc">GPG signature</a>), Version 3.β, 96 KiB, 2009-06-26,
diff --git a/website/favicon.png b/website/favicon.png
deleted file mode 100644 (file)
index 6bd0731..0000000
Binary files a/website/favicon.png and /dev/null differ
index d4d9af165ad348942b4fc455ac2d979273e3acc0..0923d095bf147b97ef7b22d8f714c2f7f09bc7e6 100644 (file)
@@ -2,7 +2,6 @@
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
 <head>
-<link rel="icon" type="image/png" href="/favicon.png">
 <title>i3lock — a slightly improved version of slock</title>
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 <style type="text/css">
index 83a2f46cc292c55ba560c1d2228346888953bbfe..f5c970b0fd6136cd55215dbc2ddc91ac3bea68eb 100644 (file)
@@ -3,7 +3,6 @@
 <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
 <head>
 <title>i3status – Generates a status line for dzen2 or wmii</title>
-<link rel="icon" type="image/png" href="/favicon.png">
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 <style type="text/css">
 body {
@@ -21,19 +20,17 @@ li {
 <h1>i3status</h1>
 
 <p>
-  i3status is a small program (less than 1000 SLOC) for generating a status bar
-  for dzen2, xmobar or similar programs. It is designed to be very efficient by
-  issuing a very small number of systemcalls, as one generally wants to update
-  such a status line every second. This ensures that even under high load, your
-  status bar is updated correctly. Also, it saves a bit of energy by not hogging
-  your CPU as much as spawning the corresponding amount of shell commands would.
+  i3status is a small program (around 500 SLOC) for filling dzen2 or wmii’s status
+  bar via its 9P pseudo filesystem. It is designed to be very efficient by issuing
+  a very small number of systemcalls (as the bar should be updated every second or
+  at your specified interval). This ensures that even under high load, your status
+  bar is updated correctly and it saves a little bit of battery life by not spawning
+  new processes every second like shell scripts do.
 </p>
 
 <h2>Releases</h2>
 
 <ul>
-  <li><a href="/i3status/i3status-2.0.tar.bz2">i3status-2.0.tar.bz2</a>
-  (<a href="/i3status/i3status-2.0.tar.bz2.asc">GPG signature</a>, 2009-10-27)</li>
   <li><a href="/i3status/i3status-1.2.tar.bz2">i3status-1.2.tar.bz2</a>
   (<a href="/i3status/i3status-1.2.tar.bz2.asc">GPG signature</a>, 2009-06-21)</li>
   <li><a href="/i3status/i3status-1.1.tar.bz2">i3status-1.1.tar.bz2</a>
index 3e0cf9aa40659a5e13810978eaa2346aff734557..d72bc92e2ee01aff06bf47fda86734cfb19fc378 100644 (file)
@@ -1,65 +1,49 @@
-<!DOCTYPE html>
-<html>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
 <head>
 <title>i3 - an improved dynamic tiling window manager</title>
-<link rel="icon" type="image/png" href="/favicon.png">
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-<meta name="description" content="i3 is a dynamic tiling window manager with clean, readable and documented code, featuring extended XRandR support, usage of libxcb instead of xlib and several improvements over wmii">
-<meta name="keywords" content="i3, window, manager, tiling, keyboard, wmii, x11, xcb, xinerama, utf8">
-<meta name="author" content="i3 developers">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta name="description" content="i3 is a dynamic tiling window manager with clean, readable and documented code, featuring extended Xinerama support, usage of libxcb instead of xlib and several improvements over wmii" />
+<meta name="keywords" content="i3, window, manager, tiling, keyboard, wmii, x11, xcb, xinerama, utf8" />
+<meta name="author" content="i3 developers" />
 <style type="text/css">
 body {
        text-align: justify;
-       max-width: 900px;
-       background-color: #1e1e1e;
-       color: #c0c0c0;
+       max-width: 800px;
 }
 
-ol li {
+li {
        margin-bottom: 10px;
 }
-
 #menu {
-       margin-left: -15px;
        overflow: hidden;
        list-style-type: none;
 }
-
 #menu li {
        margin-left: 30px;
        margin-right: 30px;
        float: left;
 }
 
-a {
-        color: #c0c0c0;
-       text-decoration: underline;
-}
-
 </style>
 </head>
 <body>
 
-<img width="131" height="125" src="http://i3.zekjur.net/logo.png" style="margin-right: 15px" alt="logo" id="logo" align="left">
-
-<h1 style="color: red; font-family: georgia; font-size: 3em;margin-bottom: 0;">i3</h1>
-<h1 style="font-family: georgia; font-size: 2em;margin-bottom: 0;">improved tiling wm</h1>
-
-<br style="height: 0; clear: both;">
-<hr style="border: 1px solid black; clear: both; margin-top: 5px;">
+<h1>i3 - an improved dynamic tiling window manager</h1>
 
 <ul id="menu">
   <li>
     <a href="/">Goals</a>
   </li>
   <li>
-    <a href="/docs/">Docs</a>
+    <a href="/docs/">Documentation</a>
   </li>
   <li>
     <a href="/downloads/">Downloads</a>
   </li>
   <li>
-    <a href="/screenshots/">Screens</a>
+    <a href="/screenshots/">Screenshots</a>
   </li>
   <li>
     <a href="/bugs">Bugtracker</a>
@@ -68,23 +52,17 @@ a {
     <a href="/contact/">Contact</a>
   </li>
   <li>
-    <a href="/impress.html">Impressum</a>
+    <a href="/impress.html">Impressum/Imprint</a>
   </li>
 </ul>
 
-<hr style="border: 1px solid black; clear: both; margin-top: 15px;">
-
-<p>
-  i3 is a tiling window manager, completely written from scratch.
-</p>
-
 <p>
   i3 was created because wmii, our favorite window manager at the time, didn’t
-  provide some features we wanted (multi-monitor done right, for example), had
-  some bugs, didn’t progress since quite some time and wasn’t easy to hack at
-  all (source code comments/documentation completely lacking). Still, we think
-  the wmii developers and contributors did a great job. Thank you for inspiring
-  us to create i3.
+  provide some features we wanted (Xinerama done right, for example), had some
+  bugs, didn’t progress since quite some time and wasn’t easy to hack at all
+  (source code comments/documentation completely lacking). Still, we think the
+  wmii developers and contributors did a great job. Thank you for inspiring us
+  to create i3.
 </p>
 
 <p>
@@ -114,7 +92,7 @@ a {
     in quite a lot of situations.
   </li>
   <li>
-    Implement multi-monitor correctly, that is by assigning each workspace to a
+    Implement Xinerama correctly, that is by assigning each workspace to a
     virtual screen. Especially make sure that attaching and detaching new monitors
     like video projectors works during operation and does the right thing.
   </li>
index 3cfdeb76babc09e98b47d5e76214f124ff29a1c3..a46ea8ebab6d2d943803a64dbea99f7f29760d39 100644 (file)
@@ -1,65 +1,49 @@
-<!DOCTYPE html>
-<html>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
 <head>
 <title>i3 - an improved dynamic tiling window manager</title>
-<link rel="icon" type="image/png" href="/favicon.png">
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-<meta name="description" content="i3 is a dynamic tiling window manager with clean, readable and documented code, featuring extended XRandR support, usage of libxcb instead of xlib and several improvements over wmii">
-<meta name="keywords" content="i3, window, manager, tiling, keyboard, wmii, x11, xcb, xinerama, utf8">
-<meta name="author" content="i3 developers">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta name="description" content="i3 is a dynamic tiling window manager with clean, readable and documented code, featuring extended Xinerama support, usage of libxcb instead of xlib and several improvements over wmii" />
+<meta name="keywords" content="i3, window, manager, tiling, keyboard, wmii, x11, xcb, xinerama, utf8" />
+<meta name="author" content="i3 developers" />
 <style type="text/css">
 body {
        text-align: justify;
-       max-width: 900px;
-       background-color: #1e1e1e;
-       color: #c0c0c0;
+       max-width: 800px;
 }
 
-ol li {
+li {
        margin-bottom: 10px;
 }
-
 #menu {
-       margin-left: -15px;
        overflow: hidden;
        list-style-type: none;
 }
-
 #menu li {
        margin-left: 30px;
        margin-right: 30px;
        float: left;
 }
 
-a {
-        color: #c0c0c0;
-       text-decoration: underline;
-}
-
 </style>
 </head>
 <body>
 
-<img width="131" height="125" src="http://i3.zekjur.net/logo.png" style="margin-right: 15px" alt="logo" id="logo" align="left">
-
-<h1 style="color: red; font-family: georgia; font-size: 3em;margin-bottom: 0;">i3</h1>
-<h1 style="font-family: georgia; font-size: 2em;margin-bottom: 0;">improved tiling wm</h1>
-
-<br style="height: 0; clear: both;">
-<hr style="border: 1px solid black; clear: both; margin-top: 5px;">
+<h1>i3 - an improved dynamic tiling window manager</h1>
 
 <ul id="menu">
   <li>
     <a href="/">Goals</a>
   </li>
   <li>
-    <a href="/docs/">Docs</a>
+    <a href="/docs/">Documentation</a>
   </li>
   <li>
     <a href="/downloads/">Downloads</a>
   </li>
   <li>
-    <a href="/screenshots/">Screens</a>
+    <strong><a href="/screenshots/">Screenshots</a></strong>
   </li>
   <li>
     <a href="/bugs">Bugtracker</a>
@@ -68,12 +52,10 @@ a {
     <a href="/contact/">Contact</a>
   </li>
   <li>
-    <a href="/impress.html">Impressum</a>
+    <a href="/impress.html">Impressum/Imprint</a>
   </li>
 </ul>
 
-<hr style="border: 1px solid black; clear: both; margin-top: 15px;">
-
 <h2>Screenshots</h2>
 
 <p>
@@ -91,21 +73,11 @@ a {
     <a href="/screenshots/i3-3.png">i3 v3.α-bf1</a>, PCManFM, ROXTerm, evince, i3status + dzen2
   </li>
   <li>
-    <a href="/screenshots/i3-4.png">i3 v3.β</a>, Atsutane had too much free time and formed an i3 logo out of terminals :-)
+    <a href="/screenshots/i3-4.png">i3 v3.b (not yet released)</a>, Atsutane had too much free time and formed an i3 logo out of terminals :-)
   </li>
   <li>
     <a href="/screenshots/i3-5.png">i3 v3.α-bf2</a>, mc, vim, xosview, mplayer, irssi, gajim, i3status
   </li>
-  <li>
-    <a href="/screenshots/i3-6.png">i3 v3.β</a>, vim, zsh, i3status running on FreeBSD
-  </li>
-  <li>
-    <a href="/screenshots/i3-7.png">i3 v3.β</a>, GIMP (with floating toolbars), urxvt (also floating)
-  </li>
-  <li>
-    <a href="/screenshots/i3-8.jpg">i3 v3.γ</a>, git, synergy, htop, i3status + dzen2, urxvt
-  </li>
-
 </ul>
 
 <h2>Screencasts</h2>
@@ -117,9 +89,8 @@ a {
 
 <ul>
   <li>
-    <a href="/screenshots/screencast-3.d.mkv">i3 v3.δ</a> Matroska container with x264 video
-    (Thanks to Sebastian)<br>
-    You can also <a href="http://www.youtube.com/watch?v=pKfP7Ws-CN8" target="_blank">watch it on youtube</a>.
+    <a href="/screenshots/screencast.mkv">i3 v3.α</a> Matroska container with x264 video
+    (Thanks to Sebastian, sorry for the bad quality)
   </li>
 </ul>