]> git.sur5r.net Git - i3/i3/commitdiff
Merge branch 'master' into next
authorMichael Stapelberg <michael@stapelberg.de>
Fri, 14 Sep 2012 11:18:04 +0000 (13:18 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Fri, 14 Sep 2012 11:18:04 +0000 (13:18 +0200)
237 files changed:
.gitignore
DEPENDS
Makefile
RELEASE-NOTES-4.3 [new file with mode: 0644]
common.mk
contrib/trivial-bar-script.sh [new file with mode: 0755]
debian/control
debian/i3-wm.docs
debian/i3-wm.manpages
docs/Makefile
docs/asciidoc-git.conf
docs/docs.mk [new file with mode: 0644]
docs/hacking-howto
docs/i3-pod2html [new file with mode: 0755]
docs/i3bar-protocol
docs/ipc
docs/refcard.html
docs/testsuite
docs/userguide
generate-command-parser.pl
i3-config-wizard/Makefile
i3-config-wizard/cfgparse.y
i3-config-wizard/i3-config-wizard.mk [new file with mode: 0644]
i3-config-wizard/main.c
i3-dump-log/Makefile
i3-dump-log/i3-dump-log.mk [new file with mode: 0644]
i3-dump-log/main.c
i3-input/Makefile
i3-input/i3-input.mk [new file with mode: 0644]
i3-input/main.c
i3-migrate-config-to-v4
i3-msg/Makefile
i3-msg/i3-msg.mk [new file with mode: 0644]
i3-msg/main.c
i3-nagbar/Makefile
i3-nagbar/i3-nagbar.mk [new file with mode: 0644]
i3-nagbar/main.c
i3-sensible-terminal
i3.config
i3.config.keycodes
i3bar/Makefile
i3bar/doc/Makefile [deleted file]
i3bar/doc/i3bar.man [deleted file]
i3bar/i3bar.mk [new file with mode: 0644]
i3bar/include/child.h
i3bar/include/common.h
i3bar/include/determine_json_version.h [new file with mode: 0644]
i3bar/include/ipc.h
i3bar/include/outputs.h
i3bar/include/util.h
i3bar/include/workspaces.h
i3bar/include/xcb.h
i3bar/src/child.c
i3bar/src/determine_json_version.c [new file with mode: 0644]
i3bar/src/ipc.c
i3bar/src/main.c
i3bar/src/outputs.c
i3bar/src/workspaces.c
i3bar/src/xcb.c
include/all.h
include/atoms.xmacro
include/commands.h
include/con.h
include/config.h
include/data.h
include/display_version.h [new file with mode: 0644]
include/i3/ipc.h
include/key_press.h [new file with mode: 0644]
include/libi3.h
include/log.h
include/regex.h
include/scratchpad.h
include/shmlog.h
include/tree.h
libi3/Makefile
libi3/font.c
libi3/get_visualtype.c [new file with mode: 0644]
libi3/libi3.mk [new file with mode: 0644]
libi3/root_atom_contents.c
libi3/string.c [new file with mode: 0644]
man/Makefile
man/i3-dump-log.man
man/i3-msg.man
man/i3-sensible-terminal.man
man/i3.man
man/i3bar.man [new file with mode: 0644]
man/man.mk [new file with mode: 0644]
parser-specs/commands.spec
src/Makefile [new file with mode: 0644]
src/assignments.c
src/cfgparse.l
src/cfgparse.y
src/click.c
src/commands.c
src/commands_parser.c
src/con.c
src/config.c
src/debug.c
src/display_version.c [new file with mode: 0644]
src/ewmh.c
src/fake_outputs.c
src/floating.c
src/handlers.c
src/i3.mk [new file with mode: 0644]
src/ipc.c
src/key_press.c [new file with mode: 0644]
src/load_layout.c
src/log.c
src/main.c
src/manage.c
src/match.c
src/move.c
src/output.c
src/randr.c
src/regex.c
src/render.c
src/resize.c
src/scratchpad.c
src/sighandler.c
src/startup.c
src/tree.c
src/util.c
src/window.c
src/workspace.c
src/x.c
src/xcb.c
src/xcursor.c
src/xinerama.c
testcases/Makefile.PL
testcases/complete-run.pl
testcases/lib/SocketActivation.pm
testcases/lib/StartXDummy.pm
testcases/lib/TestWorker.pm
testcases/lib/i3test.pm
testcases/lib/i3test/Test.pm [new file with mode: 0644]
testcases/t/001-tile.t
testcases/t/002-i3-sync.t
testcases/t/003-ipc.t
testcases/t/004-unmanaged.t
testcases/t/005-floating.t
testcases/t/100-fullscreen.t
testcases/t/101-focus.t
testcases/t/102-dock.t
testcases/t/103-move.t
testcases/t/104-focus-stack.t
testcases/t/105-stacking.t
testcases/t/111-goto.t
testcases/t/112-floating-resize.t
testcases/t/113-urgent.t
testcases/t/114-client-leader.t
testcases/t/115-ipc-workspaces.t
testcases/t/116-nestedcons.t
testcases/t/117-workspace.t
testcases/t/118-openkill.t
testcases/t/119-match.t
testcases/t/120-multiple-cmds.t
testcases/t/121-next-prev.t
testcases/t/122-split.t
testcases/t/124-move.t
testcases/t/126-regress-close.t
testcases/t/127-regress-floating-parent.t
testcases/t/128-open-order.t
testcases/t/129-focus-after-close.t
testcases/t/130-close-empty-split.t
testcases/t/131-stacking-order.t
testcases/t/132-move-workspace.t
testcases/t/133-size-hints.t
testcases/t/134-invalid-command.t
testcases/t/135-floating-focus.t
testcases/t/136-floating-ws-empty.t
testcases/t/137-floating-unmap.t
testcases/t/138-floating-attach.t
testcases/t/139-ws-numbers.t
testcases/t/140-focus-lost.t
testcases/t/141-resize.t
testcases/t/142-regress-move-floating.t
testcases/t/143-regress-floating-restart.t
testcases/t/144-regress-floating-resize.t
testcases/t/145-flattening.t
testcases/t/146-floating-reinsert.t
testcases/t/147-regress-floatingmove.t
testcases/t/148-regress-floatingmovews.t
testcases/t/150-regress-dock-restart.t
testcases/t/151-regress-float-size.t
testcases/t/152-regress-level-up.t
testcases/t/153-floating-originalsize.t
testcases/t/154-regress-multiple-dock.t
testcases/t/155-floating-split-size.t
testcases/t/156-fullscreen-focus.t
testcases/t/157-regress-fullscreen-level-up.t [deleted file]
testcases/t/158-wm_take_focus.t
testcases/t/159-socketpaths.t
testcases/t/161-regress-borders-restart.t
testcases/t/162-regress-dock-urgent.t
testcases/t/163-wm-state.t
testcases/t/164-kill-win-vs-client.t
testcases/t/165-for_window.t
testcases/t/166-assign.t
testcases/t/167-workspace_layout.t
testcases/t/168-regress-fullscreen-restart.t
testcases/t/169-border-toggle.t
testcases/t/170-force_focus_wrapping.t
testcases/t/171-config-migrate.t
testcases/t/172-start-on-named-ws.t
testcases/t/173-get-marks.t
testcases/t/173-regress-focus-assign.t
testcases/t/174-border-config.t
testcases/t/174-regress-focus-toggle.t
testcases/t/175-startup-notification.t
testcases/t/176-workspace-baf.t
testcases/t/177-bar-config.t
testcases/t/178-regress-workspace-open.t
testcases/t/179-regress-multiple-ws.t
testcases/t/180-fd-leaks.t
testcases/t/181-regress-float-border.t
testcases/t/182-regress-focus-dock.t
testcases/t/183-config-variables.t
testcases/t/184-regress-float-split-resize.t
testcases/t/185-scratchpad.t
testcases/t/186-regress-assign-focus-parent.t
testcases/t/187-commands-parser.t
testcases/t/188-regress-focus-restart.t
testcases/t/189-floating-constraints.t
testcases/t/190-scratchpad-diff-ws.t
testcases/t/191-resize-levels.t [new file with mode: 0644]
testcases/t/192-layout.t [new file with mode: 0644]
testcases/t/193-ipc-version.t [new file with mode: 0644]
testcases/t/194-regress-floating-size.t [new file with mode: 0644]
testcases/t/195-net-active-window.t [new file with mode: 0644]
testcases/t/196-randr-output-names.t [new file with mode: 0644]
testcases/t/197-regression-move-vanish.t [new file with mode: 0644]
testcases/t/500-multi-monitor.t
testcases/t/501-scratchpad.t
testcases/t/502-focus-output.t
testcases/t/503-workspace.t
testcases/t/504-move-workspace-to-output.t
testcases/t/505-scratchpad-resolution.t [new file with mode: 0644]

index 705314b2a9488932bea9212a4a51e30232ebc2f8..e50eb4fb1fb9b7881e66913b854e9b6b0cb09f00 100644 (file)
@@ -1,33 +1,25 @@
 *.o
 tags
-include/loglevels.h
 include/GENERATED_*.h
-loglevels.tmp
+include/all.h.pch
 *.swp
 *.gcda
 *.gcno
 testcases/testsuite-*
 testcases/latest
 testcases/Makefile
+testcases/Makefile.old
+testcases/.last_run_timings.json
+testcases/_Inline
+testcases/inc
+testcases/META.yml
 test.commands_parser
 *.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
+man/*.1
+man/*.xml
+man/*.html
 *.tar.bz2*
 i3
 i3-input/i3-input
@@ -35,5 +27,7 @@ i3-nagbar/i3-nagbar
 i3-msg/i3-msg
 i3-config-wizard/i3-config-wizard
 i3-dump-log/i3-dump-log
-libi3/libi3.a
+libi3.a
 docs/*.pdf
+docs/*.html
+!/docs/refcard.html
diff --git a/DEPENDS b/DEPENDS
index 61fb958697263632b09f5d1466748e1b3d81e39c..fe9ba17f9eb66ad960ac734d1d7034ec7059a084 100644 (file)
--- a/DEPENDS
+++ b/DEPENDS
@@ -8,7 +8,6 @@
 │ 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       │ 4.0    │ 4.04   │ http://libev.schmorp.de/               │
 │ yajl        │ 1.0.8  │ 2.0.1  │ http://lloyd.github.com/yajl/          │
 │ asciidoc    │ 8.3.0  │ 8.6.4  │ http://www.methods.co.nz/asciidoc/     │
 │ xmlto       │ 0.0.23 │ 0.0.23 │ http://www.methods.co.nz/asciidoc/     │
+│ Pod::Simple²│ 3.22   │ 3.22   │ http://search.cpan.org/~dwheeler/Pod-Simple-3.23/
 │ docbook-xml │ 4.5    │ 4.5    │ http://www.methods.co.nz/asciidoc/     │
 │ libxcursor  │ 1.1.11 │ 1.1.11 │ http://ftp.x.org/pub/current/src/lib/  │
 │ Xlib        │ 1.3.3  │ 1.4.3  │ http://ftp.x.org/pub/current/src/lib/  │
 │ PCRE        │ 8.12   │ 8.12   │ http://www.pcre.org/                   │
 │ libsn¹      │ 0.10   │ 0.12   │ http://freedesktop.org/wiki/Software/startup-notification
+│ pango       │ 1.30.0 | 1.30.0 │ http://www.pango.org/                  │
+│ cairo       │ 1.12.2 │ 1.12.2 │ http://cairographics.org/              │
 └─────────────┴────────┴────────┴────────────────────────────────────────┘
  ¹ libsn = libstartup-notification
+ ² Pod::Simple is a Perl module required for converting the testsuite
+   documentation to HTML. See http://michael.stapelberg.de/cpan/#Pod::Simple
 
  i3bar, i3-msg, i3-input, i3-nagbar and i3-config-wizard do not introduce any
  new dependencies.
index 065cdbcbe5a1568cfcf161a9415243897f7f1d41..3b6750344b9eff42cf6f5f73e0274d773b2f74a1 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -2,151 +2,58 @@ 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
-FILES:=$(filter-out $(AUTOGENERATED),$(wildcard src/*.c))
-FILES:=$(FILES:.c=.o)
-HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h))
-CMDPARSE_HEADERS:=include/GENERATED_call.h include/GENERATED_enums.h include/GENERATED_tokens.h
+SUBDIRS:=
 
-# Recursively generate loglevels.h by explicitly calling make
-# We need this step because we need to ensure that loglevels.h will be
-# 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)
-else
-UNUSED:=$(shell $(MAKE) loglevels.h)
-endif
+ALL_TARGETS =
+INSTALL_TARGETS =
+CLEAN_TARGETS =
+DISTCLEAN_TARGETS =
 
-SUBDIRS:=i3-msg i3-input i3-nagbar i3-config-wizard i3bar i3-dump-log
+all: real-all
 
-# Depend on the specific file (.c for each .o) and on all headers
-src/%.o: src/%.c ${HEADERS}
-       echo "[i3] CC $<"
-       $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR; exit 0; }' loglevels.tmp))" -c -o $@ $<
+include libi3/libi3.mk
+include src/i3.mk
+include i3-config-wizard/i3-config-wizard.mk
+include i3-msg/i3-msg.mk
+include i3-input/i3-input.mk
+include i3-nagbar/i3-nagbar.mk
+include i3bar/i3bar.mk
+include i3-dump-log/i3-dump-log.mk
+include docs/docs.mk
+include man/man.mk
 
-all: i3 subdirs
+real-all: $(ALL_TARGETS)
 
-i3: libi3/libi3.a src/cfgparse.y.o src/cfgparse.yy.o ${FILES}
-       echo "[i3] LINK i3"
-       $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS)
-
-libi3/%.a: libi3/*.c
-       $(MAKE) -C libi3
-
-subdirs:
-       for dir in $(SUBDIRS); do \
-               echo ""; \
-               echo "MAKE $$dir"; \
-               $(MAKE) -C $$dir; \
-       done
-
-loglevels.h:
-       echo "[i3] LOGLEVELS"
-       for file in $$(ls src/*.c src/*.y src/*.l | grep -v 'cfgparse.\(tab\|yy\).c'); \
-       do \
-               echo $$(basename $$file .c); \
-       done > loglevels.tmp
-       (echo "char *loglevels[] = {"; for file in $$(cat loglevels.tmp); \
-       do \
-               echo "\"$$file\", "; \
-       done; \
-       echo "};") > include/loglevels.h;
-
-# The GENERATED_* files are actually all created from a single pass, so all
-# files just depend on the first one.
-include/GENERATED_call.h: generate-command-parser.pl parser-specs/commands.spec
-       echo "[i3] Generating command parser"
-       (cd include; ../generate-command-parser.pl)
-include/GENERATED_enums.h: include/GENERATED_call.h
-include/GENERATED_tokens.h: include/GENERATED_call.h
-
-# This target compiles the command parser twice:
-# Once with -DTEST_PARSER, creating a stand-alone executable used for tests,
-# and once as an object file for i3.
-src/commands_parser.o: src/commands_parser.c ${HEADERS} ${CMDPARSE_HEADERS}
-       echo "[i3] CC $<"
-       $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -DTEST_PARSER -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR; exit 0; }' loglevels.tmp))" -o test.commands_parser $< $(LIBS)
-       $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR; exit 0; }' loglevels.tmp))" -c -o $@ $<
-
-src/cfgparse.yy.o: src/cfgparse.l src/cfgparse.y.o ${HEADERS}
-       echo "[i3] LEX $<"
-       $(FLEX) -i -o$(@:.o=.c) $<
-       $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c)
-
-
-src/cfgparse.y.o: src/cfgparse.y ${HEADERS}
-       echo "[i3] YACC $<"
-       $(BISON) --debug --verbose -b $(basename $< .y) -d $<
-       $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c)
-
-
-install: all
-       echo "[i3] INSTALL"
-       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
-       $(INSTALL) -d -m 0755 $(DESTDIR)$(SYSCONFDIR)/i3
-       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/include/i3
-       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/xsessions
-       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/applications
-       $(INSTALL) -m 0755 i3 $(DESTDIR)$(PREFIX)/bin/
-       $(INSTALL) -m 0755 i3-migrate-config-to-v4 $(DESTDIR)$(PREFIX)/bin/
-       $(INSTALL) -m 0755 i3-sensible-editor $(DESTDIR)$(PREFIX)/bin/
-       $(INSTALL) -m 0755 i3-sensible-pager $(DESTDIR)$(PREFIX)/bin/
-       $(INSTALL) -m 0755 i3-sensible-terminal $(DESTDIR)$(PREFIX)/bin/
-       test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config
-       test -e $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes || $(INSTALL) -m 0644 i3.config.keycodes $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes
-       $(INSTALL) -m 0644 i3.xsession.desktop $(DESTDIR)$(PREFIX)/share/xsessions/i3.desktop
-       $(INSTALL) -m 0644 i3.applications.desktop $(DESTDIR)$(PREFIX)/share/applications/i3.desktop
-       $(INSTALL) -m 0644 include/i3/ipc.h $(DESTDIR)$(PREFIX)/include/i3/
-       for dir in $(SUBDIRS); do \
-               $(MAKE) -C $$dir install; \
-       done
+install: $(INSTALL_TARGETS)
 
 dist: distclean
        [ ! -d i3-${VERSION} ] || rm -rf i3-${VERSION}
        [ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2
        mkdir i3-${VERSION}
-       cp i3-migrate-config-to-v4 generate-command-parser.pl i3-sensible-* i3.config.keycodes DEPENDS LICENSE PACKAGE-MAINTAINER RELEASE-NOTES-${VERSION} i3.config i3.xsession.desktop i3.applications.desktop pseudo-doc.doxygen Makefile i3-${VERSION}
-       cp -r src libi3 i3-msg i3-nagbar i3-config-wizard i3bar i3-dump-log yajl-fallback include man parser-specs i3-${VERSION}
+       cp i3-migrate-config-to-v4 generate-command-parser.pl i3-sensible-* i3.config.keycodes DEPENDS LICENSE PACKAGE-MAINTAINER RELEASE-NOTES-${VERSION} i3.config i3.xsession.desktop i3.applications.desktop pseudo-doc.doxygen common.mk Makefile i3-${VERSION}
+       cp -r src libi3 i3-msg i3-nagbar i3-config-wizard i3bar i3-dump-log yajl-fallback include man parser-specs testcases i3-${VERSION}
        # Only copy toplevel documentation (important stuff)
        mkdir i3-${VERSION}/docs
        # Pre-generate documentation
-       $(MAKE) -C docs
-       $(MAKE) -C i3bar/doc
+       $(MAKE) docs
        # Cleanup τεχ output files
        find docs -regex ".*\.\(aux\|out\|log\|toc\|bm\|dvi\|log\)" -exec rm '{}' \;
        find docs -maxdepth 1 -type f ! \( -name "*.xcf" -or -name "*.svg" \) -exec cp '{}' i3-${VERSION}/docs \;
        # Only copy source code from i3-input
        mkdir i3-${VERSION}/i3-input
-       find i3-input -maxdepth 1 -type f \( -name "*.c" -or -name "*.h" -or -name "Makefile" \) -exec cp '{}' i3-${VERSION}/i3-input \;
-       sed -e 's/^GIT_VERSION:=\(.*\)/GIT_VERSION:=$(shell /bin/echo '${GIT_VERSION}' | sed 's/\\/\\\\/g')/g;s/^VERSION:=\(.*\)/VERSION:=${VERSION}/g' common.mk > i3-${VERSION}/common.mk
+       find i3-input -maxdepth 1 -type f \( -name "*.c" -or -name "*.mk" -or -name "*.h" -or -name "Makefile" \) -exec cp '{}' i3-${VERSION}/i3-input \;
+       echo -n ${I3_VERSION} > i3-${VERSION}/I3_VERSION
+       echo -n ${VERSION} > i3-${VERSION}/VERSION
        # Pre-generate a manpage to allow distributors to skip this step and save some dependencies
-       $(MAKE) -C man
+       $(MAKE) mans
        cp man/*.1 i3-${VERSION}/man/
-       cp i3bar/doc/*.1 i3-${VERSION}/i3bar/doc/
        tar cfj i3-${VERSION}.tar.bz2 i3-${VERSION}
        rm -rf i3-${VERSION}
 
-clean:
-       rm -f src/*.o src/*.gcno src/cmdparse.* src/cfgparse.tab.{c,h} src/cfgparse.yy.c src/cfgparse.{output,dot} loglevels.tmp include/loglevels.h include/GENERATED_*
+clean: $(CLEAN_TARGETS)
        (which lcov >/dev/null 2>&1 && lcov -d . --zerocounters) || true
-       $(MAKE) -C libi3 clean
-       $(MAKE) -C docs clean
-       $(MAKE) -C man clean
-       for dir in $(SUBDIRS); do \
-               echo ""; \
-               echo "CLEAN $$dir"; \
-               $(MAKE) TOPDIR=$(TOPDIR) -C $$dir distclean; \
-       done
 
-distclean: clean
-       rm -f i3
-       for dir in $(SUBDIRS); do \
-               echo ""; \
-               echo "DISTCLEAN $$dir"; \
-               $(MAKE) TOPDIR=$(TOPDIR) -C $$dir distclean; \
-       done
+distclean: clean $(DISTCLEAN_TARGETS)
 
 coverage:
        rm -f /tmp/i3-coverage.info
diff --git a/RELEASE-NOTES-4.3 b/RELEASE-NOTES-4.3
new file mode 100644 (file)
index 0000000..d828a0d
--- /dev/null
@@ -0,0 +1,165 @@
+
+ ┌──────────────────────────────┐
+ │ Release notes for i3 v4.3    │
+ └──────────────────────────────┘
+
+This is the third release of the new major version of i3, v4.3. It is
+considered stable. All users of i3 are strongly encouraged to upgrade.
+
+One rather visible change is that commands which could not be parsed properly
+ will now spawn i3-nagbar. In case you used "bindsym $mod+x firefox" (and
+ forgot the "exec" keyword) or you made a typo in your config, you will now
+ notice that :).
+
+We also made the orientation (horizontal/vertical) part of the layout
+  mechanism: Before, we got the default layout and you could change
+  orientation. Now, there are two new layouts "splitv" and "splith", which
+  replace the default layout. The "split h" and "split v" commands continue to
+  work as before, they split the current container and you will end up in a
+  split container with layout splith (after "split h") or splitv (after "split
+  v").
+
+  To change a splith container into a splitv container, use either "layout
+  splitv" or "layout toggle split". The latter command is used in the
+  default config as mod+l (previously "layout default"). In case you have
+  "layout default" in your config file, it is recommended to just replace
+  it by "layout toggle split", which will work as "layout default" did
+  before when pressing it once, but toggle between horizontal/vertical
+  when pressing it repeatedly.
+
+  The rationale behind this commit is that it’s cleaner to have all
+  parameters that influence how windows are rendered in the layout itself
+  rather than having a special parameter in combination with only one
+  layout. This enables us to change existing split containers in all cases
+  without breaking existing features (see ticket #464). Also, users should
+  feel more confident about whether they are actually splitting or just
+  changing an existing split container now.
+
+  As a nice side-effect, this commit brings back the "layout toggle"
+  feature we once had in i3 version 3 (see the userguide).
+
+
+Another very important change is that we now support pango for rendering text.
+  The default is still to use misc-fixed (X core fonts), but you can use a font
+  specification starting with "xft:" now, such as "xft:DejaVu Sans Mono 10" and
+  i3 will use pango. The sole motivation for this is NOT to have fancier window
+  decorations, but to support fonts which have more glyphs (think Japanese for
+  example) and to support right-to-left rendering (open http://www.ftpal.net/
+  for an example). Supporting users from all over the planet is important, and
+  as such I would strongly advise distribution packagers to leave pango support
+  in. In case you are working on a very low-spec embedded device, it is easy
+  enough to disable pango support, see common.mk.
+
+
+Also, the 'layout' command now always works on the parent split container. This
+  allows you to do things like this:
+
+      for_window [class="XTerm"] layout tabbed
+
+  When you now open XTerm on an empty workspace, the whole workspace will be
+  set to tabbed. In case you want to open XTerm in its own tabbed split
+  container, you need to split before:
+
+      for_window [class="XTerm"] split v, layout tabbed
+
+ ┌────────────────────────────┐
+ │ Changes in v4.3            │
+ └────────────────────────────┘
+
+  • docs/refcard: update for v4
+  • docs/userguide: clarify the default for focus_follows_mouse and new_window
+  • docs/userguide: add section about implicit containers
+  • docs/ipc: document the 'window' field in the GET_TREE reply
+  • docs/ipc: update links to ipc libraries
+  • docs/ipc: make the reply sections consistent
+  • docs/i3bar-protocol: add example (illustration-only!) shell script
+  • man/i3bar.man: reference i3bar-protocol
+  • IPC: Commands now lead to proper error messages in general. If we forgot
+    about a specific one, please open a ticket.
+  • IPC: implement GET_VERSION to find out the i3 version
+  • i3-dump-log now comes with a massively more helpful error message that
+    should cover all the use cases.
+  • 'workspace number <number>' now opens a new workspace
+  • 'workspace number <number>' now works with the back_and_forth option
+  • Allow focus with target (criteria) when in fullscreen mode in some cases
+  • Allow focus child/parent when in fullscreen mode
+  • Restrict directional focus when in fullscreen mode
+  • Prevent moving out of fullscreen containers
+  • Add 'move to workspace current' (useful when used with criteria)
+  • replace loglevels by a global debug logging
+  • make: new makefile layout
+  • make: canonicalize path when compiling. This leads to sth like
+    ../i3-4.2/src/main.c in backtraces, clearly identifying i3 code.
+  • automatically hide i3bar when it’s unneeded (after urgency hints)
+  • i3-config-wizard: use the level 0 keysym whenever it’s unambiguous
+  • i3-nagbar: use custom scripts to work around different terminal emulators
+    using different ways of interpreting the arguments to -e
+  • i3-sensible-terminal: add xfce4-terminal
+  • default config: require confirmation when exiting i3
+  • Display i3-nagbar when a command leads to an error.
+  • testcases: complete-run now supports --xtrace
+  • testcases: handle EAGAIN (fixes hangs)
+  • testcases: handle test bailouts
+  • Introduce splith/splitv layouts, remove orientation
+  • Implement hide_edge_borders option
+  • Support _NET_ACTIVE_WINDOW ClientMessages
+  • Set I3_PID atom on the X11 root window
+  • Implement i3 --moreversion, handy for figuring out whether you run the
+    latest binary which is installed.
+  • i3bar: be less strict about the {"version":1} JSON header
+  • shm-logging: implement i3-dump-log -f (follow)
+  • Implement pango support
+  • 'move workspace number n' will now create the workspace if it doesn’t exist
+  • Accept slashes in RandR output names
+  • Keep startup-notification sequences around for 30s after completion
+
+ ┌────────────────────────────┐
+ │ Bugfixes                   │
+ └────────────────────────────┘
+
+  • Fix floating precision bug when floating windows are moved between outputs.
+  • i3bar won’t crash when full_text is missing or null in the JSON input
+  • When having "workspace number 1" in your config, there will no longer be a
+    stray workspace "number 1".
+  • i3.config.keycodes used bindsym instead of bindcode for the arrow key
+    resizing bindings by mistake
+  • Fix 'move to workspace' when used with criteria
+  • Handle clicks to the very left edge of i3bar
+  • When using i3 -C, don’t send remaining arguments as an IPC command
+  • Fix reload crashes in rare cases
+  • i3bar: inform all clients of new tray selection owner (fixes tray problems
+    with X-Chat and possibly others)
+  • resizing: traverse containers up properly (fixes non-working resizing when
+    having a h-split within a h-split for example)
+  • Fix floating coordinates when moving assigned workspaces
+  • Properly fix floating coordinates when disabling outputs
+  • floating_fix_coordinates: properly deal with negative positions
+  • floating windows: add deco_height only when in normal border mode (fixes
+    initial floating window position/size when using a different default border
+    setting).
+  • Fix moving scratchpad window
+  • Cleanup zero-byte logfile on immediate exit (they are created by i3
+    --get-socketpath for example).
+  • Fix resizing floating windows by height
+  • Fix back_and_forth in 'workspace number' for named workspaces
+  • Grab server and process pending events before managing existing windows
+    (fixes problems with GIMP windows not being managed after an in-place
+     restart)
+  • Don’t allow ConfigureRequests while in fullscreen (fixes a compatibility
+    issue with gnome-terminal and xfce’s terminal)
+  • Fix flickering with 1pixel border tabbed layouts
+  • Use _exit() instead of exit() when i3 utility programs cannot be executed
+
+ ┌────────────────────────────┐
+ │ Thanks!                    │
+ └────────────────────────────┘
+
+Thanks for testing, bugfixes, discussions and everything I forgot go out to:
+
+  aksr, Axel Wagner, darkraven, David Coppa, eeemsi, Felicitus, Fernando Tarlá
+  Cardoso Lemos, Iakov Davydov, jh, Joel Stemmer, Julius Plenz, Marcel Hellwig,
+  Marcus, mloskot, Moritz Bandemer, oblique, Ondrej Grover, Pavel Löbl, Philipp
+  Middendorf, prg, Quentin Glidic, Sebastian Ullrich, somelauw, stfn, tucos,
+  TunnelWicht, Valentin Haenel
+
+-- Michael Stapelberg, 2012-09-06
index 439490596d38ff0b910c6b34896d400fea577af8..bb5cf79346b3b9b06265ad7716f81b263b967b43 100644 (file)
--- a/common.mk
+++ b/common.mk
@@ -14,9 +14,52 @@ ifndef SYSCONFDIR
     SYSCONFDIR=$(PREFIX)/etc
   endif
 endif
-# The escaping is absurd, but we need to escape for shell, sed, make, define
-GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch $(shell [ -f $(TOPDIR)/.git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' $(TOPDIR)/.git/HEAD || echo 'unknown'))"
-VERSION:=$(shell git describe --tags --abbrev=0)
+
+# In dist tarballs, the version is stored in the I3_VERSION and VERSION files.
+I3_VERSION := '$(shell [ -f $(TOPDIR)/I3_VERSION ] && cat $(TOPDIR)/I3_VERSION)'
+VERSION := '$(shell [ -f $(TOPDIR)/VERSION ] && cat $(TOPDIR)/VERSION)'
+ifeq ('',$(I3_VERSION))
+VERSION := $(shell git describe --tags --abbrev=0)
+I3_VERSION := '$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch \"$(shell git describe --tags --always --all | sed s:heads/::)\")'
+endif
+
+MAJOR_VERSION := $(shell echo ${VERSION} | cut -d '.' -f 1)
+MINOR_VERSION := $(shell echo ${VERSION} | cut -d '.' -f 2)
+PATCH_VERSION := $(shell echo ${VERSION} | cut -d '.' -f 3)
+ifeq (${PATCH_VERSION},)
+PATCH_VERSION := 0
+endif
+
+## Generic flags
+
+# Default CFLAGS that users should be able to override
+ifeq ($(DEBUG),1)
+# Extended debugging flags, macros shall be available in gcc
+CFLAGS ?= -pipe -gdwarf-2 -g3
+else
+CFLAGS ?= -pipe -O2 -freorder-blocks-and-partition
+endif
+
+# Default LDFLAGS that users should be able to override
+LDFLAGS ?= $(as_needed_LDFLAG)
+
+# Common CFLAGS for all i3 related binaries
+I3_CFLAGS  = -std=c99
+I3_CFLAGS += -Wall
+# unused-function, unused-label, unused-variable are turned on by -Wall
+# We don’t want unused-parameter because of the use of many callbacks
+I3_CFLAGS += -Wunused-value
+I3_CFLAGS += -Iinclude
+
+I3_CPPFLAGS  = -DI3_VERSION=\"${I3_VERSION}\"
+I3_CPPFLAGS += -DMAJOR_VERSION=${MAJOR_VERSION}
+I3_CPPFLAGS += -DMINOR_VERSION=${MINOR_VERSION}
+I3_CPPFLAGS += -DPATCH_VERSION=${PATCH_VERSION}
+I3_CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\"
+I3_CPPFLAGS += -DI3__FILE__=__FILE__
+
+
+## Libraries flags
 
 ifeq ($(shell which pkg-config 2>/dev/null 1>/dev/null || echo 1),1)
 $(error "pkg-config was not found")
@@ -35,79 +78,92 @@ endif
 cflags_for_lib = $(shell pkg-config --silence-errors --cflags $(1) 2>/dev/null)
 ldflags_for_lib = $(shell pkg-config --exists 2>/dev/null $(1) && pkg-config --libs $(1) 2>/dev/null || echo -l$(2))
 
-CFLAGS += -std=c99
-CFLAGS += -pipe
-CFLAGS += -Wall
-# unused-function, unused-label, unused-variable are turned on by -Wall
-# We don’t want unused-parameter because of the use of many callbacks
-CFLAGS += -Wunused-value
-CFLAGS += -Iinclude
-CFLAGS += $(call cflags_for_lib, xcb-keysyms)
+# XCB common stuff
+XCB_CFLAGS  := $(call cflags_for_lib, xcb)
+XCB_CFLAGS  += $(call cflags_for_lib, xcb-event)
+XCB_LIBS    := $(call ldflags_for_lib, xcb,xcb)
+XCB_LIBS    += $(call ldflags_for_lib, xcb-event,xcb-event)
 ifeq ($(shell pkg-config --exists xcb-util 2>/dev/null || echo 1),1)
-CPPFLAGS += -DXCB_COMPAT
-CFLAGS += $(call cflags_for_lib, xcb-atom)
-CFLAGS += $(call cflags_for_lib, xcb-aux)
+XCB_CFLAGS  += $(call cflags_for_lib, xcb-atom)
+XCB_CFLAGS  += $(call cflags_for_lib, xcb-aux)
+XCB_LIBS    += $(call ldflags_for_lib, xcb-atom,xcb-atom)
+XCB_LIBS    += $(call ldflags_for_lib, xcb-aux,xcb-aux)
+XCB_CPPFLAGS+= -DXCB_COMPAT
 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)
-CFLAGS += $(call cflags_for_lib, libpcre)
-CFLAGS += $(call cflags_for_lib, libstartup-notification-1.0)
-CPPFLAGS += -DI3_VERSION=\"${GIT_VERSION}\"
-CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\"
+XCB_CFLAGS  += $(call cflags_for_lib, xcb-util)
+XCB_LIBS    += $(call ldflags_for_lib, xcb-util)
+endif
+
+# XCB keyboard stuff
+XCB_KBD_CFLAGS := $(call cflags_for_lib, xcb-keysyms)
+XCB_KBD_LIBS   := $(call ldflags_for_lib, xcb-keysyms,xcb-keysyms)
+
+# XCB WM stuff
+XCB_WM_CFLAGS := $(call cflags_for_lib, xcb-icccm)
+XCB_WM_CFLAGS += $(call cflags_for_lib, xcb-xinerama)
+XCB_WM_CFLAGS += $(call cflags_for_lib, xcb-randr)
+XCB_WM_LIBS   := $(call ldflags_for_lib, xcb-icccm,xcb-icccm)
+XCB_WM_LIBS   += $(call ldflags_for_lib, xcb-xinerama,xcb-xinerama)
+XCB_WM_LIBS   += $(call ldflags_for_lib, xcb-randr,xcb-randr)
+
+# Xlib
+X11_CFLAGS := $(call cflags_for_lib, x11)
+X11_LIBS   := $(call ldflags_for_lib, x11,X11)
+
+# Xcursor
+XCURSOR_CFLAGS := $(call cflags_for_lib, xcursor)
+XCURSOR_LIBS   := $(call ldflags_for_lib, xcursor,Xcursor)
 
+# yajl
+YAJL_CFLAGS := $(call cflags_for_lib, yajl)
+# Fallback for libyajl 1 which did not include yajl_version.h. We need
+# YAJL_MAJOR from that file to decide which code path should be used.
+YAJL_CFLAGS += -idirafter $(TOPDIR)/yajl-fallback
+YAJL_LIBS   := $(call ldflags_for_lib, yajl,yajl)
+
+#libev
+LIBEV_CFLAGS := $(call cflags_for_lib, libev)
+LIBEV_LIBS   := $(call ldflags_for_lib, libev,ev)
+
+# libpcre
+PCRE_CFLAGS := $(call cflags_for_lib, libpcre)
 ifeq ($(shell pkg-config --atleast-version=8.10 libpcre 2>/dev/null && echo 1),1)
-CPPFLAGS += -DPCRE_HAS_UCP=1
+I3_CPPFLAGS += -DPCRE_HAS_UCP=1
 endif
+PCRE_LIBS   := $(call ldflags_for_lib, libpcre,pcre)
 
-LIBS += -lm
-# Darwin (Mac OS X) doesn’t have librt
-ifneq ($(UNAME),Darwin)
-LIBS += -lrt
-endif
-LIBS += -L $(TOPDIR)/libi3 -li3
-LIBS += $(call ldflags_for_lib, xcb-event,xcb-event)
-LIBS += $(call ldflags_for_lib, xcb-keysyms,xcb-keysyms)
-ifeq ($(shell pkg-config --exists xcb-util 2>/dev/null || echo 1),1)
-LIBS += $(call ldflags_for_lib, xcb-atom,xcb-atom)
-LIBS += $(call ldflags_for_lib, xcb-aux,xcb-aux)
-else
-LIBS += $(call ldflags_for_lib, xcb-util)
-endif
-LIBS += $(call ldflags_for_lib, xcb-icccm,xcb-icccm)
-LIBS += $(call ldflags_for_lib, xcb-xinerama,xcb-xinerama)
-LIBS += $(call ldflags_for_lib, xcb-randr,xcb-randr)
-LIBS += $(call ldflags_for_lib, xcb,xcb)
-LIBS += $(call ldflags_for_lib, xcursor,Xcursor)
-LIBS += $(call ldflags_for_lib, x11,X11)
-LIBS += $(call ldflags_for_lib, yajl,yajl)
-LIBS += $(call ldflags_for_lib, libev,ev)
-LIBS += $(call ldflags_for_lib, libpcre,pcre)
-LIBS += $(call ldflags_for_lib, libstartup-notification-1.0,startup-notification-1)
+# startup-notification
+LIBSN_CFLAGS := $(call cflags_for_lib, libstartup-notification-1.0)
+LIBSN_LIBS   := $(call ldflags_for_lib, libstartup-notification-1.0,startup-notification-1)
+
+# Pango
+PANGO_CFLAGS := $(call cflags_for_lib, cairo)
+PANGO_CFLAGS += $(call cflags_for_lib, pangocairo)
+I3_CPPFLAGS  += -DPANGO_SUPPORT=1
+PANGO_LIBS   := $(call ldflags_for_lib, cairo)
+PANGO_LIBS   += $(call ldflags_for_lib, pangocairo)
+
+# libi3
+LIBS = -L$(TOPDIR) -li3
+
+## Platform-specific flags
 
 # Please test if -Wl,--as-needed works on your platform and send me a patch.
 # it is known not to work on Darwin (Mac OS X)
 ifneq (,$(filter Linux GNU GNU/%, $(UNAME)))
-LDFLAGS += -Wl,--as-needed
+as_needed_LDFLAG = -Wl,--as-needed
 endif
 
 ifeq ($(UNAME),NetBSD)
 # We need -idirafter instead of -I to prefer the system’s iconv over GNU libiconv
-CFLAGS += -idirafter /usr/pkg/include
-LDFLAGS += -Wl,-rpath,/usr/local/lib -Wl,-rpath,/usr/pkg/lib
+I3_CFLAGS += -idirafter /usr/pkg/include
+I3_LDFLAGS += -Wl,-rpath,/usr/local/lib -Wl,-rpath,/usr/pkg/lib
 endif
 
 ifeq ($(UNAME),OpenBSD)
-CFLAGS += -I${X11BASE}/include
+I3_CFLAGS += -I${X11BASE}/include
 LIBS += -liconv
-LDFLAGS += -L${X11BASE}/lib
+I3_LDFLAGS += -L${X11BASE}/lib
 endif
 
 ifeq ($(UNAME),FreeBSD)
@@ -116,33 +172,32 @@ endif
 
 ifeq ($(UNAME),Darwin)
 LIBS += -liconv
+else
+# Darwin (Mac OS X) doesn’t have librt
+LIBS += -lrt
 endif
 
-# Fallback for libyajl 1 which did not include yajl_version.h. We need
-# YAJL_MAJOR from that file to decide which code path should be used.
-CFLAGS += -idirafter $(TOPDIR)/yajl-fallback
-
 ifneq (,$(filter Linux GNU GNU/%, $(UNAME)))
-CPPFLAGS += -D_GNU_SOURCE
+I3_CPPFLAGS += -D_GNU_SOURCE
 endif
 
-ifeq ($(DEBUG),1)
-# Extended debugging flags, macros shall be available in gcc
-CFLAGS += -gdwarf-2
-CFLAGS += -g3
-else
-CFLAGS += -O2
-CFLAGS += -freorder-blocks-and-partition
-endif
 
 ifeq ($(COVERAGE),1)
-CFLAGS += -fprofile-arcs -ftest-coverage
+I3_CFLAGS += -fprofile-arcs -ftest-coverage
 LIBS += -lgcov
 endif
 
+V ?= 0
+ifeq ($(V),0)
 # Don’t print command lines which are run
 .SILENT:
 
+# echo-ing vars
+V_ASCIIDOC = echo ASCIIDOC $@;
+V_POD2HTML = echo POD2HTML $@;
+V_A2X = echo A2X $@;
+endif
+
 # Always remake the following targets
 .PHONY: install clean dist distclean
 
diff --git a/contrib/trivial-bar-script.sh b/contrib/trivial-bar-script.sh
new file mode 100755 (executable)
index 0000000..15bc7de
--- /dev/null
@@ -0,0 +1,29 @@
+#!/bin/sh
+# vim:ts=4:sw=4:expandtab
+# © 2012 Michael Stapelberg, Public Domain
+
+# This script is a trivial shell script to send your own output to i3bar while
+# using the JSON protocol.
+#
+# It is ugly and that is inherent to using JSON with shell scripts. You
+# _really_ should not do that. See i3status or i3status’s contrib/ directory
+# for examples of how to handle the output in higher-level languages.
+#
+# This example is purely for illustration of the protocol. DO NOT USE IT IN THE
+# REAL WORLD.
+
+# Send the header so that i3bar knows we want to use JSON:
+echo '{ "version": 1 }'
+
+# Begin the endless array.
+echo '['
+
+# We send an empty first array of blocks to make the loop simpler:
+echo '[]'
+
+# Now send blocks with information forever:
+while :;
+do
+       echo ",[{\"name\":\"time\",\"full_text\":\"$(date)\"}]"
+       sleep 1
+done
index 3a4adc68ec127965277b2491688f9194cdf1ed89..02f00de25f2e7cb13c2d2e2250ba7fb040ec1846 100644 (file)
@@ -2,14 +2,34 @@ Source: i3-wm
 Section: x11
 Priority: extra
 Maintainer: Michael Stapelberg <stapelberg@debian.org>
-Build-Depends: debhelper (>= 7.0.50~), libx11-dev, libxcb-util0-dev (>= 0.3.8), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-icccm4-dev, libxcursor-dev, asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, libpcre3-dev, libstartup-notification0-dev (>= 0.10)
+Build-Depends: debhelper (>= 7.0.50~),
+               libx11-dev,
+               libxcb-util0-dev (>= 0.3.8),
+               libxcb-keysyms1-dev,
+               libxcb-xinerama0-dev (>= 1.1),
+               libxcb-randr0-dev,
+               libxcb-icccm4-dev,
+               libxcursor-dev,
+               asciidoc (>= 8.4.4),
+               xmlto,
+               docbook-xml,
+               pkg-config,
+               libev-dev,
+               flex,
+               bison,
+               libyajl-dev,
+               libpcre3-dev,
+               libstartup-notification0-dev (>= 0.10),
+               libcairo2-dev,
+               libpango1.0-dev,
+               libpod-simple-perl
 Standards-Version: 3.9.3
 Homepage: http://i3wm.org/
 
 Package: i3
 Architecture: any
 Depends: i3-wm (=${binary:Version}), ${misc:Depends}
-Recommends: i3lock (>= 2.2), suckless-tools, i3status (>= 2.3)
+Recommends: i3lock (>= 2.2), suckless-tools, i3status (>= 2.3), dunst
 Description: metapackage (i3 window manager, screen locker, menu, statusbar)
  This metapackage installs the i3 window manager (i3-wm), the i3lock screen
  locker, i3status (for system information) and suckless-tools (for dmenu).
index a14b8152cd8e5fbc329f6adf1cd38a5d3509b020..6bd9c5ba0cb436d7164104301dd8ed92b89df068 100644 (file)
@@ -28,3 +28,5 @@ docs/tree-shot4.png
 docs/refcard.html
 docs/refcard_style.css
 docs/logo-30.png
+docs/lib-i3test.html
+docs/lib-i3test-test.html
index 4df7233c697e8ff16c374451c4c160807bb67e16..a1b05bd3554dc2c33d7270bfc069dd3269759ce2 100644 (file)
@@ -8,4 +8,4 @@ man/i3-migrate-config-to-v4.1
 man/i3-sensible-pager.1
 man/i3-sensible-editor.1
 man/i3-sensible-terminal.1
-i3bar/doc/i3bar.1
+man/i3bar.1
index fc41236f2acf5276ea1957f38bc8aa5b54e1bf40..d6e670c9de1441eddf97d4f033f3d264c6c0603b 100644 (file)
@@ -1,36 +1,7 @@
-# To pass additional parameters for asciidoc
-ASCIIDOC=asciidoc
-
-ASCIIDOC_TARGETS:=hacking-howto.html debugging.html debugging-release-version.html userguide.html ipc.html multi-monitor.html wsbar.html testsuite.html i3bar-protocol.html
-
-all: ${ASCIIDOC_TARGETS}
-
-hacking-howto.html: hacking-howto
-       $(ASCIIDOC) -a toc -n $<
-
-i3bar-protocol.html: i3bar-protocol
-       $(ASCIIDOC) -a toc -n $<
-
-debugging.html: debugging
-       $(ASCIIDOC) -n $<
-
-debugging-release-version.html: debugging-release-version
-       $(ASCIIDOC) -n $<
-
-userguide.html: userguide
-       $(ASCIIDOC) -a toc -n $<
-
-testsuite.html: testsuite
-       $(ASCIIDOC) -a toc -n $<
-
-ipc.html: ipc
-       $(ASCIIDOC) -a toc -n $<
-
-multi-monitor.html: multi-monitor
-       $(ASCIIDOC) -a toc -n $<
-
-wsbar.html: wsbar
-       $(ASCIIDOC) -a toc -n $<
+all:
+       $(MAKE) -C .. docs
 
 clean:
-       rm -f ${ASCIIDOC_TARGETS}
+       $(MAKE) -C .. clean-docs
+
+.PHONY: all clean
index 24dcb596237e6b2fb5f787d73662f65986d6bdc3..cc135ae959a6a87a6fdb6abf3a87ff9581678c9b 100644 (file)
@@ -647,7 +647,7 @@ endif::doctype-manpage[]
 </div>\r
 {disable-javascript%<div id="footnotes"><hr /></div>}\r
 <div id="footer" lang="de">\r
-© 2009-2011 Michael Stapelberg, <a href="/impress.html">Impressum</a>\r
+© 2009-2012 Michael Stapelberg, <a href="/impress.html">Impressum</a>
 </div>\r
 </body>\r
 </html>\r
diff --git a/docs/docs.mk b/docs/docs.mk
new file mode 100644 (file)
index 0000000..c0daed6
--- /dev/null
@@ -0,0 +1,46 @@
+DISTCLEAN_TARGETS += clean-docs
+
+# To pass additional parameters for asciidoc
+ASCIIDOC = asciidoc
+I3POD2HTML = ./docs/i3-pod2html
+
+ASCIIDOC_NOTOC_TARGETS = \
+       docs/debugging.html \
+       docs/debugging-release-version.html
+
+ASCIIDOC_TOC_TARGETS = \
+       docs/hacking-howto.html \
+       docs/userguide.html \
+       docs/ipc.html \
+       docs/multi-monitor.html \
+       docs/wsbar.html \
+       docs/testsuite.html \
+       docs/i3bar-protocol.html
+
+ASCIIDOC_TARGETS = \
+       $(ASCIIDOC_TOC_TARGETS) \
+       $(ASCIIDOC_NOTOC_TARGETS)
+
+ASCIIDOC_CALL = $(V_ASCIIDOC)$(ASCIIDOC) -n $(ASCIIDOC_FLAGS) -o $@ $<
+ASCIIDOC_TOC_CALL = $(V_ASCIIDOC)$(ASCIIDOC) -a toc -n $(ASCIIDOC_FLAGS) -o $@ $<
+
+POD2HTML_TARGETS = \
+       docs/lib-i3test.html \
+       docs/lib-i3test-test.html
+
+docs/lib-i3test.html: testcases/lib/i3test.pm
+       $(V_POD2HTML)$(I3POD2HTML) $< $@
+
+docs/lib-i3test-test.html: testcases/lib/i3test/Test.pm
+       $(V_POD2HTML)$(I3POD2HTML) $< $@
+
+docs: $(ASCIIDOC_TARGETS) $(POD2HTML_TARGETS)
+
+$(ASCIIDOC_TOC_TARGETS): docs/%.html: docs/%
+       $(ASCIIDOC_TOC_CALL)
+
+$(ASCIIDOC_NOTOC_TARGETS): docs/%.html: docs/%
+       $(ASCIIDOC_CALL)
+
+clean-docs:
+       rm -f $(ASCIIDOC_TARGETS) $(POD2HTML_TARGETS)
index 73ae96335991d5312e1b61b5a0c265be8a16926f..7f2c35e65d0b6ba06727ffb37159a0632264f874 100644 (file)
@@ -141,7 +141,7 @@ src/load_layout.c::
 Contains code for loading layouts from JSON files.
 
 src/log.c::
-Handles the setting of loglevels, contains the logging functions.
+Contains the logging functions.
 
 src/main.c::
 Initializes the window manager.
diff --git a/docs/i3-pod2html b/docs/i3-pod2html
new file mode 100755 (executable)
index 0000000..56a769f
--- /dev/null
@@ -0,0 +1,90 @@
+#!/usr/bin/env perl
+# vim:ts=4:sw=4:expandtab
+
+use strict;
+use warnings;
+use Pod::Simple::HTML;
+use v5.10;
+
+$Pod::Simple::HTML::Tagmap{'Verbatim'} = '<pre><tt>';
+$Pod::Simple::HTML::Tagmap{'VerbatimFormatted'} = '<pre><tt>';
+$Pod::Simple::HTML::Tagmap{'/Verbatim'} = '</tt></pre>';
+$Pod::Simple::HTML::Tagmap{'/VerbatimFormatted'} = '</tt></pre>';
+
+if (@ARGV < 2) {
+    say STDERR "Syntax: i3-pod2html <pod-input-path> <html-output-path>";
+    exit 1;
+}
+
+open(my $in, '<', $ARGV[0]) or die "Couldn’t open $ARGV[0] for reading: $!\n";
+open(my $out, '>', $ARGV[1]) or die "Couldn’t open $ARGV[1] for writing: $!\n";
+
+my $parser = Pod::Simple::HTML->new();
+
+$parser->index(1);
+$parser->html_header_before_title(
+<<'EOF'
+<!doctype html>
+<html lang="en">
+<head>
+<link rel="icon" type="image/png" href="/favicon.png">
+<meta charset="utf-8">
+<meta name="generator" content="Pod::Simple::HTML">
+<meta name="description" content="i3 Perl documentation (testsuite)">
+<link rel="stylesheet" href="http://i3wm.org/css/style.css" type="text/css" />
+<style type="text/css">
+.pod pre {
+    background: #333;
+    border: 1px solid #555;
+    border-left: 5px solid #555;
+    padding: 0.5em;
+    padding-left: 0;
+    padding-right: 0.5em;
+    white-space: pre;
+    color: white;
+}
+
+.pod ul {
+    list-style-type: none;
+}
+.pod li {
+    margin-bottom: 0 !important;
+}
+
+tt {
+    font-family: 'Droid Sans Mono', sans-serif;
+    font-size: inherit;
+}
+
+.pod h1 a, .pod h2 a, .pod h3 a, .pod h4 a {
+    text-decoration: none;
+    color: white;
+}
+</style>
+<title>
+EOF
+);
+$parser->html_header_after_title(
+<<'EOF'
+</title>
+</head>
+<body>
+
+        <div id="main">
+            <a href="/"><h1 id="title">i3 - improved tiling WM</h1></a>
+                       <ul id="nav">
+                               <li><a style="border-bottom: 2px solid #fff" href="/docs">Docs</a></li>
+                               <li><a href="/screenshots">Screens</a></li>
+                               <li><a href="http://faq.i3wm.org/">FAQ</a></li>
+                               <li><a href="/contact">Contact</a></li>
+                               <li><a href="http://bugs.i3wm.org/">Bugs</a></li>
+                       </ul>
+       <br style="clear: both">
+<div id="content" class="pod">
+<h1>i3 Perl documentation (testsuite)</h1>
+
+EOF
+);
+
+$parser->output_fh($out);
+$parser->parse_file($in);
index f66c7a9a6129a8c90b9175aa4a963bbe9ed8c7a2..21ba9aa0f8e350252e1fdbc8f7a8f76b9d125056 100644 (file)
@@ -1,7 +1,7 @@
 i3bar input protocol
 ====================
 Michael Stapelberg <michael@i3wm.org>
-February 2012
+August 2012
 
 This document explains the protocol in which i3bar expects its input. It
 provides support for colors, urgency, shortening and easy manipulation.
@@ -49,6 +49,9 @@ consists of a single JSON hash:
 { "version": 1 }
 ----------------
 
+(Note that before i3 v4.3 the precise format had to be +{"version":1}+,
+byte-for-byte.)
+
 What follows is an infinite array (so it should be parsed by a streaming JSON
 parser, but as described above you can go for a simpler solution), whose
 elements are one array per status line. A status line is one unit of
@@ -86,6 +89,10 @@ Please note that this example was pretty printed for human consumption.
 i3status and others will output single statuslines in one line, separated by
 \n.
 
+You can find an example of a shell script which can be used as your
++status_command+ in the bar configuration at
+http://code.stapelberg.de/git/i3/tree/contrib/trivial-bar-script.sh?h=next
+
 === Blocks in detail
 
 full_text::
index 83ab7218714df269cc01ad55302c47fe8b334a31..f8dfa78e4ca0fdb117f0dd4b79365cd98bdba850 100644 (file)
--- a/docs/ipc
+++ b/docs/ipc
@@ -1,7 +1,7 @@
 IPC interface (interprocess communication)
 ==========================================
 Michael Stapelberg <michael@i3wm.org>
-February 2012
+August 2012
 
 This document describes how to interface with i3 from a separate process. This
 is useful for example to remote-control i3 (to write test cases for example) or
@@ -70,6 +70,9 @@ GET_BAR_CONFIG (6)::
        Gets the configuration (as JSON map) of the workspace bar with the
        given ID. If no ID is provided, an array with all configured bar IDs is
        returned instead.
+GET_VERSION (7)::
+       Gets the version of i3. The reply will be a JSON-encoded dictionary
+       with the major, minor, patch and human-readable version.
 
 So, a typical message could look like this:
 --------------------------------------------------
@@ -125,6 +128,8 @@ MARKS (5)::
        Reply to the GET_MARKS message.
 BAR_CONFIG (6)::
        Reply to the GET_BAR_CONFIG message.
+VERSION (7)::
+       Reply to the GET_VERSION message.
 
 === COMMAND reply
 
@@ -206,7 +211,7 @@ default) or whether a JSON parse error occurred.
 { "success": true }
 -------------------
 
-=== GET_OUTPUTS reply
+=== OUTPUTS reply
 
 The reply consists of a serialized list of outputs. Each output has the
 following properties:
@@ -270,12 +275,15 @@ 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".
+       Can be either "splith", "splitv", "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".
+       THIS FIELD IS OBSOLETE. It is still present, but your code should not
+       use it. Instead, rely on the layout field.
 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
@@ -296,6 +304,11 @@ window_rect (map)::
 geometry (map)::
        The original geometry the window specified when i3 mapped it. Used when
        switching a window to floating mode, for example.
+window (integer)::
+       The X11 window ID of the *actual client window* inside this container.
+       This field is set to null for split containers or otherwise empty
+       containers. This ID corresponds to what xwininfo(1) and other
+       X11-related tools display (usually in hex).
 urgent (bool)::
        Whether this container (window or workspace) has the urgency hint set.
 focused (bool)::
@@ -526,6 +539,35 @@ urgent_workspace_text/urgent_workspace_bar::
 }
 --------------
 
+=== VERSION reply
+
+The reply consists of a single JSON dictionary with the following keys:
+
+major (integer)::
+       The major version of i3, such as +4+.
+minor (integer)::
+       The minor version of i3, such as +2+. Changes in the IPC interface (new
+       features) will only occur with new minor (or major) releases. However,
+       bugfixes might be introduced in patch releases, too.
+patch (integer)::
+       The patch version of i3, such as +1+ (when the complete version is
+       +4.2.1+). For versions such as +4.2+, patch will be set to +0+.
+human_readable (string)::
+       A human-readable version of i3 containing the precise git version,
+       build date and branch name. When you need to display the i3 version to
+       your users, use the human-readable version whenever possible (since
+       this is what +i3 --version+ displays, too).
+
+*Example:*
+-------------------
+{
+   "human_readable" : "4.2-169-gf80b877 (2012-08-05, branch \"next\")",
+   "minor" : 2,
+   "patch" : 0,
+   "major" : 4
+}
+-------------------
+
 == Events
 
 [[events]]
@@ -621,7 +663,7 @@ C::
 Ruby::
        http://github.com/badboy/i3-ipc
 Perl::
-       http://search.cpan.org/search?query=AnyEvent::I3
+       https://metacpan.org/module/AnyEvent::I3
 Python::
-       https://github.com/whitelynx/i3ipc
-       https://github.com/ziberna/i3-py (includes higher-level features)
+       https://github.com/whitelynx/i3ipc
+       https://github.com/ziberna/i3-py (includes higher-level features)
index a4427f4f261380b85377cd8e8e29a8de2c735733..7156da368e72721b443f424365dd6026765fd348 100644 (file)
                </p>
        </header>
 
+
        <section>
        <h2>Basics</h2>
        <table>
                <tr>
-                       <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd></kbd>
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd>
                        <td>open new terminal
-
                <tr>
-                       <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd>j</kbd>
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>j</kbd>
                        <td>focus left
 
                <tr>
-                       <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd>k</kbd>
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>k</kbd>
                        <td>focus down
 
                <tr>
-                       <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd>l</kbd>
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>l</kbd>
                        <td>focus up
 
                <tr>
-                       <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd>;</kbd>
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>;</kbd>
                        <td>focus right
+               <tr>
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd>
+                       <td>toggle focus mode
        </table>
        </section>
 
-
        <section>
-       <h2>Changing the container layout</h2>
+       <h2>Moving windows</h2>
        <table>
                <tr>
-                       <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd>e</kbd>
-                       <td>default
-
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd> + <kbd>j</kbd>
+                       <td>move window left
                <tr>
-                       <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd>s</kbd>
-                       <td>stacking
-
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd> + <kbd>k</kbd>
+                       <td>move window down
                <tr>
-                       <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd>w</kbd>
-                       <td>tabbed
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd> + <kbd>l</kbd>
+                       <td>move window up
+               <tr>
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd> + <kbd>;</kbd>
+                       <td>move window right
        </table>
        </section>
 
 </div><div>
 
        <section>
-       <h2>Fullscreen mode</h2>
+       <h2>Modifying windows</h2>
        <table>
                <tr>
-                       <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd>f</kbd>
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>f</kbd>
                        <td>toggle fullscreen
+               <tr>
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>v</kbd>
+                       <td>split a window vertically
+               <tr>
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>h</kbd>
+                       <td>split a window horizontally
+               <tr>
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>r</kbd>
+                       <td>resize mode
        </table>
+       <p class="ref">Look at the “Resizing containers / windows” section of the user guide.</p>
        </section>
 
-
        <section>
-       <h2>Opening other applications</h2>
+       <h2>Changing the container layout</h2>
        <table>
                <tr>
-                       <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd>d</kbd>
-                       <td>open application (with dmenu)
-       </table>
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>e</kbd>
+                       <td>default
 
+               <tr>
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>s</kbd>
+                       <td>stacking
 
-       <section>
-       <h2>Closing windows</h2>
-       <table>
                <tr>
-                       <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd> </kbd>+ <kbd>q</kbd>
-                       <td>kill a window
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>w</kbd>
+                       <td>tabbed
        </table>
        </section>
 
-
        <section>
-       <h2>Using workspaces</h2>
+       <h2>Floating</h2>
        <table>
                <tr>
-                       <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd>1</kbd>–<kbd>9</kbd>
-                       <td>switch to another workspace
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd> + <kbd></kbd>
+                       <td>toggle floating
+               <tr>
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd>
+                       <td>drag floating
        </table>
        </section>
 
 
        <section>
-       <h2>Moving windows to workspaces</h2>
+       <h2>Using workspaces</h2>
        <table>
                <tr>
-                       <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd></kbd> + <kbd>1</kbd>–<kbd>9</kbd>
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>0</kbd>-<kbd>9</kbd>
+                       <td>switch to another workspace
+               <tr>
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd> + <kbd>0</kbd>-<kbd>9</kbd>
                        <td>move a window to another workspace
        </table>
        </section>
 </div><div>
 
        <section>
-       <h2>Resizing</h2>
-       <p class="ref">Look at “Resizing containers / windows” section of the user guide.</p>
-       </section>
-
-
-       <section>
-       <h2>Restart / Exit</h2>
+       <h2>Opening applications / Closing windows</h2>
        <table>
                <tr>
-                       <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd></kbd> + <kbd>r</kbd>
-                       <td>restart i3 inplace
-
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>d</kbd>
+                       <td>open application launcher (dmenu)
                <tr>
-                       <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd></kbd> + <kbd>e</kbd>
-
-       </section><td>exit i3
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd> + <kbd>q</kbd>
+                       <td>kill a window
        </table>
-
+       </section>
 
        <section>
-       <h2>Floating</h2>
+       <h2>Restart / Exit</h2>
        <table>
                <tr>
-                       <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd></kbd> + <kbd></kbd>
-                       <td>toggle floating
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd> + <kbd>c</kbd>
+                        <td>reload the configuration file
+               <tr>
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd> + <kbd>r</kbd>
+                       <td>restart i3 inplace
 
                <tr>
-                       <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd></kbd>
-                       <td>drag floating
-       </table>
+                       <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd> + <kbd>e</kbd>
+                        <td>exit i3
        </section>
+       </table>
+
 
        <!-- footer -->
        <p id="copyright">
                <br />
                All rights reserved
                <br />
-               Designed by Zeus Panchenko
+               Designed by Zeus Panchenko, updated by Moritz Bandemer
        </p>
        <p id="licence">
                Permission is granted to copy, distribute and/or modify this document provided
index 720ff39475bd5da87eb1786c739e0e25ab8ef687..4dcf16708ba7b559ac85c60e0f833ec338023f13 100644 (file)
@@ -1,7 +1,7 @@
 i3 testsuite
 ============
-Michael Stapelberg <michael+i3@stapelberg.de>
-September 2011
+Michael Stapelberg <michael@i3wm.org>
+September 2012
 
 This document explains how the i3 testsuite works, how to use it and extend it.
 It is targeted at developers who not necessarily have been doing testing before
@@ -33,6 +33,19 @@ able to easily test if the feature is working correctly. Many developers will
 test manually if everything works. Having a testcase not only helps you with
 that, but it will also be useful for every future change.
 
+== Relevant documentation
+
+Apart from this document, you should also have a look at:
+
+1. The "Modern Perl" book, which can be found at
+   http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+2. The latest Perl documentation of the "i3test" (general testcase setup) and
+   "i3test::Test" (additional test instructions) modules:
+   http://build.i3wm.org/docs/lib-i3test.html respectively
+   http://build.i3wm.org/docs/lib-i3test-test.html
+3. The latest documentation on i3’s IPC interface:
+   http://build.i3wm.org/docs/ipc.html
+
 == Implementation
 
 For several reasons, the i3 testsuite has been implemented in Perl:
@@ -45,6 +58,8 @@ For several reasons, the i3 testsuite has been implemented in Perl:
 
 2. Perl is widely available and has a well-working package infrastructure.
 3. The author is familiar with Perl :).
+4. It is a good idea to use a different language for the tests than the
+   implementation itself.
 
 Please do not start programming language flamewars at this point.
 
index 853fc5e622b5c755ba4cef0d004c203d631621b3..e42a6acb64fa390ac4280ffae3090b4744072855 100644 (file)
@@ -1,7 +1,7 @@
 i3 User’s Guide
 ===============
-Michael Stapelberg <michael+i3@stapelberg.de>
-April 2012
+Michael Stapelberg <michael@i3wm.org>
+August 2012
 
 This document contains all the information you need to configure and use the i3
 window manager. If it does not, please contact us on IRC (preferred) or post your
@@ -61,16 +61,18 @@ windows.
 
 TODO: picture of the tree
 
-To split a window vertically, press +mod+v+. To split it horizontally, press
-+mod+h+.
+To split a window vertically, press +mod+v+ before you create the new window.
+To split it horizontally, press +mod+h+.
 
 === Changing the container layout
 
 A split container can have one of the following layouts:
 
-default::
+splith/splitv::
 Windows are sized so that every window gets an equal amount of space in the
-container.
+container. splith distributes the windows horizontally (windows are right next
+to each other), splitv distributes them vertically (windows are on top of each
+other).
 stacking::
 Only the focused window in the container is displayed. You get a list of
 windows at the top of the container.
@@ -78,8 +80,8 @@ tabbed::
 The same principle as +stacking+, but the list of windows at the top is only
 a single line which is vertically split.
 
-To switch modes, press +mod+e+ for default, +mod+s+ for stacking and
-+mod+w+ for tabbed.
+To switch modes, press +mod+e+ for splith/splitv (it toggles), +mod+s+ for
+stacking and +mod+w+ for tabbed.
 
 image:modes.png[Container modes]
 
@@ -196,20 +198,21 @@ image::tree-shot4.png["shot4",title="Two terminals on standard workspace"]
 
 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:
+orientation (horizontal, vertical or unspecified) and the orientation depends
+on the layout the container is in (vertical for splitv and stacking, horizontal
+for splith and tabbed). So, in our example with the workspace, the default
+layout of the workspace +Container+ is splith (most monitors are widescreen
+nowadays). If you change the layout to splitv (+mod+l+ in the default config)
+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
+An interesting new feature of i3 since version 4 is the ability to split anything:
+Let’s assume you have two terminals on a workspace (with splith layout, that is
+horizontal orientation), focus is on the right terminal. Now you want to open
+another terminal window below the current one. If you would just open a new
+terminal window, it would show up to the right due to the splith layout.
+Instead, press +mod+v+ to split the container with the splitv layout (to
 open a +Horizontal Split Container+, use +mod+h+). Now you can open a new
 terminal and it will open below the current one:
 
@@ -291,11 +294,21 @@ a # and can only be used at the beginning of a line:
 # This is a comment
 -------------------
 
+[[fonts]]
+
 === Fonts
 
-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.
+i3 has support for both X core fonts and FreeType fonts (through Pango) to
+render window titles.
+
+To generate an X core font description, you can use +xfontsel(1)+. To see
+special characters (Unicode), you need to use a font which supports the
+ISO-10646 encoding.
+
+A FreeType font description is composed by a font family, a style, a weight,
+a variant, a stretch and a size.
+FreeType fonts support right-to-left rendering and contain often more
+Unicode glyphs than X core fonts.
 
 If i3 cannot open the configured font, it will output an error in the logfile
 and fall back to a working font.
@@ -303,11 +316,13 @@ and fall back to a working font.
 *Syntax*:
 ------------------------------
 font <X core font description>
+font xft:<a FreeType font description>
 ------------------------------
 
 *Examples*:
 --------------------------------------------------------------
 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+font xft:DejaVu Sans Mono 10
 --------------------------------------------------------------
 
 [[keybindings]]
@@ -333,10 +348,15 @@ your bindings in the same physical location on the keyboard, use keycodes.
 If you don’t switch layouts, and want a clean and simple config file, use
 keysyms.
 
+Some tools (such as +import+ or +xdotool+) might be unable to run upon a
+KeyPress event, because the keyboard/pointer is still grabbed. For these
+situations, the +--release+ flag can be used, which will execute the command
+after the keys have been released.
+
 *Syntax*:
 ----------------------------------
-bindsym [Modifiers+]keysym command
-bindcode [Modifiers+]keycode command
+bindsym [--release] [Modifiers+]keysym command
+bindcode [--release] [Modifiers+]keycode command
 ----------------------------------
 
 *Examples*:
@@ -348,7 +368,13 @@ bindsym mod+f fullscreen
 bindsym mod+Shift+r restart
 
 # Notebook-specific hotkeys
-bindcode 214 exec /home/michael/toggle_beamer.sh
+bindcode 214 exec --no-startup-id /home/michael/toggle_beamer.sh
+
+# Simulate ctrl+v upon pressing $mod+x
+bindsym --release $mod+x exec --no-startup-id xdotool key --clearmodifiers ctrl+v
+
+# Take a screenshot upon pressing $mod+x (select an area)
+bindsym --release $mod+x exec --no-startup-id import /tmp/latest-screenshot.png
 --------------------------------
 
 Available Modifiers:
@@ -465,6 +491,22 @@ new_window <normal|1pixel|none>
 new_window 1pixel
 ---------------------
 
+=== Hiding vertical borders
+
+You can hide vertical borders adjacent to the screen edges using
++hide_edge_borders+. This is useful if you are using scrollbars, or do not want
+to waste even two pixels in displayspace. Default is none.
+
+*Syntax*:
+----------------------------
+hide_edge_borders <none|vertical|horizontal|both>
+----------------------------
+
+*Example*:
+----------------------
+hide_edge_borders vertical
+----------------------
+
 === Arbitrary commands for specific windows (for_window)
 
 With the +for_window+ command, you can let i3 execute any command when it
@@ -573,6 +615,22 @@ logfile first (see http://i3wm.org/docs/debugging.html). It includes more
 details about the matching process and the window’s actual class, instance and
 title when starting up.
 
+Note that if you want to start an application just once on a specific
+workspace, but you don’t want to assign all instances of it permanently, you
+can make use of i3’s startup-notification support (see <<exec>>) in your config
+file in the following way:
+
+*Start iceweasel on workspace 3 (once)*:
+-------------------------------------------------------------------------------
+# Start iceweasel on workspace 3, then switch back to workspace 1
+# (Being a command-line utility, i3-msg does not support startup notifications,
+#  hence the exec --no-startup-id.)
+# (Starting iceweasel with i3’s exec command is important in order to make i3
+#  create a startup notification context, without which the iceweasel window(s)
+#  cannot be matched onto the workspace on which the command was started.)
+exec --no-startup-id i3-msg 'workspace 3; exec iceweasel; workspace 1'
+-------------------------------------------------------------------------------
+
 === Automatically starting applications on i3 startup
 
 By using the +exec+ keyword outside a keybinding, you can configure
@@ -1010,8 +1068,7 @@ xrandr --output <output> --primary
 
 === Font
 
-Specifies the font (again, X core font, not Xft, just like in i3) to be used in
-the bar.
+Specifies the font to be used in the bar. See <<fonts>>.
 
 *Syntax*:
 ---------------------
@@ -1022,6 +1079,7 @@ font <font>
 --------------------------------------------------------------
 bar {
     font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+    font xft:DejaVu Sans Mono 10
 }
 --------------------------------------------------------------
 
@@ -1190,13 +1248,15 @@ cursor for 60 seconds.
 === 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).
+can contain multiple windows. Depending on the layout of the split container,
+new windows get placed to the right of the current one (splith) or new windows
+get placed below the current one (splitv).
 
 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).
+orientation will be changed (if it does not have more than one window). Use
++layout toggle split+ to change the layout of any split container from splitv
+to splith or vice-versa.
 
 *Syntax*:
 ---------------------------
@@ -1211,19 +1271,32 @@ bindsym mod+h split horizontal
 
 === Manipulating layout
 
-Use +layout default+, +layout stacking+ or +layout tabbed+ to change the
-current container layout to default, stacking or tabbed layout, respectively.
+Use +layout toggle split+, +layout stacking+ or +layout tabbed+ to change the
+current container layout to splith/splitv, 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+):
 
+*Syntax*:
+--------------
+layout <tabbed|stacking>
+layout toggle [split|all]
+--------------
+
 *Examples*:
 --------------
 bindsym mod+s layout stacking
-bindsym mod+l layout default
+bindsym mod+l layout toggle split
 bindsym mod+w layout tabbed
 
+# Toggle between stacking/tabbed/split:
+bindsym mod+x layout toggle
+
+# Toggle between stacking/tabbed/splith/splitv:
+bindsym mod+x layout toggle all
+
 # Toggle fullscreen
 bindsym mod+f fullscreen
 
@@ -1313,8 +1386,9 @@ You can also switch to the next and previous workspace with the commands
 workspace 1, 3, 4 and 9 and you want to cycle through them with a single key
 combination. To restrict those to the current output, use +workspace
 next_on_output+ and +workspace prev_on_output+. Similarly, you can use +move
-container to workspace next+ and +move container to workspace prev+ to move a
-container to the next/previous workspace.
+container to workspace next+, +move container to workspace prev+ to move a
+container to the next/previous workspace and +move container to workspace current+
+(the last one makes sense only when used with criteria).
 
 [[back_and_forth]]
 To switch back to the previously focused workspace, use +workspace
@@ -1330,6 +1404,18 @@ you can use the +move workspace to output+ command followed by the name of the
 target output. You may also use +left+, +right+, +up+, +down+ instead of the
 xrandr output name to move to the next output in the specified direction.
 
+*Syntax*:
+-----------------------------------
+workspace <next|prev|next_on_output|prev_on_output>
+workspace back_and_forth
+workspace <name>
+workspace number <number>
+
+move [window|container] [to] workspace <name>
+move [window|container] [to] workspace number <number>
+move [window|container] [to] workspace <prev|next|current>
+-----------------------------------
+
 *Examples*:
 -------------------------
 bindsym mod+1 workspace 1
@@ -1345,6 +1431,9 @@ bindsym mod+b workspace back_and_forth
 
 # move the whole workspace to the next output
 bindsym mod+x move workspace to output right
+
+# move firefox to current workspace
+bindsym mod+F1 [class="Firefox"] move workspace current
 -------------------------
 
 ==== Named workspaces
index 993c64fcaa570dcc73d229ef4bc6130223078369..01cbe462ec64d05c7a51c2d80da8e7b710c5820c 100755 (executable)
@@ -152,7 +152,7 @@ for my $state (@keys) {
         $cmd =~ s/[^(]+\(//;
         $cmd =~ s/\)$//;
         $cmd = ", $cmd" if length($cmd) > 0;
-        say $callfh qq|           printf("$fmt\\n"$cmd);|;
+        say $callfh qq|           fprintf(stderr, "$fmt\\n"$cmd);|;
         say $callfh '#endif';
         say $callfh "             state = $next_state;";
         say $callfh "             break;";
index 75d4684f8bcd56f919a3957f54b23646ed398706..d5ac18c665df6fecd4a4c172575d6e5578312ba1 100644 (file)
@@ -1,47 +1,10 @@
-# Default value so one can compile i3-input standalone
-TOPDIR=..
+all:
+       $(MAKE) -C .. i3-config-wizard/i3-config-wizard
 
-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)
-
-CPPFLAGS += -I$(TOPDIR)/include
-
-# Depend on the specific file (.c for each .o) and on all headers
-%.o: %.c ${HEADERS}
-       echo "[i3-config-wizard] CC $<"
-       $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
-
-all: i3-config-wizard
-
-i3-config-wizard: $(TOPDIR)/libi3/libi3.a cfgparse.y.o cfgparse.yy.o ${FILES}
-       echo "[i3-config-wizard] LINK i3-config-wizard"
-       $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS)
-
-$(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c
-       $(MAKE) -C $(TOPDIR)/libi3
-
-cfgparse.yy.o: cfgparse.l cfgparse.y.o ${HEADERS}
-       echo "[i3-config-wizard] LEX $<"
-       $(FLEX) -i -o$(@:.o=.c) $<
-       $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(@:.o=.c)
-
-cfgparse.y.o: cfgparse.y ${HEADERS}
-       echo "[i3-config-wizard] YACC $<"
-       $(BISON) --debug --verbose -b $(basename $< .y) -d $<
-       $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(<:.y=.tab.c)
-
-
-install: all
-       echo "[i3-config-wizard] INSTALL"
-       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
-       $(INSTALL) -m 0755 i3-config-wizard $(DESTDIR)$(PREFIX)/bin/
+install:
+       $(MAKE) -C .. install-i3-config-wizard
 
 clean:
-       rm -f *.o cfgparse.tab.{c,h} cfgparse.output cfgparse.yy.c
+       $(MAKE) -C .. clean-i3-config-wizard
 
-distclean: clean
-       rm -f i3-config-wizard
+.PHONY: all install clean
index e5a86868e9139f477f9d772e037ac1763f205936..17c3953c449a6c470810ed76fbea6dd5a61d71ab 100644 (file)
@@ -39,6 +39,8 @@ extern FILE *yyin;
 YY_BUFFER_STATE yy_scan_string(const char *);
 
 static struct context *context;
+static xcb_connection_t *conn;
+static xcb_key_symbols_t *keysyms;
 
 /* We don’t need yydebug for now, as we got decent error messages using
  * yyerror(). Should you ever want to extend the parser, it might be handy
@@ -67,6 +69,13 @@ int yywrap() {
 char *rewrite_binding(const char *bindingline) {
     char *result = NULL;
 
+    conn = xcb_connect(NULL, NULL);
+    if (conn == NULL || xcb_connection_has_error(conn)) {
+        fprintf(stderr, "Cannot open display\n");
+        exit(1);
+    }
+    keysyms = xcb_key_symbols_alloc(conn);
+
     context = calloc(sizeof(struct context), 1);
 
     yy_scan_string(bindingline);
@@ -81,6 +90,8 @@ char *rewrite_binding(const char *bindingline) {
     if (context->line_copy)
         free(context->line_copy);
     free(context);
+    xcb_key_symbols_free(keysyms);
+    xcb_disconnect(conn);
 
     return result;
 }
@@ -101,6 +112,28 @@ static char *modifier_to_string(int modifiers) {
     else return strdup("");
 }
 
+/*
+ * Returns true if sym is bound to any key except for 'except_keycode' on the
+ * first four layers (normal, shift, mode_switch, mode_switch + shift).
+ *
+ */
+static bool keysym_used_on_other_key(KeySym sym, xcb_keycode_t except_keycode) {
+    xcb_keycode_t i,
+                  min_keycode = xcb_get_setup(conn)->min_keycode,
+                  max_keycode = xcb_get_setup(conn)->max_keycode;
+
+    for (i = min_keycode; i && i <= max_keycode; i++) {
+        if (i == except_keycode)
+            continue;
+        for (int level = 0; level < 4; level++) {
+            if (xcb_key_symbols_get_keysym(keysyms, i, level) != sym)
+                continue;
+            return true;
+        }
+    }
+    return false;
+}
+
 %}
 
 %error-verbose
@@ -139,11 +172,22 @@ bindcode:
              * different key than the upper-case one (unlikely for letters, but
              * more likely for special characters). */
             level = 1;
+
+            /* Try to use the keysym on the first level (lower-case). In case
+             * this doesn’t make it ambiguous (think of a keyboard layout
+             * having '1' on two different keys, but '!' only on keycode 10),
+             * we’ll stick with the keysym of the first level.
+             *
+             * This reduces a lot of confusion for users who switch keyboard
+             * layouts from qwerty to qwertz or other slight variations of
+             * qwerty (yes, that happens quite often). */
+            KeySym sym = XkbKeycodeToKeysym(dpy, $4, 0, 0);
+            if (!keysym_used_on_other_key(sym, $4))
+                level = 0;
         }
         KeySym sym = XkbKeycodeToKeysym(dpy, $4, 0, level);
         char *str = XKeysymToString(sym);
         char *modifiers = modifier_to_string($<number>3);
-        // TODO: modifier to string
         sasprintf(&(context->result), "bindsym %s%s %s\n", modifiers, str, $<string>6);
         free(modifiers);
     }
diff --git a/i3-config-wizard/i3-config-wizard.mk b/i3-config-wizard/i3-config-wizard.mk
new file mode 100644 (file)
index 0000000..1598cfe
--- /dev/null
@@ -0,0 +1,37 @@
+ALL_TARGETS += i3-config-wizard/i3-config-wizard
+INSTALL_TARGETS += install-i3-config-wizard
+CLEAN_TARGETS += clean-i3-config-wizard
+
+i3_config_wizard_SOURCES_GENERATED  = i3-config-wizard/cfgparse.tab.c i3-config-wizard/cfgparse.yy.c
+i3_config_wizard_SOURCES           := $(filter-out $(i3_config_wizard_SOURCES_GENERATED),$(wildcard i3-config-wizard/*.c))
+i3_config_wizard_HEADERS           := $(wildcard i3-config-wizard/*.h)
+i3_config_wizard_CFLAGS             = $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(X11_CFLAGS) $(PANGO_CFLAGS)
+i3_config_wizard_LIBS               = $(XCB_LIBS) $(XCB_KBD_LIBS) $(X11_LIBS) $(PANGO_LIBS)
+
+i3_config_wizard_OBJECTS := $(i3_config_wizard_SOURCES_GENERATED:.c=.o) $(i3_config_wizard_SOURCES:.c=.o)
+
+
+i3-config-wizard/%.o: i3-config-wizard/%.c $(i3_config_wizard_HEADERS)
+       echo "[i3-config-wizard] CC $<"
+       $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_config_wizard_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $<
+
+i3-config-wizard/cfgparse.yy.c: i3-config-wizard/cfgparse.l i3-config-wizard/cfgparse.tab.o $(i3_config_wizard_HEADERS)
+       echo "[i3-config-wizard] LEX $<"
+       $(FLEX) -i -o $@ $<
+
+i3-config-wizard/cfgparse.tab.c: i3-config-wizard/cfgparse.y $(i3_config_wizard_HEADERS)
+       echo "[i3-config-wizard] YACC $<"
+       $(BISON) --debug --verbose -b $(basename $< .y) -d $<
+
+i3-config-wizard/i3-config-wizard: libi3.a $(i3_config_wizard_OBJECTS)
+       echo "[i3-config-wizard] Link i3-config-wizard"
+       $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(LIBS) $(i3_config_wizard_LIBS)
+
+install-i3-config-wizard: i3-config-wizard/i3-config-wizard
+       echo "[i3-config-wizard] Install"
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
+       $(INSTALL) -m 0755 i3-config-wizard/i3-config-wizard $(DESTDIR)$(PREFIX)/bin/
+
+clean-i3-config-wizard:
+       echo "[i3-config-wizard] Clean"
+       rm -f $(i3_config_wizard_OBJECTS) $(i3_config_wizard_SOURCES_GENERATED) i3-config-wizard/i3-config-wizard i3-config-wizard/cfgparse.{output,dot}
index be042673f2c53a02a1bbe8855fa13f38ced2b9c0..679c5e6db5206eefb951c7d115099cb7fbf86044 100644 (file)
@@ -69,6 +69,7 @@ enum { MOD_Mod1, MOD_Mod4 } modifier = MOD_Mod4;
 static char *config_path;
 static uint32_t xcb_numlock_mask;
 xcb_connection_t *conn;
+xcb_screen_t *root_screen;
 static xcb_get_modifier_mapping_reply_t *modmap_reply;
 static i3Font font;
 static i3Font bold_font;
@@ -83,6 +84,26 @@ Display *dpy;
 char *rewrite_binding(const char *bindingline);
 static void finish();
 
+/*
+ * Having verboselog() and errorlog() is necessary when using libi3.
+ *
+ */
+void verboselog(char *fmt, ...) {
+    va_list args;
+
+    va_start(args, fmt);
+    vfprintf(stdout, fmt, args);
+    va_end(args);
+}
+
+void errorlog(char *fmt, ...) {
+    va_list args;
+
+    va_start(args, fmt);
+    vfprintf(stderr, fmt, args);
+    va_end(args);
+}
+
 /*
  * This function resolves ~ in pathnames.
  * It may resolve wildcards in the first part of the path, but if no match
@@ -128,7 +149,7 @@ static int handle_expose() {
     set_font(&font);
 
 #define txt(x, row, text) \
-    draw_text(text, strlen(text), false, pixmap, pixmap_gc,\
+    draw_text_ascii(text, pixmap, pixmap_gc,\
             x, (row - 1) * font.height + 4, 300 - x * 2)
 
     if (current_step == STEP_WELCOME) {
@@ -456,7 +477,7 @@ int main(int argc, char *argv[]) {
     #include "atoms.xmacro"
     #undef xmacro
 
-    xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
+    root_screen = xcb_aux_get_screen(conn, screens);
     root = root_screen->root;
 
     if (!(modmap_reply = xcb_get_modifier_mapping_reply(conn, modmap_cookie, NULL)))
index 18076e519113d9772b325652b20153b3ff127dc8..2b9e4fe939b44cf0131db7d19733c6e1be5ef7a5 100644 (file)
@@ -1,32 +1,10 @@
-# Default value so one can compile i3-dump-log standalone
-TOPDIR=..
+all:
+       $(MAKE) -C .. i3-dump-log/i3-dump-log
 
-include $(TOPDIR)/common.mk
-
-CFLAGS += -I$(TOPDIR)/include
-
-# Depend on the object files of all source-files in src/*.c and on all header files
-FILES=$(patsubst %.c,%.o,$(wildcard *.c))
-HEADERS=$(wildcard *.h)
-
-# Depend on the specific file (.c for each .o) and on all headers
-%.o: %.c ${HEADERS}
-       echo "[i3-dump-log] CC $<"
-       $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
-
-all: i3-dump-log
-
-i3-dump-log: ${FILES}
-       echo "[i3-dump-log] LINK i3-dump-log"
-       $(CC) $(LDFLAGS) -o i3-dump-log ${FILES} $(LIBS)
-
-install: all
-       echo "[i3-dump-log] INSTALL"
-       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
-       $(INSTALL) -m 0755 i3-dump-log $(DESTDIR)$(PREFIX)/bin/
+install:
+       $(MAKE) -C .. install-i3-dump-log
 
 clean:
-       rm -f *.o
+       $(MAKE) -C .. clean-i3-dump-log
 
-distclean: clean
-       rm -f i3-dump-log
+.PHONY: all install clean
diff --git a/i3-dump-log/i3-dump-log.mk b/i3-dump-log/i3-dump-log.mk
new file mode 100644 (file)
index 0000000..bbce356
--- /dev/null
@@ -0,0 +1,28 @@
+ALL_TARGETS += i3-dump-log/i3-dump-log
+INSTALL_TARGETS += install-i3-dump-log
+CLEAN_TARGETS += clean-i3-dump-log
+
+i3_dump_log_SOURCES := $(wildcard i3-dump-log/*.c)
+i3_dump_log_HEADERS := $(wildcard i3-dump-log/*.h)
+i3_dump_log_CFLAGS   = $(XCB_CFLAGS) $(PANGO_CFLAGS)
+i3_dump_log_LIBS     = $(XCB_LIBS)
+
+i3_dump_log_OBJECTS := $(i3_dump_log_SOURCES:.c=.o)
+
+
+i3-dump-log/%.o: i3-dump-log/%.c $(i3_dump_log_HEADERS)
+       echo "[i3-dump-log] CC $<"
+       $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_dump_log_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $<
+
+i3-dump-log/i3-dump-log: libi3.a $(i3_dump_log_OBJECTS)
+       echo "[i3-dump-log] Link i3-dump-log"
+       $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(LIBS) $(i3_dump_log_LIBS)
+
+install-i3-dump-log: i3-dump-log/i3-dump-log
+       echo "[i3-dump-log] Install"
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
+       $(INSTALL) -m 0755 i3-dump-log/i3-dump-log $(DESTDIR)$(PREFIX)/bin/
+
+clean-i3-dump-log:
+       echo "[i3-dump-log] Clean"
+       rm -f $(i3_dump_log_OBJECTS) i3-dump-log/i3-dump-log
index 6b500ea3d6053724850eff4cee8aabb7aaf8088d..48465c75718e696a44779638a5198f98c1e993dd 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * i3-dump-log/main.c: Dumps the i3 SHM log to stdout.
  *
 #include "shmlog.h"
 #include <i3/ipc.h>
 
+static uint32_t offset_next_write,
+                wrap_count;
+
+static i3_shmlog_header *header;
+static char *logbuffer,
+            *walk;
+
+static int check_for_wrap(void) {
+    if (wrap_count == header->wrap_count)
+        return 0;
+
+    /* The log wrapped. Print the remaining content and reset walk to the top
+     * of the log. */
+    wrap_count = header->wrap_count;
+    write(STDOUT_FILENO, walk, ((logbuffer + header->offset_last_wrap) - walk));
+    walk = logbuffer + sizeof(i3_shmlog_header);
+    return 1;
+}
+
+static void print_till_end(void) {
+    check_for_wrap();
+    int n = write(STDOUT_FILENO, walk, ((logbuffer + header->offset_next_write) - walk));
+    if (n > 0) {
+        walk += n;
+    }
+}
+
 int main(int argc, char *argv[]) {
     int o, option_index = 0;
-    bool verbose = false;
+    bool verbose = false,
+         follow = false;
 
     static struct option long_options[] = {
         {"version", no_argument, 0, 'v'},
         {"verbose", no_argument, 0, 'V'},
+        {"follow", no_argument, 0, 'f'},
         {"help", no_argument, 0, 'h'},
         {0, 0, 0, 0}
     };
 
-    char *options_string = "s:vVh";
+    char *options_string = "s:vfVh";
 
     while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
         if (o == 'v') {
@@ -47,9 +76,11 @@ int main(int argc, char *argv[]) {
             return 0;
         } else if (o == 'V') {
             verbose = true;
+        } else if (o == 'f') {
+            follow = true;
         } else if (o == 'h') {
             printf("i3-dump-log " I3_VERSION "\n");
-            printf("i3-dump-log [-s <socket>]\n");
+            printf("i3-dump-log [-f] [-s <socket>]\n");
             return 0;
         }
     }
@@ -90,45 +121,61 @@ int main(int argc, char *argv[]) {
 
     struct stat statbuf;
 
-    int logbuffer_shm = shm_open(shmname, O_RDONLY, 0);
+    /* NB: While we must never read, we need O_RDWR for the pthread condvar. */
+    int logbuffer_shm = shm_open(shmname, O_RDWR, 0);
     if (logbuffer_shm == -1)
         err(EXIT_FAILURE, "Could not shm_open SHM segment for the i3 log (%s)", shmname);
 
     if (fstat(logbuffer_shm, &statbuf) != 0)
         err(EXIT_FAILURE, "stat(%s)", shmname);
 
-    char *logbuffer = mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, logbuffer_shm, 0);
+    /* NB: While we must never read, we need O_RDWR for the pthread condvar. */
+    logbuffer = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, logbuffer_shm, 0);
     if (logbuffer == MAP_FAILED)
         err(EXIT_FAILURE, "Could not mmap SHM segment for the i3 log");
 
-    i3_shmlog_header *header = (i3_shmlog_header*)logbuffer;
+    header = (i3_shmlog_header*)logbuffer;
 
     if (verbose)
         printf("next_write = %d, last_wrap = %d, logbuffer_size = %d, shmname = %s\n",
                header->offset_next_write, header->offset_last_wrap, header->size, shmname);
-    int chars;
-    char *walk = logbuffer + header->offset_next_write;
-    /* Skip the first line, it very likely is mangled. Not a problem, though,
-     * the log is chatty enough to have plenty lines left. */
-    while (*walk != '\0')
-        walk++;
+    walk = logbuffer + header->offset_next_write;
 
-    /* Print the oldest log lines. We use printf("%s") to stop on \0. */
-    while (walk < (logbuffer + header->offset_last_wrap)) {
-        chars = printf("%s", walk);
-        /* Shortcut: If there are two consecutive \0 bytes, this part of the
-         * buffer was never touched. To not call printf() for every byte of the
-         * buffer, we directly exit the loop. */
-        if (*walk == '\0' && *(walk+1) == '\0')
-            break;
-        walk += (chars > 0 ? chars : 1);
+    /* We first need to print old content in case there was at least one
+     * wrapping already. */
+
+    if (*walk != '\0') {
+        /* In case there was a write to the buffer already, skip the first
+         * old line, it very likely is mangled. Not a problem, though, the log
+         * is chatty enough to have plenty lines left. */
+        while (*walk != '\n')
+            walk++;
+        walk++;
     }
 
+    /* In case there was no wrapping, this is a no-op, otherwise it prints the
+     * old lines. */
+    wrap_count = 0;
+    check_for_wrap();
+
     /* Then start from the beginning and print the newer lines */
     walk = logbuffer + sizeof(i3_shmlog_header);
-    while (walk < (logbuffer + header->offset_next_write)) {
-        chars = printf("%s", walk);
-        walk += (chars > 0 ? chars : 1);
+    print_till_end();
+
+    if (follow) {
+        /* Since pthread_cond_wait() expects a mutex, we need to provide one.
+         * To not lock i3 (that’s bad, mhkay?) we just define one outside of
+         * the shared memory. */
+        pthread_mutex_t dummy_mutex = PTHREAD_MUTEX_INITIALIZER;
+        pthread_mutex_lock(&dummy_mutex);
+        while (1) {
+            pthread_cond_wait(&(header->condvar), &dummy_mutex);
+            /* If this was not a spurious wakeup, print the new lines. */
+            if (header->offset_next_write != offset_next_write) {
+                offset_next_write = header->offset_next_write;
+                print_till_end();
+            }
+        }
     }
 
     return 0;
index 493df784d7e1aacfe5c549fa6dd60dbbf8c09240..e1c8eae5603f9eb5cabaf8494fa5aa19d41b7808 100644 (file)
@@ -1,35 +1,10 @@
-# Default value so one can compile i3-input standalone
-TOPDIR=..
+all:
+       $(MAKE) -C .. i3-input/i3-input
 
-include $(TOPDIR)/common.mk
-
-CPPFLAGS += -I$(TOPDIR)/include
-
-# Depend on the object files of all source-files in src/*.c and on all header files
-FILES=$(patsubst %.c,%.o,$(wildcard *.c))
-HEADERS=$(wildcard *.h)
-
-# Depend on the specific file (.c for each .o) and on all headers
-%.o: %.c ${HEADERS}
-       echo "[i3-input] CC $<"
-       $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
-
-all: i3-input
-
-i3-input: $(TOPDIR)/libi3/libi3.a ${FILES}
-       echo "[i3-input] LINK i3-input"
-       $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS)
-
-$(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c
-       $(MAKE) -C $(TOPDIR)/libi3
-
-install: all
-       echo "[i3-input] INSTALL"
-       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
-       $(INSTALL) -m 0755 i3-input $(DESTDIR)$(PREFIX)/bin/
+install:
+       $(MAKE) -C .. install-i3-input
 
 clean:
-       rm -f *.o
+       $(MAKE) -C .. clean-i3-input
 
-distclean: clean
-       rm -f i3-input
+.PHONY: all install clean
diff --git a/i3-input/i3-input.mk b/i3-input/i3-input.mk
new file mode 100644 (file)
index 0000000..03f4e0a
--- /dev/null
@@ -0,0 +1,28 @@
+ALL_TARGETS += i3-input/i3-input
+INSTALL_TARGETS += install-i3-input
+CLEAN_TARGETS += clean-i3-input
+
+i3_input_SOURCES := $(wildcard i3-input/*.c)
+i3_input_HEADERS := $(wildcard i3-input/*.h)
+i3_input_CFLAGS   = $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(PANGO_CFLAGS)
+i3_input_LIBS     = $(XCB_LIBS) $(XCB_KBD_LIBS) $(PANGO_LIBS)
+
+i3_input_OBJECTS := $(i3_input_SOURCES:.c=.o)
+
+
+i3-input/%.o: i3-input/%.c $(i3_input_HEADERS)
+       echo "[i3-input] CC $<"
+       $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_input_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $<
+
+i3-input/i3-input: libi3.a $(i3_input_OBJECTS)
+       echo "[i3-input] Link i3-input"
+       $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(LIBS) $(i3_input_LIBS)
+
+install-i3-input: i3-input/i3-input
+       echo "[i3-input] Install"
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
+       $(INSTALL) -m 0755 i3-input/i3-input $(DESTDIR)$(PREFIX)/bin/
+
+clean-i3-input:
+       echo "[i3-input] Clean"
+       rm -f $(i3_input_OBJECTS) i3-input/i3-input
index b570952397ccfd5f1e55ef75d1eb625f3d0f89e4..b3e626e617c8f0e11f51dcbf863aedbcd507a680 100644 (file)
@@ -45,15 +45,36 @@ static bool modeswitch_active = false;
 static xcb_window_t win;
 static xcb_pixmap_t pixmap;
 static xcb_gcontext_t pixmap_gc;
-static char *glyphs_ucs[512];
+static xcb_char2b_t glyphs_ucs[512];
 static char *glyphs_utf8[512];
 static int input_position;
 static i3Font font;
-static char *prompt;
-static size_t prompt_len;
+static i3String *prompt;
+static int prompt_offset = 0;
 static int limit;
 xcb_window_t root;
 xcb_connection_t *conn;
+xcb_screen_t *root_screen;
+
+/*
+ * Having verboselog() and errorlog() is necessary when using libi3.
+ *
+ */
+void verboselog(char *fmt, ...) {
+    va_list args;
+
+    va_start(args, fmt);
+    vfprintf(stdout, fmt, args);
+    va_end(args);
+}
+
+void errorlog(char *fmt, ...) {
+    va_list args;
+
+    va_start(args, fmt);
+    vfprintf(stderr, fmt, args);
+    va_end(args);
+}
 
 /*
  * Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for
@@ -96,25 +117,21 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t
     /* restore font color */
     set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
 
-    /* draw the text */
-    uint8_t *con = concat_strings(glyphs_ucs, input_position);
-    char *full_text = (char*)con;
+    /* draw the prompt … */
     if (prompt != NULL) {
-        full_text = malloc((prompt_len + input_position) * 2 + 1);
-        if (full_text == NULL)
-            err(EXIT_FAILURE, "malloc() failed\n");
-        memcpy(full_text, prompt, prompt_len * 2);
-        memcpy(full_text + (prompt_len * 2), con, input_position * 2);
+        draw_text(prompt, pixmap, pixmap_gc, 4, 4, 492);
+    }
+    /* … and the text */
+    if (input_position > 0)
+    {
+        i3String *input = i3string_from_ucs2(glyphs_ucs, input_position);
+        draw_text(input, pixmap, pixmap_gc, prompt_offset + 4, 4, 492);
+        i3string_free(input);
     }
-    if (input_position + prompt_len != 0)
-        draw_text(full_text, input_position + prompt_len, true, pixmap, pixmap_gc, 4, 4, 492);
 
     /* Copy the contents of the pixmap to the real window */
     xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, font.height + 8);
     xcb_flush(conn);
-    free(con);
-    if (prompt != NULL)
-        free(full_text);
 
     return 1;
 }
@@ -126,16 +143,6 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t
 static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event) {
     printf("releasing %d, state raw = %d\n", event->detail, event->state);
 
-    /* See the documentation of xcb_key_symbols_get_keysym for this one.
-     * Basically: We get either col 0 or col 1, depending on whether shift is
-     * pressed. */
-    int col = (event->state & XCB_MOD_MASK_SHIFT);
-
-    /* If modeswitch is currently active, we need to look in group 2 or 3,
-     * respectively. */
-    if (modeswitch_active)
-        col += 2;
-
     xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state);
     if (sym == XK_Mode_switch) {
         printf("Mode switch disabled\n");
@@ -226,7 +233,6 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
             return 1;
 
         input_position--;
-        free(glyphs_ucs[input_position]);
         free(glyphs_utf8[input_position]);
 
         handle_expose(NULL, conn, NULL);
@@ -257,18 +263,16 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
         return 1;
     }
 
-    /* store the UCS into a string */
-    uint8_t inp[3] = {(ucs & 0xFF00) >> 8, (ucs & 0xFF), 0};
+    xcb_char2b_t inp;
+    inp.byte1 = ( ucs & 0xff00 ) >> 2;
+    inp.byte2 = ( ucs & 0x00ff ) >> 0;
 
-    printf("inp[0] = %02x, inp[1] = %02x, inp[2] = %02x\n", inp[0], inp[1], inp[2]);
+    printf("inp.byte1 = %02x, inp.byte2 = %02x\n", inp.byte1, inp.byte2);
     /* convert it to UTF-8 */
-    char *out = convert_ucs2_to_utf8((xcb_char2b_t*)inp, 1);
+    char *out = convert_ucs2_to_utf8(&inp, 1);
     printf("converted to %s\n", out);
 
-    glyphs_ucs[input_position] = malloc(3 * sizeof(uint8_t));
-    if (glyphs_ucs[input_position] == NULL)
-        err(EXIT_FAILURE, "malloc() failed\n");
-    memcpy(glyphs_ucs[input_position], inp, 3);
+    glyphs_ucs[input_position] = inp;
     glyphs_utf8[input_position] = out;
     input_position++;
 
@@ -318,8 +322,8 @@ int main(int argc, char *argv[]) {
                 limit = atoi(optarg);
                 break;
             case 'P':
-                FREE(prompt);
-                prompt = strdup(optarg);
+                i3string_free(prompt);
+                prompt = i3string_from_utf8(optarg);
                 break;
             case 'f':
                 FREE(pattern);
@@ -349,15 +353,12 @@ int main(int argc, char *argv[]) {
 
     sockfd = ipc_connect(socket_path);
 
-    if (prompt != NULL)
-        prompt = (char*)convert_utf8_to_ucs2(prompt, &prompt_len);
-
     int screens;
     conn = xcb_connect(NULL, &screens);
     if (!conn || xcb_connection_has_error(conn))
         die("Cannot open display\n");
 
-    xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
+    root_screen = xcb_aux_get_screen(conn, screens);
     root = root_screen->root;
 
     symbols = xcb_key_symbols_alloc(conn);
@@ -365,6 +366,9 @@ int main(int argc, char *argv[]) {
     font = load_font(pattern, true);
     set_font(&font);
 
+    if (prompt != NULL)
+        prompt_offset = predict_text_width(prompt);
+
     /* Open an input window */
     win = xcb_generate_id(conn);
     xcb_create_window(
index c8ff41c933bf368efad8677c7983aea2d2fafaff..ae5bf4deb02811e715885dc622c741dcf114a6cd 100755 (executable)
@@ -202,7 +202,7 @@ sub convert_command {
     # simple replacements
     my @replace = (
         qr/^s/ => 'layout stacking',
-        qr/^d/ => 'layout default',
+        qr/^d/ => 'layout toggle split',
         qr/^T/ => 'layout tabbed',
         qr/^f($|[^go])/ => 'fullscreen',
         qr/^fg/ => 'fullscreen global',
index 617df93279b774de32383538528183353cf391d0..fedb31e7902a1611f4d0f43f843dd3c6b1b25826 100644 (file)
@@ -1,32 +1,10 @@
-# Default value so one can compile i3-msg standalone
-TOPDIR=..
+all:
+       $(MAKE) -C .. i3-msg/i3-msg
 
-include $(TOPDIR)/common.mk
-
-CFLAGS += -I$(TOPDIR)/include
-
-# Depend on the object files of all source-files in src/*.c and on all header files
-FILES=$(patsubst %.c,%.o,$(wildcard *.c))
-HEADERS=$(wildcard *.h)
-
-# Depend on the specific file (.c for each .o) and on all headers
-%.o: %.c ${HEADERS}
-       echo "[i3-msg] CC $<"
-       $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
-
-all: i3-msg
-
-i3-msg: ${FILES}
-       echo "[i3-msg] LINK i3-msg"
-       $(CC) $(LDFLAGS) -o i3-msg ${FILES} $(LIBS)
-
-install: all
-       echo "[i3-msg] INSTALL"
-       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
-       $(INSTALL) -m 0755 i3-msg $(DESTDIR)$(PREFIX)/bin/
+install:
+       $(MAKE) -C .. install-i3-msg
 
 clean:
-       rm -f *.o
+       $(MAKE) -C .. clean-i3-msg
 
-distclean: clean
-       rm -f i3-msg
+.PHONY: all install clean
diff --git a/i3-msg/i3-msg.mk b/i3-msg/i3-msg.mk
new file mode 100644 (file)
index 0000000..fef9581
--- /dev/null
@@ -0,0 +1,28 @@
+ALL_TARGETS += i3-msg/i3-msg
+INSTALL_TARGETS += install-i3-msg
+CLEAN_TARGETS += clean-i3-msg
+
+i3_msg_SOURCES := $(wildcard i3-msg/*.c)
+i3_msg_HEADERS := $(wildcard i3-msg/*.h)
+i3_msg_CFLAGS   = $(XCB_CFLAGS) $(PANGO_CFLAGS)
+i3_msg_LIBS     = $(XCB_LIBS)
+
+i3_msg_OBJECTS := $(i3_msg_SOURCES:.c=.o)
+
+
+i3-msg/%.o: i3-msg/%.c $(i3_msg_HEADERS)
+       echo "[i3-msg] CC $<"
+       $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_msg_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $<
+
+i3-msg/i3-msg: libi3.a $(i3_msg_OBJECTS)
+       echo "[i3-msg] Link i3-msg"
+       $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(LIBS) $(i3_msg_LIBS)
+
+install-i3-msg: i3-msg/i3-msg
+       echo "[i3-msg] Install"
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
+       $(INSTALL) -m 0755 i3-msg/i3-msg $(DESTDIR)$(PREFIX)/bin/
+
+clean-i3-msg:
+       echo "[i3-msg] Clean"
+       rm -f $(i3_msg_OBJECTS) i3-msg/i3-msg
index ccf6e10f276a54ce8d2716b593db7d9895890442..a04e6690e67fa4a177df81dd86cbab5dd87748a4 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * i3-msg/main.c: Utility which sends messages to a running i3-instance using
  * IPC via UNIX domain sockets.
@@ -73,9 +73,11 @@ int main(int argc, char *argv[]) {
                 message_type = I3_IPC_MESSAGE_TYPE_GET_MARKS;
             else if (strcasecmp(optarg, "get_bar_config") == 0)
                 message_type = I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG;
+            else if (strcasecmp(optarg, "get_version") == 0)
+                message_type = I3_IPC_MESSAGE_TYPE_GET_VERSION;
             else {
                 printf("Unknown message type\n");
-                printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config\n");
+                printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_version\n");
                 exit(EXIT_FAILURE);
             }
         } else if (o == 'q') {
index 05a5b911c3f40224d13c33f61012b6f8fa0638ee..12748e218e477b564bcdd9e3c3166316f7ed77ff 100644 (file)
@@ -1,35 +1,10 @@
-# Default value so one can compile i3-nagbar standalone
-TOPDIR=..
+all:
+       $(MAKE) -C .. i3-nagbar/i3-nagbar
 
-include $(TOPDIR)/common.mk
-
-CPPFLAGS += -I$(TOPDIR)/include
-
-# Depend on the object files of all source-files in src/*.c and on all header files
-FILES=$(patsubst %.c,%.o,$(wildcard *.c))
-HEADERS=$(wildcard *.h)
-
-# Depend on the specific file (.c for each .o) and on all headers
-%.o: %.c ${HEADERS}
-       echo "[i3-nagbar] CC $<"
-       $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
-
-all: i3-nagbar
-
-i3-nagbar: $(TOPDIR)/libi3/libi3.a ${FILES}
-       echo "[i3-nagbar] LINK i3-nagbar"
-       $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS)
-
-$(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c
-       $(MAKE) -C $(TOPDIR)/libi3
-
-install: all
-       echo "[i3-nagbar] INSTALL"
-       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
-       $(INSTALL) -m 0755 i3-nagbar $(DESTDIR)$(PREFIX)/bin/
+install:
+       $(MAKE) -C .. install-i3-nagbar
 
 clean:
-       rm -f *.o
+       $(MAKE) -C .. clean-i3-nagbar
 
-distclean: clean
-       rm -f i3-nagbar
+.PHONY: all install clean
diff --git a/i3-nagbar/i3-nagbar.mk b/i3-nagbar/i3-nagbar.mk
new file mode 100644 (file)
index 0000000..e54aa65
--- /dev/null
@@ -0,0 +1,28 @@
+ALL_TARGETS += i3-nagbar/i3-nagbar
+INSTALL_TARGETS += install-i3-nagbar
+CLEAN_TARGETS += clean-i3-nagbar
+
+i3_nagbar_SOURCES := $(wildcard i3-nagbar/*.c)
+i3_nagbar_HEADERS := $(wildcard i3-nagbar/*.h)
+i3_nagbar_CFLAGS   = $(XCB_CFLAGS) $(PANGO_CFLAGS)
+i3_nagbar_LIBS     = $(XCB_LIBS) $(PANGO_LIBS)
+
+i3_nagbar_OBJECTS := $(i3_nagbar_SOURCES:.c=.o)
+
+
+i3-nagbar/%.o: i3-nagbar/%.c $(i3_nagbar_HEADERS)
+       echo "[i3-nagbar] CC $<"
+       $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_nagbar_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $<
+
+i3-nagbar/i3-nagbar: libi3.a $(i3_nagbar_OBJECTS)
+       echo "[i3-nagbar] Link i3-nagbar"
+       $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(LIBS) $(i3_nagbar_LIBS)
+
+install-i3-nagbar: i3-nagbar/i3-nagbar
+       echo "[i3-nagbar] Install"
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
+       $(INSTALL) -m 0755 i3-nagbar/i3-nagbar $(DESTDIR)$(PREFIX)/bin/
+
+clean-i3-nagbar:
+       echo "[i3-nagbar] Clean"
+       rm -f $(i3_nagbar_OBJECTS) i3-nagbar/i3-nagbar
index 1dbd77369b28a184a07ccd7973f1e0443dc57e06..7aee191c5adf9b808c6653815207999d42ca9edc 100644 (file)
@@ -30,7 +30,7 @@
 #include "i3-nagbar.h"
 
 typedef struct {
-    char *label;
+    i3String *label;
     char *action;
     int16_t x;
     uint16_t width;
@@ -41,7 +41,7 @@ static xcb_pixmap_t pixmap;
 static xcb_gcontext_t pixmap_gc;
 static xcb_rectangle_t rect = { 0, 0, 600, 20 };
 static i3Font font;
-static char *prompt;
+static i3String *prompt;
 static button_t *buttons;
 static int buttoncnt;
 
@@ -54,6 +54,27 @@ static uint32_t color_text;              /* color of the text */
 
 xcb_window_t root;
 xcb_connection_t *conn;
+xcb_screen_t *root_screen;
+
+/*
+ * Having verboselog() and errorlog() is necessary when using libi3.
+ *
+ */
+void verboselog(char *fmt, ...) {
+    va_list args;
+
+    va_start(args, fmt);
+    vfprintf(stdout, fmt, args);
+    va_end(args);
+}
+
+void errorlog(char *fmt, ...) {
+    va_list args;
+
+    va_start(args, fmt);
+    vfprintf(stderr, fmt, args);
+    va_end(args);
+}
 
 /*
  * Starts the given application by passing it through a shell. We use double fork
@@ -132,7 +153,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
 
     /* restore font color */
     set_font_colors(pixmap_gc, color_text, color_background);
-    draw_text(prompt, strlen(prompt), false, pixmap, pixmap_gc,
+    draw_text(prompt, pixmap, pixmap_gc,
             4 + 4, 4 + 4, rect.width - 4 - 4);
 
     /* render close button */
@@ -159,7 +180,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
 
     values[0] = 1;
     set_font_colors(pixmap_gc, color_text, color_button_background);
-    draw_text("X", 1, false, pixmap, pixmap_gc, y - w - line_width + w / 2 - 4,
+    draw_text_ascii("X", pixmap, pixmap_gc, y - w - line_width + w / 2 - 4,
             4 + 4 - 1, rect.width - y + w + line_width - w / 2 + 4);
     y -= w;
 
@@ -190,7 +211,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
         values[0] = color_text;
         values[1] = color_button_background;
         set_font_colors(pixmap_gc, color_text, color_button_background);
-        draw_text(buttons[c].label, strlen(buttons[c].label), false, pixmap, pixmap_gc,
+        draw_text(buttons[c].label, pixmap, pixmap_gc,
                 y - w - line_width + 6, 4 + 3, rect.width - y + w + line_width - 6);
 
         y -= w;
@@ -216,7 +237,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
 }
 
 int main(int argc, char *argv[]) {
-    char *pattern = strdup("-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1");
+    char *pattern = sstrdup("-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1");
     int o, option_index = 0;
     enum { TYPE_ERROR = 0, TYPE_WARNING = 1 } bar_type = TYPE_ERROR;
 
@@ -232,7 +253,7 @@ int main(int argc, char *argv[]) {
 
     char *options_string = "b:f:m:t:vh";
 
-    prompt = strdup("Please do not run this program.");
+    prompt = i3string_from_utf8("Please do not run this program.");
 
     while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
         switch (o) {
@@ -241,11 +262,11 @@ int main(int argc, char *argv[]) {
                 return 0;
             case 'f':
                 FREE(pattern);
-                pattern = strdup(optarg);
+                pattern = sstrdup(optarg);
                 break;
             case 'm':
-                FREE(prompt);
-                prompt = strdup(optarg);
+                i3string_free(prompt);
+                prompt = i3string_from_utf8(optarg);
                 break;
             case 't':
                 bar_type = (strcasecmp(optarg, "warning") == 0 ? TYPE_WARNING : TYPE_ERROR);
@@ -256,10 +277,10 @@ int main(int argc, char *argv[]) {
                 return 0;
             case 'b':
                 buttons = realloc(buttons, sizeof(button_t) * (buttoncnt + 1));
-                buttons[buttoncnt].label = optarg;
+                buttons[buttoncnt].label = i3string_from_utf8(optarg);
                 buttons[buttoncnt].action = argv[optind];
                 printf("button with label *%s* and action *%s*\n",
-                        buttons[buttoncnt].label,
+                        i3string_as_utf8(buttons[buttoncnt].label),
                         buttons[buttoncnt].action);
                 buttoncnt++;
                 printf("now %d buttons\n", buttoncnt);
@@ -280,7 +301,7 @@ int main(int argc, char *argv[]) {
     #include "atoms.xmacro"
     #undef xmacro
 
-    xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
+    root_screen = xcb_aux_get_screen(conn, screens);
     root = root_screen->root;
 
     if (bar_type == TYPE_ERROR) {
@@ -432,5 +453,7 @@ int main(int argc, char *argv[]) {
         free(event);
     }
 
+    FREE(pattern);
+
     return 0;
 }
index a9975740864c07c51e2c18e1095f8eb43511ea65..fddefae10048f0f546e4b0404e32caeae0c7d1f4 100755 (executable)
@@ -8,7 +8,7 @@
 # Distributions/packagers should enhance this script with a
 # distribution-specific mechanism to find the preferred terminal emulator. On
 # Debian, there is the x-terminal-emulator symlink for example.
-for terminal in $TERMINAL urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm; do
+for terminal in $TERMINAL urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal; do
     if which $terminal > /dev/null 2>&1; then
         exec $terminal "$@"
     fi
index 1a457fcab01d57c1634bd59f906bb421cd7945b8..e45b31ba40576f3ae7ed768cf77a86c23b525c98 100644 (file)
--- a/i3.config
+++ b/i3.config
@@ -9,8 +9,14 @@
 # layout, use the i3-config-wizard
 #
 
-# font for window titles. ISO 10646 = Unicode
+# Font for window titles. Will also be used by the bar unless a different font
+# is used in the bar {} block below. ISO 10646 = Unicode
 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+# The font above is very space-efficient, that is, it looks good, sharp and
+# clear in small sizes. However, if you need a lot of unicode glyphs or
+# right-to-left text rendering, you should instead use pango for rendering and
+# chose an xft font, such as:
+# font xft:DejaVu Sans Mono 10
 
 # use Mouse+Mod1 to drag floating windows to their wanted position
 floating_modifier Mod1
@@ -57,10 +63,10 @@ bindsym Mod1+v split v
 # enter fullscreen mode for the focused container
 bindsym Mod1+f fullscreen
 
-# change container layout (stacked, tabbed, default)
+# change container layout (stacked, tabbed, toggle split)
 bindsym Mod1+s layout stacking
 bindsym Mod1+w layout tabbed
-bindsym Mod1+e layout default
+bindsym Mod1+e layout toggle split
 
 # toggle tiling / floating
 bindsym Mod1+Shift+space floating toggle
@@ -103,7 +109,7 @@ 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
+bindsym Mod1+Shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'"
 
 # resize window (you can also use the mouse for that)
 mode "resize" {
index e360fff9c2a2987ba9dcfb2a8a538eba9dee6c98..162660d3342e83ea7b33b5effa9e4ebb7b9bf1c9 100644 (file)
 
 set $mod Mod1
 
-# font for window titles. ISO 10646 = Unicode
+# Font for window titles. Will also be used by the bar unless a different font
+# is used in the bar {} block below. ISO 10646 = Unicode
 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+# The font above is very space-efficient, that is, it looks good, sharp and
+# clear in small sizes. However, if you need a lot of unicode glyphs or
+# right-to-left text rendering, you should instead use pango for rendering and
+# chose an xft font, such as:
+# font xft:DejaVu Sans Mono 10
 
 # Use Mouse+$mod to drag floating windows to their wanted position
 floating_modifier $mod
@@ -58,10 +64,10 @@ bindcode $mod+55 split v
 # enter fullscreen mode for the focused container
 bindcode $mod+41 fullscreen
 
-# change container layout (stacked, tabbed, default)
+# change container layout (stacked, tabbed, toggle split)
 bindcode $mod+39 layout stacking
 bindcode $mod+25 layout tabbed
-bindcode $mod+26 layout default
+bindcode $mod+26 layout toggle split
 
 # toggle tiling / floating
 bindcode $mod+Shift+65 floating toggle
@@ -104,7 +110,7 @@ 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
+bindcode $mod+Shift+26 exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'"
 
 # resize window (you can also use the mouse for that)
 mode "resize" {
index 79d0e7cd4e0e58f43d97bb1db4064503a5785ad9..cd23cd819cfe66711303b95c91e32e74f6bcc726 100644 (file)
@@ -1,42 +1,10 @@
-TOPDIR=..
+all:
+       $(MAKE) -C .. i3bar/i3bar
 
-include $(TOPDIR)/common.mk
-
-FILES:=$(wildcard src/*.c)
-FILES:=$(FILES:.c=.o)
-HEADERS:=$(wildcard include/*.h)
-
-CPPFLAGS += -I$(TOPDIR)/include
-
-all: i3bar doc
-
-i3bar: $(TOPDIR)/libi3/libi3.a ${FILES}
-       echo "[i3bar] LINK"
-       $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS)
-
-$(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c
-       $(MAKE) -C $(TOPDIR)/libi3
-
-doc:
-       echo ""
-       echo "[i3bar] SUBDIR doc"
-       $(MAKE) -C doc
-
-src/%.o: src/%.c ${HEADERS}
-       echo "[i3bar] CC $<"
-       $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
-
-install: all
-       echo "[i3bar] INSTALL"
-       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
-       $(INSTALL) -m 0755 i3bar $(DESTDIR)$(PREFIX)/bin
+install:
+       $(MAKE) -C .. install-i3bar
 
 clean:
-       rm -f src/*.o
-       $(MAKE) -C doc clean
-
-distclean: clean
-       rm -f i3bar
-       $(MAKE) -C doc distclean
+       $(MAKE) -C .. clean-i3bar
 
-.PHONY: install clean distclean doc
+.PHONY: all install clean
diff --git a/i3bar/doc/Makefile b/i3bar/doc/Makefile
deleted file mode 100644 (file)
index 6956675..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-all: i3bar.1
-
-i3bar.1: i3bar.man
-       echo "A2X i3bar"
-       a2x --no-xmllint -f manpage i3bar.man
-clean:
-       rm -f i3bar.xml i3bar.1 i3bar.html
-
-distclean: clean
diff --git a/i3bar/doc/i3bar.man b/i3bar/doc/i3bar.man
deleted file mode 100644 (file)
index dcf3022..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-i3bar(1)
-========
-Axel Wagner <mail+i3bar@merovius.de>
-v4.1, October 2011
-
-== NAME
-
-i3bar - xcb-based status- and workspace-bar
-
-== SYNOPSIS
-
-*i3bar* [*-s* 'sock_path'] [*-b* 'bar_id'] [*-v*] [*-h*]
-
-== WARNING
-
-i3bar will automatically be invoked by i3 for every 'bar' configuration block.
-
-Starting it manually is usually not what you want to do.
-
-You have been warned!
-
-== OPTIONS
-
-*-s, --socket* 'sock_path'::
-Overwrites the path to the i3 IPC socket.
-
-*-b, --bar_id* 'bar_id'::
-Specifies the bar ID for which to get the configuration from i3.
-
-*-v, --version*::
-Display version number and exit.
-
-*-h, --help*::
-Display a short help-message and exit
-
-== DESCRIPTION
-
-*i3bar* displays a bar at the bottom (or top) of your monitor(s) containing
-workspace switching buttons and a statusline generated by i3status(1) or
-similar. It is automatically invoked (and configured through) i3.
-
-i3bar does not support any color or other markups, so stdin should be plain
-utf8, one line at a time. If you use *i3status*(1), you therefore should
-specify 'output_format = none' in the general section of its config file.
-
-== ENVIRONMENT
-
-=== I3SOCK
-
-Used as a fallback for the i3 IPC socket path if neither the commandline
-contains an argument nor the I3_SOCKET_PATH property is set on the X11 root
-window.
-
-== EXAMPLES
-
-Nothing to see here, move along. As stated above, you should not run i3bar manually.
-
-Instead, see the i3 documentation, especially the User’s Guide.
-
-== SEE ALSO
-
-+i3status(1)+ or +conky(1)+ for programs generating a statusline.
-
-+dzen2(1)+ or +xmobar(1)+ for similar programs to i3bar.
-
-== AUTHORS
-
-Axel Wagner and contributors
diff --git a/i3bar/i3bar.mk b/i3bar/i3bar.mk
new file mode 100644 (file)
index 0000000..0678025
--- /dev/null
@@ -0,0 +1,28 @@
+ALL_TARGETS += i3bar/i3bar
+INSTALL_TARGETS += install-i3bar
+CLEAN_TARGETS += clean-i3bar
+
+i3bar_SOURCES := $(wildcard i3bar/src/*.c)
+i3bar_HEADERS := $(wildcard i3bar/include/*.h)
+i3bar_CFLAGS   = $(XCB_CFLAGS) $(X11_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS)
+i3bar_LIBS     = $(XCB_LIBS) $(X11_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS)
+
+i3bar_OBJECTS := $(i3bar_SOURCES:.c=.o)
+
+
+i3bar/src/%.o: i3bar/src/%.c $(i3bar_HEADERS)
+       echo "[i3bar] CC $<"
+       $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3bar_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -Ii3bar/include -c -o $@ $<
+
+i3bar/i3bar: libi3.a $(i3bar_OBJECTS)
+       echo "[i3bar] Link i3bar"
+       $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(LIBS) $(i3bar_LIBS)
+
+install-i3bar: i3bar/i3bar
+       echo "[i3bar] Install"
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
+       $(INSTALL) -m 0755 i3bar/i3bar $(DESTDIR)$(PREFIX)/bin/
+
+clean-i3bar:
+       echo "[i3bar] Clean"
+       rm -f $(i3bar_OBJECTS) i3bar/i3bar
index 24c7e4600a2878e97974861b6a84b4db2a65884e..c0b56a013aba1fb1efee566ed6e583bf421591c9 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
  *
  * child.c: Getting Input for the statusline
  *
@@ -24,25 +24,25 @@ void start_child(char *command);
  * kill()s the child-process (if any). Called when exit()ing.
  *
  */
-void kill_child_at_exit();
+void kill_child_at_exit(void);
 
 /*
  * kill()s the child-process (if any) and closes and
  * free()s the stdin- and sigchild-watchers
  *
  */
-void kill_child();
+void kill_child(void);
 
 /*
  * Sends a SIGSTOP to the child-process (if existent)
  *
  */
-void stop_child();
+void stop_child(void);
 
 /*
  * Sends a SIGCONT to the child-process (if existent)
  *
  */
-void cont_child();
+void cont_child(void);
 
 #endif
index 212b9dd1d8d55d887de02c2aa6f693a21b4d6b3e..6f8a7b2db8ae87b378e34ce19160e8a29c686ed6 100644 (file)
@@ -11,6 +11,7 @@
 #include <stdbool.h>
 #include <xcb/xcb.h>
 #include <xcb/xproto.h>
+#include "libi3.h"
 #include "queue.h"
 
 typedef struct rect_t rect;
@@ -29,15 +30,10 @@ struct rect_t {
 /* This data structure represents one JSON dictionary, multiple of these make
  * up one status line. */
 struct status_block {
-    char *full_text;
+    i3String *full_text;
 
     char *color;
 
-    /* full_text, but converted to UCS-2. This variable is only temporarily
-     * used in refresh_statusline(). */
-    xcb_char2b_t *ucs2_full_text;
-    size_t glyph_count_full_text;
-
     /* The amount of pixels necessary to render this block. This variable is
      * only temporarily used in refresh_statusline(). */
     uint32_t width;
@@ -56,5 +52,6 @@ TAILQ_HEAD(statusline_head, status_block) statusline_head;
 #include "xcb.h"
 #include "config.h"
 #include "libi3.h"
+#include "determine_json_version.h"
 
 #endif
diff --git a/i3bar/include/determine_json_version.h b/i3bar/include/determine_json_version.h
new file mode 100644 (file)
index 0000000..52c6f5d
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
+ *
+ * determine_json_version.c: Determines the JSON protocol version based on the
+ *                           first line of input from a child program.
+ *
+ */
+#ifndef DETERMINE_JSON_VERSION_H_
+#define DETERMINE_JSON_VERSION_H_
+
+#include <stdint.h>
+
+/*
+ * Determines the JSON i3bar protocol version from the given buffer. In case
+ * the buffer does not contain valid JSON, or no version field is found, this
+ * function returns -1. The amount of bytes consumed by parsing the header is
+ * returned in *consumed (if non-NULL).
+ *
+ * The return type is an int32_t to avoid machines with different sizes of
+ * 'int' to allow different values here. It’s highly unlikely we ever exceed
+ * even an int8_t, but still…
+ *
+ */
+int32_t determine_json_version(const unsigned char *buffer, int length, unsigned int *consumed);
+
+#endif
index a0c4970420a768ca4633689b7768d6b939219ecb..f20d45f0a12463be617551c5d30728a905cc7d94 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
  *
  * ipc.c: Communicating with i3
  *
@@ -23,7 +23,7 @@ int init_connection(const char *socket_path);
  * Destroy the connection to i3.
  *
  */
-void destroy_connection();
+void destroy_connection(void);
 
 /*
  * Sends a Message to i3.
@@ -36,6 +36,6 @@ int i3_send_msg(uint32_t type, const char* payload);
  * Subscribe to all the i3-events, we need
  *
  */
-void subscribe_events();
+void subscribe_events(void);
 
 #endif
index f9ddd54bd8dff82d15f1705598bd436af6d4210a..ad249786d4a805aef37baf943b2b13e04c5fb57a 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
  *
  * outputs.c: Maintaining the output-list
  *
@@ -29,7 +29,7 @@ void parse_outputs_json(char* json);
  * Initiate the output-list
  *
  */
-void init_outputs();
+void init_outputs(void);
 
 /*
  * Returns the output with the given name
index eb05ed0828ed7dc236da3e022d364c6d43a03d86..43c56c58d3e93794d6f56becc86ae128a73d84bb 100644 (file)
@@ -11,7 +11,9 @@
 #include "queue.h"
 
 /* Get the maximum/minimum of x and y */
+#undef MAX
 #define MAX(x,y) ((x) > (y) ? (x) : (y))
+#undef MIN
 #define MIN(x,y) ((x) < (y) ? (x) : (y))
 
 /* Securely free p */
     } \
 } while(0)
 
+/* We will include libi3.h which define its own version of ELOG.
+ * We want *our* version, so we undef the libi3 one. */
+#if defined(ELOG)
+#undef ELOG
+#endif
 #define ELOG(fmt, ...) do { \
     fprintf(stderr, "[%s:%d] ERROR: " fmt, __FILE__, __LINE__, ##__VA_ARGS__); \
 } while(0)
index dfd93d9b2c18dfe14d197aeebe2a4c829d83a8af..5fe1ba1efc4da6214e02681a98ede5ee206b9653 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
  *
  * workspaces.c: Maintaining the workspace-lists
  *
@@ -28,13 +28,11 @@ void parse_workspaces_json(char *json);
  * free() all workspace data-structures
  *
  */
-void free_workspaces();
+void free_workspaces(void);
 
 struct i3_ws {
     int                num;         /* The internal number of the ws */
-    char               *name;       /* The name (in utf8) of the ws */
-    xcb_char2b_t       *ucs2_name;  /* The name (in ucs2) of the ws */
-    int                name_glyphs; /* The length (in glyphs) of the name */
+    i3String           *name;       /* The name of the ws */
     int                name_width;  /* The rendered width of the name */
     bool               visible;     /* If the ws is currently visible on an output */
     bool               focused;     /* If the ws is currently focused */
index 9ed2149675d6e5d2975e064bbf425edb53d7afbf..6c7bc567e4b07d7385667f7476cfa91980f2f922 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
  *
  * xcb.c: Communicating with X
  *
@@ -69,13 +69,13 @@ void init_colors(const struct xcb_color_strings_t *colors);
  * Called once, before the program terminates.
  *
  */
-void clean_xcb();
+void clean_xcb(void);
 
 /*
  * Get the earlier requested atoms and save them in the prepared data-structure
  *
  */
-void get_atoms();
+void get_atoms(void);
 
 /*
  * Reparents all tray clients of the specified output to the root window. This
@@ -98,24 +98,24 @@ void destroy_window(i3_output *output);
  * Reallocate the statusline-buffer
  *
  */
-void realloc_sl_buffer();
+void realloc_sl_buffer(void);
 
 /*
  * Reconfigure all bars and create new for newly activated outputs
  *
  */
-void reconfig_windows();
+void reconfig_windows(void);
 
 /*
  * Render the bars, with buttons and statusline
  *
  */
-void draw_bars();
+void draw_bars(void);
 
 /*
  * Redraw the bars, i.e. simply copy the buffer to the barwindow
  *
  */
-void redraw_bars();
+void redraw_bars(void);
 
 #endif
index 12782caf130490755f6363f2abb9baf83f6042b8..058ddb7a1f31c0518b20ae4afc6e38a6c65db85b 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
  *
  * child.c: Getting Input for the statusline
  *
@@ -56,7 +56,7 @@ char *statusline_buffer = NULL;
  * Stop and free() the stdin- and sigchild-watchers
  *
  */
-void cleanup() {
+void cleanup(void) {
     if (stdin_io != NULL) {
         ev_io_stop(main_loop, stdin_io);
         FREE(stdin_io);
@@ -80,7 +80,7 @@ static int stdin_start_array(void *context) {
     struct status_block *first;
     while (!TAILQ_EMPTY(&statusline_head)) {
         first = TAILQ_FIRST(&statusline_head);
-        FREE(first->full_text);
+        I3STRING_FREE(first->full_text);
         FREE(first->color);
         TAILQ_REMOVE(&statusline_head, first, blocks);
         free(first);
@@ -116,7 +116,7 @@ static int stdin_string(void *context, const unsigned char *val, unsigned int le
 #endif
     parser_ctx *ctx = context;
     if (strcasecmp(ctx->last_map_key, "full_text") == 0) {
-        sasprintf(&(ctx->block.full_text), "%.*s", len, val);
+        ctx->block.full_text = i3string_from_utf8_with_length((const char *)val, len);
     }
     if (strcasecmp(ctx->last_map_key, "color") == 0) {
         sasprintf(&(ctx->block.color), "%.*s", len, val);
@@ -131,7 +131,7 @@ static int stdin_end_map(void *context) {
     /* Ensure we have a full_text set, so that when it is missing (or null),
      * i3bar doesn’t crash and the user gets an annoying message. */
     if (!new_block->full_text)
-        new_block->full_text = sstrdup("SPEC VIOLATION (null)");
+        new_block->full_text = i3string_from_utf8("SPEC VIOLATION (null)");
     TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
     return 1;
 }
@@ -140,7 +140,7 @@ static int stdin_end_array(void *context) {
     DLOG("dumping statusline:\n");
     struct status_block *current;
     TAILQ_FOREACH(current, &statusline_head, blocks) {
-        DLOG("full_text = %s\n", current->full_text);
+        DLOG("full_text = %s\n", i3string_as_utf8(current->full_text));
         DLOG("color = %s\n", current->color);
     }
     DLOG("end of dump\n");
@@ -192,19 +192,18 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
     if (first_line) {
         DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer);
         /* Detect whether this is JSON or plain text. */
-        plaintext = (strncasecmp((char*)buffer, "{\"version\":", strlen("{\"version\":")) != 0);
+        unsigned int consumed = 0;
+        /* At the moment, we don’t care for the version. This might change
+         * in the future, but for now, we just discard it. */
+        plaintext = (determine_json_version(buffer, buffer_len, &consumed) == -1);
         if (plaintext) {
             /* In case of plaintext, we just add a single block and change its
              * full_text pointer later. */
             struct status_block *new_block = scalloc(sizeof(struct status_block));
             TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
         } else {
-            /* At the moment, we don’t care for the version. This might change
-             * in the future, but for now, we just discard it. */
-            while (*json_input != '\n' && *json_input != '\0') {
-                json_input++;
-                rec--;
-            }
+            json_input += consumed;
+            rec -= consumed;
         }
         first_line = false;
     }
@@ -218,18 +217,18 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
             fprintf(stderr, "[i3bar] Could not parse JSON input (code %d): %.*s\n",
                     status, rec, json_input);
         }
-        free(buffer);
     } else {
         struct status_block *first = TAILQ_FIRST(&statusline_head);
         /* Clear the old buffer if any. */
-        FREE(first->full_text);
+        I3STRING_FREE(first->full_text);
         /* Remove the trailing newline and terminate the string at the same
          * time. */
         if (buffer[rec-1] == '\n' || buffer[rec-1] == '\r')
             buffer[rec-1] = '\0';
         else buffer[rec] = '\0';
-        first->full_text = (char*)buffer;
+        first->full_text = i3string_from_utf8((const char *)buffer);
     }
+    free(buffer);
     draw_bars();
 }
 
@@ -328,7 +327,7 @@ void start_child(char *command) {
  * kill()s the child-process (if any). Called when exit()ing.
  *
  */
-void kill_child_at_exit() {
+void kill_child_at_exit(void) {
     if (child_pid != 0) {
         kill(child_pid, SIGCONT);
         kill(child_pid, SIGTERM);
@@ -340,7 +339,7 @@ void kill_child_at_exit() {
  * free()s the stdin- and sigchild-watchers
  *
  */
-void kill_child() {
+void kill_child(void) {
     if (child_pid != 0) {
         kill(child_pid, SIGCONT);
         kill(child_pid, SIGTERM);
@@ -355,7 +354,7 @@ void kill_child() {
  * Sends a SIGSTOP to the child-process (if existent)
  *
  */
-void stop_child() {
+void stop_child(void) {
     if (child_pid != 0) {
         kill(child_pid, SIGSTOP);
     }
@@ -365,7 +364,7 @@ void stop_child() {
  * Sends a SIGCONT to the child-process (if existent)
  *
  */
-void cont_child() {
+void cont_child(void) {
     if (child_pid != 0) {
         kill(child_pid, SIGCONT);
     }
diff --git a/i3bar/src/determine_json_version.c b/i3bar/src/determine_json_version.c
new file mode 100644 (file)
index 0000000..abd4303
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
+ *
+ * determine_json_version.c: Determines the JSON protocol version based on the
+ *                           first line of input from a child program.
+ *
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <err.h>
+#include <ev.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <yajl/yajl_common.h>
+#include <yajl/yajl_parse.h>
+#include <yajl/yajl_version.h>
+
+static bool version_key;
+static int32_t version_number;
+
+#if YAJL_MAJOR >= 2
+static int version_integer(void *ctx, long long val) {
+#else
+static int version_integer(void *ctx, long val) {
+#endif
+    if (version_key)
+        version_number = (uint32_t)val;
+    return 1;
+}
+
+#if YAJL_MAJOR >= 2
+static int version_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) {
+#else
+static int version_map_key(void *ctx, const unsigned char *stringval, unsigned int stringlen) {
+#endif
+    version_key = (stringlen == strlen("version") &&
+                   strncmp((const char*)stringval, "version", strlen("version")) == 0);
+    return 1;
+}
+
+static yajl_callbacks version_callbacks = {
+    NULL, /* null */
+    NULL, /* boolean */
+    &version_integer,
+    NULL, /* double */
+    NULL, /* number */
+    NULL, /* string */
+    NULL, /* start_map */
+    &version_map_key,
+    NULL, /* end_map */
+    NULL, /* start_array */
+    NULL /* end_array */
+};
+
+/*
+ * Determines the JSON i3bar protocol version from the given buffer. In case
+ * the buffer does not contain valid JSON, or no version field is found, this
+ * function returns -1. The amount of bytes consumed by parsing the header is
+ * returned in *consumed (if non-NULL).
+ *
+ * The return type is an int32_t to avoid machines with different sizes of
+ * 'int' to allow different values here. It’s highly unlikely we ever exceed
+ * even an int8_t, but still…
+ *
+ */
+int32_t determine_json_version(const unsigned char *buffer, int length, unsigned int *consumed) {
+#if YAJL_MAJOR >= 2
+    yajl_handle handle = yajl_alloc(&version_callbacks, NULL, NULL);
+    /* Allow trailing garbage. yajl 1 always behaves that way anyways, but for
+     * yajl 2, we need to be explicit. */
+    yajl_config(handle, yajl_allow_trailing_garbage, 1);
+#else
+    yajl_parser_config parse_conf = { 0, 0 };
+
+    yajl_handle handle = yajl_alloc(&version_callbacks, &parse_conf, NULL, NULL);
+#endif
+
+    version_key = false;
+    version_number = -1;
+
+    yajl_status state = yajl_parse(handle, buffer, length);
+    if (state != yajl_status_ok) {
+        version_number = -1;
+        if (consumed != NULL)
+            *consumed = 0;
+    } else {
+        if (consumed != NULL)
+            *consumed = yajl_get_bytes_consumed(handle);
+    }
+
+    yajl_free(handle);
+
+    return version_number;
+}
index 41b8e151e88aad1fb1377e465136e7237b7b07b0..2cc80cf7fac320cdf60af546e94331c6abe56d92 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
  *
  * ipc.c: Communicating with i3
  *
@@ -286,7 +286,7 @@ int init_connection(const char *socket_path) {
 /*
  * Destroy the connection to i3.
  */
-void destroy_connection() {
+void destroy_connection(void) {
     close(i3_connection->fd);
     ev_io_stop(main_loop, i3_connection);
 }
@@ -295,7 +295,7 @@ void destroy_connection() {
  * Subscribe to all the i3-events, we need
  *
  */
-void subscribe_events() {
+void subscribe_events(void) {
     if (config.disable_ws) {
         i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\" ]");
     } else {
index e648e00eca044535645dce78d1328777d1a1c42e..ea6056470c5f05381fc4c1e9efffddb1e3abbb7b 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
  *
  */
 #include <stdio.h>
 
 #include "common.h"
 
+/*
+ * Having verboselog() and errorlog() is necessary when using libi3.
+ *
+ */
+void verboselog(char *fmt, ...) {
+    va_list args;
+
+    va_start(args, fmt);
+    vfprintf(stdout, fmt, args);
+    va_end(args);
+}
+
+void errorlog(char *fmt, ...) {
+    va_list args;
+
+    va_start(args, fmt);
+    vfprintf(stderr, fmt, args);
+    va_end(args);
+}
+
 /*
  * Glob path, i.e. expand ~
  *
index eabf4d7bb2195d4c240637ae15fbc9afc4aac6a1..db9867025d86f097e02a06a5c4a14be959b0d8c6 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
  *
  * outputs.c: Maintaining the output-list
  *
@@ -266,7 +266,7 @@ yajl_callbacks outputs_callbacks = {
  * Initiate the output-list
  *
  */
-void init_outputs() {
+void init_outputs(void) {
     outputs = smalloc(sizeof(struct outputs_head));
     SLIST_INIT(outputs);
 }
index 5df1899f33a408f4a8429a52acaa3a2b060c8f7a..5e01b98d8257e011166adcfe04d8f052a425b575 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
  *
  * workspaces.c: Maintaining the workspace-lists
  *
@@ -114,23 +114,16 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, unsigne
 
         if (!strcmp(params->cur_key, "name")) {
             /* Save the name */
-            params->workspaces_walk->name = smalloc(sizeof(const unsigned char) * (len + 1));
-            strncpy(params->workspaces_walk->name, (const char*) val, len);
-            params->workspaces_walk->name[len] = '\0';
-
-            /* Convert the name to ucs2, save its length in glyphs and calculate its rendered width */
-            size_t ucs2_len;
-            xcb_char2b_t *ucs2_name = (xcb_char2b_t*) convert_utf8_to_ucs2(params->workspaces_walk->name, &ucs2_len);
-            params->workspaces_walk->ucs2_name = ucs2_name;
-            params->workspaces_walk->name_glyphs = ucs2_len;
+            params->workspaces_walk->name = i3string_from_utf8_with_length((const char *)val, len);
+
+            /* Save its rendered width */
             params->workspaces_walk->name_width =
-                predict_text_width((char *)params->workspaces_walk->ucs2_name,
-                params->workspaces_walk->name_glyphs, true);
+                predict_text_width(params->workspaces_walk->name);
 
-            DLOG("Got Workspace %s, name_width: %d, glyphs: %d\n",
-                 params->workspaces_walk->name,
+            DLOG("Got Workspace %s, name_width: %d, glyphs: %zu\n",
+                 i3string_as_utf8(params->workspaces_walk->name),
                  params->workspaces_walk->name_width,
-                 params->workspaces_walk->name_glyphs);
+                 i3string_get_num_glyphs(params->workspaces_walk->name));
             FREE(params->cur_key);
 
             return 1;
@@ -269,7 +262,7 @@ void parse_workspaces_json(char *json) {
  * free() all workspace data-structures. Does not free() the heads of the tailqueues.
  *
  */
-void free_workspaces() {
+void free_workspaces(void) {
     i3_output *outputs_walk;
     if (outputs == NULL) {
         return;
@@ -279,8 +272,7 @@ void free_workspaces() {
     SLIST_FOREACH(outputs_walk, outputs, slist) {
         if (outputs_walk->workspaces != NULL && !TAILQ_EMPTY(outputs_walk->workspaces)) {
             TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) {
-                FREE(ws_walk->name);
-                FREE(ws_walk->ucs2_name);
+                I3STRING_FREE(ws_walk->name);
             }
             FREE_TAILQ(outputs_walk->workspaces, i3_ws);
         }
index 289d7d9ed96a5e759bafe8784c24edc7c14df154..861925b96925f1aea7687dddd4eee8b97342c023 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
  *
  * xcb.c: Communicating with X
  *
@@ -47,7 +47,7 @@ xcb_atom_t               atoms[NUM_ATOMS];
 /* Variables, that are the same for all functions at all times */
 xcb_connection_t *xcb_connection;
 int              screen;
-xcb_screen_t     *xcb_screen;
+xcb_screen_t     *root_screen;
 xcb_window_t     xcb_root;
 
 /* This is needed for integration with libi3 */
@@ -108,20 +108,18 @@ int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) {
  * Redraws the statusline to the buffer
  *
  */
-void refresh_statusline() {
+void refresh_statusline(void) {
     struct status_block *block;
 
     uint32_t old_statusline_width = statusline_width;
     statusline_width = 0;
 
-    /* Convert all blocks from UTF-8 to UCS-2 and predict the text width (in
-     * pixels). */
+    /* Predict the text width of all blocks (in pixels). */
     TAILQ_FOREACH(block, &statusline_head, blocks) {
-        if (strlen(block->full_text) == 0)
+        if (i3string_get_num_bytes(block->full_text) == 0)
             continue;
 
-        block->ucs2_full_text = (xcb_char2b_t*)convert_utf8_to_ucs2(block->full_text, &(block->glyph_count_full_text));
-        block->width = predict_text_width((char*)block->ucs2_full_text, block->glyph_count_full_text, true);
+        block->width = predict_text_width(block->full_text);
         /* If this is not the last block, add some pixels for a separator. */
         if (TAILQ_NEXT(block, blocks) != NULL)
             block->width += 9;
@@ -130,24 +128,23 @@ void refresh_statusline() {
 
     /* If the statusline is bigger than our screen we need to make sure that
      * the pixmap provides enough space, so re-allocate if the width grew */
-    if (statusline_width > xcb_screen->width_in_pixels &&
+    if (statusline_width > root_screen->width_in_pixels &&
         statusline_width > old_statusline_width)
         realloc_sl_buffer();
 
     /* Clear the statusline pixmap. */
-    xcb_rectangle_t rect = { 0, 0, xcb_screen->width_in_pixels, font.height };
+    xcb_rectangle_t rect = { 0, 0, root_screen->width_in_pixels, font.height };
     xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_clear, 1, &rect);
 
     /* Draw the text of each block. */
     uint32_t x = 0;
     TAILQ_FOREACH(block, &statusline_head, blocks) {
-        if (strlen(block->full_text) == 0)
+        if (i3string_get_num_bytes(block->full_text) == 0)
             continue;
 
         uint32_t colorpixel = (block->color ? get_colorpixel(block->color) : colors.bar_fg);
         set_font_colors(statusline_ctx, colorpixel, colors.bar_bg);
-        draw_text((char*)block->ucs2_full_text, block->glyph_count_full_text,
-                  true, statusline_pm, statusline_ctx, x, 0, block->width);
+        draw_text(block->full_text, statusline_pm, statusline_ctx, x, 0, block->width);
         x += block->width;
 
         if (TAILQ_NEXT(block, blocks) != NULL) {
@@ -157,8 +154,6 @@ void refresh_statusline() {
                           statusline_ctx, 2,
                           (xcb_point_t[]){ { x - 5, 2 }, { x - 5, font.height - 2 } });
         }
-
-        FREE(block->ucs2_full_text);
     }
 }
 
@@ -166,7 +161,7 @@ void refresh_statusline() {
  * Hides all bars (unmaps them)
  *
  */
-void hide_bars() {
+void hide_bars(void) {
     if (!config.hide_on_modifier) {
         return;
     }
@@ -185,7 +180,7 @@ void hide_bars() {
  * Unhides all bars (maps them)
  *
  */
-void unhide_bars() {
+void unhide_bars(void) {
     if (!config.hide_on_modifier) {
         return;
     }
@@ -326,7 +321,8 @@ void handle_button(xcb_button_press_event_t *event) {
      * buffer, then we copy character by character. */
     int num_quotes = 0;
     size_t namelen = 0;
-    for (char *walk = cur_ws->name; *walk != '\0'; walk++) {
+    const char *utf8_name = i3string_as_utf8(cur_ws->name);
+    for (const char *walk = utf8_name; *walk != '\0'; walk++) {
         if (*walk == '"')
             num_quotes++;
         /* While we’re looping through the name anyway, we can save one
@@ -341,11 +337,11 @@ void handle_button(xcb_button_press_event_t *event) {
     for (inpos = 0, outpos = strlen("workspace \"");
          inpos < namelen;
          inpos++, outpos++) {
-        if (cur_ws->name[inpos] == '"') {
+        if (utf8_name[inpos] == '"') {
             buffer[outpos] = '\\';
             outpos++;
         }
-        buffer[outpos] = cur_ws->name[inpos];
+        buffer[outpos] = utf8_name[inpos];
     }
     buffer[outpos] = '"';
     i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, buffer);
@@ -357,7 +353,7 @@ void handle_button(xcb_button_press_event_t *event) {
  * new tray client or removing an old one.
  *
  */
-static void configure_trayclients() {
+static void configure_trayclients(void) {
     trayclient *trayclient;
     i3_output *output;
     SLIST_FOREACH(output, outputs, slist) {
@@ -828,8 +824,8 @@ char *init_xcb_early() {
     #define ATOM_DO(name) atom_cookies[name] = xcb_intern_atom(xcb_connection, 0, strlen(#name), #name);
     #include "xcb_atoms.def"
 
-    xcb_screen = xcb_aux_get_screen(xcb_connection, screen);
-    xcb_root = xcb_screen->root;
+    root_screen = xcb_aux_get_screen(xcb_connection, screen);
+    xcb_root = root_screen->root;
 
     /* We draw the statusline to a seperate pixmap, because it looks the same on all bars and
      * this way, we can choose to crop it */
@@ -852,11 +848,11 @@ char *init_xcb_early() {
 
     statusline_pm = xcb_generate_id(xcb_connection);
     xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection,
-                                                               xcb_screen->root_depth,
+                                                               root_screen->root_depth,
                                                                statusline_pm,
                                                                xcb_root,
-                                                               xcb_screen->width_in_pixels,
-                                                               xcb_screen->height_in_pixels);
+                                                               root_screen->width_in_pixels,
+                                                               root_screen->height_in_pixels);
 
 
     /* The various Watchers to communicate with xcb */
@@ -970,7 +966,7 @@ void init_xcb_late(char *fontname) {
  * atom. Afterwards, tray clients will send ClientMessages to our window.
  *
  */
-void init_tray() {
+void init_tray(void) {
     DLOG("Initializing system tray functionality\n");
     /* request the tray manager atom for the X11 display we are running on */
     char atomname[strlen("_NET_SYSTEM_TRAY_S") + 11];
@@ -984,14 +980,14 @@ void init_tray() {
     uint32_t selmask = XCB_CW_OVERRIDE_REDIRECT;
     uint32_t selval[] = { 1 };
     xcb_create_window(xcb_connection,
-                      xcb_screen->root_depth,
+                      root_screen->root_depth,
                       selwin,
                       xcb_root,
                       -1, -1,
                       1, 1,
                       1,
                       XCB_WINDOW_CLASS_INPUT_OUTPUT,
-                      xcb_screen->root_visual,
+                      root_screen->root_visual,
                       selmask,
                       selval);
 
@@ -1059,7 +1055,7 @@ void init_tray() {
  * Called once, before the program terminates.
  *
  */
-void clean_xcb() {
+void clean_xcb(void) {
     i3_output *o_walk;
     free_workspaces();
     SLIST_FOREACH(o_walk, outputs, slist) {
@@ -1087,7 +1083,7 @@ void clean_xcb() {
  * Get the earlier requested atoms and save them in the prepared data structure
  *
  */
-void get_atoms() {
+void get_atoms(void) {
     xcb_intern_atom_reply_t *reply;
     #define ATOM_DO(name) reply = xcb_intern_atom_reply(xcb_connection, atom_cookies[name], NULL); \
         if (reply == NULL) { \
@@ -1149,17 +1145,17 @@ void destroy_window(i3_output *output) {
  * Reallocate the statusline-buffer
  *
  */
-void realloc_sl_buffer() {
-    DLOG("Re-allocating statusline-buffer, statusline_width = %d, xcb_screen->width_in_pixels = %d\n",
-         statusline_width, xcb_screen->width_in_pixels);
+void realloc_sl_buffer(void) {
+    DLOG("Re-allocating statusline-buffer, statusline_width = %d, root_screen->width_in_pixels = %d\n",
+         statusline_width, root_screen->width_in_pixels);
     xcb_free_pixmap(xcb_connection, statusline_pm);
     statusline_pm = xcb_generate_id(xcb_connection);
     xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection,
-                                                               xcb_screen->root_depth,
+                                                               root_screen->root_depth,
                                                                statusline_pm,
                                                                xcb_root,
-                                                               MAX(xcb_screen->width_in_pixels, statusline_width),
-                                                               xcb_screen->height_in_pixels);
+                                                               MAX(root_screen->width_in_pixels, statusline_width),
+                                                               root_screen->height_in_pixels);
 
     uint32_t mask = XCB_GC_FOREGROUND;
     uint32_t vals[2] = { colors.bar_bg, colors.bar_bg };
@@ -1193,7 +1189,7 @@ void realloc_sl_buffer() {
  * Reconfigure all bars and create new bars for recently activated outputs
  *
  */
-void reconfig_windows() {
+void reconfig_windows(void) {
     uint32_t mask;
     uint32_t values[5];
     static bool tray_configured = false;
@@ -1229,20 +1225,20 @@ void reconfig_windows() {
                 values[2] |= XCB_EVENT_MASK_BUTTON_PRESS;
             }
             xcb_void_cookie_t win_cookie = xcb_create_window_checked(xcb_connection,
-                                                                     xcb_screen->root_depth,
+                                                                     root_screen->root_depth,
                                                                      walk->bar,
                                                                      xcb_root,
                                                                      walk->rect.x, walk->rect.y + walk->rect.h - font.height - 6,
                                                                      walk->rect.w, font.height + 6,
                                                                      1,
                                                                      XCB_WINDOW_CLASS_INPUT_OUTPUT,
-                                                                     xcb_screen->root_visual,
+                                                                     root_screen->root_visual,
                                                                      mask,
                                                                      values);
 
             /* The double-buffer we use to render stuff off-screen */
             xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection,
-                                                                    xcb_screen->root_depth,
+                                                                    root_screen->root_depth,
                                                                     walk->buffer,
                                                                     walk->bar,
                                                                     walk->rect.w,
@@ -1382,7 +1378,7 @@ void reconfig_windows() {
 
             DLOG("Recreating buffer for output %s", walk->name);
             xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection,
-                                                                    xcb_screen->root_depth,
+                                                                    root_screen->root_depth,
                                                                     walk->buffer,
                                                                     walk->bar,
                                                                     walk->rect.w,
@@ -1402,7 +1398,7 @@ void reconfig_windows() {
  * Render the bars, with buttons and statusline
  *
  */
-void draw_bars() {
+void draw_bars(void) {
     DLOG("Drawing Bars...\n");
     int i = 0;
 
@@ -1464,8 +1460,11 @@ void draw_bars() {
         }
 
         i3_ws *ws_walk;
+        static char *last_urgent_ws = NULL;
+        bool has_urgent = false, walks_away = true;
+
         TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) {
-            DLOG("Drawing Button for WS %s at x = %d, len = %d\n", ws_walk->name, i, ws_walk->name_width);
+            DLOG("Drawing Button for WS %s at x = %d, len = %d\n", i3string_as_utf8(ws_walk->name), i, ws_walk->name_width);
             uint32_t fg_color = colors.inactive_ws_fg;
             uint32_t bg_color = colors.inactive_ws_bg;
             uint32_t border_color = colors.inactive_ws_border;
@@ -1478,13 +1477,20 @@ void draw_bars() {
                     fg_color = colors.focus_ws_fg;
                     bg_color = colors.focus_ws_bg;
                     border_color = colors.focus_ws_border;
+                    if (last_urgent_ws && strcmp(i3string_as_utf8(ws_walk->name), last_urgent_ws) == 0)
+                        walks_away = false;
                 }
             }
             if (ws_walk->urgent) {
-                DLOG("WS %s is urgent!\n", ws_walk->name);
+                DLOG("WS %s is urgent!\n", i3string_as_utf8(ws_walk->name));
                 fg_color = colors.urgent_ws_fg;
                 bg_color = colors.urgent_ws_bg;
                 border_color = colors.urgent_ws_border;
+                has_urgent = true;
+                if (!ws_walk->focused) {
+                    FREE(last_urgent_ws);
+                    last_urgent_ws = sstrdup(i3string_as_utf8(ws_walk->name));
+                }
                 /* The urgent-hint should get noticed, so we unhide the bars shortly */
                 unhide_bars();
             }
@@ -1512,11 +1518,15 @@ void draw_bars() {
                                     1,
                                     &rect);
             set_font_colors(outputs_walk->bargc, fg_color, bg_color);
-            draw_text((char*)ws_walk->ucs2_name, ws_walk->name_glyphs, true,
-                    outputs_walk->buffer, outputs_walk->bargc, i + 5, 2, ws_walk->name_width);
+            draw_text(ws_walk->name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 2, ws_walk->name_width);
             i += 10 + ws_walk->name_width + 1;
         }
 
+        if (!has_urgent && !mod_pressed && walks_away) {
+            FREE(last_urgent_ws);
+            hide_bars();
+        }
+
         i = 0;
     }
 
@@ -1527,7 +1537,7 @@ void draw_bars() {
  * Redraw the bars, i.e. simply copy the buffer to the barwindow
  *
  */
-void redraw_bars() {
+void redraw_bars(void) {
     i3_output *outputs_walk;
     SLIST_FOREACH(outputs_walk, outputs, slist) {
         if (!outputs_walk->active) {
index 11eaaba49d7f5a48dda5ca4729e6b3ccb4f5a375..b83b9f4e07df2c876a7c0e216a25dd5988822bae 100644 (file)
@@ -54,6 +54,7 @@
 #include "i3.h"
 #include "x.h"
 #include "click.h"
+#include "key_press.h"
 #include "floating.h"
 #include "config.h"
 #include "handlers.h"
@@ -79,5 +80,6 @@
 #include "commands.h"
 #include "commands_parser.h"
 #include "fake_outputs.h"
+#include "display_version.h"
 
 #endif
index b907f41e9ca2f67f60a87e794a72c02da0acfd9a..af60b9660453d3eb93c58e66f31d691702420c09 100644 (file)
@@ -27,3 +27,4 @@ xmacro(I3_SOCKET_PATH)
 xmacro(I3_CONFIG_PATH)
 xmacro(I3_SYNC)
 xmacro(I3_SHMLOG_PATH)
+xmacro(I3_PID)
index 85057d1919e9d31a171598d115d5491eabb07263..37ee98d9c6d797886b1500b5110b4da7300605c7 100644 (file)
@@ -200,11 +200,17 @@ void cmd_fullscreen(I3_CMD, char *fullscreen_mode);
 void cmd_move_direction(I3_CMD, char *direction, char *move_px);
 
 /**
- * Implementation of 'layout default|stacked|stacking|tabbed'.
+ * Implementation of 'layout default|stacked|stacking|tabbed|splitv|splith'.
  *
  */
 void cmd_layout(I3_CMD, char *layout_str);
 
+/**
+ * Implementation of 'layout toggle [all|split]'.
+ *
+ */
+void cmd_layout_toggle(I3_CMD, char *toggle_mode);
+
 /**
  * Implementaiton of 'exit'.
  *
index b14c477e53de6fb864b8d1dc06e29c1b36510fd3..20e83df935bdf7501ec0da3a8c7918245d8f36a1 100644 (file)
@@ -221,6 +221,12 @@ Con *con_descend_direction(Con *con, direction_t direction);
  */
 Rect con_border_style_rect(Con *con);
 
+/**
+ * Returns adjacent borders of the window. We need this if hide_edge_borders is
+ * enabled.
+ */
+adjacent_t con_adjacent_borders(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.
@@ -248,6 +254,15 @@ void con_set_border_style(Con *con, int border_style);
  */
 void con_set_layout(Con *con, int layout);
 
+/**
+ * This function toggles the layout of a given container. toggle_mode can be
+ * either 'default' (toggle only between stacked/tabbed/last_split_layout),
+ * 'split' (toggle only between splitv/splith) or 'all' (toggle between all
+ * layouts).
+ *
+ */
+void con_toggle_layout(Con *con, const char *toggle_mode);
+
 /**
  * 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
@@ -255,4 +270,27 @@ void con_set_layout(Con *con, int layout);
  */
 Rect con_minimum_size(Con *con);
 
+/**
+ * Returns true if changing the focus to con would be allowed considering
+ * the fullscreen focus constraints. Specifically, if a fullscreen container or
+ * any of its descendants is focused, this function returns true if and only if
+ * focusing con would mean that focus would still be visible on screen, i.e.,
+ * the newly focused container would not be obscured by a fullscreen container.
+ *
+ * In the simplest case, if a fullscreen container or any of its descendants is
+ * fullscreen, this functions returns true if con is the fullscreen container
+ * itself or any of its descendants, as this means focus wouldn't escape the
+ * boundaries of the fullscreen container.
+ *
+ * In case the fullscreen container is of type CF_OUTPUT, this function returns
+ * true if con is on a different workspace, as focus wouldn't be obscured by
+ * the fullscreen container that is constrained to a different workspace.
+ *
+ * Note that this same logic can be applied to moving containers. If a
+ * container can be focused under the fullscreen focus constraints, it can also
+ * become a parent or sibling to the currently focused container.
+ *
+ */
+bool con_fullscreen_permits_focusing(Con *con);
+
 #endif
index 310f8b02e6b7aa8a9e61d9c0aec3952a3270c69e..1a48016a386ef635d8232afda17638ba112f867c 100644 (file)
@@ -108,6 +108,12 @@ struct Config {
      * It is not planned to add any different focus models. */
     bool disable_focus_follows_mouse;
 
+    /** Remove borders if they are adjacent to the screen edge.
+     * This is useful if you are reaching scrollbar on the edge of the
+     * screen or do not want to waste a single pixel of displayspace.
+     * By default, this is disabled. */
+    adjacent_t hide_edge_borders;
+
     /** By default, a workspace bar is drawn at the bottom of the screen.
      * If you want to have a more fancy bar, it is recommended to replace
      * the whole bar by dzen2, for example using the i3-wsbar script which
@@ -306,7 +312,7 @@ void switch_mode(const char *new_mode);
  * or NULL if no such binding exists.
  *
  */
-Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode);
+Binding *get_binding(uint16_t modifiers, bool key_release, xcb_keycode_t keycode);
 
 /**
  * Kills the configerror i3-nagbar process, if any.
index f4ed9a3ef598250fbb5a0f26471337c36a68f27d..e8df78c7e65f5bc2d36aab4b36525f7d45a9c805 100644 (file)
@@ -19,6 +19,7 @@
 #include <pcre.h>
 #include <sys/time.h>
 
+#include "libi3.h"
 #include "queue.h"
 
 /*
@@ -60,6 +61,13 @@ typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_1PIXEL = 2 } border_style_t;
  * only this specific window or the whole X11 client */
 typedef enum { DONT_KILL_WINDOW = 0, KILL_WINDOW = 1, KILL_CLIENT = 2 } kill_window_t;
 
+/** describes if the window is adjacent to the output (physical screen) edges. */
+typedef enum { ADJ_NONE = 0,
+               ADJ_LEFT_SCREEN_EDGE = (1 << 0),
+               ADJ_RIGHT_SCREEN_EDGE = (1 << 1),
+               ADJ_UPPER_SCREEN_EDGE = (1 << 2),
+               ADJ_LOWER_SCREEN_EDGE = (1 << 4)} adjacent_t;
+
 enum {
     BIND_NONE = 0,
     BIND_SHIFT = XCB_MOD_MASK_SHIFT,        /* (1 << 0) */
@@ -160,6 +168,9 @@ struct Startup_Sequence {
     char *workspace;
     /** libstartup-notification context for this launch */
     SnLauncherContext *context;
+    /** time at which this sequence should be deleted (after it was marked as
+     * completed) */
+    time_t delete_at;
 
     TAILQ_ENTRY(Startup_Sequence) sequences;
 };
@@ -189,6 +200,20 @@ struct regex {
  *
  */
 struct Binding {
+    /** If true, the binding should be executed upon a KeyRelease event, not a
+     * KeyPress (the default). */
+    enum {
+        /* This binding will only be executed upon KeyPress events */
+        B_UPON_KEYPRESS = 0,
+        /* This binding will be executed either upon a KeyRelease event, or… */
+        B_UPON_KEYRELEASE = 1,
+        /* …upon a KeyRelease event, even if the modifiers don’t match. This
+         * state is triggered from get_binding() when the corresponding
+         * KeyPress (!) happens, so that users can release the modifier keys
+         * before releasing the actual key. */
+        B_UPON_KEYRELEASE_IGNORE_MODS = 2,
+    } release;
+
     /** 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) */
@@ -280,9 +305,8 @@ struct Window {
     char *class_class;
     char *class_instance;
 
-    /** 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 name of the window. */
+    i3String *name;
 
     /** The WM_WINDOW_ROLE of this window (for example, the pidgin buddy window
      * sets "buddy list"). Useful to match specific windows in assignments or
@@ -292,13 +316,6 @@ struct Window {
     /** Flag to force re-rendering the decoration upon changes */
     bool name_x_changed;
 
-    /** 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) */
-    size_t name_len;
-
     /** Whether the application used _NET_WM_NAME */
     bool uses_net_wm_name;
 
@@ -423,6 +440,8 @@ struct Assignment {
  */
 struct Con {
     bool mapped;
+    /** whether this is a split container or not */
+    bool split;
     enum {
         CT_ROOT = 0,
         CT_OUTPUT = 1,
@@ -431,7 +450,6 @@ struct Con {
         CT_WORKSPACE = 4,
         CT_DOCKAREA = 5
     } type;
-    orientation_t orientation;
     struct Con *parent;
 
     struct Rect rect;
@@ -496,7 +514,15 @@ struct Con {
     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;
+    enum {
+        L_DEFAULT = 0,
+        L_STACKED = 1,
+        L_TABBED = 2,
+        L_DOCKAREA = 3,
+        L_OUTPUT = 4,
+        L_SPLITV = 5,
+        L_SPLITH = 6
+    } layout, last_split_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
diff --git a/include/display_version.h b/include/display_version.h
new file mode 100644 (file)
index 0000000..97b3902
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * display_version.c: displays the running i3 version, runs as part of
+ *                    i3 --moreversion.
+ */
+#ifndef _DISPLAY_VERSION_H
+#define _DISPLAY_VERSION_H
+
+/**
+ * Connects to i3 to find out the currently running version. Useful since it
+ * might be different from the version compiled into this binary (maybe the
+ * user didn’t correctly install i3 or forgot te restart it).
+ *
+ * The output looks like this:
+ * Running i3 version: 4.2-202-gb8e782c (2012-08-12, branch "next") (pid 14804)
+ *
+ * The i3 binary you just called: /home/michael/i3/i3
+ * The i3 binary you are running: /home/michael/i3/i3
+ *
+ */
+void display_running_version(void);
+
+#endif
index bfadf4cf87d086571c96584c54b3c34e49cd6362..0906b7f919bbb98d1e21598557aec29b00b2ad7b 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * This public header defines the different constants and message types to use
  * for the IPC interface to i3 (see docs/ipc for more information).
@@ -40,6 +40,9 @@
 /** Request the configuration for a specific 'bar' */
 #define I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG      6
 
+/** Request the i3 version */
+#define I3_IPC_MESSAGE_TYPE_GET_VERSION         7
+
 /*
  * Messages from i3 to clients
  *
@@ -66,6 +69,9 @@
 /** Bar config reply type */
 #define I3_IPC_REPLY_TYPE_BAR_CONFIG            6
 
+/** i3 version reply type */
+#define I3_IPC_REPLY_TYPE_VERSION               7
+
 /*
  * Events from i3 to clients. Events have the first bit set high.
  *
diff --git a/include/key_press.h b/include/key_press.h
new file mode 100644 (file)
index 0000000..4d469ba
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * key_press.c: key press handler
+ *
+ */
+#ifndef _KEY_PRESS_H
+#define _KEY_PRESS_H
+
+/**
+ * There was a key press. We compare this key code with our bindings table and pass
+ * the bound action to parse_command().
+ *
+ */
+void handle_key_press(xcb_key_press_event_t *event);
+
+/**
+ * Kills the commanderror i3-nagbar process, if any.
+ *
+ * Called when reloading/restarting, since the user probably fixed his wrong
+ * keybindings.
+ *
+ * If wait_for_it is set (restarting), this function will waitpid(), otherwise,
+ * ev is assumed to handle it (reloading).
+ *
+ */
+void kill_commanderror_nagbar(bool wait_for_it);
+
+#endif
index 2126e100afbd36b2f9843b6aca86620ba820a97c..d4df901fe4cde7400a895d4d2e6d585f93590a92 100644 (file)
 #include <xcb/xproto.h>
 #include <xcb/xcb_keysyms.h>
 
+#if PANGO_SUPPORT
+#include <pango/pango.h>
+#endif
+
+/**
+ * Opaque data structure for storing strings.
+ *
+ */
+typedef struct _i3String i3String;
+
 typedef struct Font i3Font;
 
 /**
@@ -27,23 +37,44 @@ typedef struct Font i3Font;
  *
  */
 struct Font {
-    /** The xcb-id for the font */
-    xcb_font_t id;
-
-    /** Font information gathered from the server */
-    xcb_query_font_reply_t *info;
-
-    /** Font table for this font (may be NULL) */
-    xcb_charinfo_t *table;
+    /** The type of font */
+    enum {
+        FONT_TYPE_NONE = 0,
+        FONT_TYPE_XCB,
+        FONT_TYPE_PANGO
+    } type;
 
     /** The height of the font, built from font_ascent + font_descent */
     int height;
+
+    union {
+        struct {
+            /** The xcb-id for the font */
+            xcb_font_t id;
+
+            /** Font information gathered from the server */
+            xcb_query_font_reply_t *info;
+
+            /** Font table for this font (may be NULL) */
+            xcb_charinfo_t *table;
+        } xcb;
+
+#if PANGO_SUPPORT
+        /** The pango font description */
+        PangoFontDescription *pango_desc;
+#endif
+    } specific;
 };
 
 /* Since this file also gets included by utilities which don’t use the i3 log
  * infrastructure, we define a fallback. */
+#if !defined(LOG)
+void verboselog(char *fmt, ...);
+#define LOG(fmt, ...) verboselog("[libi3] " __FILE__ " " fmt, ##__VA_ARGS__)
+#endif
 #if !defined(ELOG)
-#define ELOG(fmt, ...) fprintf(stderr, "ERROR: " fmt, ##__VA_ARGS__)
+void errorlog(char *fmt, ...);
+#define ELOG(fmt, ...) errorlog("[libi3] ERROR: " fmt, ##__VA_ARGS__)
 #endif
 
 /**
@@ -91,6 +122,71 @@ char *sstrdup(const char *str);
  */
 int sasprintf(char **strp, const char *fmt, ...);
 
+/**
+ * Build an i3String from an UTF-8 encoded string.
+ * Returns the newly-allocated i3String.
+ *
+ */
+i3String *i3string_from_utf8(const char *from_utf8);
+
+/**
+ * Build an i3String from an UTF-8 encoded string with fixed length.
+ * To be used when no proper NUL-terminaison is available.
+ * Returns the newly-allocated i3String.
+ *
+ */
+i3String *i3string_from_utf8_with_length(const char *from_utf8, size_t num_bytes);
+
+/**
+ * Build an i3String from an UCS-2 encoded string.
+ * Returns the newly-allocated i3String.
+ *
+ */
+i3String *i3string_from_ucs2(const xcb_char2b_t *from_ucs2, size_t num_glyphs);
+
+/**
+ * Free an i3String.
+ *
+ */
+void i3string_free(i3String *str);
+
+/**
+ * Securely i3string_free by setting the pointer to NULL
+ * to prevent accidentally using freed memory.
+ *
+ */
+#define I3STRING_FREE(str) \
+do { \
+ if (str != NULL) { \
+ i3string_free(str); \
+ str = NULL; \
+ } \
+} while (0)
+
+/**
+ * Returns the UTF-8 encoded version of the i3String.
+ *
+ */
+const char *i3string_as_utf8(i3String *str);
+
+/**
+ * Returns the UCS-2 encoded version of the i3String.
+ *
+ */
+const xcb_char2b_t *i3string_as_ucs2(i3String *str);
+
+/**
+ * Returns the number of bytes (UTF-8 encoded) in an i3String.
+ *
+ */
+size_t i3string_get_num_bytes(i3String *str);
+
+/**
+ * Returns the number of glyphs in an i3String.
+ *
+ */
+size_t i3string_get_num_glyphs(i3String *str);
+
 /**
  * Connects to the i3 IPC socket and returns the file descriptor for the
  * socket. die()s if anything goes wrong.
@@ -226,21 +322,31 @@ void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background
  * specified coordinates (from the top left corner of the leftmost, uppermost
  * glyph) and using the provided gc.
  *
- * Text can be specified as UCS-2 or UTF-8. If it's specified as UCS-2, then
- * text_len must be the number of glyphs in the string. If it's specified as
- * UTF-8, then text_len must be the number of bytes in the string (not counting
- * the null terminator).
+ * Text must be specified as an i3String.
+ *
+ */
+void draw_text(i3String *text, xcb_drawable_t drawable,
+        xcb_gcontext_t gc, int x, int y, int max_width);
+
+/**
+ * ASCII version of draw_text to print static strings.
  *
  */
-void draw_text(char *text, size_t text_len, bool is_ucs2, xcb_drawable_t drawable,
+void draw_text_ascii(const char *text, xcb_drawable_t drawable,
         xcb_gcontext_t gc, int x, int y, int max_width);
 
 /**
- * Predict the text width in pixels for the given text. Text can be specified
- * as UCS-2 or UTF-8.
+ * Predict the text width in pixels for the given text. Text must be
+ * specified as an i3String.
+ *
+ */
+int predict_text_width(i3String *text);
+
+/**
+ * Returns the visual type associated with the given screen.
  *
  */
-int predict_text_width(char *text, size_t text_len, bool is_ucs2);
+xcb_visualtype_t *get_visualtype(xcb_screen_t *screen);
 
 /**
  * Returns true if this version of i3 is a debug build (anything which is not a
index e5e20dc12c00611aff2f36781e04e00647e78e62..7822fba50f66b9ba51943e2eef62776bfc3e799d 100644 (file)
@@ -4,7 +4,7 @@
  * i3 - an improved dynamic tiling window manager
  * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * log.c: Setting of loglevels, logging functions.
+ * log.c: Logging functions.
  *
  */
 #ifndef _LOG_H
 #include <stdarg.h>
 #include <stdbool.h>
 
+/* We will include libi3.h which define its own version of LOG, ELOG.
+ * We want *our* version, so we undef the libi3 one. */
+#if defined(LOG)
+#undef LOG
+#endif
+#if defined(ELOG)
+#undef ELOG
+#endif
 /** ##__VA_ARGS__ means: leave out __VA_ARGS__ completely if it is empty, that
    is, delete the preceding comma */
 #define LOG(fmt, ...) verboselog(fmt, ##__VA_ARGS__)
 #define ELOG(fmt, ...) errorlog("ERROR: " fmt, ##__VA_ARGS__)
-#define DLOG(fmt, ...) debuglog(LOGLEVEL, "%s:%s:%d - " fmt, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__)
+#define DLOG(fmt, ...) debuglog("%s:%s:%d - " fmt, I3__FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__)
 
-extern char *loglevels[];
 extern char *errorfilename;
 extern char *shmlogname;
 extern int shmlog_size;
@@ -32,10 +39,10 @@ extern int shmlog_size;
 void init_logging(void);
 
 /**
- * Enables the given loglevel.
+ * Set debug logging.
  *
  */
-void add_loglevel(const char *level);
+void set_debug_logging(const bool _debug_logging);
 
 /**
  * Set verbosity of i3. If verbose is set to true, informative messages will
@@ -47,29 +54,32 @@ void set_verbosity(bool _verbose);
 
 /**
  * Logs the given message to stdout while prefixing the current time to it,
- * but only if the corresponding debug loglevel was activated.
+ * but only if debug logging was activated.
  *
  */
-void debuglog(uint64_t lev, char *fmt, ...);
+void debuglog(char *fmt, ...)
+    __attribute__ ((format (printf, 1, 2)));
 
 /**
  * Logs the given message to stdout while prefixing the current time to it.
  *
  */
-void errorlog(char *fmt, ...);
+void errorlog(char *fmt, ...)
+    __attribute__ ((format (printf, 1, 2)));
 
 /**
  * Logs the given message to stdout while prefixing the current time to it,
  * but only if verbose mode is activated.
  *
  */
-void verboselog(char *fmt, ...);
+void verboselog(char *fmt, ...)
+    __attribute__ ((format (printf, 1, 2)));
 
 /**
- * Logs the given message to stdout while prefixing the current time to it.
- * This is to be called by LOG() which includes filename/linenumber
- *
+ * Deletes the unused log files. Useful if i3 exits immediately, eg.
+ * because --get-socketpath was called. We don't care for syscall
+ * failures. This function is invoked automatically when exiting.
  */
-void slog(char *fmt, va_list args);
+void purge_zerobyte_logfile(void);
 
 #endif
index d55bb6cba19869af8d4cc16eb9f650752a7436db..fe1e9f9537f47dcb13d029618763362c2048d054 100644 (file)
@@ -31,7 +31,7 @@ void regex_free(struct regex *regex);
 /**
  * Checks if the given regular expression matches the given input and returns
  * true if it does. In either case, it logs the outcome using LOG(), so it will
- * be visible without any debug loglevel.
+ * be visible without debug logging.
  *
  */
 bool regex_matches(struct regex *regex, const char *input);
index 4fb7523a0bf4df581c25c2831d23380a774ce4e2..4d5533273983b1a8a11b8bc6d357fcdbd569fac2 100644 (file)
@@ -30,4 +30,14 @@ void scratchpad_move(Con *con);
  */
 void scratchpad_show(Con *con);
 
+/**
+ * When starting i3 initially (and after each change to the connected outputs),
+ * this function fixes the resolution of the __i3 pseudo-output. When that
+ * resolution is not set to a function which shares a common divisor with every
+ * active output’s resolution, floating point calculation errors will lead to
+ * the scratchpad window moving when shown repeatedly.
+ *
+ */
+void scratchpad_fix_resolution(void);
+
 #endif
index c513babf698e3e931ecabb011e024dde2a95cba3..e755d2f139cdc003e9d5fc655d7aec7c882bd79a 100644 (file)
 #define _I3_SHMLOG_H
 
 #include <stdint.h>
+#include <pthread.h>
 
+/*
+ * Header of the shmlog file. Used by i3/src/log.c and i3/i3-dump-log/main.c.
+ *
+ */
 typedef struct i3_shmlog_header {
+    /* Byte offset where the next line will be written to. */
     uint32_t offset_next_write;
+
+    /* Byte offset where the last wrap occured. */
     uint32_t offset_last_wrap;
+
+    /* The size of the logfile in bytes. Since the size is limited to 25 MiB
+     * an uint32_t is sufficient. */
     uint32_t size;
+
+    /* wrap counter. We need it to reliably signal to clients that we just
+     * wrapped (clients cannot use offset_last_wrap because that might
+     * coincidentally be exactly the same as previously). Overflows can happen
+     * and don’t matter — clients use an equality check (==). */
+    uint32_t wrap_count;
+
+    /* pthread condvar which will be broadcasted whenever there is a new
+     * message in the log. i3-dump-log uses this to implement -f (follow, like
+     * tail -f) in an efficient way. */
+    pthread_cond_t condvar;
 } i3_shmlog_header;
 
 #endif
index b9159e3b5b134851ae759ebb0935aadcdcd47230..8816b19a640a55d0fa84300bdd59295c56c24943 100644 (file)
@@ -39,16 +39,16 @@ Con *tree_open_con(Con *con, i3Window *window);
 void tree_split(Con *con, orientation_t orientation);
 
 /**
- * Moves focus one level up.
+ * Moves focus one level up. Returns true if focus changed.
  *
  */
-void level_up(void);
+bool level_up(void);
 
 /**
- * Moves focus one level down.
+ * Moves focus one level down. Returns true if focus changed.
  *
  */
-void level_down(void);
+bool level_down(void);
 
 /**
  * Renders the tree, that is rendering all outputs using render_con() and
index e9efcf7bdc2a4a3bff62d80a07606baea03f137e..2c2f68ad7dc94338282d16f756b6258fdb23ded9 100644 (file)
@@ -1,26 +1,7 @@
-# Default value so one can compile i3-msg standalone
-TOPDIR=..
-
-include $(TOPDIR)/common.mk
-
-CFLAGS += -I$(TOPDIR)/include
-
-# Depend on the object files of all source-files in src/*.c and on all header files
-FILES=$(patsubst %.c,%.o,$(wildcard *.c))
-HEADERS=$(wildcard *.h)
-
-# Depend on the specific file (.c for each .o) and on all headers
-%.o: %.c ${HEADERS}
-       echo "[libi3] CC $<"
-       $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
-
-all: libi3.a
-
-libi3.a: ${FILES}
-       echo "[libi3] AR libi3.a"
-       ar rcs libi3.a ${FILES}
+all:
+       $(MAKE) -C .. libi3.a
 
 clean:
-       rm -f *.o libi3.a
+       $(MAKE) -C .. clean-libi3
 
-distclean: clean
+.PHONY: all clean
index 0b276b0b3f52c48f9afe5b3011c1dfcf8bb8dbc9..23d7420de33fb805e486500435f8dff3d338aa3d 100644 (file)
 #include <stdbool.h>
 #include <err.h>
 
+#if PANGO_SUPPORT
+#include <cairo/cairo-xcb.h>
+#include <pango/pangocairo.h>
+#endif
+
 #include "libi3.h"
 
 extern xcb_connection_t *conn;
+extern xcb_screen_t *root_screen;
+
 static const i3Font *savedFont = NULL;
 
+#if PANGO_SUPPORT
+static xcb_visualtype_t *root_visual_type;
+static double pango_font_red;
+static double pango_font_green;
+static double pango_font_blue;
+
+/*
+ * Loads a Pango font description into an i3Font structure. Returns true
+ * on success, false otherwise.
+ *
+ */
+static bool load_pango_font(i3Font *font, const char *desc) {
+    /* Load the font description */
+    font->specific.pango_desc = pango_font_description_from_string(desc);
+    if (!font->specific.pango_desc) {
+        ELOG("Could not open font %s with Pango, fallback to X font.\n", desc);
+        return false;
+    }
+
+    LOG("Using Pango font %s, size %d\n",
+        pango_font_description_get_family(font->specific.pango_desc),
+        pango_font_description_get_size(font->specific.pango_desc) / PANGO_SCALE
+        );
+
+    /* We cache root_visual_type here, since you must call
+     * load_pango_font before any other pango function
+     * that would need root_visual_type */
+    root_visual_type = get_visualtype(root_screen);
+
+    /* Create a dummy Pango layout to compute the font height */
+    cairo_surface_t *surface = cairo_xcb_surface_create(conn, root_screen->root, root_visual_type, 1, 1);
+    cairo_t *cr = cairo_create(surface);
+    PangoLayout *layout = pango_cairo_create_layout(cr);
+    pango_layout_set_font_description(layout, font->specific.pango_desc);
+
+    /* Get the font height */
+    gint height;
+    pango_layout_get_pixel_size(layout, NULL, &height);
+    font->height = height;
+
+    /* Free resources */
+    g_object_unref(layout);
+    cairo_destroy(cr);
+    cairo_surface_destroy(surface);
+
+    /* Set the font type and return successfully */
+    font->type = FONT_TYPE_PANGO;
+    return true;
+}
+
+/*
+ * Draws text using Pango rendering.
+ *
+ */
+static void draw_text_pango(const char *text, size_t text_len,
+        xcb_drawable_t drawable, int x, int y, int max_width) {
+    /* Create the Pango layout */
+    /* root_visual_type is cached in load_pango_font */
+    cairo_surface_t *surface = cairo_xcb_surface_create(conn, drawable,
+            root_visual_type, x + max_width, y + savedFont->height);
+    cairo_t *cr = cairo_create(surface);
+    PangoLayout *layout = pango_cairo_create_layout(cr);
+    pango_layout_set_font_description(layout, savedFont->specific.pango_desc);
+    pango_layout_set_width(layout, max_width * PANGO_SCALE);
+    pango_layout_set_wrap(layout, PANGO_WRAP_CHAR);
+    pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
+
+    /* Do the drawing */
+    cairo_set_source_rgb(cr, pango_font_red, pango_font_green, pango_font_blue);
+    cairo_move_to(cr, x, y);
+    pango_layout_set_text(layout, text, text_len);
+    pango_cairo_update_layout(cr, layout);
+    pango_cairo_show_layout(cr, layout);
+
+    /* Free resources */
+    g_object_unref(layout);
+    cairo_destroy(cr);
+    cairo_surface_destroy(surface);
+}
+
+/*
+ * Calculate the text width using Pango rendering.
+ *
+ */
+static int predict_text_width_pango(const char *text, size_t text_len) {
+    /* Create a dummy Pango layout */
+    /* root_visual_type is cached in load_pango_font */
+    cairo_surface_t *surface = cairo_xcb_surface_create(conn, root_screen->root, root_visual_type, 1, 1);
+    cairo_t *cr = cairo_create(surface);
+    PangoLayout *layout = pango_cairo_create_layout(cr);
+
+    /* Get the font width */
+    gint width;
+    pango_layout_set_font_description(layout, savedFont->specific.pango_desc);
+    pango_layout_set_text(layout, text, text_len);
+    pango_cairo_update_layout(cr, layout);
+    pango_layout_get_pixel_size(layout, &width, NULL);
+
+    /* Free resources */
+    g_object_unref(layout);
+    cairo_destroy(cr);
+    cairo_surface_destroy(surface);
+
+    return width;
+}
+#endif
+
 /*
  * Loads a font for usage, also getting its metrics. If fallback is true,
  * the fonts 'fixed' or '-misc-*' will be loaded instead of exiting.
@@ -24,12 +138,22 @@ static const i3Font *savedFont = NULL;
  */
 i3Font load_font(const char *pattern, const bool fallback) {
     i3Font font;
+    font.type = FONT_TYPE_NONE;
+
+#if PANGO_SUPPORT
+    /* Try to load a pango font if specified */
+    if (strlen(pattern) > strlen("xft:") && !strncmp(pattern, "xft:", strlen("xft:"))) {
+        pattern += strlen("xft:");
+        if (load_pango_font(&font, pattern))
+            return font;
+    }
+#endif
 
     /* Send all our requests first */
-    font.id = xcb_generate_id(conn);
-    xcb_void_cookie_t font_cookie = xcb_open_font_checked(conn, font.id,
+    font.specific.xcb.id = xcb_generate_id(conn);
+    xcb_void_cookie_t font_cookie = xcb_open_font_checked(conn, font.specific.xcb.id,
             strlen(pattern), pattern);
-    xcb_query_font_cookie_t info_cookie = xcb_query_font(conn, font.id);
+    xcb_query_font_cookie_t info_cookie = xcb_query_font(conn, font.specific.xcb.id);
 
     /* Check for errors. If errors, fall back to default font. */
     xcb_generic_error_t *error;
@@ -40,8 +164,9 @@ i3Font load_font(const char *pattern, const bool fallback) {
         ELOG("Could not open font %s (X error %d). Trying fallback to 'fixed'.\n",
              pattern, error->error_code);
         pattern = "fixed";
-        font_cookie = xcb_open_font_checked(conn, font.id, strlen(pattern), pattern);
-        info_cookie = xcb_query_font(conn, font.id);
+        font_cookie = xcb_open_font_checked(conn, font.specific.xcb.id,
+                strlen(pattern), pattern);
+        info_cookie = xcb_query_font(conn, font.specific.xcb.id);
 
         /* Check if we managed to open 'fixed' */
         error = xcb_request_check(conn, font_cookie);
@@ -50,8 +175,9 @@ i3Font load_font(const char *pattern, const bool fallback) {
         if (error != NULL) {
             ELOG("Could not open fallback font 'fixed', trying with '-misc-*'.\n");
             pattern = "-misc-*";
-            font_cookie = xcb_open_font_checked(conn, font.id, strlen(pattern), pattern);
-            info_cookie = xcb_query_font(conn, font.id);
+            font_cookie = xcb_open_font_checked(conn, font.specific.xcb.id,
+                    strlen(pattern), pattern);
+            info_cookie = xcb_query_font(conn, font.specific.xcb.id);
 
             if ((error = xcb_request_check(conn, font_cookie)) != NULL)
                 errx(EXIT_FAILURE, "Could open neither requested font nor fallbacks "
@@ -59,19 +185,23 @@ i3Font load_font(const char *pattern, const bool fallback) {
         }
     }
 
+    LOG("Using X font %s\n", pattern);
+
     /* Get information (height/name) for this font */
-    if (!(font.info = xcb_query_font_reply(conn, info_cookie, NULL)))
+    if (!(font.specific.xcb.info = xcb_query_font_reply(conn, info_cookie, NULL)))
         errx(EXIT_FAILURE, "Could not load font \"%s\"", pattern);
 
     /* Get the font table, if possible */
-    if (xcb_query_font_char_infos_length(font.info) == 0)
-        font.table = NULL;
+    if (xcb_query_font_char_infos_length(font.specific.xcb.info) == 0)
+        font.specific.xcb.table = NULL;
     else
-        font.table = xcb_query_font_char_infos(font.info);
+        font.specific.xcb.table = xcb_query_font_char_infos(font.specific.xcb.info);
 
     /* Calculate the font height */
-    font.height = font.info->font_ascent + font.info->font_descent;
+    font.height = font.specific.xcb.info->font_ascent + font.specific.xcb.info->font_descent;
 
+    /* Set the font type and return successfully */
+    font.type = FONT_TYPE_XCB;
     return font;
 }
 
@@ -88,10 +218,27 @@ void set_font(i3Font *font) {
  *
  */
 void free_font(void) {
-    /* Close the font and free the info */
-    xcb_close_font(conn, savedFont->id);
-    if (savedFont->info)
-        free(savedFont->info);
+    switch (savedFont->type) {
+        case FONT_TYPE_NONE:
+            /* Nothing to do */
+            break;
+        case FONT_TYPE_XCB: {
+            /* Close the font and free the info */
+            xcb_close_font(conn, savedFont->specific.xcb.id);
+            if (savedFont->specific.xcb.info)
+                free(savedFont->specific.xcb.info);
+            break;
+        }
+#if PANGO_SUPPORT
+        case FONT_TYPE_PANGO:
+            /* Free the font description */
+            pango_font_description_free(savedFont->specific.pango_desc);
+            break;
+#endif
+        default:
+            assert(false);
+            break;
+    }
 }
 
 /*
@@ -100,50 +247,49 @@ void free_font(void) {
  */
 void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background) {
     assert(savedFont != NULL);
-    uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
-    uint32_t values[] = { foreground, background, savedFont->id };
-    xcb_change_gc(conn, gc, mask, values);
+
+    switch (savedFont->type) {
+        case FONT_TYPE_NONE:
+            /* Nothing to do */
+            break;
+        case FONT_TYPE_XCB: {
+            /* Change the font and colors in the GC */
+            uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
+            uint32_t values[] = { foreground, background, savedFont->specific.xcb.id };
+            xcb_change_gc(conn, gc, mask, values);
+            break;
+        }
+#if PANGO_SUPPORT
+        case FONT_TYPE_PANGO:
+            /* Save the foreground font */
+            pango_font_red = ((foreground >> 16) & 0xff) / 255.0;
+            pango_font_green = ((foreground >> 8) & 0xff) / 255.0;
+            pango_font_blue = (foreground & 0xff) / 255.0;
+            break;
+#endif
+        default:
+            assert(false);
+            break;
+    }
 }
 
-/*
- * Draws text onto the specified X drawable (normally a pixmap) at the
- * specified coordinates (from the top left corner of the leftmost, uppermost
- * glyph) and using the provided gc.
- *
- * Text can be specified as UCS-2 or UTF-8. If it's specified as UCS-2, then
- * text_len must be the number of glyphs in the string. If it's specified as
- * UTF-8, then text_len must be the number of bytes in the string (not counting
- * the null terminator).
- *
- */
-void draw_text(char *text, size_t text_len, bool is_ucs2, xcb_drawable_t drawable,
-               xcb_gcontext_t gc, int x, int y, int max_width) {
-    assert(savedFont != NULL);
-    assert(text_len != 0);
+static int predict_text_width_xcb(const xcb_char2b_t *text, size_t text_len);
 
+static void draw_text_xcb(const xcb_char2b_t *text, size_t text_len, xcb_drawable_t drawable,
+               xcb_gcontext_t gc, int x, int y, int max_width) {
     /* X11 coordinates for fonts start at the baseline */
-    int pos_y = y + savedFont->info->font_ascent;
-
-    /* As an optimization, check if we can bypass conversion */
-    if (!is_ucs2 && text_len <= 255) {
-        xcb_image_text_8(conn, text_len, drawable, gc, x, pos_y, text);
-        return;
-    }
-
-    /* Convert the text into UCS-2 so we can do basic pointer math */
-    char *input = (is_ucs2 ? text : (char*)convert_utf8_to_ucs2(text, &text_len));
+    int pos_y = y + savedFont->specific.xcb.info->font_ascent;
 
     /* The X11 protocol limits text drawing to 255 chars, so we may need
      * multiple calls */
-    int pos_x = x;
     int offset = 0;
     for (;;) {
         /* Calculate the size of this chunk */
         int chunk_size = (text_len > 255 ? 255 : text_len);
-        xcb_char2b_t *chunk = (xcb_char2b_t*)input + offset;
+        const xcb_char2b_t *chunk = text + offset;
 
         /* Draw it */
-        xcb_image_text_16(conn, chunk_size, drawable, gc, pos_x, pos_y, chunk);
+        xcb_image_text_16(conn, chunk_size, drawable, gc, x, pos_y, chunk);
 
         /* Advance the offset and length of the text to draw */
         offset += chunk_size;
@@ -154,15 +300,83 @@ void draw_text(char *text, size_t text_len, bool is_ucs2, xcb_drawable_t drawabl
             break;
 
         /* Advance pos_x based on the predicted text width */
-        pos_x += predict_text_width((char*)chunk, chunk_size, true);
+        x += predict_text_width_xcb(chunk, chunk_size);
+    }
+}
+
+/*
+ * Draws text onto the specified X drawable (normally a pixmap) at the
+ * specified coordinates (from the top left corner of the leftmost, uppermost
+ * glyph) and using the provided gc.
+ *
+ * Text must be specified as an i3String.
+ *
+ */
+void draw_text(i3String *text, xcb_drawable_t drawable,
+               xcb_gcontext_t gc, int x, int y, int max_width) {
+    assert(savedFont != NULL);
+
+    switch (savedFont->type) {
+        case FONT_TYPE_NONE:
+            /* Nothing to do */
+            return;
+        case FONT_TYPE_XCB:
+            draw_text_xcb(i3string_as_ucs2(text), i3string_get_num_glyphs(text),
+                      drawable, gc, x, y, max_width);
+            break;
+#if PANGO_SUPPORT
+        case FONT_TYPE_PANGO:
+            /* Render the text using Pango */
+            draw_text_pango(i3string_as_utf8(text), i3string_get_num_bytes(text),
+                            drawable, x, y, max_width);
+            return;
+#endif
+        default:
+            assert(false);
     }
+}
+
+/*
+ * ASCII version of draw_text to print static strings.
+ *
+ */
+void draw_text_ascii(const char *text, xcb_drawable_t drawable,
+               xcb_gcontext_t gc, int x, int y, int max_width) {
+    assert(savedFont != NULL);
 
-    /* If we had to convert, free the converted string */
-    if (!is_ucs2)
-        free(input);
+    switch (savedFont->type) {
+        case FONT_TYPE_NONE:
+            /* Nothing to do */
+            return;
+        case FONT_TYPE_XCB:
+        {
+            size_t text_len = strlen(text);
+            if (text_len > 255) {
+                /* The text is too long to draw it directly to X */
+                i3String *str = i3string_from_utf8(text);
+                draw_text(str, drawable, gc, x, y, max_width);
+                i3string_free(str);
+            } else {
+                /* X11 coordinates for fonts start at the baseline */
+                int pos_y = y + savedFont->specific.xcb.info->font_ascent;
+
+                xcb_image_text_8(conn, text_len, drawable, gc, x, pos_y, text);
+            }
+            break;
+        }
+#if PANGO_SUPPORT
+        case FONT_TYPE_PANGO:
+            /* Render the text using Pango */
+            draw_text_pango(text, strlen(text),
+                            drawable, x, y, max_width);
+            return;
+#endif
+        default:
+            assert(false);
+    }
 }
 
-static int xcb_query_text_width(xcb_char2b_t *text, size_t text_len) {
+static int xcb_query_text_width(const xcb_char2b_t *text, size_t text_len) {
     /* Make the user know we’re using the slow path, but only once. */
     static bool first_invocation = true;
     if (first_invocation) {
@@ -173,7 +387,7 @@ static int xcb_query_text_width(xcb_char2b_t *text, size_t text_len) {
     /* Query the text width */
     xcb_generic_error_t *error;
     xcb_query_text_extents_cookie_t cookie = xcb_query_text_extents(conn,
-            savedFont->id, text_len, (xcb_char2b_t*)text);
+            savedFont->specific.xcb.id, text_len, (xcb_char2b_t*)text);
     xcb_query_text_extents_reply_t *reply = xcb_query_text_extents_reply(conn,
             cookie, &error);
     if (reply == NULL) {
@@ -181,7 +395,7 @@ static int xcb_query_text_width(xcb_char2b_t *text, size_t text_len) {
          * a crash. Plus, the user will see the error in his log. */
         fprintf(stderr, "Could not get text extents (X error code %d)\n",
                 error->error_code);
-        return savedFont->info->max_bounds.character_width * text_len;
+        return savedFont->specific.xcb.info->max_bounds.character_width * text_len;
     }
 
     int width = reply->overall_width;
@@ -189,27 +403,18 @@ static int xcb_query_text_width(xcb_char2b_t *text, size_t text_len) {
     return width;
 }
 
-/*
- * Predict the text width in pixels for the given text. Text can be specified
- * as UCS-2 or UTF-8.
- *
- */
-int predict_text_width(char *text, size_t text_len, bool is_ucs2) {
-    /* Convert the text into UTF-16 so we can do basic pointer math */
-    xcb_char2b_t *input;
-    if (is_ucs2)
-        input = (xcb_char2b_t*)text;
-    else
-        input = convert_utf8_to_ucs2(text, &text_len);
+static int predict_text_width_xcb(const xcb_char2b_t *input, size_t text_len) {
+    if (text_len == 0)
+        return 0;
 
     int width;
-    if (savedFont->table == NULL) {
+    if (savedFont->specific.xcb.table == NULL) {
         /* If we don't have a font table, fall back to querying the server */
         width = xcb_query_text_width(input, text_len);
     } else {
         /* Save some pointers for convenience */
-        xcb_query_font_reply_t *font_info = savedFont->info;
-        xcb_charinfo_t *font_table = savedFont->table;
+        xcb_query_font_reply_t *font_info = savedFont->specific.xcb.info;
+        xcb_charinfo_t *font_table = savedFont->specific.xcb.table;
 
         /* Calculate the width using the font table */
         width = 0;
@@ -239,9 +444,30 @@ int predict_text_width(char *text, size_t text_len, bool is_ucs2) {
         }
     }
 
-    /* If we had to convert, free the converted string */
-    if (!is_ucs2)
-        free(input);
-
     return width;
 }
+
+/*
+ * Predict the text width in pixels for the given text. Text must be
+ * specified as an i3String.
+ *
+ */
+int predict_text_width(i3String *text) {
+    assert(savedFont != NULL);
+
+    switch (savedFont->type) {
+        case FONT_TYPE_NONE:
+            /* Nothing to do */
+            return 0;
+        case FONT_TYPE_XCB:
+            return predict_text_width_xcb(i3string_as_ucs2(text), i3string_get_num_glyphs(text));
+#if PANGO_SUPPORT
+        case FONT_TYPE_PANGO:
+            /* Calculate extents using Pango */
+            return predict_text_width_pango(i3string_as_utf8(text), i3string_get_num_bytes(text));
+#endif
+        default:
+            assert(false);
+            return 0;
+    }
+}
diff --git a/libi3/get_visualtype.c b/libi3/get_visualtype.c
new file mode 100644 (file)
index 0000000..d11722f
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include "libi3.h"
+
+/*
+ * Returns the visual type associated with the given screen.
+ *
+ */
+xcb_visualtype_t *get_visualtype(xcb_screen_t *screen) {
+    xcb_depth_iterator_t depth_iter;
+    for (depth_iter = xcb_screen_allowed_depths_iterator(screen);
+         depth_iter.rem;
+         xcb_depth_next(&depth_iter)) {
+        xcb_visualtype_iterator_t visual_iter;
+        for (visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
+             visual_iter.rem;
+             xcb_visualtype_next(&visual_iter)) {
+            if (screen->root_visual == visual_iter.data->visual_id)
+                return visual_iter.data;
+        }
+    }
+    return NULL;
+}
diff --git a/libi3/libi3.mk b/libi3/libi3.mk
new file mode 100644 (file)
index 0000000..d99bacf
--- /dev/null
@@ -0,0 +1,21 @@
+CLEAN_TARGETS += clean-libi3
+
+libi3_SOURCES := $(wildcard libi3/*.c)
+libi3_HEADERS := $(wildcard libi3/*.h)
+libi3_CFLAGS   = $(PANGO_CFLAGS)
+libi3_LIBS     =
+
+libi3_OBJECTS := $(libi3_SOURCES:.c=.o)
+
+
+libi3/%.o: libi3/%.c $(libi3_HEADERS)
+       echo "[libi3] CC $<"
+       $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(libi3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $<
+
+libi3.a: $(libi3_OBJECTS)
+       echo "[libi3] AR libi3.a"
+       ar rcs $@ $^ $(libi3_LIBS)
+
+clean-libi3:
+       echo "[libi3] Clean"
+       rm -f $(libi3_OBJECTS) libi3.a
index 927cc5f8cf1945d0f2baab4531f8c5e8196e9bff..cabaaf2c3965f70c27be20937dbee3c377cc8ccf 100644 (file)
@@ -28,7 +28,7 @@ char *root_atom_contents(const char *atomname) {
     xcb_intern_atom_cookie_t atom_cookie;
     xcb_intern_atom_reply_t *atom_reply;
     int screen;
-    char *socket_path;
+    char *content;
 
     if ((conn = xcb_connect(NULL, &screen)) == NULL ||
         xcb_connection_has_error(conn))
@@ -50,10 +50,17 @@ char *root_atom_contents(const char *atomname) {
     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;
+    if (prop_reply->type == XCB_ATOM_CARDINAL) {
+        /* We treat a CARDINAL as a >= 32-bit unsigned int. The only CARDINAL
+         * we query is I3_PID, which is 32-bit. */
+        if (asprintf(&content, "%u", *((unsigned int*)xcb_get_property_value(prop_reply))) == -1)
+            return NULL;
+    } else {
+        if (asprintf(&content, "%.*s", xcb_get_property_value_length(prop_reply),
+                     (char*)xcb_get_property_value(prop_reply)) == -1)
+            return NULL;
+    }
     xcb_disconnect(conn);
-    return socket_path;
+    return content;
 }
 
diff --git a/libi3/string.c b/libi3/string.c
new file mode 100644 (file)
index 0000000..009312d
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * string.c: Define an i3String type to automagically handle UTF-8/UCS-2
+ *           conversions. Some font backends need UCS-2 (X core fonts),
+ *           others want UTF-8 (Pango).
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "libi3.h"
+
+struct _i3String {
+    char *utf8;
+    xcb_char2b_t *ucs2;
+    size_t num_glyphs;
+    size_t num_bytes;
+};
+
+/*
+ * Build an i3String from an UTF-8 encoded string.
+ * Returns the newly-allocated i3String.
+ *
+ */
+i3String *i3string_from_utf8(const char *from_utf8) {
+    i3String *str = scalloc(sizeof(i3String));
+
+    /* Get the text */
+    str->utf8 = sstrdup(from_utf8);
+
+    /* Compute and store the length */
+    str->num_bytes = strlen(str->utf8);
+
+    return str;
+}
+
+/*
+ * Build an i3String from an UTF-8 encoded string with fixed length.
+ * To be used when no proper NUL-terminaison is available.
+ * Returns the newly-allocated i3String.
+ *
+ */
+i3String *i3string_from_utf8_with_length(const char *from_utf8, size_t num_bytes) {
+    i3String *str = scalloc(sizeof(i3String));
+
+    /* Copy the actual text to our i3String */
+    str->utf8 = scalloc(sizeof(char) * (num_bytes + 1));
+    strncpy(str->utf8, from_utf8, num_bytes);
+    str->utf8[num_bytes] = '\0';
+
+    /* Store the length */
+    str->num_bytes = num_bytes;
+
+    return str;
+}
+
+/*
+ * Build an i3String from an UCS-2 encoded string.
+ * Returns the newly-allocated i3String.
+ *
+ */
+i3String *i3string_from_ucs2(const xcb_char2b_t *from_ucs2, size_t num_glyphs) {
+    i3String *str = scalloc(sizeof(i3String));
+
+    /* Copy the actual text to our i3String */
+    size_t num_bytes = num_glyphs * sizeof(xcb_char2b_t);
+    str->ucs2 = scalloc(num_bytes);
+    memcpy(str->ucs2, from_ucs2, num_bytes);
+
+    /* Store the length */
+    str->num_glyphs = num_glyphs;
+
+    str->utf8 = NULL;
+    str->num_bytes = 0;
+
+    return str;
+}
+
+/*
+ * Free an i3String.
+ *
+ */
+void i3string_free(i3String *str) {
+    if (str == NULL)
+        return;
+    free(str->utf8);
+    free(str->ucs2);
+    free(str);
+}
+
+static void i3string_ensure_utf8(i3String *str) {
+    if (str->utf8 != NULL)
+        return;
+    if ((str->utf8 = convert_ucs2_to_utf8(str->ucs2, str->num_glyphs)) != NULL)
+        str->num_bytes = strlen(str->utf8);
+}
+
+static void i3string_ensure_ucs2(i3String *str) {
+    if (str->ucs2 != NULL)
+        return;
+    str->ucs2 = convert_utf8_to_ucs2(str->utf8, &str->num_glyphs);
+}
+
+/*
+ * Returns the UTF-8 encoded version of the i3String.
+ *
+ */
+const char *i3string_as_utf8(i3String *str) {
+    i3string_ensure_utf8(str);
+    return str->utf8;
+}
+
+/*
+ * Returns the UCS-2 encoded version of the i3String.
+ *
+ */
+const xcb_char2b_t *i3string_as_ucs2(i3String *str) {
+    i3string_ensure_ucs2(str);
+    return str->ucs2;
+}
+
+/*
+ * Returns the number of bytes (UTF-8 encoded) in an i3String.
+ *
+ */
+size_t i3string_get_num_bytes(i3String *str) {
+    i3string_ensure_utf8(str);
+    return str->num_bytes;
+}
+
+/*
+ * Returns the number of glyphs in an i3String.
+ *
+ */
+size_t i3string_get_num_glyphs(i3String *str) {
+    i3string_ensure_ucs2(str);
+    return str->num_glyphs;
+}
index ff08dc57d7921240bcf7ceaecba6d2e4c93a58aa..e4cee0cc3c5433e8ad7fb981670e2aebeebb13ed 100644 (file)
@@ -1,15 +1,7 @@
-A2M:=a2x -f manpage --asciidoc-opts="-f asciidoc.conf"
-
-all: i3.1 i3-msg.1 i3-input.1 i3-nagbar.1 i3-config-wizard.1 i3-migrate-config-to-v4.1 i3-sensible-editor.1 i3-sensible-pager.1 i3-sensible-terminal.1 i3-dump-log.1
-
-%.1: %.man asciidoc.conf
-       ${A2M} $<
+all:
+       $(MAKE) -C .. mans
 
 clean:
-       for file in $$(echo i3 i3-msg i3-input i3-nagbar i3-wsbar i3-config-wizard i3-migrate-config-to-v4 i3-sensible-editor i3-sensible-pager i3-sensible-terminal i3-dump-log); \
-       do \
-               rm -f $${file}.1 $${file}.html $${file}.xml; \
-       done
+       $(MAKE) -C .. clean-mans
 
-distclean: clean
-       rm -f *.1
+.PHONY: all clean
index 8e9094ff0078c285954eb03d38b628e2f58ce920..eb8ba2f76286c6d10697abdef79c4291371d36f0 100644 (file)
@@ -14,7 +14,7 @@ i3-dump-log [-s <socketpath>]
 == DESCRIPTION
 
 Debug versions of i3 automatically use 1% of your RAM (but 25 MiB max) to store
-full debug loglevel log output. This is extremely helpful for bugreports and
+full debug log output. This is extremely helpful for bugreports and
 figuring out what is going on, without permanently logging to a file.
 
 With i3-dump-log, you can dump the SHM log to stdout.
index 891c6c283dd7ab17fa6db541f5a1007e96865361..6b548d363bb01d5beea1c13835d35b76da429995 100644 (file)
@@ -1,7 +1,7 @@
 i3-msg(1)
 =========
-Michael Stapelberg <michael+i3@stapelberg.de>
-v4.2, January 2012
+Michael Stapelberg <michael@i3wm.org>
+v4.2, August 2012
 
 == NAME
 
@@ -38,6 +38,9 @@ get_bar_config::
 Gets the configuration (as JSON map) of the workspace bar with the given ID. If
 no ID is provided, an array with all configured bar IDs is returned instead.
 
+get_version::
+Gets the version of i3. The reply will be a JSON-encoded dictionary with the
+major, minor, patch and human-readable version.
 
 == DESCRIPTION
 
index 7e32aab4f0337d1580739e2fa6cd79965c56a47a..1d9f9ff8647c3b4bba8949cb52e47abc8f36abc2 100644 (file)
@@ -1,7 +1,7 @@
 i3-sensible-terminal(1)
 =======================
-Michael Stapelberg <michael+i3@stapelberg.de>
-v4.1, November 2011
+Michael Stapelberg <michael@i3wm.org>
+v4.2, August 2012
 
 == NAME
 
@@ -30,6 +30,7 @@ It tries to start one of the following (in that order):
 * xterm
 * gnome-terminal
 * roxterm
+* xfce4-terminal
 
 Please don’t complain about the order: If the user has any preference, he will
 have $TERMINAL set or modified his i3 configuration file.
index 9d34c71038a316e01601ebe7543bffb15e7038a7..096a359b95720311b01fbf274b525216612b0ad5 100644 (file)
@@ -1,7 +1,7 @@
 i3(1)
 =====
-Michael Stapelberg <michael+i3@stapelberg.de>
-v4.0, July 2011
+Michael Stapelberg <michael@i3wm.org>
+v4.2, August 2012
 
 == NAME
 
@@ -9,7 +9,7 @@ i3 - an improved dynamic, tiling window manager
 
 == SYNOPSIS
 
-i3 [-a] [-c configfile] [-C] [-d <loglevel>] [-v] [-V]
+i3 [-a] [-c configfile] [-C] [-d all] [-v] [-V]
 
 == OPTIONS
 
@@ -22,8 +22,9 @@ Specifies an alternate configuration file path.
 -C::
 Check the configuration file for validity and exit.
 
--d::
-Specifies the debug loglevel. To see the most output, use -d all.
+-d all::
+Enables debug logging.
+The 'all' parameter is present for historical reasons.
 
 -v::
 Display version number (and date of the last commit).
@@ -47,8 +48,8 @@ Please be aware that i3 is primarily targeted at advanced users and developers.
 === IMPORTANT NOTE TO nVidia BINARY DRIVER USERS
 
 If you are using the nVidia binary graphics driver (also known as 'blob')
-you need to use the +--force-xinerama+ flag (in your ~/.xsession) when starting
-i3, like so:
+before version 302.17, you need to use the +--force-xinerama+ flag (in your
+~/.xsession) when starting i3, like so:
 
 ----------------------------------------------
 exec i3 --force-xinerama -V >>~/.i3/i3log 2>&1
@@ -91,6 +92,12 @@ are connected to these outputs.
 
 Here is a short overview of the default keybindings:
 
+Mod1+Enter::
+Open a new terminal emulator window.
+
+Mod1+d::
+Open dmenu for starting any application by typing (part of) its name.
+
 j/k/l/;::
 Direction keys (left, down, up, right). They are on your homerow (see the mark
 on your "j" key). Alternatively, you can use the cursor keys.
@@ -261,19 +268,15 @@ xset -b
 # Enable zapping (C-A-<Bksp> kills X)
 setxkbmap -option terminate:ctrl_alt_bksp
 
-# Enforce correct locales from the beginning
-unset LC_COLLATE
-export LC_CTYPE=de_DE.UTF-8
-export LC_TIME=de_DE.UTF-8
-export LC_NUMERIC=de_DE.UTF-8
-export LC_MONETARY=de_DE.UTF-8
+# Enforce correct locales from the beginning:
+# LC_ALL is unset since it overwrites everything
+# LANG=de_DE.UTF-8 is used, except for:
+# LC_MESSAGES=C never translates program output
+# LC_TIME=en_DK leads to yyyy-mm-dd hh:mm date/time output
+unset LC_ALL
+export LANG=de_DE.UTF-8
 export LC_MESSAGES=C
-export LC_PAPER=de_DE.UTF-8
-export LC_NAME=de_DE.UTF-8
-export LC_ADDRESS=de_DE.UTF-8
-export LC_TELEPHONE=de_DE.UTF-8
-export LC_MEASUREMENT=de_DE.UTF-8
-export LC_IDENTIFICATION=de_DE.UTF-8
+export LC_TIME=en_DK.UTF-8
 
 # Use XToolkit in java applications
 export AWT_TOOLKIT=XToolkit
diff --git a/man/i3bar.man b/man/i3bar.man
new file mode 100644 (file)
index 0000000..fcefce7
--- /dev/null
@@ -0,0 +1,67 @@
+i3bar(1)
+========
+Axel Wagner <mail+i3bar@merovius.de>
+v4.1, October 2011
+
+== NAME
+
+i3bar - xcb-based status- and workspace-bar
+
+== SYNOPSIS
+
+*i3bar* [*-s* 'sock_path'] [*-b* 'bar_id'] [*-v*] [*-h*]
+
+== WARNING
+
+i3bar will automatically be invoked by i3 for every 'bar' configuration block.
+
+Starting it manually is usually not what you want to do.
+
+You have been warned!
+
+== OPTIONS
+
+*-s, --socket* 'sock_path'::
+Overwrites the path to the i3 IPC socket.
+
+*-b, --bar_id* 'bar_id'::
+Specifies the bar ID for which to get the configuration from i3.
+
+*-v, --version*::
+Display version number and exit.
+
+*-h, --help*::
+Display a short help-message and exit
+
+== DESCRIPTION
+
+*i3bar* displays a bar at the bottom (or top) of your monitor(s) containing
+workspace switching buttons and a statusline generated by i3status(1) or
+similar. It is automatically invoked (and configured through) i3.
+
+i3bar supports colors via a JSON protocol starting from v4.2, see
+http://i3wm.org/docs/i3bar-protocol.html
+
+== ENVIRONMENT
+
+=== I3SOCK
+
+Used as a fallback for the i3 IPC socket path if neither the commandline
+contains an argument nor the I3_SOCKET_PATH property is set on the X11 root
+window.
+
+== EXAMPLES
+
+Nothing to see here, move along. As stated above, you should not run i3bar manually.
+
+Instead, see the i3 documentation, especially the User’s Guide.
+
+== SEE ALSO
+
++i3status(1)+, +j4status(1)+ or +conky(1)+ for programs generating a statusline.
+
++dzen2(1)+ or +xmobar(1)+ for similar programs to i3bar.
+
+== AUTHORS
+
+Axel Wagner and contributors
diff --git a/man/man.mk b/man/man.mk
new file mode 100644 (file)
index 0000000..f999dc7
--- /dev/null
@@ -0,0 +1,32 @@
+DISTCLEAN_TARGETS += clean-mans
+
+A2X = a2x
+
+A2X_MAN_CALL = $(V_A2X)$(A2X) -f manpage --asciidoc-opts="-f man/asciidoc.conf" $(A2X_FLAGS) $<
+
+MANS_1 = \
+       man/i3.1 \
+       man/i3bar.1 \
+       man/i3-msg.1 \
+       man/i3-input.1 \
+       man/i3-nagbar.1 \
+       man/i3-config-wizard.1 \
+       man/i3-migrate-config-to-v4.1 \
+       man/i3-sensible-editor.1 \
+       man/i3-sensible-pager.1 \
+       man/i3-sensible-terminal.1 \
+       man/i3-dump-log.1
+
+MANS = \
+       $(MANS_1)
+
+mans: $(MANS)
+
+$(MANS_1): %.1: %.man man/asciidoc.conf
+       $(A2X_MAN_CALL)
+
+clean-mans:
+       for file in $(notdir $(MANS)); \
+       do \
+               rm -f man/$${file} man/$${file%.*}.html man/$${file%.*}.xml; \
+       done
index b0fb9e0118a4e0f462a10c762d6891864da8a97b..b4c9e005bd8ab16583bae3ef2dc8aeab8984f1f9 100644 (file)
@@ -66,10 +66,20 @@ state BORDER:
   border_style = 'normal', 'none', '1pixel', 'toggle'
       -> call cmd_border($border_style)
 
-# layout default|stacked|stacking|tabbed
+# layout default|stacked|stacking|tabbed|splitv|splith
+# layout toggle [split|all]
 state LAYOUT:
-  layout_mode = 'default', 'stacked', 'stacking', 'tabbed'
+  layout_mode = 'default', 'stacked', 'stacking', 'tabbed', 'splitv', 'splith'
       -> call cmd_layout($layout_mode)
+  'toggle'
+      -> LAYOUT_TOGGLE
+
+# layout toggle [split|all]
+state LAYOUT_TOGGLE:
+  end
+      -> call cmd_layout_toggle($toggle_mode)
+  toggle_mode = 'split', 'all'
+      -> call cmd_layout_toggle($toggle_mode)
 
 # append_layout <path>
 state APPEND_LAYOUT:
@@ -190,7 +200,7 @@ state RENAME_WORKSPACE_TO:
       -> call cmd_rename_workspace($old_name, $new_name)
 
 # move <direction> [<pixels> [px]]
-# move [window|container] [to] workspace <str>
+# move [window|container] [to] workspace [<str>|next|prev|current]
 # move [window|container] [to] output <str>
 # move [window|container] [to] scratchpad
 # move workspace to [output] <str>
@@ -231,7 +241,7 @@ state MOVE_DIRECTION_PX:
 state MOVE_WORKSPACE:
   'to'
       -> MOVE_WORKSPACE_TO_OUTPUT
-  workspace = 'next', 'prev', 'next_on_output', 'prev_on_output'
+  workspace = 'next', 'prev', 'next_on_output', 'prev_on_output', 'current'
       -> call cmd_move_con_to_workspace($workspace)
   'number'
       -> MOVE_WORKSPACE_NUMBER
diff --git a/src/Makefile b/src/Makefile
new file mode 100644 (file)
index 0000000..6a37f56
--- /dev/null
@@ -0,0 +1,10 @@
+all:
+       $(MAKE) -C .. i3
+
+install:
+       $(MAKE) -C .. install-i3
+
+clean:
+       $(MAKE) -C .. clean-i3
+
+.PHONY: all install clean
index ae4affaa53ce82421562e9404911e38d63d2ddd6..655816a3bc0ffd1ad464c92a3adbffe0f7c42c26 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "assignments.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
index cdf110d35cd4387479a3c772a305ffcb6c795729..8ee2a1dad75fdcb5f414b59437c9f46c9662e815 100644 (file)
@@ -66,6 +66,7 @@ EOL     (\r?\n)
 %x BAR_COLOR
 
 %x EXEC
+%x OPTRELEASE
 
 %%
 
@@ -159,7 +160,7 @@ EOL     (\r?\n)
                                   return STR;
                                 }
 <WANT_STRING>[^\n]+             { yy_pop_state(); yylval.string = sstrdup(yytext); return STR; }
-<OUTPUT_COND>[a-zA-Z0-9_-]+     { yy_pop_state(); yylval.string = sstrdup(yytext); return OUTPUT; }
+<OUTPUT_COND>[a-zA-Z0-9\/_-]+   { yy_pop_state(); yylval.string = sstrdup(yytext); return OUTPUT; }
 ^[ \t]*#[^\n]*                  { return TOKCOMMENT; }
 <COLOR_COND>#[0-9a-fA-F]+       { yy_pop_state(); yylval.string = sstrdup(yytext); return HEXCOLOR; }
 <COLOR_COND>{EOL}               {
@@ -172,12 +173,14 @@ EOL     (\r?\n)
 <ASSIGN_TARGET_COND>[ \t]+      { BEGIN(WANT_STRING); }
 <EXEC>--no-startup-id           { printf("no startup id\n"); yy_pop_state(); return TOK_NO_STARTUP_ID; }
 <EXEC>.                         { printf("anything else: *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); }
+<OPTRELEASE>--release           { printf("--release\n"); yy_pop_state(); return TOK_RELEASE; }
+<OPTRELEASE>.                   { printf("anything else (optrelease): *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); }
 [0-9-]+                         { yylval.number = atoi(yytext); return NUMBER; }
 bar                             { yy_push_state(BAR); return TOK_BAR; }
 mode                            { return TOKMODE; }
 bind                            { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; }
-bindcode                        { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; }
-bindsym                         { yy_push_state(BINDSYM_COND); yy_push_state(EAT_WHITESPACE); return TOKBINDSYM; }
+bindcode                        { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); yy_push_state(OPTRELEASE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; }
+bindsym                         { yy_push_state(BINDSYM_COND); yy_push_state(EAT_WHITESPACE); yy_push_state(OPTRELEASE); yy_push_state(EAT_WHITESPACE); return TOKBINDSYM; }
 floating_maximum_size           { return TOKFLOATING_MAXIMUM_SIZE; }
 floating_minimum_size           { return TOKFLOATING_MINIMUM_SIZE; }
 floating_modifier               { return TOKFLOATING_MODIFIER; }
@@ -200,6 +203,8 @@ new_float                       { return TOKNEWFLOAT; }
 normal                          { return TOK_NORMAL; }
 none                            { return TOK_NONE; }
 1pixel                          { return TOK_1PIXEL; }
+hide_edge_borders               { return TOK_HIDE_EDGE_BORDERS; }
+both                            { return TOK_BOTH; }
 focus_follows_mouse             { return TOKFOCUSFOLLOWSMOUSE; }
 force_focus_wrapping            { return TOK_FORCE_FOCUS_WRAPPING; }
 force_xinerama                  { return TOK_FORCE_XINERAMA; }
index ab8be57c120d6555d73a4712c452f0cad890bc96..29c519f0469f3aa6544993cb5701a9a8499e5759 100644 (file)
@@ -3,6 +3,8 @@
  * vim:ts=4:sw=4:expandtab
  *
  */
+#undef I3__FILE__
+#define I3__FILE__ "cfgparse.y"
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
@@ -19,6 +21,9 @@ static Barconfig current_bar;
  * store this in a separate variable because in the i3 config struct we just
  * store the i3Font. */
 static char *font_pattern;
+/* The path to the temporary script files used by i3-nagbar. We need to keep
+ * them around to delete the files in the i3-nagbar SIGCHLD handler. */
+static char *edit_script_path, *pager_script_path;
 
 typedef struct yy_buffer_state *YY_BUFFER_STATE;
 extern int yylex(struct context *context);
@@ -233,6 +238,12 @@ static char *migrate_config(char *input, off_t size) {
  */
 static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
     ev_child_stop(EV_A_ watcher);
+
+    if (unlink(edit_script_path) != 0)
+        warn("Could not delete temporary i3-nagbar script %s", edit_script_path);
+    if (unlink(pager_script_path) != 0)
+        warn("Could not delete temporary i3-nagbar script %s", pager_script_path);
+
     if (!WIFEXITED(watcher->rstatus)) {
         fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n");
         return;
@@ -264,6 +275,23 @@ static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) {
 }
 #endif
 
+/*
+ * Writes the given command as a shell script to path.
+ * Returns true unless something went wrong.
+ *
+ */
+static bool write_nagbar_script(const char *path, const char *command) {
+    int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IXUSR);
+    if (fd == -1) {
+        warn("Could not create temporary script to store the nagbar command");
+        return false;
+    }
+    write(fd, "#!/bin/sh\n", strlen("#!/bin/sh\n"));
+    write(fd, command, strlen(command));
+    close(fd);
+    return true;
+}
+
 /*
  * Starts an i3-nagbar process which alerts the user that his configuration
  * file contains one or more errors. Also offers two buttons: One to launch an
@@ -276,6 +304,18 @@ static void start_configerror_nagbar(const char *config_path) {
         return;
 
     fprintf(stderr, "Starting i3-nagbar due to configuration errors\n");
+
+    /* We need to create a custom script containing our actual command
+     * since not every terminal emulator which is contained in
+     * i3-sensible-terminal supports -e with multiple arguments (and not
+     * all of them support -e with one quoted argument either).
+     *
+     * NB: The paths need to be unique, that is, don’t assume users close
+     * their nagbars at any point in time (and they still need to work).
+     * */
+    edit_script_path = get_process_filename("nagbar-cfgerror-edit");
+    pager_script_path = get_process_filename("nagbar-cfgerror-pager");
+
     configerror_pid = fork();
     if (configerror_pid == -1) {
         warn("Could not fork()");
@@ -284,10 +324,17 @@ static void start_configerror_nagbar(const char *config_path) {
 
     /* child */
     if (configerror_pid == 0) {
+        char *edit_command, *pager_command;
+        sasprintf(&edit_command, "i3-sensible-editor \"%s\" && i3-msg reload\n", config_path);
+        sasprintf(&pager_command, "i3-sensible-pager \"%s\"\n", errorfilename);
+        if (!write_nagbar_script(edit_script_path, edit_command) ||
+            !write_nagbar_script(pager_script_path, pager_command))
+            return;
+
         char *editaction,
              *pageraction;
-        sasprintf(&editaction, "i3-sensible-terminal -e sh -c \"i3-sensible-editor \\\"%s\\\" && i3-msg reload\"", config_path);
-        sasprintf(&pageraction, "i3-sensible-terminal -e i3-sensible-pager \"%s\"", errorfilename);
+        sasprintf(&editaction, "i3-sensible-terminal -e \"%s\"", edit_script_path);
+        sasprintf(&pageraction, "i3-sensible-terminal -e \"%s\"", pager_script_path);
         char *argv[] = {
             NULL, /* will be replaced by the executable path */
             "-t",
@@ -384,8 +431,10 @@ static void check_for_duplicate_bindings(struct context *context) {
             /* Check if the keycodes or modifiers are different. If so, they
              * can't be duplicate */
             if (bind->keycode != current->keycode ||
-                bind->mods != current->mods)
+                bind->mods != current->mods ||
+                bind->release != current->release)
                 continue;
+
             context->has_errors = true;
             if (current->keycode != 0) {
                 ELOG("Duplicate keybinding in config file:\n  modmask %d with keycode %d, command \"%s\"\n",
@@ -690,6 +739,8 @@ void parse_file(const char *f) {
 %token                  TOK_NORMAL                  "normal"
 %token                  TOK_NONE                    "none"
 %token                  TOK_1PIXEL                  "1pixel"
+%token                  TOK_HIDE_EDGE_BORDERS       "hide_edge_borders"
+%token                  TOK_BOTH                    "both"
 %token                  TOKFOCUSFOLLOWSMOUSE        "focus_follows_mouse"
 %token                  TOK_FORCE_FOCUS_WRAPPING    "force_focus_wrapping"
 %token                  TOK_FORCE_XINERAMA          "force_xinerama"
@@ -735,6 +786,7 @@ void parse_file(const char *f) {
 %token                  TOK_BAR_COLOR_INACTIVE_WORKSPACE "inactive_workspace"
 %token                  TOK_BAR_COLOR_URGENT_WORKSPACE "urgent_workspace"
 %token                  TOK_NO_STARTUP_ID           "--no-startup-id"
+%token                  TOK_RELEASE                 "--release"
 
 %token              TOK_MARK            "mark"
 %token              TOK_CLASS           "class"
@@ -754,6 +806,8 @@ void parse_file(const char *f) {
 %type   <number>        layout_mode
 %type   <number>        border_style
 %type   <number>        new_window
+%type   <number>        hide_edge_borders
+%type   <number>        edge_hiding_mode
 %type   <number>        new_float
 %type   <number>        colorpixel
 %type   <number>        bool
@@ -762,6 +816,7 @@ void parse_file(const char *f) {
 %type   <number>        bar_mode_mode
 %type   <number>        bar_modifier_modifier
 %type   <number>        optional_no_startup_id
+%type   <number>        optional_release
 %type   <string>        command
 %type   <string>        word_or_number
 %type   <string>        qstring_or_number
@@ -788,6 +843,7 @@ line:
     | workspace_layout
     | new_window
     | new_float
+    | hide_edge_borders
     | focus_follows_mouse
     | force_focus_wrapping
     | force_xinerama
@@ -829,33 +885,40 @@ binding:
     ;
 
 bindcode:
-    binding_modifiers NUMBER command
+    optional_release binding_modifiers NUMBER command
     {
-        printf("\tFound keycode binding mod%d with key %d and command %s\n", $1, $2, $3);
+        DLOG("bindcode: release = %d, mod = %d, key = %d, command = %s\n", $1, $2, $3, $4);
         Binding *new = scalloc(sizeof(Binding));
 
-        new->keycode = $2;
-        new->mods = $1;
-        new->command = $3;
+        new->release = $1;
+        new->keycode = $3;
+        new->mods = $2;
+        new->command = $4;
 
         $$ = new;
     }
     ;
 
 bindsym:
-    binding_modifiers word_or_number command
+    optional_release binding_modifiers word_or_number command
     {
-        printf("\tFound keysym binding mod%d with key %s and command %s\n", $1, $2, $3);
+        DLOG("bindsym: release = %d, mod = %d, key = %s, command = %s\n", $1, $2, $3, $4);
         Binding *new = scalloc(sizeof(Binding));
 
-        new->symbol = $2;
-        new->mods = $1;
-        new->command = $3;
+        new->release = $1;
+        new->symbol = $3;
+        new->mods = $2;
+        new->command = $4;
 
         $$ = new;
     }
     ;
 
+optional_release:
+    /* empty */ { $$ = B_UPON_KEYPRESS; }
+    | TOK_RELEASE  { $$ = B_UPON_KEYRELEASE; }
+    ;
+
 for_window:
     TOK_FOR_WINDOW match command
     {
@@ -1429,6 +1492,22 @@ bool:
     }
     ;
 
+hide_edge_borders:
+    TOK_HIDE_EDGE_BORDERS edge_hiding_mode
+    {
+        DLOG("hide edge borders = %d\n", $2);
+        config.hide_edge_borders = $2;
+    }
+    ;
+
+edge_hiding_mode:
+    TOK_NONE        { $$ = ADJ_NONE; }
+    | TOK_VERT      { $$ = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE; }
+    | TOK_HORIZ     { $$ = ADJ_UPPER_SCREEN_EDGE | ADJ_LOWER_SCREEN_EDGE; }
+    | TOK_BOTH      { $$ = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE | ADJ_UPPER_SCREEN_EDGE | ADJ_LOWER_SCREEN_EDGE; }
+    | bool          { $$ = ($1 ? ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE : ADJ_NONE); }
+    ;
+
 focus_follows_mouse:
     TOKFOCUSFOLLOWSMOUSE bool
     {
index ca2a10371d20b1097a66a17f39277b3eb42eeb8b..23b6be4f15483d417101221f8f7a778c9cf0c2c5 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "click.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -35,13 +37,13 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press
     Con *resize_con = con;
     while (resize_con->type != CT_WORKSPACE &&
            resize_con->type != CT_FLOATING_CON &&
-           resize_con->parent->orientation != orientation)
+           con_orientation(resize_con->parent) != orientation)
         resize_con = resize_con->parent;
 
     DLOG("resize_con = %p\n", resize_con);
     if (resize_con->type != CT_WORKSPACE &&
         resize_con->type != CT_FLOATING_CON &&
-        resize_con->parent->orientation == orientation) {
+        con_orientation(resize_con->parent) == orientation) {
         first = resize_con;
         second = (way == 'n') ? TAILQ_NEXT(first, nodes) : TAILQ_PREV(first, nodes_head, nodes);
         if (second == TAILQ_END(&(first->nodes_head))) {
@@ -145,7 +147,7 @@ static bool tiling_resize(Con *con, xcb_button_press_event_t *event, const click
 
         if ((check_con->layout == L_STACKED ||
              check_con->layout == L_TABBED ||
-             check_con->orientation == HORIZ) &&
+             con_orientation(check_con) == HORIZ) &&
             con_num_children(check_con) > 1) {
             DLOG("Not handling this resize, this container has > 1 child.\n");
             return false;
index 0cd7852bc7f089e1d33a32b7a85cb35150e53942..2d8fce3cc410cf7802eb2a72c280cdbc1b238553 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "commands.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -23,7 +25,7 @@
 } while (0)
 
 /** When the command did not include match criteria (!), we use the currently
- * focused command. Do not confuse this case with a command which included
+ * focused container. 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.
  */
@@ -351,7 +353,7 @@ void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue) {
 
 /*
  * Implementation of 'move [window|container] [to] workspace
- * next|prev|next_on_output|prev_on_output'.
+ * next|prev|next_on_output|prev_on_output|current'.
  *
  */
 void cmd_move_con_to_workspace(I3_CMD, char *which) {
@@ -359,6 +361,15 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) {
 
     DLOG("which=%s\n", which);
 
+    /* We have nothing to move:
+     *  when criteria was specified but didn't match any window or
+     *  when criteria wasn't specified and we don't have any window focused. */
+    if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
+        (match_is_empty(current_match) && focused->type == CT_WORKSPACE)) {
+        ysuccess(false);
+        return;
+    }
+
     HANDLE_EMPTY_MATCH;
 
     /* get the workspace */
@@ -371,6 +382,8 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) {
         ws = workspace_next_on_output();
     else if (strcmp(which, "prev_on_output") == 0)
         ws = workspace_prev_on_output();
+    else if (strcmp(which, "current") == 0)
+        ws = con_get_workspace(focused);
     else {
         ELOG("BUG: called with which=%s\n", which);
         ysuccess(false);
@@ -400,9 +413,17 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) {
 
     owindow *current;
 
-    /* Error out early to not create a non-existing workspace (in
-     * workspace_get()) if we are not actually able to move anything. */
+    /* We have nothing to move:
+     *  when criteria was specified but didn't match any window or
+     *  when criteria wasn't specified and we don't have any window focused. */
+    if (!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) {
+        ELOG("No windows match your criteria, cannot move.\n");
+        ysuccess(false);
+        return;
+    }
+
     if (match_is_empty(current_match) && focused->type == CT_WORKSPACE) {
+        ELOG("No window to move, you have focused a workspace.\n");
         ysuccess(false);
         return;
     }
@@ -430,14 +451,16 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) {
 void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
     owindow *current;
 
-    /* Error out early to not create a non-existing workspace (in
-     * workspace_get()) if we are not actually able to move anything. */
-    if (match_is_empty(current_match) && focused->type == CT_WORKSPACE) {
+    /* We have nothing to move:
+     *  when criteria was specified but didn't match any window or
+     *  when criteria wasn't specified and we don't have any window focused. */
+    if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
+        (match_is_empty(current_match) && focused->type == CT_WORKSPACE)) {
         ysuccess(false);
         return;
     }
 
-    LOG("should move window to workspace with number %d\n", which);
+    LOG("should move window to workspace %s\n", which);
     /* get the workspace */
     Con *output, *workspace = NULL;
 
@@ -463,14 +486,7 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
             child->num == parsed_num);
 
     if (!workspace) {
-        y(map_open);
-        ystr("success");
-        y(bool, false);
-        ystr("error");
-        // TODO: better error message
-        ystr("No such workspace");
-        y(map_close);
-        return;
+        workspace = workspace_get(which, NULL);
     }
 
     HANDLE_EMPTY_MATCH;
@@ -504,6 +520,8 @@ static bool cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int
     LOG("tiling resize\n");
     /* get the appropriate current container (skip stacked/tabbed cons) */
     Con *current = focused;
+    Con *other = NULL;
+    double percentage = 0;
     while (current->parent->layout == L_STACKED ||
            current->parent->layout == L_TABBED)
         current = current->parent;
@@ -512,40 +530,50 @@ static bool cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int
     orientation_t search_orientation =
         (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0 ? HORIZ : VERT);
 
-    while (current->type != CT_WORKSPACE &&
-           current->type != CT_FLOATING_CON &&
-           current->parent->orientation != search_orientation)
-        current = current->parent;
+    do {
+        if (con_orientation(current->parent) != search_orientation) {
+            current = current->parent;
+            continue;
+        }
 
-    /* get the default percentage */
-    int children = con_num_children(current->parent);
-    Con *other;
-    LOG("ins. %d children\n", children);
-    double percentage = 1.0 / children;
-    LOG("default percentage = %f\n", percentage);
+        /* get the default percentage */
+        int children = con_num_children(current->parent);
+        LOG("ins. %d children\n", children);
+        percentage = 1.0 / children;
+        LOG("default percentage = %f\n", percentage);
 
-    orientation_t orientation = current->parent->orientation;
+        orientation_t orientation = con_orientation(current->parent);
 
-    if ((orientation == HORIZ &&
-         (strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0)) ||
-        (orientation == VERT &&
-         (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0))) {
-        LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
-            (orientation == HORIZ ? "horizontal" : "vertical"));
-        ysuccess(false);
-        return false;
-    }
+        if ((orientation == HORIZ &&
+             (strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0)) ||
+            (orientation == VERT &&
+             (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0))) {
+            LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
+                (orientation == HORIZ ? "horizontal" : "vertical"));
+            ysuccess(false);
+            return false;
+        }
 
-    if (strcmp(direction, "up") == 0 || strcmp(direction, "left") == 0) {
-        other = TAILQ_PREV(current, nodes_head, nodes);
-    } else {
-        other = TAILQ_NEXT(current, nodes);
-    }
-    if (other == TAILQ_END(workspaces)) {
-        LOG("No other container in this direction found, cannot resize.\n");
+        if (strcmp(direction, "up") == 0 || strcmp(direction, "left") == 0) {
+            other = TAILQ_PREV(current, nodes_head, nodes);
+        } else {
+            other = TAILQ_NEXT(current, nodes);
+        }
+        if (other == TAILQ_END(workspaces)) {
+            LOG("No other container in this direction found, trying to look further up in the tree...\n");
+            current = current->parent;
+            continue;
+        }
+        break;
+    } while (current->type != CT_WORKSPACE &&
+             current->type != CT_FLOATING_CON);
+
+    if (other == NULL) {
+        LOG("No other container in this direction found, trying to look further up in the tree...\n");
         ysuccess(false);
         return false;
     }
+
     LOG("other->percent = %f\n", other->percent);
     LOG("current->percent before = %f\n", current->percent);
     if (current->percent == 0.0)
@@ -585,7 +613,7 @@ static bool cmd_resize_tiling_width_height(I3_CMD, char *way, char *direction, i
 
     while (current->type != CT_WORKSPACE &&
            current->type != CT_FLOATING_CON &&
-           current->parent->orientation != search_orientation)
+           con_orientation(current->parent) != search_orientation)
         current = current->parent;
 
     /* get the default percentage */
@@ -594,7 +622,7 @@ static bool cmd_resize_tiling_width_height(I3_CMD, char *way, char *direction, i
     double percentage = 1.0 / children;
     LOG("default percentage = %f\n", percentage);
 
-    orientation_t orientation = current->parent->orientation;
+    orientation_t orientation = con_orientation(current->parent);
 
     if ((orientation == HORIZ &&
          strcmp(direction, "height") == 0) ||
@@ -808,17 +836,15 @@ void cmd_workspace_number(I3_CMD, char *which) {
             child->num == parsed_num);
 
     if (!workspace) {
-        LOG("There is no workspace with number %d, creating a new one.\n", parsed_num);
+        LOG("There is no workspace with number %ld, creating a new one.\n", parsed_num);
         ysuccess(true);
         /* terminate the which string after the endposition of the number */
         *endptr = '\0';
-        if (maybe_back_and_forth(cmd_output, which))
-            return;
         workspace_show_by_name(which);
         cmd_output->needs_tree_render = true;
         return;
     }
-    if (maybe_back_and_forth(cmd_output, which))
+    if (maybe_back_and_forth(cmd_output, workspace->name))
         return;
     workspace_show(workspace);
 
@@ -1088,9 +1114,17 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
  *
  */
 void cmd_split(I3_CMD, char *direction) {
+    owindow *current;
     /* TODO: use matches */
     LOG("splitting in direction %c\n", direction[0]);
-    tree_split(focused, (direction[0] == 'v' ? VERT : HORIZ));
+    if (match_is_empty(current_match))
+        tree_split(focused, (direction[0] == 'v' ? VERT : HORIZ));
+    else {
+        TAILQ_FOREACH(current, &owindows, owindows) {
+            DLOG("matching: %p / %s\n", current->con, current->con->name);
+            tree_split(current->con, (direction[0] == 'v' ? VERT : HORIZ));
+        }
+    }
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
@@ -1226,23 +1260,26 @@ void cmd_focus_window_mode(I3_CMD, char *window_mode) {
  *
  */
 void cmd_focus_level(I3_CMD, char *level) {
-    if (focused &&
-        focused->type != CT_WORKSPACE &&
-        focused->fullscreen_mode != CF_NONE) {
-        LOG("Cannot change focus while in fullscreen mode.\n");
-        ysuccess(false);
-        return;
-    }
-
     DLOG("level = %s\n", level);
+    bool success = false;
+
+    /* Focusing the parent can only be allowed if the newly
+     * focused container won't escape the fullscreen container. */
+    if (strcmp(level, "parent") == 0) {
+        if (focused && focused->parent) {
+            if (con_fullscreen_permits_focusing(focused->parent))
+                success = level_up();
+            else
+                ELOG("'focus parent': Currently in fullscreen, not going up\n");
+        }
+    }
 
-    if (strcmp(level, "parent") == 0)
-        level_up();
-    else level_down();
+    /* Focusing a child should always be allowed. */
+    else success = level_down();
 
-    cmd_output->needs_tree_render = true;
+    cmd_output->needs_tree_render = success;
     // XXX: default reply for now, make this a better reply
-    ysuccess(true);
+    ysuccess(success);
 }
 
 /*
@@ -1275,13 +1312,9 @@ void cmd_focus(I3_CMD) {
         if (!ws)
             continue;
 
-        /* Don't allow the focus switch if the focused and current
-         * containers are in the same workspace. */
-        if (focused &&
-            focused->type != CT_WORKSPACE &&
-            focused->fullscreen_mode != CF_NONE &&
-            con_get_workspace(focused) == ws) {
-            LOG("Cannot change focus while in fullscreen mode (same workspace).\n");
+        /* Check the fullscreen focus constraints. */
+        if (!con_fullscreen_permits_focusing(current->con)) {
+            LOG("Cannot change focus while in fullscreen mode (fullscreen rules).\n");
             ysuccess(false);
             return;
         }
@@ -1376,21 +1409,35 @@ void cmd_move_direction(I3_CMD, char *direction, char *move_px) {
 }
 
 /*
- * Implementation of 'layout default|stacked|stacking|tabbed'.
+ * Implementation of 'layout default|stacked|stacking|tabbed|splitv|splith'.
  *
  */
 void cmd_layout(I3_CMD, char *layout_str) {
     if (strcmp(layout_str, "stacking") == 0)
         layout_str = "stacked";
-    DLOG("changing layout to %s\n", layout_str);
     owindow *current;
-    int layout = (strcmp(layout_str, "default") == 0 ? L_DEFAULT :
-                  (strcmp(layout_str, "stacked") == 0 ? L_STACKED :
-                   L_TABBED));
+    int layout;
+    /* default is a special case which will be handled in con_set_layout(). */
+    if (strcmp(layout_str, "default") == 0)
+        layout = L_DEFAULT;
+    else if (strcmp(layout_str, "stacked") == 0)
+        layout = L_STACKED;
+    else if (strcmp(layout_str, "tabbed") == 0)
+        layout = L_TABBED;
+    else if (strcmp(layout_str, "splitv") == 0)
+        layout = L_SPLITV;
+    else if (strcmp(layout_str, "splith") == 0)
+        layout = L_SPLITH;
+    else {
+        ELOG("Unknown layout \"%s\", this is a mismatch between code and parser spec.\n", layout_str);
+        return;
+    }
+
+    DLOG("changing layout to %s (%d)\n", layout_str, layout);
 
     /* check if the match is empty, not if the result is empty */
     if (match_is_empty(current_match))
-        con_set_layout(focused->parent, layout);
+        con_set_layout(focused, layout);
     else {
         TAILQ_FOREACH(current, &owindows, owindows) {
             DLOG("matching: %p / %s\n", current->con, current->con->name);
@@ -1403,12 +1450,40 @@ void cmd_layout(I3_CMD, char *layout_str) {
     ysuccess(true);
 }
 
+/*
+ * Implementation of 'layout toggle [all|split]'.
+ *
+ */
+void cmd_layout_toggle(I3_CMD, char *toggle_mode) {
+    owindow *current;
+
+    if (toggle_mode == NULL)
+        toggle_mode = "default";
+
+    DLOG("toggling layout (mode = %s)\n", toggle_mode);
+
+    /* check if the match is empty, not if the result is empty */
+    if (match_is_empty(current_match))
+        con_toggle_layout(focused, toggle_mode);
+    else {
+        TAILQ_FOREACH(current, &owindows, owindows) {
+            DLOG("matching: %p / %s\n", current->con, current->con->name);
+            con_toggle_layout(current->con, toggle_mode);
+        }
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    ysuccess(true);
+}
+
 /*
  * Implementaiton of 'exit'.
  *
  */
 void cmd_exit(I3_CMD) {
     LOG("Exiting due to user command.\n");
+    xcb_disconnect(conn);
     exit(0);
 
     /* unreached */
@@ -1421,6 +1496,7 @@ void cmd_exit(I3_CMD) {
 void cmd_reload(I3_CMD) {
     LOG("reloading\n");
     kill_configerror_nagbar(false);
+    kill_commanderror_nagbar(false);
     load_configuration(conn, NULL, true);
     x_set_i3_atoms();
     /* Send an IPC event just in case the ws names have changed */
@@ -1449,6 +1525,7 @@ void cmd_restart(I3_CMD) {
 void cmd_open(I3_CMD) {
     LOG("opening new container\n");
     Con *con = tree_open_con(NULL, NULL);
+    con->layout = L_SPLITH;
     con_focus(con);
 
     y(map_open);
index 73a14565ff3201ff0f8b741fcfdadf8ee51cd3a6..d739f4e146ad7ae11449829c464457a019d4df96 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "commands_parser.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -104,7 +106,6 @@ static void push_string(const char *identifier, char *str) {
 // XXX: ideally, this would be const char. need to check if that works with all
 // called functions.
 static char *get_string(const char *identifier) {
-    DLOG("Getting string %s from stack...\n", identifier);
     for (int c = 0; c < 10; c++) {
         if (stack[c].identifier == NULL)
             break;
@@ -115,7 +116,6 @@ static char *get_string(const char *identifier) {
 }
 
 static void clear_stack(void) {
-    DLOG("clearing stack.\n");
     for (int c = 0; c < 10; c++) {
         if (stack[c].str != NULL)
             free(stack[c].str);
@@ -187,8 +187,6 @@ static struct CommandResult command_output;
 
 static void next_state(const cmdp_token *token) {
     if (token->next_state == __CALL) {
-        DLOG("should call stuff, yay. call_id = %d\n",
-                token->extra.call_identifier);
         subcommand_output.json_gen = command_output.json_gen;
         subcommand_output.needs_tree_render = false;
         GENERATED_call(token->extra.call_identifier, &subcommand_output);
@@ -206,9 +204,8 @@ static void next_state(const cmdp_token *token) {
     }
 }
 
-/* TODO: Return parsing errors via JSON. */
 struct CommandResult *parse_command(const char *input) {
-    DLOG("new parser handling: %s\n", input);
+    DLOG("COMMAND: *%s*\n", input);
     state = INITIAL;
 
 /* A YAJL JSON generator used for formatting replies. */
@@ -240,19 +237,14 @@ struct CommandResult *parse_command(const char *input) {
                 *walk == '\r' || *walk == '\n') && *walk != '\0')
             walk++;
 
-        DLOG("remaining input = %s\n", walk);
-
         cmdp_token_ptr *ptr = &(tokens[state]);
         token_handled = false;
         for (c = 0; c < ptr->n; c++) {
             token = &(ptr->array[c]);
-            DLOG("trying token %d = %s\n", c, token->name);
 
             /* A literal. */
             if (token->name[0] == '\'') {
-                DLOG("literal\n");
                 if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
-                    DLOG("found literal, moving to next state\n");
                     if (token->identifier != NULL)
                         push_string(token->identifier, sstrdup(token->name + 1));
                     walk += strlen(token->name) - 1;
@@ -265,7 +257,6 @@ struct CommandResult *parse_command(const char *input) {
 
             if (strcmp(token->name, "string") == 0 ||
                 strcmp(token->name, "word") == 0) {
-                DLOG("parsing this as a string\n");
                 const char *beginning = walk;
                 /* Handle quoted strings (or words). */
                 if (*walk == '"') {
@@ -310,7 +301,6 @@ struct CommandResult *parse_command(const char *input) {
                     }
                     if (token->identifier)
                         push_string(token->identifier, str);
-                    DLOG("str is \"%s\"\n", str);
                     /* If we are at the end of a quoted string, skip the ending
                      * double quote. */
                     if (*walk == '"')
@@ -322,9 +312,7 @@ struct CommandResult *parse_command(const char *input) {
             }
 
             if (strcmp(token->name, "end") == 0) {
-                DLOG("checking for the end token.\n");
                 if (*walk == '\0' || *walk == ',' || *walk == ';') {
-                    DLOG("yes, indeed. end\n");
                     next_state(token);
                     token_handled = true;
                     /* To make sure we start with an appropriate matching
@@ -389,14 +377,19 @@ struct CommandResult *parse_command(const char *input) {
                 position[(copywalk - input)] = (copywalk >= walk ? '^' : ' ');
             position[len] = '\0';
 
-            printf("%s\n", errormessage);
-            printf("Your command: %s\n", input);
-            printf("              %s\n", position);
+            ELOG("%s\n", errormessage);
+            ELOG("Your command: %s\n", input);
+            ELOG("              %s\n", position);
 
             /* Format this error message as a JSON reply. */
             y(map_open);
             ystr("success");
             y(bool, false);
+            /* We set parse_error to true to distinguish this from other
+             * errors. i3-nagbar is spawned upon keypresses only for parser
+             * errors. */
+            ystr("parse_error");
+            y(bool, true);
             ystr("error");
             ystr(errormessage);
             ystr("input");
@@ -414,7 +407,6 @@ struct CommandResult *parse_command(const char *input) {
 
     y(array_close);
 
-    DLOG("command_output.needs_tree_render = %d\n", command_output.needs_tree_render);
     return &command_output;
 }
 
@@ -427,15 +419,23 @@ struct CommandResult *parse_command(const char *input) {
 
 /*
  * Logs the given message to stdout while prefixing the current time to it,
- * but only if the corresponding debug loglevel was activated.
+ * but only if debug logging was activated.
  * This is to be called by DLOG() which includes filename/linenumber
  *
  */
-void debuglog(uint64_t lev, char *fmt, ...) {
+void debuglog(char *fmt, ...) {
+    va_list args;
+
+    va_start(args, fmt);
+    fprintf(stdout, "# ");
+    vfprintf(stdout, fmt, args);
+    va_end(args);
+}
+
+void errorlog(char *fmt, ...) {
     va_list args;
 
     va_start(args, fmt);
-    fprintf(stderr, "# ");
     vfprintf(stderr, fmt, args);
     va_end(args);
 }
index 23cf156887c8d85af07e3afcd43be3311557bfaf..1d86d73a9825f44d7cdb8ca7e98efcd4ad857e8d 100644 (file)
--- a/src/con.c
+++ b/src/con.c
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "con.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -217,8 +219,8 @@ bool con_accepts_window(Con *con) {
     if (con->type == CT_WORKSPACE)
         return false;
 
-    if (con->orientation != NO_ORIENTATION) {
-        DLOG("container %p does not accepts windows, orientation != NO_ORIENTATION\n", con);
+    if (con->split) {
+        DLOG("container %p does not accept windows, it is a split container.\n", con);
         return false;
     }
 
@@ -265,8 +267,11 @@ Con *con_parent_with_orientation(Con *con, orientation_t orientation) {
     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)
+        /* Abort when we reach a floating con, or an output con */
+        if (parent &&
+            (parent->type == CT_FLOATING_CON ||
+             parent->type == CT_OUTPUT ||
+             (parent->parent && parent->parent->type == CT_OUTPUT)))
             parent = NULL;
         if (parent == NULL)
             break;
@@ -576,11 +581,27 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
         return;
     }
 
+    /* Prevent moving if this would violate the fullscreen focus restrictions. */
+    if (!con_fullscreen_permits_focusing(workspace)) {
+        LOG("Cannot move out of a fullscreen container");
+        return;
+    }
+
     if (con_is_floating(con)) {
         DLOG("Using FLOATINGCON instead\n");
         con = con->parent;
     }
 
+    Con *source_ws = con_get_workspace(con);
+    if (workspace == source_ws) {
+        DLOG("Not moving, already there\n");
+        return;
+    }
+
+    /* Save the current workspace. So we can call workspace_show() by the end
+     * of this function. */
+    Con *current_ws = con_get_workspace(focused);
+
     Con *source_output = con_get_output(con),
         *dest_output = con_get_output(workspace);
 
@@ -665,8 +686,12 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
         /* Descend focus stack in case focus_next is a workspace which can
          * occur if we move to the same workspace.  Also show current workspace
          * to ensure it is focused. */
-        workspace_show(con_get_workspace(focus_next));
-        con_focus(con_descend_focused(focus_next));
+        workspace_show(current_ws);
+
+        /* Set focus only if con was on current workspace before moving.
+         * Otherwise we would give focus to some window on different workspace. */
+        if (source_ws == current_ws)
+            con_focus(con_descend_focused(focus_next));
     }
 
     CALL(parent, on_remove_child);
@@ -679,14 +704,32 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
  *
  */
 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;
+    switch (con->layout) {
+        case L_SPLITV:
+        /* stacking containers behave like they are in vertical orientation */
+        case L_STACKED:
+            return VERT;
+
+        case L_SPLITH:
+        /* tabbed containers behave like they are in vertical orientation */
+        case L_TABBED:
+            return HORIZ;
+
+        case L_DEFAULT:
+            DLOG("Someone called con_orientation() on a con with L_DEFAULT, this is a bug in the code.\n");
+            assert(false);
+            return HORIZ;
+
+        case L_DOCKAREA:
+        case L_OUTPUT:
+            DLOG("con_orientation() called on dockarea/output (%d) container %p\n", con->layout, con);
+            assert(false);
+            return HORIZ;
+
+        default:
+            DLOG("con_orientation() ran into default\n");
+            assert(false);
+    }
 }
 
 /*
@@ -895,12 +938,46 @@ Con *con_descend_direction(Con *con, direction_t direction) {
  *
  */
 Rect con_border_style_rect(Con *con) {
-    switch (con_border_style(con)) {
+    adjacent_t borders_to_hide = ADJ_NONE;
+    Rect result;
+    /* Shortcut to avoid calling con_adjacent_borders() on dock containers. */
+    int border_style = con_border_style(con);
+    if (border_style == BS_NONE)
+        return (Rect){ 0, 0, 0, 0 };
+    borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
+    switch (border_style) {
     case BS_NORMAL:
-        return (Rect){2, 0, -(2 * 2), -2};
+        result = (Rect){2, 0, -(2 * 2), -2};
+        if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) {
+            result.x -= 2;
+            result.width += 2;
+        }
+        if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) {
+            result.width += 2;
+        }
+        /* With normal borders we never hide the upper border */
+        if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) {
+            result.height += 2;
+        }
+        return result;
 
     case BS_1PIXEL:
-        return (Rect){1, 1, -2, -2};
+        result = (Rect){1, 1, -2, -2};
+        if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) {
+            result.x -= 1;
+            result.width += 1;
+        }
+        if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) {
+            result.width += 1;
+        }
+        if (borders_to_hide & ADJ_UPPER_SCREEN_EDGE) {
+            result.y -= 1;
+            result.height += 1;
+        }
+        if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) {
+            result.height += 1;
+        }
+        return result;
 
     case BS_NONE:
         return (Rect){0, 0, 0, 0};
@@ -910,6 +987,24 @@ Rect con_border_style_rect(Con *con) {
     }
 }
 
+/*
+ * Returns adjacent borders of the window. We need this if hide_edge_borders is
+ * enabled.
+ */
+adjacent_t con_adjacent_borders(Con *con) {
+    adjacent_t result = ADJ_NONE;
+    Con *workspace = con_get_workspace(con);
+    if (con->rect.x == workspace->rect.x)
+        result |= ADJ_LEFT_SCREEN_EDGE;
+    if (con->rect.x + con->rect.width == workspace->rect.x + workspace->rect.width)
+        result |= ADJ_RIGHT_SCREEN_EDGE;
+    if (con->rect.y == workspace->rect.y)
+        result |= ADJ_UPPER_SCREEN_EDGE;
+    if (con->rect.y + con->rect.height == workspace->rect.y + workspace->rect.height)
+        result |= ADJ_LOWER_SCREEN_EDGE;
+    return result;
+}
+
 /*
  * 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.
@@ -989,33 +1084,43 @@ void con_set_border_style(Con *con, int border_style) {
  *
  */
 void con_set_layout(Con *con, int layout) {
+    DLOG("con_set_layout(%p, %d), con->type = %d\n",
+         con, layout, con->type);
+
+    /* Users can focus workspaces, but not any higher in the hierarchy.
+     * Focus on the workspace is a special case, since in every other case, the
+     * user means "change the layout of the parent split container". */
+    if (con->type != CT_WORKSPACE)
+        con = con->parent;
+
+    /* We fill in last_split_layout when switching to a different layout
+     * since there are many places in the code that don’t use
+     * con_set_layout(). */
+    if (con->layout == L_SPLITH || con->layout == L_SPLITV)
+        con->last_split_layout = con->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) {
+    if (con->type == CT_WORKSPACE &&
+        (layout == L_STACKED || layout == L_TABBED)) {
         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 */
+        /* 2: Set the requested layout on the split container and mark it as
+         * split. */
         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;
-        }
+        new->last_split_layout = con->last_split_layout;
+        new->split = true;
 
         Con *old_focused = TAILQ_FIRST(&(con->focus_head));
         if (old_focused == TAILQ_END(&(con->focus_head)))
             old_focused = NULL;
 
-        /* 4: move the existing cons of this workspace below the new con */
+        /* 3: move the existing cons of this workspace below the new con */
         DLOG("Moving cons\n");
         Con *child;
         while (!TAILQ_EMPTY(&(con->nodes_head))) {
@@ -1036,7 +1141,72 @@ void con_set_layout(Con *con, int layout) {
         return;
     }
 
-    con->layout = layout;
+    if (layout == L_DEFAULT) {
+        /* Special case: the layout formerly known as "default" (in combination
+         * with an orientation). Since we switched to splith/splitv layouts,
+         * using the "default" layout (which "only" should happen when using
+         * legacy configs) is using the last split layout (either splith or
+         * splitv) in order to still do the same thing.
+         *
+         * Starting from v4.6 though, we will nag users about using "layout
+         * default", and in v4.9 we will remove it entirely (with an
+         * appropriate i3-migrate-config mechanism). */
+        con->layout = con->last_split_layout;
+        /* In case last_split_layout was not initialized… */
+        if (con->layout == L_DEFAULT)
+            con->layout = L_SPLITH;
+    } else {
+        con->layout = layout;
+    }
+}
+
+/*
+ * This function toggles the layout of a given container. toggle_mode can be
+ * either 'default' (toggle only between stacked/tabbed/last_split_layout),
+ * 'split' (toggle only between splitv/splith) or 'all' (toggle between all
+ * layouts).
+ *
+ */
+void con_toggle_layout(Con *con, const char *toggle_mode) {
+    Con *parent = con;
+    /* Users can focus workspaces, but not any higher in the hierarchy.
+     * Focus on the workspace is a special case, since in every other case, the
+     * user means "change the layout of the parent split container". */
+    if (con->type != CT_WORKSPACE)
+        parent = con->parent;
+    DLOG("con_toggle_layout(%p, %s), parent = %p\n", con, toggle_mode, parent);
+
+    if (strcmp(toggle_mode, "split") == 0) {
+        /* Toggle between splits. When the current layout is not a split
+         * layout, we just switch back to last_split_layout. Otherwise, we
+         * change to the opposite split layout. */
+        if (parent->layout != L_SPLITH && parent->layout != L_SPLITV)
+            con_set_layout(con, parent->last_split_layout);
+        else {
+            if (parent->layout == L_SPLITH)
+                con_set_layout(con, L_SPLITV);
+            else con_set_layout(con, L_SPLITH);
+        }
+    } else {
+        if (parent->layout == L_STACKED)
+            con_set_layout(con, L_TABBED);
+        else if (parent->layout == L_TABBED) {
+            if (strcmp(toggle_mode, "all") == 0)
+                con_set_layout(con, L_SPLITH);
+            else con_set_layout(con, parent->last_split_layout);
+        } else if (parent->layout == L_SPLITH || parent->layout == L_SPLITV) {
+            if (strcmp(toggle_mode, "all") == 0) {
+                /* When toggling through all modes, we toggle between
+                 * splith/splitv, whereas normally we just directly jump to
+                 * stacked. */
+                if (parent->layout == L_SPLITH)
+                    con_set_layout(con, L_SPLITV);
+                else con_set_layout(con, L_STACKED);
+            } else {
+                con_set_layout(con, L_STACKED);
+            }
+        }
+    }
 }
 
 /*
@@ -1113,12 +1283,12 @@ Rect con_minimum_size(Con *con) {
     /* For horizontal/vertical split containers we sum up the width (h-split)
      * or height (v-split) and use the maximum of the height (h-split) or width
      * (v-split) as minimum size. */
-    if (con->orientation == HORIZ || con->orientation == VERT) {
+    if (con->split) {
         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) {
+            if (con->layout == L_SPLITH) {
                 width += min.width;
                 height = max(height, min.height);
             } else {
@@ -1130,7 +1300,70 @@ Rect con_minimum_size(Con *con) {
         return (Rect){ 0, 0, width, height };
     }
 
-    ELOG("Unhandled case, type = %d, layout = %d, orientation = %d\n",
-         con->type, con->layout, con->orientation);
+    ELOG("Unhandled case, type = %d, layout = %d, split = %d\n",
+         con->type, con->layout, con->split);
     assert(false);
 }
+
+/*
+ * Returns true if changing the focus to con would be allowed considering
+ * the fullscreen focus constraints. Specifically, if a fullscreen container or
+ * any of its descendants is focused, this function returns true if and only if
+ * focusing con would mean that focus would still be visible on screen, i.e.,
+ * the newly focused container would not be obscured by a fullscreen container.
+ *
+ * In the simplest case, if a fullscreen container or any of its descendants is
+ * fullscreen, this functions returns true if con is the fullscreen container
+ * itself or any of its descendants, as this means focus wouldn't escape the
+ * boundaries of the fullscreen container.
+ *
+ * In case the fullscreen container is of type CF_OUTPUT, this function returns
+ * true if con is on a different workspace, as focus wouldn't be obscured by
+ * the fullscreen container that is constrained to a different workspace.
+ *
+ * Note that this same logic can be applied to moving containers. If a
+ * container can be focused under the fullscreen focus constraints, it can also
+ * become a parent or sibling to the currently focused container.
+ *
+ */
+bool con_fullscreen_permits_focusing(Con *con) {
+    /* No focus, no problem. */
+    if (!focused)
+        return true;
+
+    /* Find the first fullscreen ascendent. */
+    Con *fs = focused;
+    while (fs && fs->fullscreen_mode == CF_NONE)
+        fs = fs->parent;
+
+    /* fs must be non-NULL since the workspace con doesn’t have CF_NONE and
+     * there always has to be a workspace con in the hierarchy. */
+    assert(fs != NULL);
+    /* The most common case is we hit the workspace level. In this
+     * situation, changing focus is also harmless. */
+    assert(fs->fullscreen_mode != CF_NONE);
+    if (fs->type == CT_WORKSPACE)
+        return true;
+
+    /* Allow it if the container itself is the fullscreen container. */
+    if (con == fs)
+        return true;
+
+    /* If fullscreen is per-output, the focus being in a different workspace is
+     * sufficient to guarantee that change won't leave fullscreen in bad shape. */
+    if (fs->fullscreen_mode == CF_OUTPUT &&
+        con_get_workspace(con) != con_get_workspace(fs)) {
+            return true;
+    }
+
+    /* Allow it only if the container to be focused is contained within the
+     * current fullscreen container. */
+    do {
+        if (con->parent == fs)
+            return true;
+        con = con->parent;
+    } while (con);
+
+    /* Focusing con would hide it behind a fullscreen window, disallow it. */
+    return false;
+}
index 50ec28234030702fe573d7a8ea858d2bb97b6300..fcf3841ed3be21387a334deef437e974d4c82171 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "config.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -24,27 +26,27 @@ struct barconfig_head barconfigs = TAILQ_HEAD_INITIALIZER(barconfigs);
  *
  */
 void ungrab_all_keys(xcb_connection_t *conn) {
-        DLOG("Ungrabbing all keys\n");
-        xcb_ungrab_key(conn, XCB_GRAB_ANY, root, XCB_BUTTON_MASK_ANY);
+    DLOG("Ungrabbing all keys\n");
+    xcb_ungrab_key(conn, XCB_GRAB_ANY, root, XCB_BUTTON_MASK_ANY);
 }
 
 static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint32_t keycode) {
-        DLOG("Grabbing %d\n", keycode);
-        /* Grab the key in all combinations */
-        #define GRAB_KEY(modifier) \
-                do { \
-                        xcb_grab_key(conn, 0, root, modifier, keycode, \
-                                     XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); \
-                } while (0)
-        int mods = bind->mods;
-        if ((bind->mods & BIND_MODE_SWITCH) != 0) {
-                mods &= ~BIND_MODE_SWITCH;
-                if (mods == 0)
-                        mods = XCB_MOD_MASK_ANY;
-        }
-        GRAB_KEY(mods);
-        GRAB_KEY(mods | xcb_numlock_mask);
-        GRAB_KEY(mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
+    DLOG("Grabbing %d\n", keycode);
+    /* Grab the key in all combinations */
+    #define GRAB_KEY(modifier) \
+        do { \
+            xcb_grab_key(conn, 0, root, modifier, keycode, \
+                         XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); \
+        } while (0)
+    int mods = bind->mods;
+    if ((bind->mods & BIND_MODE_SWITCH) != 0) {
+        mods &= ~BIND_MODE_SWITCH;
+        if (mods == 0)
+            mods = XCB_MOD_MASK_ANY;
+    }
+    GRAB_KEY(mods);
+    GRAB_KEY(mods | xcb_numlock_mask);
+    GRAB_KEY(mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
 }
 
 /*
@@ -52,29 +54,55 @@ static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint
  * or NULL if no such binding exists.
  *
  */
-Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode) {
-        Binding *bind;
+Binding *get_binding(uint16_t modifiers, bool key_release, xcb_keycode_t keycode) {
+    Binding *bind;
 
+    if (!key_release) {
+        /* On a KeyPress event, we first reset all
+         * B_UPON_KEYRELEASE_IGNORE_MODS bindings back to B_UPON_KEYRELEASE */
         TAILQ_FOREACH(bind, bindings, bindings) {
-                /* First compare the modifiers */
-                if (bind->mods != modifiers)
-                        continue;
-
-                /* If a symbol was specified by the user, we need to look in
-                 * the array of translated keycodes for the event’s keycode */
-                if (bind->symbol != NULL) {
-                        if (memmem(bind->translated_to,
-                                   bind->number_keycodes * sizeof(xcb_keycode_t),
-                                   &keycode, sizeof(xcb_keycode_t)) != NULL)
-                                break;
-                } else {
-                        /* This case is easier: The user specified a keycode */
-                        if (bind->keycode == keycode)
-                                break;
-                }
+            if (bind->release == B_UPON_KEYRELEASE_IGNORE_MODS)
+                bind->release = B_UPON_KEYRELEASE;
+        }
+
+        /* Then we transition the KeyRelease bindings into a state where the
+         * modifiers no longer matter for the KeyRelease event so that users
+         * can release the modifier key before releasing the actual key. */
+        TAILQ_FOREACH(bind, bindings, bindings) {
+            if (bind->release == B_UPON_KEYRELEASE && !key_release)
+                bind->release = B_UPON_KEYRELEASE_IGNORE_MODS;
+        }
+    }
+
+    TAILQ_FOREACH(bind, bindings, bindings) {
+        /* First compare the modifiers (unless this is a
+         * B_UPON_KEYRELEASE_IGNORE_MODS binding and this is a KeyRelease
+         * event) */
+        if (bind->mods != modifiers &&
+            (bind->release != B_UPON_KEYRELEASE_IGNORE_MODS ||
+             !key_release))
+            continue;
+
+        /* Check if the binding is for a KeyPress or a KeyRelease event */
+        if ((bind->release == B_UPON_KEYPRESS && key_release) ||
+            (bind->release >= B_UPON_KEYRELEASE && !key_release))
+            continue;
+
+        /* If a symbol was specified by the user, we need to look in
+         * the array of translated keycodes for the event’s keycode */
+        if (bind->symbol != NULL) {
+            if (memmem(bind->translated_to,
+                       bind->number_keycodes * sizeof(xcb_keycode_t),
+                       &keycode, sizeof(xcb_keycode_t)) != NULL)
+                break;
+        } else {
+            /* This case is easier: The user specified a keycode */
+            if (bind->keycode == keycode)
+                break;
         }
+    }
 
-        return (bind == TAILQ_END(bindings) ? NULL : bind);
+    return (bind == TAILQ_END(bindings) ? NULL : bind);
 }
 
 /*
@@ -131,22 +159,22 @@ void translate_keysyms(void) {
  *
  */
 void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) {
-        Binding *bind;
-        TAILQ_FOREACH(bind, bindings, bindings) {
-                if ((bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) == 0) ||
-                    (!bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) != 0))
-                        continue;
-
-                /* The easy case: the user specified a keycode directly. */
-                if (bind->keycode > 0) {
-                        grab_keycode_for_binding(conn, bind, bind->keycode);
-                        continue;
-                }
-
-                xcb_keycode_t *walk = bind->translated_to;
-                for (int i = 0; i < bind->number_keycodes; i++)
-                        grab_keycode_for_binding(conn, bind, *walk++);
+    Binding *bind;
+    TAILQ_FOREACH(bind, bindings, bindings) {
+        if ((bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) == 0) ||
+            (!bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) != 0))
+            continue;
+
+        /* The easy case: the user specified a keycode directly. */
+        if (bind->keycode > 0) {
+            grab_keycode_for_binding(conn, bind, bind->keycode);
+            continue;
         }
+
+        xcb_keycode_t *walk = bind->translated_to;
+        for (int i = 0; i < bind->number_keycodes; i++)
+            grab_keycode_for_binding(conn, bind, *walk++);
+    }
 }
 
 /*
@@ -154,22 +182,22 @@ void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) {
  *
  */
 void switch_mode(const char *new_mode) {
-        struct Mode *mode;
+    struct Mode *mode;
 
-        LOG("Switching to mode %s\n", new_mode);
+    LOG("Switching to mode %s\n", new_mode);
 
-        SLIST_FOREACH(mode, &modes, modes) {
-                if (strcasecmp(mode->name, new_mode) != 0)
-                        continue;
+    SLIST_FOREACH(mode, &modes, modes) {
+        if (strcasecmp(mode->name, new_mode) != 0)
+            continue;
 
-                ungrab_all_keys(conn);
-                bindings = mode->bindings;
-                translate_keysyms();
-                grab_all_keys(conn, false);
-                return;
-        }
+        ungrab_all_keys(conn);
+        bindings = mode->bindings;
+        translate_keysyms();
+        grab_all_keys(conn, false);
+        return;
+    }
 
-        ELOG("ERROR: Mode not found\n");
+    ELOG("ERROR: Mode not found\n");
 }
 
 /*
@@ -392,7 +420,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
         grab_all_keys(conn, false);
     }
 
-    if (config.font.id == 0) {
+    if (config.font.type == FONT_TYPE_NONE) {
         ELOG("You did not specify required configuration option \"font\"\n");
         config.font = load_font("fixed", true);
         set_font(&config.font);
index 30822353db2ab69a4e48e801759e12209f4a011c..2dcdb56a428ee4b8ba7df87824c2121c305d83a4 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "debug.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
diff --git a/src/display_version.c b/src/display_version.c
new file mode 100644 (file)
index 0000000..ac1a622
--- /dev/null
@@ -0,0 +1,172 @@
+#undef I3__FILE__
+#define I3__FILE__ "key_press.c"
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * display_version.c: displays the running i3 version, runs as part of
+ *                    i3 --moreversion.
+ *
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <fcntl.h>
+#include "all.h"
+
+static bool human_readable_key;
+static char *human_readable_version;
+
+#if YAJL_MAJOR >= 2
+static int version_string(void *ctx, const unsigned char *val, size_t len) {
+#else
+static int version_string(void *ctx, const unsigned char *val, unsigned int len) {
+#endif
+    if (human_readable_key)
+        sasprintf(&human_readable_version, "%.*s", (int)len, val);
+    return 1;
+}
+
+#if YAJL_MAJOR >= 2
+static int version_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) {
+#else
+static int version_map_key(void *ctx, const unsigned char *stringval, unsigned int stringlen) {
+#endif
+    human_readable_key = (stringlen == strlen("human_readable") &&
+                   strncmp((const char*)stringval, "human_readable", strlen("human_readable")) == 0);
+    return 1;
+}
+
+static yajl_callbacks version_callbacks = {
+    NULL, /* null */
+    NULL, /* boolean */
+    NULL, /* integer */
+    NULL, /* double */
+    NULL, /* number */
+    &version_string,
+    NULL, /* start_map */
+    &version_map_key,
+    NULL, /* end_map */
+    NULL, /* start_array */
+    NULL /* end_array */
+};
+
+/*
+ * Connects to i3 to find out the currently running version. Useful since it
+ * might be different from the version compiled into this binary (maybe the
+ * user didn’t correctly install i3 or forgot te restart it).
+ *
+ * The output looks like this:
+ * Running i3 version: 4.2-202-gb8e782c (2012-08-12, branch "next") (pid 14804)
+ *
+ * The i3 binary you just called: /home/michael/i3/i3
+ * The i3 binary you are running: /home/michael/i3/i3
+ *
+ */
+void display_running_version(void) {
+    char *socket_path = root_atom_contents("I3_SOCKET_PATH");
+    if (socket_path == NULL)
+        exit(EXIT_SUCCESS);
+
+    char *pid_from_atom = root_atom_contents("I3_PID");
+    if (pid_from_atom == NULL) {
+        /* If I3_PID is not set, the running version is older than 4.2-200. */
+        printf("\nRunning version: < 4.2-200\n");
+        exit(EXIT_SUCCESS);
+    }
+
+    /* Inform the user of what we are doing. While a single IPC request is
+     * really fast normally, in case i3 hangs, this will not terminate. */
+    printf("(Getting version from running i3, press ctrl-c to abort…)");
+    fflush(stdout);
+
+    /* TODO: refactor this with the code for sending commands */
+    int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
+    if (sockfd == -1)
+        err(EXIT_FAILURE, "Could not create socket");
+
+    struct sockaddr_un addr;
+    memset(&addr, 0, sizeof(struct sockaddr_un));
+    addr.sun_family = AF_LOCAL;
+    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
+    if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
+        err(EXIT_FAILURE, "Could not connect to i3");
+
+    if (ipc_send_message(sockfd, 0, I3_IPC_MESSAGE_TYPE_GET_VERSION,
+                         (uint8_t*)"") == -1)
+        err(EXIT_FAILURE, "IPC: write()");
+
+    uint32_t reply_length;
+    uint8_t *reply;
+    int ret;
+    if ((ret = ipc_recv_message(sockfd, I3_IPC_MESSAGE_TYPE_GET_VERSION,
+                                &reply_length, &reply)) != 0) {
+        if (ret == -1)
+            err(EXIT_FAILURE, "IPC: read()");
+        exit(EXIT_FAILURE);
+    }
+
+#if YAJL_MAJOR >= 2
+    yajl_handle handle = yajl_alloc(&version_callbacks, NULL, NULL);
+#else
+    yajl_parser_config parse_conf = { 0, 0 };
+
+    yajl_handle handle = yajl_alloc(&version_callbacks, &parse_conf, NULL, NULL);
+#endif
+
+    yajl_status state = yajl_parse(handle, (const unsigned char*)reply, (int)reply_length);
+    if (state != yajl_status_ok)
+        errx(EXIT_FAILURE, "Could not parse my own reply. That's weird. reply is %.*s", (int)reply_length, reply);
+
+    printf("\rRunning i3 version: %s (pid %s)\n", human_readable_version, pid_from_atom);
+
+#ifdef __linux__
+    char exepath[PATH_MAX],
+         destpath[PATH_MAX];
+    ssize_t linksize;
+
+    snprintf(exepath, sizeof(exepath), "/proc/%d/exe", getpid());
+
+    if ((linksize = readlink(exepath, destpath, sizeof(destpath))) == -1)
+        err(EXIT_FAILURE, "readlink(%s)", exepath);
+
+    /* readlink() does not NULL-terminate strings, so we have to. */
+    destpath[linksize] = '\0';
+
+    printf("\n");
+    printf("The i3 binary you just called: %s\n", destpath);
+
+    snprintf(exepath, sizeof(exepath), "/proc/%s/exe", pid_from_atom);
+
+    if ((linksize = readlink(exepath, destpath, sizeof(destpath))) == -1)
+        err(EXIT_FAILURE, "readlink(%s)", exepath);
+
+    /* readlink() does not NULL-terminate strings, so we have to. */
+    destpath[linksize] = '\0';
+
+    /* Check if "(deleted)" is the readlink result. If so, the running version
+     * does not match the file on disk. */
+    if (strstr(destpath, "(deleted)") != NULL)
+        printf("RUNNING BINARY DIFFERENT FROM BINARY ON DISK!\n");
+
+    /* Since readlink() might put a "(deleted)" somewhere in the buffer and
+     * stripping that out seems hackish and ugly, we read the process’s argv[0]
+     * instead. */
+    snprintf(exepath, sizeof(exepath), "/proc/%s/cmdline", pid_from_atom);
+
+    int fd;
+    if ((fd = open(exepath, O_RDONLY)) == -1)
+        err(EXIT_FAILURE, "open(%s)", exepath);
+    if (read(fd, destpath, sizeof(destpath)) == -1)
+        err(EXIT_FAILURE, "read(%s)", exepath);
+    close(fd);
+
+    printf("The i3 binary you are running: %s\n", destpath);
+#endif
+
+    yajl_free(handle);
+}
index 1c6d918bc30bce445c5c516cbf30fb3b0d38631c..45d4e5fec19bff8578f40cb2cd4d3876fda211e1 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "ewmh.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
index 512a808f94c2133840ed05c1fac2b5103fb2a65d..e115329952fc14f0a2860276d856b11a8f354ba9 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "fake_outputs.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
index b90eac3e3fde78eb1da2ace2796affea5eafd2c7..3d2c1d3191fb4f8b1367bf19a2f2aaf9cf64e691 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "floating.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -40,7 +42,7 @@ void floating_enable(Con *con, bool automatic) {
     }
 
     /* 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
+     * split-container with the same layout 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) {
@@ -52,7 +54,7 @@ void floating_enable(Con *con, bool automatic) {
         /* TODO: refactor this with src/con.c:con_set_layout */
         Con *new = con_new(NULL, NULL);
         new->parent = con;
-        new->orientation = con->orientation;
+        new->layout = con->layout;
 
         /* since the new container will be set into floating mode directly
          * afterwards, we need to copy the workspace rect. */
@@ -97,8 +99,9 @@ void floating_enable(Con *con, bool automatic) {
      * otherwise. */
     Con *ws = con_get_workspace(con);
     nc->parent = ws;
-    nc->orientation = NO_ORIENTATION;
+    nc->split = true;
     nc->type = CT_FLOATING_CON;
+    nc->layout = L_SPLITH;
     /* We insert nc already, even though its rect is not yet calculated. This
      * is necessary because otherwise the workspace might be empty (and get
      * closed in tree_close()) even though it’s not. */
@@ -174,11 +177,25 @@ void floating_enable(Con *con, bool automatic) {
             nc->rect.width = max(nc->rect.width, config.floating_minimum_width);
     }
 
-    /* add pixels for the decoration */
-    /* TODO: don’t add them when the user automatically puts new windows into
-     * 1pixel/borderless mode */
-    nc->rect.height += deco_height + 2;
-    nc->rect.width += 4;
+    /* 3: attach the child to the new parent container. We need to do this
+     * because con_border_style_rect() needs to access con->parent. */
+    con->parent = nc;
+    con->percent = 1.0;
+    con->floating = FLOATING_USER_ON;
+
+    /* 4: set the border style as specified with new_float */
+    if (automatic)
+        con->border_style = config.default_floating_border;
+
+    /* Add pixels for the decoration. */
+    Rect border_style_rect = con_border_style_rect(con);
+
+    nc->rect.height -= border_style_rect.height;
+    nc->rect.width -= border_style_rect.width;
+
+    /* Add some more pixels for the title bar */
+    if(con_border_style(con) == BS_NORMAL)
+        nc->rect.height += deco_height;
 
     /* Honor the X11 border */
     nc->rect.height += con->border_width * 2;
@@ -218,15 +235,6 @@ void floating_enable(Con *con, bool automatic) {
 
     DLOG("Floating rect: (%d, %d) with %d x %d\n", nc->rect.x, nc->rect.y, nc->rect.width, nc->rect.height);
 
-    /* 3: attach the child to the new parent container */
-    con->parent = nc;
-    con->percent = 1.0;
-    con->floating = FLOATING_USER_ON;
-
-    /* 4: set the border style as specified with new_float */
-    if (automatic)
-        con->border_style = config.default_floating_border;
-
     /* 5: Subtract the deco_height in order to make the floating window appear
      * at precisely the position it specified in its original geometry (which
      * is what applications might remember). */
@@ -492,7 +500,7 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
                 confine_to, border_t border, callback_t callback, const void *extra)
 {
     uint32_t new_x, new_y;
-    Rect old_rect;
+    Rect old_rect = { 0, 0, 0, 0 };
     if (con != NULL)
         memcpy(&old_rect, &(con->rect), sizeof(Rect));
 
@@ -619,12 +627,12 @@ void floating_fix_coordinates(Con *con, Rect *old_rect, Rect *new_rect) {
     int32_t rel_y = (con->rect.y - old_rect->y);
     /* Then we calculate a fraction, for example 0.63 for a window
      * which is at y = 1212 of a 1920 px high output */
-    double fraction_x = ((double)rel_x / (int32_t)old_rect->width);
-    double fraction_y = ((double)rel_y / (int32_t)old_rect->height);
     DLOG("rel_x = %d, rel_y = %d, fraction_x = %f, fraction_y = %f, output->w = %d, output->h = %d\n",
-         rel_x, rel_y, fraction_x, fraction_y, old_rect->width, old_rect->height);
-    con->rect.x = (int32_t)new_rect->x + (fraction_x * (int32_t)new_rect->width);
-    con->rect.y = (int32_t)new_rect->y + (fraction_y * (int32_t)new_rect->height);
+          rel_x, rel_y, (double)rel_x / old_rect->width, (double)rel_y / old_rect->height,
+          old_rect->width, old_rect->height);
+    /* Here we have to multiply at first. Or we will lose precision when not compiled with -msse2 */
+    con->rect.x = (int32_t)new_rect->x + (double)(rel_x * (int32_t)new_rect->width)  / (int32_t)old_rect->width;
+    con->rect.y = (int32_t)new_rect->y + (double)(rel_y * (int32_t)new_rect->height) / (int32_t)old_rect->height;
     DLOG("Resulting coordinates: x = %d, y = %d\n", con->rect.x, con->rect.y);
 }
 
index d63a4e5c873684df28286dea79f31b99de278a84..21a873420d989430394f7d1b25043ea76ca6e100 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "handlers.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -77,58 +79,6 @@ bool event_is_ignored(const int sequence, const int response_type) {
     return false;
 }
 
-
-/*
- * There was a key press. We compare this key code with our bindings table and pass
- * the bound action to parse_command().
- *
- */
-static void handle_key_press(xcb_key_press_event_t *event) {
-
-    last_timestamp = event->time;
-
-    DLOG("Keypress %d, state raw = %d\n", event->detail, event->state);
-
-    /* Remove the numlock bit, all other bits are modifiers we can bind to */
-    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;
-        }
-    }
-
-    char *command_copy = sstrdup(bind->command);
-    struct CommandResult *command_output = parse_command(command_copy);
-    free(command_copy);
-
-    if (command_output->needs_tree_render)
-        tree_render();
-
-    yajl_gen_free(command_output->json_gen);
-}
-
 /*
  * Called with coordinates of an enter_notify event or motion_notify event
  * to check if the user crossed virtual screen boundaries and adjust the
@@ -263,6 +213,7 @@ static void handle_motion_notify(xcb_motion_notify_event_t *event) {
 
     Con *con;
     if ((con = con_by_frame_id(event->event)) == NULL) {
+        DLOG("MotionNotify for an unknown container, checking if it crosses screen boundaries.\n");
         check_crossing_screen_boundary(event->root_x, event->root_y);
         return;
     }
@@ -468,6 +419,8 @@ static void handle_screen_change(xcb_generic_event_t *e) {
 
     randr_query_outputs();
 
+    scratchpad_fix_resolution();
+
     ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}");
 
     return;
@@ -686,12 +639,29 @@ static void handle_client_message(xcb_client_message_event_t *event) {
         }
 
         tree_render();
-        x_push_changes(croot);
+    } else if (event->type == A__NET_ACTIVE_WINDOW) {
+        DLOG("_NET_ACTIVE_WINDOW: Window 0x%08x should be activated\n", event->window);
+        Con *con = con_by_window_id(event->window);
+        if (con == NULL) {
+            DLOG("Could not get window for client message\n");
+            return;
+        }
+
+        Con *ws = con_get_workspace(con);
+        if (!workspace_is_visible(ws)) {
+            DLOG("Workspace not visible, ignoring _NET_ACTIVE_WINDOW\n");
+            return;
+        }
+
+        if (ws != con_get_workspace(focused))
+            workspace_show(ws);
+
+        con_focus(con);
+        tree_render();
     } else if (event->type == A_I3_SYNC) {
-        DLOG("i3 sync, yay\n");
         xcb_window_t window = event->data.data32[0];
         uint32_t rnd = event->data.data32[1];
-        DLOG("Sending random value %d back to X11 window 0x%08x\n", rnd, window);
+        DLOG("[i3 sync protocol] Sending random value %d back to X11 window 0x%08x\n", rnd, window);
 
         void *reply = scalloc(32);
         xcb_client_message_event_t *ev = reply;
@@ -1075,6 +1045,7 @@ void handle_event(int type, xcb_generic_event_t *event) {
 
     switch (type) {
         case XCB_KEY_PRESS:
+        case XCB_KEY_RELEASE:
             handle_key_press((xcb_key_press_event_t*)event);
             break;
 
diff --git a/src/i3.mk b/src/i3.mk
new file mode 100644 (file)
index 0000000..a93cc4c
--- /dev/null
+++ b/src/i3.mk
@@ -0,0 +1,85 @@
+ALL_TARGETS += i3
+INSTALL_TARGETS += install-i3
+CLEAN_TARGETS += clean-i3
+
+i3_SOURCES_GENERATED  = src/cfgparse.tab.c src/cfgparse.yy.c
+i3_SOURCES           := $(filter-out $(i3_SOURCES_GENERATED),$(wildcard src/*.c))
+i3_HEADERS_CMDPARSER := $(wildcard include/GENERATED_*.h)
+i3_HEADERS           := $(filter-out $(i3_HEADERS_CMDPARSER),$(wildcard include/*.h))
+i3_CFLAGS             = $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(XCB_WM_CFLAGS) $(X11_CFLAGS) $(XCURSOR_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) $(PCRE_CFLAGS) $(LIBSN_CFLAGS)
+i3_LIBS               = $(XCB_LIBS) $(XCB_KBD_LIBS) $(XCB_WM_LIBS) $(X11_LIBS) $(XCURSOR_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) $(PCRE_LIBS) $(LIBSN_LIBS) -lm -lpthread
+
+# When using clang, we use pre-compiled headers to speed up the build. With
+# gcc, this actually makes the build slower.
+ifeq ($(CC),clang)
+i3_HEADERS_DEP       := $(i3_HEADERS) include/all.h.pch
+PCH_FLAGS            := -include include/all.h
+else
+i3_HEADERS_DEP       := $(i3_HEADERS)
+PCH_FLAGS            :=
+endif
+
+i3_OBJECTS := $(i3_SOURCES_GENERATED:.c=.o) $(i3_SOURCES:.c=.o)
+
+# The basename/pwd calls are for canonicalizing the path: Instead
+# of src/main.c, we will see something like ../i3-4.2/src/main.c in
+# debugger backtraces, making it clearer which code belongs to i3 and
+# which code doesn’t.
+# We only do this for src/ since all the other subdirectories contain i3 in
+# their name already.
+canonical_path := ../$(shell basename $(shell pwd -P))
+
+include/all.h.pch: $(i3_HEADERS)
+       echo "[i3] PCH all.h"
+       $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -x c-header include/all.h -o include/all.h.pch
+
+src/%.o: src/%.c $(i3_HEADERS_DEP)
+       echo "[i3] CC $<"
+       $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(PCH_FLAGS) -c -o $@ ${canonical_path}/$<
+
+src/cfgparse.yy.c: src/cfgparse.l src/cfgparse.tab.o $(i3_HEADERS_DEP)
+       echo "[i3] LEX $<"
+       $(FLEX) -i -o $@ ${canonical_path}/$<
+
+src/cfgparse.tab.c: src/cfgparse.y $(i3_HEADERS_DEP)
+       echo "[i3] YACC $<"
+       $(BISON) --debug --verbose -b $(basename $< .y) -d ${canonical_path}/$<
+
+# This target compiles the command parser twice:
+# Once with -DTEST_PARSER, creating a stand-alone executable used for tests,
+# and once as an object file for i3.
+src/commands_parser.o: src/commands_parser.c $(i3_HEADERS_DEP) i3-command-parser.stamp
+       echo "[i3] CC $<"
+       $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(I3_LDFLAGS) $(LDFLAGS) -DTEST_PARSER -o test.commands_parser $< $(LIBS) $(i3_LIBS)
+       $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ ${canonical_path}/$<
+
+i3-command-parser.stamp: generate-command-parser.pl parser-specs/commands.spec
+       echo "[i3] Generating command parser"
+       (cd include; ../generate-command-parser.pl)
+       touch $@
+
+i3: libi3.a $(i3_OBJECTS)
+       echo "[i3] Link i3"
+       $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(LIBS) $(i3_LIBS)
+
+install-i3: i3
+       echo "[i3] Install"
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(SYSCONFDIR)/i3
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/include/i3
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/xsessions
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/applications
+       $(INSTALL) -m 0755 i3 $(DESTDIR)$(PREFIX)/bin/
+       $(INSTALL) -m 0755 i3-migrate-config-to-v4 $(DESTDIR)$(PREFIX)/bin/
+       $(INSTALL) -m 0755 i3-sensible-editor $(DESTDIR)$(PREFIX)/bin/
+       $(INSTALL) -m 0755 i3-sensible-pager $(DESTDIR)$(PREFIX)/bin/
+       $(INSTALL) -m 0755 i3-sensible-terminal $(DESTDIR)$(PREFIX)/bin/
+       test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config
+       test -e $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes || $(INSTALL) -m 0644 i3.config.keycodes $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes
+       $(INSTALL) -m 0644 i3.xsession.desktop $(DESTDIR)$(PREFIX)/share/xsessions/i3.desktop
+       $(INSTALL) -m 0644 i3.applications.desktop $(DESTDIR)$(PREFIX)/share/applications/i3.desktop
+       $(INSTALL) -m 0644 include/i3/ipc.h $(DESTDIR)$(PREFIX)/include/i3/
+
+clean-i3:
+       echo "[i3] Clean"
+       rm -f $(i3_OBJECTS) $(i3_SOURCES_GENERATED) $(i3_HEADERS_CMDPARSER) include/loglevels.h loglevels.tmp include/all.h.pch i3-command-parser.stamp i3 src/*.gcno src/cfgparse.{output,dot} src/cmdparse.*
index 60ce814540d6c7a3c61ac3969b1a652770226c06..1c6de798803bf2f0b729558aef819f523448077e 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "ipc.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -161,17 +163,14 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
     ystr("type");
     y(integer, con->type);
 
+    /* provided for backwards compatibility only. */
     ystr("orientation");
-    switch (con->orientation) {
-        case NO_ORIENTATION:
-            ystr("none");
-            break;
-        case HORIZ:
+    if (!con->split)
+        ystr("none");
+    else {
+        if (con_orientation(con) == HORIZ)
             ystr("horizontal");
-            break;
-        case VERT:
-            ystr("vertical");
-            break;
+        else ystr("vertical");
     }
 
     ystr("scratchpad_state");
@@ -203,10 +202,20 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
     ystr("focused");
     y(bool, (con == focused));
 
+    ystr("split");
+    y(bool, con->split);
+
     ystr("layout");
     switch (con->layout) {
         case L_DEFAULT:
-            ystr("default");
+            DLOG("About to dump layout=default, this is a bug in the code.\n");
+            assert(false);
+            break;
+        case L_SPLITV:
+            ystr("splitv");
+            break;
+        case L_SPLITH:
+            ystr("splith");
             break;
         case L_STACKED:
             ystr("stacked");
@@ -222,6 +231,16 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
             break;
     }
 
+    ystr("last_split_layout");
+    switch (con->layout) {
+        case L_SPLITV:
+            ystr("splitv");
+            break;
+        default:
+            ystr("splith");
+            break;
+    }
+
     ystr("border");
     switch (con->border_style) {
         case BS_NORMAL:
@@ -240,8 +259,8 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
     dump_rect(gen, "geometry", con->geometry);
 
     ystr("name");
-    if (con->window && con->window->name_json)
-        ystr(con->window->name_json);
+    if (con->window && con->window->name)
+        ystr(i3string_as_utf8(con->window->name));
     else
         ystr(con->name);
 
@@ -519,6 +538,44 @@ IPC_HANDLER(get_marks) {
     y(free);
 }
 
+/*
+ * Returns the version of i3
+ *
+ */
+IPC_HANDLER(get_version) {
+#if YAJL_MAJOR >= 2
+    yajl_gen gen = yajl_gen_alloc(NULL);
+#else
+    yajl_gen gen = yajl_gen_alloc(NULL, NULL);
+#endif
+    y(map_open);
+
+    ystr("major");
+    y(integer, MAJOR_VERSION);
+
+    ystr("minor");
+    y(integer, MINOR_VERSION);
+
+    ystr("patch");
+    y(integer, PATCH_VERSION);
+
+    ystr("human_readable");
+    ystr(I3_VERSION);
+
+    y(map_close);
+
+    const unsigned char *payload;
+#if YAJL_MAJOR >= 2
+    size_t length;
+#else
+    unsigned int length;
+#endif
+    y(get_buf, &payload, &length);
+
+    ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_VERSION, payload);
+    y(free);
+}
+
 /*
  * Formats the reply message for a GET_BAR_CONFIG request and sends it to the
  * client.
@@ -703,7 +760,7 @@ static int add_subscription(void *extra, const unsigned char *s,
 #endif
     ipc_client *client = extra;
 
-    DLOG("should add subscription to extra %p, sub %.*s\n", client, len, s);
+    DLOG("should add subscription to extra %p, sub %.*s\n", client, (int)len, s);
     int event = client->num_events;
 
     client->num_events++;
@@ -775,7 +832,7 @@ IPC_HANDLER(subscribe) {
 
 /* The index of each callback function corresponds to the numeric
  * value of the message type (see include/i3/ipc.h) */
-handler_t handlers[7] = {
+handler_t handlers[8] = {
     handle_command,
     handle_get_workspaces,
     handle_subscribe,
@@ -783,6 +840,7 @@ handler_t handlers[7] = {
     handle_tree,
     handle_get_marks,
     handle_get_bar_config,
+    handle_get_version,
 };
 
 /*
diff --git a/src/key_press.c b/src/key_press.c
new file mode 100644 (file)
index 0000000..5919e64
--- /dev/null
@@ -0,0 +1,312 @@
+#undef I3__FILE__
+#define I3__FILE__ "key_press.c"
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * key_press.c: key press handler
+ *
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include "all.h"
+
+static int current_nesting_level;
+static bool parse_error_key;
+static bool command_failed;
+
+/* XXX: I don’t want to touch too much of the nagbar code at once, but we
+ * should refactor this with src/cfgparse.y into a clean generic nagbar
+ * interface. It might come in handy in other situations within i3, too. */
+static char *pager_script_path;
+static pid_t nagbar_pid = -1;
+
+/*
+ * Handler which will be called when we get a SIGCHLD for the nagbar, meaning
+ * it exited (or could not be started, depending on the exit code).
+ *
+ */
+static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
+    ev_child_stop(EV_A_ watcher);
+
+    if (unlink(pager_script_path) != 0)
+        warn("Could not delete temporary i3-nagbar script %s", pager_script_path);
+
+    if (!WIFEXITED(watcher->rstatus)) {
+        fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n");
+        return;
+    }
+
+    int exitcode = WEXITSTATUS(watcher->rstatus);
+    printf("i3-nagbar process exited with status %d\n", exitcode);
+    if (exitcode == 2) {
+        fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n");
+    }
+
+    nagbar_pid = -1;
+}
+
+/* We need ev >= 4 for the following code. Since it is not *that* important (it
+ * only makes sure that there are no i3-nagbar instances left behind) we still
+ * support old systems with libev 3. */
+#if EV_VERSION_MAJOR >= 4
+/*
+ * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal
+ * SIGKILL (9) to make sure there are no left-over i3-nagbar processes.
+ *
+ */
+static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) {
+    if (nagbar_pid != -1) {
+        LOG("Sending SIGKILL (%d) to i3-nagbar with PID %d\n", SIGKILL, nagbar_pid);
+        kill(nagbar_pid, SIGKILL);
+    }
+}
+#endif
+
+/*
+ * Writes the given command as a shell script to path.
+ * Returns true unless something went wrong.
+ *
+ */
+static bool write_nagbar_script(const char *path, const char *command) {
+    int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IXUSR);
+    if (fd == -1) {
+        warn("Could not create temporary script to store the nagbar command");
+        return false;
+    }
+    write(fd, "#!/bin/sh\n", strlen("#!/bin/sh\n"));
+    write(fd, command, strlen(command));
+    close(fd);
+    return true;
+}
+
+/*
+ * Starts an i3-nagbar process which alerts the user that his configuration
+ * file contains one or more errors. Also offers two buttons: One to launch an
+ * $EDITOR on the config file and another one to launch a $PAGER on the error
+ * logfile.
+ *
+ */
+static void start_commanderror_nagbar(void) {
+    if (nagbar_pid != -1) {
+        DLOG("i3-nagbar for command error already running, not starting again.\n");
+        return;
+    }
+
+    DLOG("Starting i3-nagbar due to command error\n");
+
+    /* We need to create a custom script containing our actual command
+     * since not every terminal emulator which is contained in
+     * i3-sensible-terminal supports -e with multiple arguments (and not
+     * all of them support -e with one quoted argument either).
+     *
+     * NB: The paths need to be unique, that is, don’t assume users close
+     * their nagbars at any point in time (and they still need to work).
+     * */
+    pager_script_path = get_process_filename("nagbar-cfgerror-pager");
+
+    nagbar_pid = fork();
+    if (nagbar_pid == -1) {
+        warn("Could not fork()");
+        return;
+    }
+
+    /* child */
+    if (nagbar_pid == 0) {
+        char *pager_command;
+        sasprintf(&pager_command, "i3-sensible-pager \"%s\"\n", errorfilename);
+        if (!write_nagbar_script(pager_script_path, pager_command))
+            return;
+
+        char *pageraction;
+        sasprintf(&pageraction, "i3-sensible-terminal -e \"%s\"", pager_script_path);
+        char *argv[] = {
+            NULL, /* will be replaced by the executable path */
+            "-t",
+            "error",
+            "-m",
+            "The configured command for this shortcut could not be run successfully.",
+            "-b",
+            "show errors",
+            pageraction,
+            NULL
+        };
+        exec_i3_utility("i3-nagbar", argv);
+    }
+
+    /* parent */
+    /* install a child watcher */
+    ev_child *child = smalloc(sizeof(ev_child));
+    ev_child_init(child, &nagbar_exited, nagbar_pid, 0);
+    ev_child_start(main_loop, child);
+
+/* We need ev >= 4 for the following code. Since it is not *that* important (it
+ * only makes sure that there are no i3-nagbar instances left behind) we still
+ * support old systems with libev 3. */
+#if EV_VERSION_MAJOR >= 4
+    /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is
+     * still running) */
+    ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup));
+    ev_cleanup_init(cleanup, nagbar_cleanup);
+    ev_cleanup_start(main_loop, cleanup);
+#endif
+}
+
+/*
+ * Kills the commanderror i3-nagbar process, if any.
+ *
+ * Called when reloading/restarting, since the user probably fixed his wrong
+ * keybindings.
+ *
+ * If wait_for_it is set (restarting), this function will waitpid(), otherwise,
+ * ev is assumed to handle it (reloading).
+ *
+ */
+void kill_commanderror_nagbar(bool wait_for_it) {
+    if (nagbar_pid == -1)
+        return;
+
+    if (kill(nagbar_pid, SIGTERM) == -1)
+        warn("kill(configerror_nagbar) failed");
+
+    if (!wait_for_it)
+        return;
+
+    /* When restarting, we don’t enter the ev main loop anymore and after the
+     * exec(), our old pid is no longer watched. So, ev won’t handle SIGCHLD
+     * for us and we would end up with a <defunct> process. Therefore we
+     * waitpid() here. */
+    waitpid(nagbar_pid, NULL, 0);
+}
+
+static int json_boolean(void *ctx, int boolval) {
+    DLOG("Got bool: %d, parse_error_key %d, nesting_level %d\n", boolval, parse_error_key, current_nesting_level);
+
+    if (parse_error_key && current_nesting_level == 1 && boolval)
+        command_failed = true;
+
+    return 1;
+}
+
+#if YAJL_MAJOR >= 2
+static int json_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) {
+#else
+static int json_map_key(void *ctx, const unsigned char *stringval, unsigned int stringlen) {
+#endif
+    parse_error_key = (stringlen >= strlen("parse_error") &&
+                       strncmp((const char*)stringval, "parse_error", strlen("parse_error")) == 0);
+    return 1;
+}
+
+static int json_start_map(void *ctx) {
+    current_nesting_level++;
+    return 1;
+}
+
+static int json_end_map(void *ctx) {
+    current_nesting_level--;
+    return 1;
+}
+
+static yajl_callbacks command_error_callbacks = {
+    NULL,
+    &json_boolean,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    &json_start_map,
+    &json_map_key,
+    &json_end_map,
+    NULL,
+    NULL
+};
+
+/*
+ * There was a KeyPress or KeyRelease (both events have the same fields). We
+ * compare this key code with our bindings table and pass the bound action to
+ * parse_command().
+ *
+ */
+void handle_key_press(xcb_key_press_event_t *event) {
+    bool key_release = (event->response_type == XCB_KEY_RELEASE);
+
+    last_timestamp = event->time;
+
+    DLOG("%s %d, state raw = %d\n", (key_release ? "KeyRelease" : "KeyPress"), 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, key_release, 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, key_release, event->detail)) == NULL) {
+            /* This is not a real error since we can have release and
+             * non-release keybindings. On a KeyPress event for which there is
+             * only a !release-binding, but no release-binding, the
+             * corresponding KeyRelease event will trigger this. No problem,
+             * though. */
+            DLOG("Could not lookup key binding (modifiers %d, keycode %d)\n",
+                 state_filtered, event->detail);
+            return;
+        }
+    }
+
+    char *command_copy = sstrdup(bind->command);
+    struct CommandResult *command_output = parse_command(command_copy);
+    free(command_copy);
+
+    if (command_output->needs_tree_render)
+        tree_render();
+
+    /* We parse the JSON reply to figure out whether there was an error
+     * ("success" being false in on of the returned dictionaries). */
+    const unsigned char *reply;
+#if YAJL_MAJOR >= 2
+    size_t length;
+    yajl_handle handle = yajl_alloc(&command_error_callbacks, NULL, NULL);
+#else
+    unsigned int length;
+    yajl_parser_config parse_conf = { 0, 0 };
+
+    yajl_handle handle = yajl_alloc(&command_error_callbacks, &parse_conf, NULL, NULL);
+#endif
+    yajl_gen_get_buf(command_output->json_gen, &reply, &length);
+
+    current_nesting_level = 0;
+    parse_error_key = false;
+    command_failed = false;
+    yajl_status state = yajl_parse(handle, reply, length);
+    if (state != yajl_status_ok) {
+        ELOG("Could not parse my own reply. That's weird. reply is %.*s\n", (int)length, reply);
+    } else {
+        if (command_failed)
+            start_commanderror_nagbar();
+    }
+
+    yajl_free(handle);
+
+    yajl_gen_free(command_output->json_gen);
+}
index a8063dcada1d7cc6b875cb65d4690a57d13a0dbb..795fb6d8ce516e86ecb9e40df40751bdae09b4a8 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "load_layout.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -139,7 +141,7 @@ 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);
+    LOG("string: %.*s for key %s\n", (int)len, val, last_key);
     if (parsing_swallows) {
         /* TODO: the other swallowing keys */
         if (strcasecmp(last_key, "class") == 0) {
@@ -156,15 +158,25 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) {
             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) {
+            /* Upgrade path from older versions of i3 (doing an inplace restart
+             * to a newer version):
+             * "orientation" is dumped before "layout". Therefore, we store
+             * whether the orientation was horizontal or vertical in the
+             * last_split_layout. When we then encounter layout == "default",
+             * we will use the last_split_layout as layout instead. */
             char *buf = NULL;
             sasprintf(&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;
+            if (strcasecmp(buf, "none") == 0 ||
+                strcasecmp(buf, "horizontal") == 0)
+                json_node->last_split_layout = L_SPLITH;
             else if (strcasecmp(buf, "vertical") == 0)
-                json_node->orientation = VERT;
+                json_node->last_split_layout = L_SPLITV;
             else LOG("Unhandled orientation: %s\n", buf);
+
+            /* What used to be an implicit check whether orientation !=
+             * NO_ORIENTATION is now a proper separate flag. */
+            if (strcasecmp(buf, "none") != 0)
+                json_node->split = true;
             free(buf);
         } else if (strcasecmp(last_key, "border") == 0) {
             char *buf = NULL;
@@ -181,17 +193,33 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) {
             char *buf = NULL;
             sasprintf(&buf, "%.*s", (int)len, val);
             if (strcasecmp(buf, "default") == 0)
-                json_node->layout = L_DEFAULT;
+                /* This set above when we read "orientation". */
+                json_node->layout = json_node->last_split_layout;
             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)
+            else if (strcasecmp(buf, "dockarea") == 0) {
                 json_node->layout = L_DOCKAREA;
-            else if (strcasecmp(buf, "output") == 0)
+                /* Necessary for migrating from older versions of i3. */
+                json_node->split = false;
+            } else if (strcasecmp(buf, "output") == 0)
                 json_node->layout = L_OUTPUT;
+            else if (strcasecmp(buf, "splith") == 0)
+                json_node->layout = L_SPLITH;
+            else if (strcasecmp(buf, "splitv") == 0)
+                json_node->layout = L_SPLITV;
             else LOG("Unhandled \"layout\": %s\n", buf);
             free(buf);
+        } else if (strcasecmp(last_key, "last_split_layout") == 0) {
+            char *buf = NULL;
+            sasprintf(&buf, "%.*s", (int)len, val);
+            if (strcasecmp(buf, "splith") == 0)
+                json_node->last_split_layout = L_SPLITH;
+            else if (strcasecmp(buf, "splitv") == 0)
+                json_node->last_split_layout = L_SPLITV;
+            else LOG("Unhandled \"last_splitlayout\": %s\n", buf);
+            free(buf);
         } else if (strcasecmp(last_key, "mark") == 0) {
             char *buf = NULL;
             sasprintf(&buf, "%.*s", (int)len, val);
@@ -288,6 +316,9 @@ static int json_bool(void *ctx, int val) {
         to_focus = json_node;
     }
 
+    if (strcasecmp(last_key, "split") == 0)
+        json_node->split = val;
+
     if (parsing_swallows) {
         if (strcasecmp(last_key, "restart_mode") == 0)
             current_swallow->restart_mode = val;
index 92e8f57c758f75c4ca2a98b31a5ab42a35ebf8cc..16fa0beded1acd51cce8bc9faf3fe78370bc103d 100644 (file)
--- a/src/log.c
+++ b/src/log.c
@@ -1,10 +1,12 @@
+#undef I3__FILE__
+#define I3__FILE__ "log.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
  * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * log.c: Setting of loglevels, logging functions.
+ * log.c: Logging functions.
  *
  */
 #include <stdarg.h>
@@ -18,6 +20,7 @@
 #include <sys/mman.h>
 #include <sys/stat.h>
 #include <errno.h>
+#include <pthread.h>
 #if defined(__APPLE__)
 #include <sys/types.h>
 #include <sys/sysctl.h>
 #include "libi3.h"
 #include "shmlog.h"
 
-/* loglevels.h is autogenerated at make time */
-#include "loglevels.h"
-
-static uint64_t loglevel = 0;
+static bool debug_logging = false;
 static bool verbose = false;
 static FILE *errorfile;
 char *errorfilename;
@@ -49,6 +49,8 @@ int shmlog_size = 0;
 static char *logbuffer;
 /* A pointer (within logbuffer) where data will be written to next. */
 static char *logwalk;
+/* A pointer to the shmlog header */
+static i3_shmlog_header *header;
 /* A pointer to the byte where we last wrapped. Necessary to not print the
  * left-overs at the end of the ringbuffer. */
 static char *loglastwrap;
@@ -64,8 +66,6 @@ static int logbuffer_shm;
  *
  */
 static void store_log_markers(void) {
-    i3_shmlog_header *header = (i3_shmlog_header*)logbuffer;
-
     header->offset_next_write = (logwalk - logbuffer);
     header->offset_last_wrap = (loglastwrap - logbuffer);
     header->size = logbuffer_size;
@@ -129,10 +129,23 @@ void init_logging(void) {
             logbuffer = NULL;
             return;
         }
+
+        /* Initialize with 0-bytes, just to be sure… */
+        memset(logbuffer, '\0', logbuffer_size);
+
+        header = (i3_shmlog_header*)logbuffer;
+
+        pthread_condattr_t cond_attr;
+        pthread_condattr_init(&cond_attr);
+        if (pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED) != 0)
+            ELOG("pthread_condattr_setpshared() failed, i3-dump-log -f will not work!\n");
+        pthread_cond_init(&(header->condvar), &cond_attr);
+
         logwalk = logbuffer + sizeof(i3_shmlog_header);
         loglastwrap = logbuffer + logbuffer_size;
         store_log_markers();
     }
+    atexit(purge_zerobyte_logfile);
 }
 
 /*
@@ -146,26 +159,11 @@ void set_verbosity(bool _verbose) {
 }
 
 /*
- * Enables the given loglevel.
+ * Set debug logging.
  *
  */
-void add_loglevel(const char *level) {
-    /* 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;
-    }
+void set_debug_logging(const bool _debug_logging) {
+    debug_logging = _debug_logging;
 }
 
 /*
@@ -214,22 +212,25 @@ static void vlog(const bool print, const char *fmt, va_list args) {
             fprintf(stderr, "BUG: single log message > 4k\n");
         }
 
-        /* If there is no space for the current message (plus trailing
-         * nullbyte) in the ringbuffer, we need to wrap and write to the
-         * beginning again. */
-        if ((len+1) >= (logbuffer_size - (logwalk - logbuffer))) {
+        /* If there is no space for the current message in the ringbuffer, we
+         * need to wrap and write to the beginning again. */
+        if (len >= (logbuffer_size - (logwalk - logbuffer))) {
             loglastwrap = logwalk;
             logwalk = logbuffer + sizeof(i3_shmlog_header);
+            store_log_markers();
+            header->wrap_count++;
         }
 
-        /* Copy the buffer, terminate it, move the write pointer to the byte after
-         * our current message. */
+        /* Copy the buffer, move the write pointer to the byte after our
+         * current message. */
         strncpy(logwalk, message, len);
-        logwalk[len] = '\0';
-        logwalk += len + 1;
+        logwalk += len;
 
         store_log_markers();
 
+        /* Wake up all (i3-dump-log) processes waiting for condvar. */
+        pthread_cond_broadcast(&(header->condvar));
+
         if (print)
             fwrite(message, len, 1, stdout);
     }
@@ -271,17 +272,44 @@ void errorlog(char *fmt, ...) {
 
 /*
  * Logs the given message to stdout while prefixing the current time to it,
- * but only if the corresponding debug loglevel was activated.
+ * but only if debug logging was activated.
  * This is to be called by DLOG() which includes filename/linenumber
  *
  */
-void debuglog(uint64_t lev, char *fmt, ...) {
+void debuglog(char *fmt, ...) {
     va_list args;
 
-    if (!logbuffer && !(loglevel & lev))
+    if (!logbuffer && !(debug_logging))
         return;
 
     va_start(args, fmt);
-    vlog((loglevel & lev), fmt, args);
+    vlog(debug_logging, fmt, args);
     va_end(args);
 }
+
+/*
+ * Deletes the unused log files. Useful if i3 exits immediately, eg.
+ * because --get-socketpath was called. We don't care for syscall
+ * failures. This function is invoked automatically when exiting.
+ */
+void purge_zerobyte_logfile(void) {
+    struct stat st;
+    char *slash;
+
+    if (!errorfilename)
+        return;
+
+    /* don't delete the log file if it contains something */
+    if ((stat(errorfilename, &st)) == -1 || st.st_size > 0)
+        return;
+
+    if (unlink(errorfilename) == -1)
+        return;
+
+    if ((slash = strrchr(errorfilename, '/')) != NULL) {
+        *slash = '\0';
+        /* possibly fails with ENOTEMPTY if there are files (or
+         * sockets) left. */
+        rmdir(errorfilename);
+    }
+}
index e332f5b45d46e070d7f677dcd773413098b0046e..7936e75889bb15946251104f7b8e4515becbdc79 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "main.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -121,7 +123,7 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
                 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",
+                DLOG("X11 Error received (probably harmless)! sequence 0x%x, error_code = %d\n",
                      error->sequence, error->error_code);
             }
             free(event);
@@ -255,6 +257,9 @@ int main(int argc, char *argv[]) {
         {"no-autostart", no_argument, 0, 'a'},
         {"config", required_argument, 0, 'c'},
         {"version", no_argument, 0, 'v'},
+        {"moreversion", no_argument, 0, 'm'},
+        {"more-version", no_argument, 0, 'm'},
+        {"more_version", no_argument, 0, 'm'},
         {"help", no_argument, 0, 'h'},
         {"layout", required_argument, 0, 'L'},
         {"restart", required_argument, 0, 0},
@@ -292,7 +297,7 @@ int main(int argc, char *argv[]) {
 
     start_argv = argv;
 
-    while ((opt = getopt_long(argc, argv, "c:CvaL:hld:V", long_options, &option_index)) != -1) {
+    while ((opt = getopt_long(argc, argv, "c:CvmaL:hld:V", long_options, &option_index)) != -1) {
         switch (opt) {
             case 'a':
                 LOG("Autostart disabled using -a\n");
@@ -314,12 +319,18 @@ int main(int argc, char *argv[]) {
             case 'v':
                 printf("i3 version " I3_VERSION " © 2009-2012 Michael Stapelberg and contributors\n");
                 exit(EXIT_SUCCESS);
+                break;
+            case 'm':
+                printf("Binary i3 version:  " I3_VERSION " © 2009-2012 Michael Stapelberg and contributors\n");
+                display_running_version();
+                exit(EXIT_SUCCESS);
+                break;
             case 'V':
                 set_verbosity(true);
                 break;
             case 'd':
-                LOG("Enabling debug loglevel %s\n", optarg);
-                add_loglevel(optarg);
+                LOG("Enabling debug logging\n");
+                set_debug_logging(true);
                 break;
             case 'l':
                 /* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */
@@ -342,10 +353,10 @@ int main(int argc, char *argv[]) {
                     char *socket_path = root_atom_contents("I3_SOCKET_PATH");
                     if (socket_path) {
                         printf("%s\n", socket_path);
-                        return 0;
+                        exit(EXIT_SUCCESS);
                     }
 
-                    return 1;
+                    exit(EXIT_FAILURE);
                 } else if (strcmp(long_options[option_index].name, "shmlog-size") == 0 ||
                            strcmp(long_options[option_index].name, "shmlog_size") == 0) {
                     shmlog_size = atoi(optarg);
@@ -367,12 +378,12 @@ int main(int argc, char *argv[]) {
                 }
                 /* fall-through */
             default:
-                fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V] [-C]\n", argv[0]);
+                fprintf(stderr, "Usage: %s [-c configfile] [-d all] [-a] [-v] [-V] [-C]\n", argv[0]);
                 fprintf(stderr, "\n");
                 fprintf(stderr, "\t-a          disable autostart ('exec' lines in config)\n");
                 fprintf(stderr, "\t-c <file>   use the provided configfile instead\n");
                 fprintf(stderr, "\t-C          validate configuration file and exit\n");
-                fprintf(stderr, "\t-d <level>  enable debug output with the specified loglevel\n");
+                fprintf(stderr, "\t-d all      enable debug output\n");
                 fprintf(stderr, "\t-L <file>   path to the serialized layout during restarts\n");
                 fprintf(stderr, "\t-v          display version and exit\n");
                 fprintf(stderr, "\t-V          enable verbose mode\n");
@@ -424,7 +435,7 @@ int main(int argc, char *argv[]) {
             }
             optind++;
         }
-        LOG("Command is: %s (%d bytes)\n", payload, strlen(payload));
+        DLOG("Command is: %s (%zd bytes)\n", payload, strlen(payload));
         char *socket_path = root_atom_contents("I3_SOCKET_PATH");
         if (!socket_path) {
             ELOG("Could not get i3 IPC socket path\n");
@@ -483,7 +494,7 @@ int main(int argc, char *argv[]) {
         }
     }
 
-    LOG("i3 (tree) version " I3_VERSION " starting\n");
+    LOG("i3 " I3_VERSION " starting\n");
 
     conn = xcb_connect(NULL, &conn_screen);
     if (xcb_connection_has_error(conn))
@@ -656,11 +667,12 @@ int main(int argc, char *argv[]) {
         randr_init(&randr_base);
     }
 
+    scratchpad_fix_resolution();
+
     xcb_query_pointer_reply_t *pointerreply;
     Output *output = NULL;
     if (!(pointerreply = xcb_query_pointer_reply(conn, pointercookie, NULL))) {
         ELOG("Could not query pointer position, using first screen\n");
-        output = get_first_output();
     } else {
         DLOG("Pointer at %d, %d\n", pointerreply->root_x, pointerreply->root_y);
         output = get_output_containing(pointerreply->root_x, pointerreply->root_y);
@@ -743,7 +755,29 @@ int main(int argc, char *argv[]) {
 
     xcb_flush(conn);
 
-    manage_existing_windows(root);
+    /* What follows is a fugly consequence of X11 protocol race conditions like
+     * the following: In an i3 in-place restart, i3 will reparent all windows
+     * to the root window, then exec() itself. In the new process, it calls
+     * manage_existing_windows. However, in case any application sent a
+     * generated UnmapNotify message to the WM (as GIMP does), this message
+     * will be handled by i3 *after* managing the window, thus i3 thinks the
+     * window just closed itself. In reality, the message was sent in the time
+     * period where i3 wasn’t running yet.
+     *
+     * To prevent this, we grab the server (disables processing of any other
+     * connections), then discard all pending events (since we didn’t do
+     * anything, there cannot be any meaningful responses), then ungrab the
+     * server. */
+    xcb_grab_server(conn);
+    {
+        xcb_aux_sync(conn);
+        xcb_generic_event_t *event;
+        while ((event = xcb_poll_for_event(conn)) != NULL) {
+            free(event);
+        }
+        manage_existing_windows(root);
+    }
+    xcb_ungrab_server(conn);
 
     struct sigaction action;
 
index ea060d97e9931a847cccbbe96ac0098b3c885c80..1dc39b9eabe18a2c88acc5e5b29e252c281721bd 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "manage.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -323,7 +325,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
         (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");
+        LOG("This window is transient for another window, setting floating\n");
         want_floating = true;
 
         if (config.popup_during_fullscreen == PDF_LEAVE_FULLSCREEN &&
index e92a95d2d8458d8e84a5c3e03ab8ca8552139720..350a2c11d0e115091e01ab7af96a31af55e29ebd 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "match.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -111,9 +113,9 @@ bool match_matches_window(Match *match, i3Window *window) {
     }
 
     if (match->title != NULL) {
-        if (window->name_json != NULL &&
-            regex_matches(match->title, window->name_json)) {
-            LOG("title matches (%s)\n", window->name_json);
+        if (window->name != NULL &&
+            regex_matches(match->title, i3string_as_utf8(window->name))) {
+            LOG("title matches (%s)\n", i3string_as_utf8(window->name));
         } else {
             return false;
         }
index d3065c2490bf1faae92e6882231ece95b1a0fe16..46b90177dd27dbbd27024bf8d820994155754760 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "move.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -169,6 +171,12 @@ void tree_move(int direction) {
     while (above->parent != same_orientation)
         above = above->parent;
 
+    /* Enforce the fullscreen focus restrictions. */
+    if (!con_fullscreen_permits_focusing(above->parent)) {
+        LOG("Cannot move out of fullscreen container\n");
+        return;
+    }
+
     DLOG("above = %p\n", above);
     Con *next;
     position_t position;
index a54cb6f35fb54ace23ff77faf2011083eb77343e..fe8d49837adc0dc3c517850dc0dc705ade1f7694 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "output.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
index d29ce12882fdbcfa312e89246fd203e8a9386a18..8b6ba1d9ac52a13a1e87423330fc50de038a0710 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "randr.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -256,7 +258,6 @@ void output_init_con(Output *output) {
     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);
@@ -278,6 +279,7 @@ void output_init_con(Output *output) {
     DLOG("adding main content container\n");
     Con *content = con_new(NULL, NULL);
     content->type = CT_CON;
+    content->layout = L_SPLITH;
     FREE(content->name);
     content->name = sstrdup("content");
 
@@ -290,7 +292,6 @@ void output_init_con(Output *output) {
     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);
@@ -460,11 +461,12 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) {
             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);
+            workspace->layout = (output->rect.height > output->rect.width) ? L_SPLITV : L_SPLITH;
+            DLOG("Setting workspace [%d,%s]'s layout to %d.\n", workspace->num, workspace->name, workspace->layout);
             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);
+                if (child->layout == L_SPLITV || child->layout == L_SPLITH)
+                    child->layout = workspace->layout;
+                DLOG("Setting child [%d,%s]'s layout to %d.\n", child->num, child->name, child->layout);
             }
         }
     }
index a0b51f66d2be15cfc4670d3e5011e42a902ad4d7..60dee5cc92a24d57e42eddefb78a7436a3ed728b 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "regex.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -67,7 +69,7 @@ void regex_free(struct regex *regex) {
 /*
  * Checks if the given regular expression matches the given input and returns
  * true if it does. In either case, it logs the outcome using LOG(), so it will
- * be visible without any debug loglevel.
+ * be visible without debug logging.
  *
  */
 bool regex_matches(struct regex *regex, const char *input) {
index afc4b761fb7355f40ef4e321e210505b5f124dd8..da993a57206ee13fa130616093dbc1e88fd91f38 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "render.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -106,9 +108,9 @@ static void render_l_output(Con *con) {
  */
 void render_con(Con *con, bool render_fullscreen) {
     int children = con_num_children(con);
-    DLOG("Rendering %snode %p / %s / layout %d / children %d / orient %d\n",
+    DLOG("Rendering %snode %p / %s / layout %d / children %d\n",
          (render_fullscreen ? "fullscreen " : ""), con, con->name, con->layout,
-         children, con->orientation);
+         children);
 
     /* Copy container rect, subtract container border */
     /* This is the actually usable space inside this container for clients */
@@ -172,19 +174,11 @@ void render_con(Con *con, bool render_fullscreen) {
             inset->width = 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);
-        }
+        /* NB: We used to respect resize increment size hints for tiling
+         * windows up until commit 0db93d9 here. However, since all terminal
+         * emulators cope with ignoring the size hints in a better way than we
+         * can (by providing their fake-transparency or background color), this
+         * code was removed. See also http://bugs.i3wm.org/540 */
 
         DLOG("child will be at %dx%d with size %dx%d\n", inset->x, inset->y, inset->width, inset->height);
     }
@@ -208,11 +202,11 @@ void render_con(Con *con, bool render_fullscreen) {
 
     /* precalculate the sizes to be able to correct rounding errors */
     int sizes[children];
-    if (con->layout == L_DEFAULT && children > 0) {
+    if ((con->layout == L_SPLITH || con->layout == L_SPLITV) && children > 0) {
         assert(!TAILQ_EMPTY(&con->nodes_head));
         Con *child;
         int i = 0, assigned = 0;
-        int total = con->orientation == HORIZ ? rect.width : rect.height;
+        int total = con_orientation(con) == 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;
@@ -289,8 +283,8 @@ void render_con(Con *con, bool render_fullscreen) {
         assert(children > 0);
 
         /* default layout */
-        if (con->layout == L_DEFAULT) {
-            if (con->orientation == HORIZ) {
+        if (con->layout == L_SPLITH || con->layout == L_SPLITV) {
+            if (con->layout == L_SPLITH) {
                 child->rect.x = x;
                 child->rect.y = y;
                 child->rect.width = sizes[i];
@@ -343,7 +337,7 @@ void render_con(Con *con, bool render_fullscreen) {
             child->rect.width = rect.width;
             child->rect.height = rect.height;
 
-            child->deco_rect.width = child->rect.width / children;
+            child->deco_rect.width = ceil((float)child->rect.width / children);
             child->deco_rect.x = x - con->rect.x + i * child->deco_rect.width;
             child->deco_rect.y = y - con->rect.y;
 
index 4b3289cdb288f47c21954534ae8bdc7d1d5d0694..b65344a276c5edbefdee04f260e0856d45ca9441 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "resize.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
index c54b1adaed868cf10eb03291491d099bddf16c3d..5072280bf35b47fbc1b99c55e0f234c83a445740 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "scratchpad.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -141,3 +143,67 @@ void scratchpad_show(Con *con) {
 
     con_focus(con_descend_focused(con));
 }
+
+/*
+ * Greatest common divisor, implemented only for the least common multiple
+ * below.
+ *
+ */
+static int _gcd(const int m, const int n) {
+    if (n == 0)
+        return m;
+    return _gcd(n, (m % n));
+}
+
+/*
+ * Least common multiple. We use it to determine the (ideally not too large)
+ * resolution for the __i3 pseudo-output on which the scratchpad is on (see
+ * below). We could just multiply the resolutions, but for some pathetic cases
+ * (many outputs), using the LCM will achieve better results.
+ *
+ * Man, when you were learning about these two algorithms for the first time,
+ * did you think you’d ever need them in a real-world software project of
+ * yours? I certainly didn’t until now. :-D
+ *
+ */
+static int _lcm(const int m, const int n) {
+    const int o = _gcd(m, n);
+    return ((m * n) / o);
+}
+
+/*
+ * When starting i3 initially (and after each change to the connected outputs),
+ * this function fixes the resolution of the __i3 pseudo-output. When that
+ * resolution is not set to a function which shares a common divisor with every
+ * active output’s resolution, floating point calculation errors will lead to
+ * the scratchpad window moving when shown repeatedly.
+ *
+ */
+void scratchpad_fix_resolution(void) {
+    Con *__i3_scratch = workspace_get("__i3_scratch", NULL);
+    Con *__i3_output = con_get_output(__i3_scratch);
+    DLOG("Current resolution: (%d, %d) %d x %d\n",
+         __i3_output->rect.x, __i3_output->rect.y,
+         __i3_output->rect.width, __i3_output->rect.height);
+    Con *output;
+    int new_width = -1,
+        new_height = -1;
+    TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
+        if (output == __i3_output)
+            continue;
+        DLOG("output %s's resolution: (%d, %d) %d x %d\n",
+             output->name, output->rect.x, output->rect.y,
+             output->rect.width, output->rect.height);
+        if (new_width == -1) {
+            new_width = output->rect.width;
+            new_height = output->rect.height;
+        } else {
+            new_width = _lcm(new_width, output->rect.width);
+            new_height = _lcm(new_height, output->rect.height);
+        }
+    }
+    DLOG("new width = %d, new height = %d\n",
+         new_width, new_height);
+    __i3_output->rect.width = new_width;
+    __i3_output->rect.height = new_height;
+}
index fa608ed89203924fc2bf007b024b7f7ec779752c..988927f01c0c254627e84fd1646d963edb3a7b87 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "sighandler.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
 #include <ev.h>
 #include <iconv.h>
 #include <signal.h>
+#include <sys/wait.h>
 
 #include <xcb/xcb_event.h>
 
 #include <X11/keysym.h>
 
+static void open_popups(void);
+
 static xcb_gcontext_t pixmap_gc;
 static xcb_pixmap_t pixmap;
 static int raised_signal;
@@ -27,17 +32,102 @@ 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,",
+    "- 'b' to save a backtrace (needs GDB),",
     "- 'r' to restart i3 in-place or",
     "- 'f' to forget the current layout and restart"
 };
 static int crash_text_longest = 5;
+static int backtrace_string_index = 3;
+static int backtrace_done = 0;
+
+/*
+ * Attach gdb to pid_parent and dump a backtrace to i3-backtrace.$pid in the
+ * tmpdir
+ */
+static int backtrace(void) {
+    char *tmpdir = getenv("TMPDIR");
+    if (tmpdir == NULL)
+        tmpdir = "/tmp";
+
+    pid_t pid_parent = getpid();
+
+    char *filename = NULL;
+    int suffix = 0;
+    struct stat bt;
+    /* Find a unique filename for the backtrace (since the PID of i3 stays the
+     * same), so that we don’t overwrite earlier backtraces. */
+    do {
+        FREE(filename);
+        sasprintf(&filename, "%s/i3-backtrace.%d.%d.txt", tmpdir, pid_parent, suffix);
+        suffix++;
+    } while (stat(filename, &bt) == 0);
+
+    pid_t pid_gdb = fork();
+    if (pid_gdb < 0) {
+        DLOG("Failed to fork for GDB\n");
+        return -1;
+    } else if (pid_gdb == 0) {
+        /* child */
+        int stdin_pipe[2],
+            stdout_pipe[2];
+
+        pipe(stdin_pipe);
+        pipe(stdout_pipe);
+
+        /* close standard streams in case i3 is started from a terminal; gdb
+         * needs to run without controlling terminal for it to work properly in
+         * this situation */
+        close(STDIN_FILENO);
+        close(STDOUT_FILENO);
+        close(STDERR_FILENO);
+
+        /* We provide pipe file descriptors for stdin/stdout because gdb < 7.5
+         * crashes otherwise, see
+         * http://sourceware.org/bugzilla/show_bug.cgi?id=14114 */
+        dup2(stdin_pipe[0], STDIN_FILENO);
+        dup2(stdout_pipe[1], STDOUT_FILENO);
+
+        char *pid_s, *gdb_log_cmd;
+        sasprintf(&pid_s, "%d", pid_parent);
+        sasprintf(&gdb_log_cmd, "set logging file %s", filename);
+
+        char *args[] = {
+            "gdb",
+            start_argv[0],
+            "-p",
+            pid_s,
+            "-batch",
+            "-nx",
+            "-ex", gdb_log_cmd,
+            "-ex", "set logging on",
+            "-ex", "bt full",
+            "-ex", "quit",
+            NULL
+        };
+        execvp(args[0], args);
+        DLOG("Failed to exec GDB\n");
+        exit(1);
+    }
+    int status = 0;
+
+    waitpid(pid_gdb, &status, 0);
+
+    /* see if the backtrace was succesful or not */
+    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+        DLOG("GDB did not run properly\n");
+        return -1;
+    } else if (stat(filename, &bt) == -1) {
+        DLOG("GDB executed succesfully, but no backtrace was generated\n");
+        return -1;
+    }
+    return 1;
+}
 
 /*
  * Draw the window containing the info text
  *
  */
-static int sig_draw_window(xcb_window_t win, int width, int height, int font_height) {
+static int sig_draw_window(xcb_window_t win, int width, int height, int font_height, i3String **crash_text_i3strings) {
     /* re-draw the background */
     xcb_rectangle_t border = { 0, 0, width, height},
                     inner = { 2, 2, width - 4, height - 4};
@@ -49,9 +139,23 @@ static int sig_draw_window(xcb_window_t win, int width, int height, int font_hei
     /* restore font color */
     set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
 
-    for (int i = 0; i < sizeof(crash_text) / sizeof(char*); i++) {
-        draw_text(crash_text[i], strlen(crash_text[i]), false, pixmap, pixmap_gc,
+    char *bt_colour = "#FFFFFF";
+    if (backtrace_done < 0)
+        bt_colour = "#AA0000";
+    else if (backtrace_done > 0)
+        bt_colour = "#00AA00";
+
+    for (int i = 0; crash_text_i3strings[i] != NULL; ++i) {
+        /* fix the colour for the backtrace line when it finished */
+        if (i == backtrace_string_index)
+            set_font_colors(pixmap_gc, get_colorpixel(bt_colour), get_colorpixel("#000000"));
+
+        draw_text(crash_text_i3strings[i], pixmap, pixmap_gc,
                 8, 5 + i * font_height, width - 16);
+
+        /* and reset the colour again for other lines */
+        if (i == backtrace_string_index)
+            set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
     }
 
     /* Copy the contents of the pixmap to the real window */
@@ -62,7 +166,7 @@ static int sig_draw_window(xcb_window_t win, int width, int height, int font_hei
 }
 
 /*
- * Handles keypresses of 'e' or 'r' to exit or restart i3
+ * Handles keypresses of 'b', 'r' and 'f' to get a backtrace or restart i3
  *
  */
 static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
@@ -75,10 +179,15 @@ static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_p
 
     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 == 'b') {
+        DLOG("User issued core-dump command.\n");
+
+        /* fork and exec/attach GDB to the parent to get a backtrace in the
+         * tmpdir */
+        backtrace_done = backtrace();
+
+        /* re-open the windows to indicate that it's finished */
+        open_popups();
     }
 
     if (sym == 'r')
@@ -127,28 +236,20 @@ static xcb_window_t open_input_window(xcb_connection_t *conn, Rect screen_rect,
     return win;
 }
 
-/*
- * Handle signals
- * It creates a window asking the user to restart in-place
- * or exit to generate a core dump
- *
- */
-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;
-
+static void open_popups() {
     /* 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);
 
+    int crash_text_length = sizeof(crash_text) / sizeof(char*);
+    i3String **crash_text_i3strings = smalloc(sizeof(i3String *) * (crash_text_length + 1));
+    /* Pre-compute i3Strings for our text */
+    for (int i = 0; i < crash_text_length; ++i) {
+        crash_text_i3strings[i] = i3string_from_utf8(crash_text[i]);
+    }
+    crash_text_i3strings[crash_text_length] = NULL;
     /* calculate width for longest text */
-    size_t text_len = strlen(crash_text[crash_text_longest]);
-    xcb_char2b_t *longest_text = convert_utf8_to_ucs2(crash_text[crash_text_longest], &text_len);
-    int font_width = predict_text_width((char *)longest_text, text_len, true);
+    int font_width = predict_text_width(crash_text_i3strings[crash_text_longest]);
     int width = font_width + 20;
 
     /* Open a popup window on each virtual screen */
@@ -172,9 +273,26 @@ void handle_signal(int sig, siginfo_t *info, void *data) {
         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);
+        sig_draw_window(win, width, height, config.font.height, crash_text_i3strings);
         xcb_flush(conn);
     }
+}
+
+/*
+ * Handle signals
+ * It creates a window asking the user to restart in-place
+ * or exit to generate a core dump
+ *
+ */
+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;
+
+    open_popups();
 
     xcb_generic_event_t *event;
     /* Yay, more own eventhandlers… */
index bcc2415af10a34c1d98b883244fa3f126a17d9f8..89324dbd250f81a84f0727f9eee95cc3b2621696 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "startup.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -55,6 +57,57 @@ static void startup_timeout(EV_P_ ev_timer *w, int revents) {
     free(w);
 }
 
+/*
+ * Some applications (such as Firefox) mark a startup sequence as completede
+ * *before* they even map a window. Therefore, we cannot entirely delete the
+ * startup sequence once it’s marked as complete. Instead, we’ll mark it for
+ * deletion in 30 seconds and use that chance to delete old sequences.
+ *
+ * This function returns the number of active (!) startup notifications, that
+ * is, those which are not marked for deletion yet. This is used for changing
+ * the root window cursor.
+ *
+ */
+static int _delete_startup_sequence(struct Startup_Sequence *sequence) {
+    time_t current_time = time(NULL);
+    int active_sequences = 0;
+
+    /* Mark the given sequence for deletion in 30 seconds. */
+    sequence->delete_at = current_time + 30;
+    DLOG("Will delete startup sequence %s at timestamp %ld\n",
+         sequence->id, sequence->delete_at);
+
+    /* Traverse the list and delete everything which was marked for deletion 30
+     * seconds ago or earlier. */
+    struct Startup_Sequence *current, *next;
+    for (next = TAILQ_FIRST(&startup_sequences);
+         next != TAILQ_END(&startup_sequences);
+         ) {
+        current = next;
+        next = TAILQ_NEXT(next, sequences);
+
+        if (current->delete_at == 0) {
+            active_sequences++;
+            continue;
+        }
+
+        if (current_time <= current->delete_at)
+            continue;
+
+        DLOG("Deleting startup sequence %s, delete_at = %ld, current_time = %ld\n",
+             current->id, current->delete_at, current_time);
+
+        /* Unref the context, will be free()d */
+        sn_launcher_context_unref(current->context);
+
+        /* Delete our internal sequence */
+        TAILQ_REMOVE(&startup_sequences, current, sequences);
+    }
+
+    return active_sequences;
+
+}
+
 /*
  * 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),
@@ -180,13 +233,7 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata) {
         case SN_MONITOR_EVENT_COMPLETED:
             DLOG("startup sequence %s completed\n", sn_startup_sequence_get_id(snsequence));
 
-            /* Unref the context, will be free()d */
-            sn_launcher_context_unref(sequence->context);
-
-            /* Delete our internal sequence */
-            TAILQ_REMOVE(&startup_sequences, sequence, sequences);
-
-            if (TAILQ_EMPTY(&startup_sequences)) {
+            if (_delete_startup_sequence(sequence) == 0) {
                 DLOG("No more startup sequences running, changing root window cursor to default pointer.\n");
                 /* Change the pointer of the root window to indicate progress */
                 if (xcursor_supported)
@@ -248,13 +295,14 @@ char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *
         break;
     }
 
-    free(startup_id);
-    free(startup_id_reply);
-
     if (!sequence) {
         DLOG("WARNING: This sequence (ID %s) was not found\n", startup_id);
+        free(startup_id);
+        free(startup_id_reply);
         return NULL;
     }
 
+    free(startup_id);
+    free(startup_id_reply);
     return sequence->workspace;
 }
index f29369c607eba2d0e64b64d630a0d6ffe78753c2..321bc78a5951b2b9fa13c78819301f53049b5d49 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "tree.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -29,7 +31,9 @@ static Con *_create___i3(void) {
     x_set_name(__i3, "[i3 con] pseudo-output __i3");
     /* For retaining the correct position/size of a scratchpad window, the
      * dimensions of the real outputs should be multiples of the __i3
-     * pseudo-output. */
+     * pseudo-output. Ensuring that is the job of scratchpad_fix_resolution()
+     * which gets called after this function and after detecting all the
+     * outputs (or whenever an output changes). */
     __i3->rect.width = 1280;
     __i3->rect.height = 1024;
 
@@ -39,6 +43,7 @@ static Con *_create___i3(void) {
     content->type = CT_CON;
     FREE(content->name);
     content->name = sstrdup("content");
+    content->layout = L_SPLITH;
 
     x_set_name(content, "[i3 con] content __i3");
     con_attach(content, __i3, false);
@@ -48,6 +53,7 @@ static Con *_create___i3(void) {
     ws->type = CT_WORKSPACE;
     ws->num = -1;
     ws->name = sstrdup("__i3_scratch");
+    ws->layout = L_SPLITH;
     con_attach(ws, content, false);
     x_set_name(ws, "[i3 con] workspace __i3_scratch");
     ws->fullscreen_mode = CF_OUTPUT;
@@ -112,6 +118,7 @@ void tree_init(xcb_get_geometry_reply_t *geometry) {
     FREE(croot->name);
     croot->name = "root";
     croot->type = CT_ROOT;
+    croot->layout = L_SPLITH;
     croot->rect = (Rect){
         geometry->x,
         geometry->y,
@@ -151,6 +158,7 @@ Con *tree_open_con(Con *con, i3Window *window) {
 
     /* 3. create the container and attach it to its parent */
     Con *new = con_new(con, window);
+    new->layout = L_SPLITH;
 
     /* 4: re-calculate child->percent for each child */
     con_fix_percent(con);
@@ -239,8 +247,7 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
         }
         FREE(con->window->class_class);
         FREE(con->window->class_instance);
-        FREE(con->window->name_x);
-        FREE(con->window->name_json);
+        i3string_free(con->window->name);
         free(con->window);
     }
 
@@ -296,7 +303,7 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
             }
         }
         else {
-            DLOG("not focusing because we're not killing anybody");
+            DLOG("not focusing because we're not killing anybody\n");
         }
     } else {
         DLOG("not focusing, was not mapped\n");
@@ -337,7 +344,7 @@ 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;
+        con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
         return;
     }
 
@@ -351,8 +358,9 @@ void tree_split(Con *con, orientation_t orientation) {
      * 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->layout == L_DEFAULT) {
-        parent->orientation = orientation;
+        (parent->layout == L_SPLITH ||
+         parent->layout == L_SPLITV)) {
+        parent->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
         DLOG("Just changing orientation of existing container\n");
         return;
     }
@@ -364,7 +372,8 @@ void tree_split(Con *con, orientation_t orientation) {
     TAILQ_REPLACE(&(parent->nodes_head), con, new, nodes);
     TAILQ_REPLACE(&(parent->focus_head), con, new, focused);
     new->parent = parent;
-    new->orientation = orientation;
+    new->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
+    new->split = true;
 
     /* 3: swap 'percent' (resize factor) */
     new->percent = con->percent;
@@ -375,38 +384,34 @@ void tree_split(Con *con, orientation_t orientation) {
 }
 
 /*
- * Moves focus one level up.
+ * Moves focus one level up. Returns true if focus changed.
  *
  */
-void level_up(void) {
-    /* 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;
-    }
+bool level_up(void) {
     /* 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;
+        ELOG("'focus parent': Focus is already on the workspace, cannot go higher than that.\n");
+        return false;
     }
     con_focus(focused->parent);
+    return true;
 }
 
 /*
- * Moves focus one level down.
+ * Moves focus one level down. Returns true if focus changed.
  *
  */
-void level_down(void) {
+bool level_down(void) {
     /* Go down the focus stack of the current node */
     Con *next = TAILQ_FIRST(&(focused->focus_head));
     if (next == TAILQ_END(&(focused->focus_head))) {
         printf("cannot go down\n");
-        return;
+        return false;
     }
     con_focus(next);
+    return true;
 }
 
 static void mark_unmapped(Con *con) {
@@ -560,6 +565,10 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
         else next = TAILQ_LAST(&(parent->nodes_head), nodes_head);
     }
 
+    /* Don't violate fullscreen focus restrictions. */
+    if (!con_fullscreen_permits_focusing(next))
+        return false;
+
     /* 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 */
@@ -594,7 +603,9 @@ void tree_flatten(Con *con) {
     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)
+    if (con->type != CT_CON ||
+        parent->layout == L_OUTPUT || /* con == "content" */
+        con->window != NULL)
         goto recurse;
 
     /* Ensure it got only one child */
@@ -602,12 +613,14 @@ void tree_flatten(Con *con) {
     if (child == NULL || TAILQ_NEXT(child, nodes) != NULL)
         goto recurse;
 
+    DLOG("child = %p, con = %p, parent = %p\n", child, con, parent);
+
     /* 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)
+    if (!con->split ||
+        !child->split ||
+        con_orientation(con) == con_orientation(child) ||
+        con_orientation(child) != con_orientation(parent))
         goto recurse;
 
     DLOG("Alright, I have to flatten this situation now. Stay calm.\n");
index 8273e0cf0150cda981d42af87b3f1171e336f10b..e623ce81b822e0121d2641665145b2da2402f05d 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "util.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -303,6 +305,7 @@ void i3_restart(bool forget_layout) {
     char *restart_filename = forget_layout ? NULL : store_restart_layout();
 
     kill_configerror_nagbar(true);
+    kill_commanderror_nagbar(true);
 
     restore_geometry();
 
index e9e61f16c01aa57c64ac0a28ea425bd81afb028a..b886c380880e865dd913340840420ab2e1cd0cbc 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "window.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -58,31 +60,11 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo
         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 */
-    size_t len;
-    xcb_char2b_t *ucs2_name = convert_utf8_to_ucs2(new_name, &len);
-    if (ucs2_name == NULL) {
-        LOG("Could not convert _NET_WM_NAME to UCS-2, ignoring new hint\n");
-        FREE(new_name);
-        free(prop);
-        return;
-    }
-    FREE(win->name_x);
-    FREE(win->name_json);
-    win->name_json = new_name;
-    win->name_x = (char*)ucs2_name;
-    win->name_len = len;
+    i3string_free(win->name);
+    win->name = i3string_from_utf8_with_length(xcb_get_property_value(prop),
+                                               xcb_get_property_value_length(prop));
     win->name_x_changed = true;
-    LOG("_NET_WM_NAME changed to \"%s\"\n", win->name_json);
+    LOG("_NET_WM_NAME changed to \"%s\"\n", i3string_as_utf8(win->name));
 
     win->uses_net_wm_name = true;
 
@@ -116,24 +98,14 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bo
         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;
-    }
+    i3string_free(win->name);
+    win->name = i3string_from_utf8_with_length(xcb_get_property_value(prop),
+                                               xcb_get_property_value_length(prop));
 
-    LOG("WM_NAME changed to \"%s\"\n", new_name);
+    LOG("WM_NAME changed to \"%s\"\n", i3string_as_utf8(win->name));
     LOG("Using legacy window title. Note that in order to get Unicode window "
         "titles in i3, the application has to set _NET_WM_NAME (UTF-8)\n");
 
-    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) {
index 928f0bd65e88b06e10a012b77a5fd178c344be87..1749959aa20c9c416fa8dde3b57b1235a43ef67d 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "workspace.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
  * back-and-forth switching. */
 static char *previous_workspace_name = NULL;
 
+/*
+ * Sets ws->layout to splith/splitv if default_orientation was specified in the
+ * configfile. Otherwise, it uses splith/splitv depending on whether the output
+ * is higher than wide.
+ *
+ */
+static void _workspace_apply_default_orientation(Con *ws) {
+    /* If default_orientation is set to NO_ORIENTATION we determine
+     * orientation depending on output resolution. */
+    if (config.default_orientation == NO_ORIENTATION) {
+        Con *output = con_get_output(ws);
+        ws->layout = (output->rect.height > output->rect.width) ? L_SPLITV : L_SPLITH;
+        DLOG("Auto orientation. Workspace size set to (%d,%d), setting layout to %d.\n",
+             output->rect.width, output->rect.height, ws->layout);
+    } else {
+        ws->layout = (config.default_orientation == HORIZ) ? L_SPLITH : L_SPLITV;
+    }
+}
+
 /*
  * Returns a pointer to the workspace with the given number (starting at 0),
  * creating the workspace if necessary (by allocating the necessary amount of
@@ -64,16 +85,8 @@ Con *workspace_get(const char *num, bool *created) {
         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;
-        }
+        workspace->parent = content;
+        _workspace_apply_default_orientation(workspace);
 
         con_attach(workspace, content, false);
 
@@ -114,14 +127,15 @@ Con *create_workspace_on_output(Output *output, Con *content) {
         /* We check if this is the workspace
          * next/prev/next_on_output/prev_on_output/back_and_forth/number command.
          * Beware: The workspace names "next", "prev", "next_on_output",
-         * "prev_on_output", "number" and "back_and_forth" are OK, so we check
-         * before stripping the double quotes */
+         * "prev_on_output", "number", "back_and_forth" and "current" are OK,
+         * so we check before stripping the double quotes */
         if (strncasecmp(target, "next", strlen("next")) == 0 ||
             strncasecmp(target, "prev", strlen("prev")) == 0 ||
             strncasecmp(target, "next_on_output", strlen("next_on_output")) == 0 ||
             strncasecmp(target, "prev_on_output", strlen("prev_on_output")) == 0 ||
             strncasecmp(target, "number", strlen("number")) == 0 ||
-            strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0)
+            strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0 ||
+            strncasecmp(target, "current", strlen("current")) == 0)
             continue;
         if (*target == '"')
             target++;
@@ -197,19 +211,12 @@ Con *create_workspace_on_output(Output *output, Con *content) {
 
     ws->fullscreen_mode = CF_OUTPUT;
 
-    /* If default_orientation is set to NO_ORIENTATION we determine
-     * orientation depending on output resolution. */
-    if (config.default_orientation == NO_ORIENTATION) {
-        ws->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ;
-        DLOG("Auto orientation. Workspace size set to (%d,%d), setting orientation to %d.\n",
-             output->rect.width, output->rect.height, ws->orientation);
-    } else {
-        ws->orientation = config.default_orientation;
-    }
+    _workspace_apply_default_orientation(ws);
 
     return ws;
 }
 
+
 /*
  * Returns true if the workspace is currently visible. Especially important for
  * multi-monitor environments, as they can have multiple currenlty active
@@ -685,18 +692,17 @@ void workspace_update_urgent_flag(Con *ws) {
 
 /*
  * '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.
+ * the same layout as the workspace and then changing the workspace layout.
  *
  */
 void ws_force_orientation(Con *ws, orientation_t orientation) {
     /* 1: create a new split container */
     Con *split = con_new(NULL, NULL);
     split->parent = ws;
+    split->split = true;
 
-    /* 2: copy layout and orientation from workspace */
+    /* 2: copy layout from workspace */
     split->layout = ws->layout;
-    split->orientation = ws->orientation;
 
     Con *old_focused = TAILQ_FIRST(&(ws->focus_head));
 
@@ -708,11 +714,12 @@ void ws_force_orientation(Con *ws, orientation_t orientation) {
         con_attach(child, split, true);
     }
 
-    /* 4: switch workspace orientation */
-    ws->orientation = orientation;
+    /* 4: switch workspace layout */
+    ws->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
+    DLOG("split->layout = %d, ws->layout = %d\n", split->layout, ws->layout);
 
     /* 5: attach the new split container to the workspace */
-    DLOG("Attaching new split to ws\n");
+    DLOG("Attaching new split (%p) to ws (%p)\n", split, ws);
     con_attach(split, ws, false);
 
     /* 6: fix the percentages */
@@ -744,19 +751,11 @@ Con *workspace_attach_to(Con *ws) {
     /* 1: create a new split container */
     Con *new = con_new(NULL, NULL);
     new->parent = ws;
+    new->split = true;
 
     /* 2: set the requested layout on the split con */
     new->layout = 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);
diff --git a/src/x.c b/src/x.c
index 08eb8fee30be8393cf4b752c57aebd7d89a47be1..24fd0eac81d425b2f7db70d6954374338b5141cb 100644 (file)
--- a/src/x.c
+++ b/src/x.c
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "x.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -299,16 +301,20 @@ void x_window_kill(xcb_window_t window, kill_window_t kill_window) {
 void x_draw_decoration(Con *con) {
     Con *parent = con->parent;
     bool leaf = con_is_leaf(con);
+
     /* 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:
+     *  • direct children of outputs or dockareas
      *  • floating containers (they don’t have a decoration)
      */
     if ((!leaf &&
          parent->layout != L_STACKED &&
          parent->layout != L_TABBED) ||
+        parent->type == CT_OUTPUT ||
+        parent->type == CT_DOCKAREA ||
         con->type == CT_FLOATING_CON)
         return;
 
@@ -396,6 +402,10 @@ void x_draw_decoration(Con *con) {
 
     /* 3: draw a rectangle in border color around the client */
     if (p->border_style != BS_NONE && p->con_is_leaf) {
+        /* We might hide some borders adjacent to the screen-edge */
+        adjacent_t borders_to_hide = ADJ_NONE;
+        borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
+
         Rect br = con_border_style_rect(con);
 #if 0
         DLOG("con->rect spans %d x %d\n", con->rect.width, con->rect.height);
@@ -408,14 +418,20 @@ void x_draw_decoration(Con *con) {
          * rectangle because some childs are not freely resizable and we want
          * their background color to "shine through". */
         xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->background });
-        xcb_rectangle_t borders[] = {
-            { 0, 0, br.x, r->height },
-            { 0, r->height + br.height + br.y, r->width, r->height },
-            { r->width + br.width + br.x, 0, r->width, r->height }
-        };
-        xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 3, borders);
+        if (!(borders_to_hide & ADJ_LEFT_SCREEN_EDGE)) {
+            xcb_rectangle_t leftline = { 0, 0, br.x, r->height };
+            xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &leftline);
+        }
+        if (!(borders_to_hide & ADJ_RIGHT_SCREEN_EDGE)) {
+            xcb_rectangle_t rightline = { r->width + br.width + br.x, 0, r->width, r->height };
+            xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &rightline);
+        }
+        if (!(borders_to_hide & ADJ_LOWER_SCREEN_EDGE)) {
+            xcb_rectangle_t bottomline = { 0, r->height + br.height + br.y, r->width, r->height };
+            xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &bottomline);
+        }
         /* 1pixel border needs an additional line at the top */
-        if (p->border_style == BS_1PIXEL) {
+        if (p->border_style == BS_1PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) {
             xcb_rectangle_t topline = { br.x, 0, con->rect.width + br.width + br.x, br.y };
             xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &topline);
         }
@@ -465,10 +481,10 @@ void x_draw_decoration(Con *con) {
     int text_offset_y = (con->deco_rect.height - config.font.height) / 2;
 
     struct Window *win = con->window;
-    if (win == NULL || win->name_x == NULL) {
+    if (win == NULL || win->name == NULL) {
         /* this is a non-leaf container, we need to make up a good description */
         // TODO: use a good description instead of just "another container"
-        draw_text("another container", strlen("another container"), false,
+        draw_text_ascii("another container",
                 parent->pixmap, parent->pm_gc,
                 con->deco_rect.x + 2, con->deco_rect.y + text_offset_y,
                 con->deco_rect.width - 2);
@@ -492,7 +508,7 @@ void x_draw_decoration(Con *con) {
     //DLOG("indent_level = %d, indent_mult = %d\n", indent_level, indent_mult);
     int indent_px = (indent_level * 5) * indent_mult;
 
-    draw_text(win->name_x, win->name_len, win->uses_net_wm_name,
+    draw_text(win->name,
             parent->pixmap, parent->pm_gc,
             con->deco_rect.x + 2 + indent_px, con->deco_rect.y + text_offset_y,
             con->deco_rect.width - 2 - indent_px);
@@ -1014,9 +1030,11 @@ void x_set_name(Con *con, const char *name) {
  *
  */
 void x_set_i3_atoms(void) {
+    pid_t pid = getpid();
     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_PID, XCB_ATOM_CARDINAL, 32, 1, &pid);
     xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_CONFIG_PATH, A_UTF8_STRING, 8,
                         strlen(current_configpath), current_configpath);
     xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_SHMLOG_PATH, A_UTF8_STRING, 8,
index 4d7a8c47e4b1153c3a40d11439e579fa95cdd01a..caa203f71208a331a622d73f23e8475d493c4d36 100644 (file)
--- a/src/xcb.c
+++ b/src/xcb.c
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "xcb.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -48,8 +50,9 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims,
         xcb_cursor_t cursor_id = xcb_generate_id(conn);
         i3Font cursor_font = load_font("cursor", false);
         int xcb_cursor = xcursor_get_xcb_cursor(cursor);
-        xcb_create_glyph_cursor(conn, cursor_id, cursor_font.id, cursor_font.id,
-                xcb_cursor, xcb_cursor + 1, 0, 0, 0, 65535, 65535, 65535);
+        xcb_create_glyph_cursor(conn, cursor_id, cursor_font.specific.xcb.id,
+                cursor_font.specific.xcb.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);
     }
@@ -193,8 +196,9 @@ void xcb_set_root_cursor(int cursor) {
     xcb_cursor_t cursor_id = xcb_generate_id(conn);
     i3Font cursor_font = load_font("cursor", false);
     int xcb_cursor = xcursor_get_xcb_cursor(cursor);
-    xcb_create_glyph_cursor(conn, cursor_id, cursor_font.id, cursor_font.id,
-            xcb_cursor, xcb_cursor + 1, 0, 0, 0, 65535, 65535, 65535);
+    xcb_create_glyph_cursor(conn, cursor_id, cursor_font.specific.xcb.id,
+            cursor_font.specific.xcb.id, xcb_cursor, xcb_cursor + 1, 0, 0, 0,
+            65535, 65535, 65535);
     xcb_change_window_attributes(conn, root, XCB_CW_CURSOR, &cursor_id);
     xcb_free_cursor(conn, cursor_id);
     xcb_flush(conn);
index 058b7ae004c740e1ab7cf6effebdca4ea10fd3d8..7683b0d37ed8fd76c0330200b4650440ea8b53ab 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "xcursor.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
index f377840f22e246ba17cd79c8287822e505981c8a..7e5b5aebb4acb60e64379ceed47ed17c0428f42e 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "xinerama.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
index 1c9873894abc3102488692d055b4fd136755af99..b1e698ae9182395efcd68f7f4f4edb1d9ee9fd31 100755 (executable)
@@ -11,6 +11,7 @@ WriteMakefile(
         'AnyEvent::I3' => '0.09',
         'X11::XCB'     => '0.03',
         'Inline'       => 0,
+        'ExtUtils::PkgConfig' => 0,
         'Test::More'   => '0.94',
     },
     PM => {}, # do not install any files from this directory
index 020e2f90b737ab3499be140f58af4c6fb6c95c44..5ea9d0783777d8f02f265eb7a83375f30a4426b3 100755 (executable)
@@ -15,6 +15,7 @@ use TAP::Harness;
 use TAP::Parser;
 use TAP::Parser::Aggregator;
 use Time::HiRes qw(time);
+use IO::Handle;
 # these are shipped with the testsuite
 use lib qw(lib);
 use StartXDummy;
@@ -46,14 +47,18 @@ my @displays = ();
 my %options = (
     valgrind => 0,
     strace => 0,
+    xtrace => 0,
     coverage => 0,
     restart => 0,
 );
+my $keep_xdummy_output = 0;
 
 my $result = GetOptions(
     "coverage-testing" => \$options{coverage},
+    "keep-xdummy-output" => \$keep_xdummy_output,
     "valgrind" => \$options{valgrind},
     "strace" => \$options{strace},
+    "xtrace" => \$options{xtrace},
     "display=s" => \@displays,
     "parallel=i" => \$parallel,
     "help|?" => \$help,
@@ -74,7 +79,7 @@ my $numtests = scalar @testfiles;
 
 # No displays specified, let’s start some Xdummy instances.
 if (@displays == 0) {
-    @displays = start_xdummy($parallel, $numtests);
+    @displays = start_xdummy($parallel, $numtests, $keep_xdummy_output);
 }
 
 # 1: create an output directory for this test-run
@@ -125,6 +130,7 @@ printf("\nRough time estimate for this run: %.2f seconds\n\n", $timings{GLOBAL})
 
 my $logfile = "$outdir/complete-run.log";
 open $log, '>', $logfile or die "Could not create '$logfile': $!";
+$log->autoflush(1);
 say "Writing logfile to '$logfile'...";
 
 # 3: run all tests
@@ -261,9 +267,16 @@ sub take_job {
 
             for (1 .. $lines) {
                 my $result = $parser->next;
-                if (defined($result) and $result->is_test) {
+                next unless defined($result);
+                if ($result->is_test) {
                     $tests_completed++;
                     status($display, "$test: [$tests_completed/??] ");
+                } elsif ($result->is_bailout) {
+                    Log status($display, "$test: BAILOUT");
+                    status_completed(scalar @done);
+                    say "";
+                    say "test $test bailed out: " . $result->explanation;
+                    exit 1;
                 }
             }
 
@@ -342,6 +355,11 @@ C<latest/valgrind-for-$test.log>.
 Runs i3 under strace to trace system calls. The output will be available in
 C<latest/strace-for-$test.log>.
 
+=item B<--xtrace>
+
+Runs i3 under xtrace to trace X11 requests/replies. The output will be
+available in C<latest/xtrace-for-$test.log>.
+
 =item B<--coverage-testing>
 
 Exits i3 cleanly (instead of kill -9) to make coverage testing work properly.
index 8f52bddc4a3eb8a92a3ba37ae8363a29cfd9b779..0a062be48199f0e705f08b536749dbf256ef42a8 100644 (file)
@@ -124,6 +124,14 @@ sub activate_i3 {
                      'sh -c "export LISTEN_PID=\$\$; ' . $cmd . '"';
         }
 
+        if ($args{xtrace}) {
+            my $out = "$outdir/xtrace-for-$test.log";
+
+            # See comment in $args{strace} branch.
+            $cmd = qq|xtrace -n -o "$out" -- | .
+                     'sh -c "export LISTEN_PID=\$\$; ' . $cmd . '"';
+        }
+
         # We need to use the shell due to using output redirections.
         exec '/bin/sh', '-c', $cmd;
 
index 5c739fca022ddf4ee678d9b9eeae6fad557567dd..f2ebcadd16fb1f67ee87e040fe880f244da39002 100644 (file)
@@ -9,6 +9,7 @@ use v5.10;
 
 our @EXPORT = qw(start_xdummy);
 
+my @pids;
 my $x_socketpath = '/tmp/.X11-unix/X';
 
 # reads in a whole file
@@ -20,13 +21,16 @@ sub slurp {
 
 # forks an Xdummy or Xdmx process
 sub fork_xserver {
+    my $keep_xdummy_output = shift;
     my $displaynum = shift;
     my $pid = fork();
     die "Could not fork: $!" unless defined($pid);
     if ($pid == 0) {
         # Child, close stdout/stderr, then start Xdummy.
-        close STDOUT;
-        close STDERR;
+        if (!$keep_xdummy_output) {
+            close STDOUT;
+            close STDERR;
+        }
 
         exec @_;
         exit 1;
@@ -37,6 +41,8 @@ sub fork_xserver {
         unlink($x_socketpath . $displaynum);
     });
 
+    push @pids, $pid;
+
     return $x_socketpath . $displaynum;
 }
 
@@ -63,11 +69,23 @@ the Xdummy processes and a list of PIDs of the processes.
 =cut
 
 sub start_xdummy {
-    my ($parallel, $numtests) = @_;
+    my ($parallel, $numtests, $keep_xdummy_output) = @_;
 
     my @displays = ();
     my @childpids = ();
 
+    $SIG{CHLD} = sub {
+        my $child = waitpid -1, POSIX::WNOHANG;
+        @pids = grep { $_ != $child } @pids;
+        return unless @pids == 0;
+        print STDERR "All Xdummy processes died.\n";
+        print STDERR "Use ./complete-run.pl --parallel 1 --keep-xdummy-output\n";
+        print STDERR "";
+        print STDERR "A frequent cause for this is missing the DUMMY Xorg module,\n";
+        print STDERR "package xserver-xorg-video-dummy on Debian.\n";
+        exit 1;
+    };
+
     # Yeah, I know it’s non-standard, but Perl’s POSIX module doesn’t have
     # _SC_NPROCESSORS_CONF.
     my $cpuinfo = slurp('/proc/cpuinfo');
@@ -93,8 +111,9 @@ sub start_xdummy {
         # We use -config /dev/null to prevent Xdummy from using the system
         # Xorg configuration. The tests should be independant from the
         # actual system X configuration.
-        my $socket = fork_xserver($displaynum, './Xdummy', ":$displaynum",
-                '-config', '/dev/null', '-nolisten', 'tcp');
+        my $socket = fork_xserver($keep_xdummy_output, $displaynum,
+                './Xdummy', ":$displaynum", '-config', '/dev/null',
+                '-nolisten', 'tcp');
         push(@displays, ":$displaynum");
         push(@sockets_waiting, $socket);
         $displaynum++;
index 66f22bc03a7bee785012f901399898e68c72b001..140537d4c74d54a46c61d576df76ee01fe642eb8 100644 (file)
@@ -8,6 +8,8 @@ use IO::Handle; # for ->autoflush
 
 use POSIX ();
 
+use Errno qw(EAGAIN);
+
 use Exporter 'import';
 our @EXPORT = qw(worker worker_next);
 
@@ -74,7 +76,12 @@ sub worker_wait {
     my $ipc = $self->{ipc};
     my $ipc_fd = fileno($ipc);
 
-    while (defined(my $file = $ipc->getline)) {
+    while (1) {
+        my $file = $ipc->getline;
+        if (!defined($file)) {
+            next if $! == EAGAIN;
+            last;
+        }
         chomp $file;
 
         exit unless $file;
@@ -105,12 +112,13 @@ sub worker_wait {
             $test->failure_output(\*STDERR);
             $test->todo_output(\*STDOUT);
 
-            @ENV{qw(DISPLAY TESTNAME OUTDIR VALGRIND STRACE COVERAGE RESTART)}
+            @ENV{qw(DISPLAY TESTNAME OUTDIR VALGRIND STRACE XTRACE COVERAGE RESTART)}
                 = ($self->{display},
                    basename($file),
                    $outdir,
                    $options->{valgrind},
                    $options->{strace},
+                   $options->{xtrace},
                    $options->{coverage},
                    $options->{restart});
 
index 4c41a7f2f27d4ccbc210f913417262dcabdd7992..12f81ea162106189ae52b24fdb81ed66b9890c67 100644 (file)
@@ -34,7 +34,6 @@ our @EXPORT = qw(
     get_dock_clients
     cmd
     sync_with_i3
-    does_i3_live
     exit_gracefully
     workspace_exists
     focused_ws
@@ -46,6 +45,39 @@ our @EXPORT = qw(
     $x
 );
 
+=head1 NAME
+
+i3test - Testcase setup module
+
+=encoding utf-8
+
+=head1 SYNOPSIS
+
+  use i3test;
+
+  my $ws = fresh_workspace;
+  is_num_children($ws, 0, 'no containers on this workspace yet');
+  cmd 'open';
+  is_num_children($ws, 1, 'one container after "open"');
+
+  done_testing;
+
+=head1 DESCRIPTION
+
+This module is used in every i3 testcase and takes care of automatically
+starting i3 before any test instructions run. It also saves you typing of lots
+of boilerplate in every test file.
+
+
+i3test automatically "use"s C<Test::More>, C<Data::Dumper>, C<AnyEvent::I3>,
+C<Time::HiRes>’s C<sleep> and C<i3test::Test> so that all of them are available
+to you in your testcase.
+
+See also C<i3test::Test> (L<http://build.i3wm.org/docs/lib-i3test-test.html>)
+which provides additional test instructions (like C<ok> or C<is>).
+
+=cut
+
 my $tester = Test::Builder->new();
 my $_cached_socket_path = undef;
 my $_sync_window = undef;
@@ -115,6 +147,7 @@ use Test::More $test_more_args;
 use Data::Dumper;
 use AnyEvent::I3;
 use Time::HiRes qw(sleep);
+use i3test::Test;
 __
     $tester->BAIL_OUT("$@") if $@;
     feature->import(":5.10");
@@ -128,15 +161,19 @@ __
     goto \&Exporter::import;
 }
 
-#
-# Waits for the next event and calls the given callback for every event to
-# determine if this is the event we are waiting for.
-#
-# Can be used to wait until a window is mapped, until a ClientMessage is
-# received, etc.
-#
-# wait_for_event $x, 0.25, sub { $_[0]->{response_type} == MAP_NOTIFY };
-#
+=head1 EXPORT
+
+=head2 wait_for_event($timeout, $callback)
+
+Waits for the next event and calls the given callback for every event to
+determine if this is the event we are waiting for.
+
+Can be used to wait until a window is mapped, until a ClientMessage is
+received, etc.
+
+  wait_for_event 0.25, sub { $_[0]->{response_type} == MAP_NOTIFY };
+
+=cut
 sub wait_for_event {
     my ($timeout, $cb) = @_;
 
@@ -165,8 +202,24 @@ sub wait_for_event {
     return $result;
 }
 
-# thin wrapper around wait_for_event which waits for MAP_NOTIFY
-# make sure to include 'structure_notify' in the window’s event_mask attribute
+=head2 wait_for_map($window)
+
+Thin wrapper around wait_for_event which waits for MAP_NOTIFY.
+Make sure to include 'structure_notify' in the window’s event_mask attribute.
+
+This function is called by C<open_window>, so in most cases, you don’t need to
+call it on your own. If you need special setup of the window before mapping,
+you might have to map it on your own and use this function:
+
+  my $window = open_window(dont_map => 1);
+  # Do something special with the window first
+  # …
+
+  # Now map it and wait until it’s been mapped
+  $window->map;
+  wait_for_map($window);
+
+=cut
 sub wait_for_map {
     my ($win) = @_;
     my $id = (blessed($win) && $win->isa('X11::XCB::Window')) ? $win->id : $win;
@@ -175,9 +228,20 @@ sub wait_for_map {
     };
 }
 
-# Wrapper around wait_for_event which waits for UNMAP_NOTIFY. Also calls
-# sync_with_i3 to make sure i3 also picked up and processed the UnmapNotify
-# event.
+=head2 wait_for_unmap($window)
+
+Wrapper around C<wait_for_event> which waits for UNMAP_NOTIFY. Also calls
+C<sync_with_i3> to make sure i3 also picked up and processed the UnmapNotify
+event.
+
+  my $ws = fresh_workspace;
+  my $window = open_window;
+  is_num_children($ws, 1, 'one window on workspace');
+  $window->unmap;
+  wait_for_unmap;
+  is_num_children($ws, 0, 'no more windows on this workspace');
+
+=cut
 sub wait_for_unmap {
     my ($win) = @_;
     # my $id = (blessed($win) && $win->isa('X11::XCB::Window')) ? $win->id : $win;
@@ -187,25 +251,71 @@ sub wait_for_unmap {
     sync_with_i3();
 }
 
-#
-# Opens a new window (see X11::XCB::Window), maps it, waits until it got mapped
-# and synchronizes with i3.
-#
-# set dont_map to a true value to avoid mapping
-#
-# if you want to change aspects of your window before it would be mapped,
-# set before_map to a coderef. $window gets passed as $_ and as first argument.
-#
-# if you set both dont_map and before_map, the coderef will be called nevertheless
-#
-#
-# default values:
-#     class => WINDOW_CLASS_INPUT_OUTPUT
-#     rect => [ 0, 0, 30, 30 ]
-#     background_color => '#c0c0c0'
-#     event_mask => [ 'structure_notify' ]
-#     name => 'Window <n>'
-#
+=head2 open_window([ $args ])
+
+Opens a new window (see C<X11::XCB::Window>), maps it, waits until it got mapped
+and synchronizes with i3.
+
+The following arguments can be passed:
+
+=over 4
+
+=item class
+
+The X11 window class (e.g. WINDOW_CLASS_INPUT_OUTPUT), not to be confused with
+the WM_CLASS!
+
+=item rect
+
+An arrayref with 4 members specifying the initial geometry (position and size)
+of the window, e.g. C<< [ 0, 100, 70, 50 ] >> for a window appearing at x=0, y=100
+with width=70 and height=50.
+
+Note that this is entirely irrelevant for tiling windows.
+
+=item background_color
+
+The background pixel color of the window, formatted as "#rrggbb", like HTML
+color codes (e.g. #c0c0c0). This is useful to tell windows apart when actually
+watching the testcases.
+
+=item event_mask
+
+An arrayref containing strings which describe the X11 event mask we use for that
+window. The default is C<< [ 'structure_notify' ] >>.
+
+=item name
+
+The window’s C<_NET_WM_NAME> (UTF-8 window title). By default, this is "Window
+n" with n being replaced by a counter to keep windows apart.
+
+=item dont_map
+
+Set to a true value to avoid mapping the window (making it visible).
+
+=item before_map
+
+A coderef which is called before the window is mapped (unless C<dont_map> is
+true). The freshly created C<$window> is passed as C<$_> and as the first
+argument.
+
+=back
+
+The default values are equivalent to this call:
+
+  open_window(
+    class => WINDOW_CLASS_INPUT_OUTPUT
+    rect => [ 0, 0, 30, 30 ]
+    background_color => '#c0c0c0'
+    event_mask => [ 'structure_notify' ]
+    name => 'Window <n>'
+  );
+
+Usually, though, calls are simpler:
+
+  my $top_window = open_window;
+
+=cut
 sub open_window {
     my %args = @_ == 1 ? %{$_[0]} : @_;
 
@@ -233,8 +343,14 @@ sub open_window {
     return $window;
 }
 
-# Thin wrapper around open_window which sets window_type to
-# _NET_WM_WINDOW_TYPE_UTILITY to make the window floating.
+=head2 open_floating_window([ $args ])
+
+Thin wrapper around open_window which sets window_type to
+C<_NET_WM_WINDOW_TYPE_UTILITY> to make the window floating.
+
+The arguments are the same as those of C<open_window>.
+
+=cut
 sub open_floating_window {
     my %args = @_ == 1 ? %{$_[0]} : @_;
 
@@ -250,6 +366,15 @@ sub open_empty_con {
     return $reply->[0]->{id};
 }
 
+=head2 get_workspace_names()
+
+Returns an arrayref containing the name of every workspace (regardless of its
+output) which currently exists.
+
+  my $workspace_names = get_workspace_names;
+  is(scalar @$workspace_names, 3, 'three workspaces exist currently');
+
+=cut
 sub get_workspace_names {
     my $i3 = i3(get_socket_path());
     my $tree = $i3->get_tree->recv;
@@ -264,6 +389,15 @@ sub get_workspace_names {
     [ map { $_->{name} } @cons ]
 }
 
+=head2 get_unused_workspace
+
+Returns a workspace name which has not yet been used. See also
+C<fresh_workspace> which directly switches to an unused workspace.
+
+  my $ws = get_unused_workspace;
+  cmd "workspace $ws";
+
+=cut
 sub get_unused_workspace {
     my @names = get_workspace_names();
     my $tmp;
@@ -271,7 +405,7 @@ sub get_unused_workspace {
     $tmp
 }
 
-=head2 fresh_workspace(...)
+=head2 fresh_workspace([ $args ])
 
 Switches to an unused workspace and returns the name of that workspace.
 
@@ -304,6 +438,30 @@ sub fresh_workspace {
     $unused
 }
 
+=head2 get_ws($workspace)
+
+Returns the container (from the i3 layout tree) which represents C<$workspace>.
+
+  my $ws = fresh_workspace;
+  my $ws_con = get_ws($ws);
+  ok(!$ws_con->{urgent}, 'fresh workspace not marked urgent');
+
+Here is an example which counts the number of urgent containers recursively,
+starting from the workspace container:
+
+  sub count_urgent {
+      my ($con) = @_;
+
+      my @children = (@{$con->{nodes}}, @{$con->{floating_nodes}});
+      my $urgent = grep { $_->{urgent} } @children;
+      $urgent += count_urgent($_) for @children;
+      return $urgent;
+  }
+  my $urgent = count_urgent(get_ws($ws));
+  is($urgent, 3, "three urgent windows on workspace $ws");
+
+
+=cut
 sub get_ws {
     my ($name) = @_;
     my $i3 = i3(get_socket_path());
@@ -322,17 +480,61 @@ sub get_ws {
     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
-#
+=head2 get_ws_content($workspace)
+
+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.
+
+  my $nodes = get_ws_content($ws);
+  is(scalar @$nodes, 4, 'there are four containers at workspace-level');
+
+Or, in array context:
+
+  my $window = open_window;
+  my ($nodes, $focus) = get_ws_content($ws);
+  is($focus->[0], $window->id, 'newly opened window focused');
+
+Note that this function does not do recursion for you! It only returns the
+containers B<on workspace level>. If you want to work with all containers (even
+nested ones) on a workspace, you have to use recursion:
+
+  # NB: This function does not count floating windows
+  sub count_urgent {
+      my ($nodes) = @_;
+
+      my $urgent = 0;
+      for my $con (@$nodes) {
+          $urgent++ if $con->{urgent};
+          $urgent += count_urgent($con->{nodes});
+      }
+
+      return $urgent;
+  }
+  my $nodes = get_ws_content($ws);
+  my $urgent = count_urgent($nodes);
+  is($urgent, 3, "three urgent windows on workspace $ws");
+
+If you also want to deal with floating windows, you have to use C<get_ws>
+instead and access C<< ->{nodes} >> and C<< ->{floating_nodes} >> on your own.
+
+=cut
 sub get_ws_content {
     my ($name) = @_;
     my $con = get_ws($name);
     return wantarray ? ($con->{nodes}, $con->{focus}) : $con->{nodes};
 }
 
+=head2 get_focused($workspace)
+
+Returns the container ID of the currently focused container on C<$workspace>.
+
+  my $ws = fresh_workspace;
+  my $first_window = open_window;
+  my $second_window = open_window;
+  is(get_focused($ws), $second_window, 'second window focused');
+
+=cut
 sub get_focused {
     my ($ws) = @_;
     my $con = get_ws($ws);
@@ -350,6 +552,16 @@ sub get_focused {
     return $lf;
 }
 
+=head2 get_dock_clients([ $dockarea ])
+
+Returns an array of all dock containers in C<$dockarea> (one of "top" or
+"bottom"). If C<$dockarea> is not specified, returns an array of all dock
+containers in any dockarea.
+
+  my @docked = get_dock_clients;
+  is(scalar @docked, 0, 'no dock clients yet');
+
+=cut
 sub get_dock_clients {
     my $which = shift;
 
@@ -374,10 +586,30 @@ sub get_dock_clients {
     return @docked;
 }
 
+=head2 cmd($command)
+
+Sends the specified command to i3.
+
+  my $ws = unused_workspace;
+  cmd "workspace $ws";
+  cmd 'focus right';
+
+=cut
 sub cmd {
     i3(get_socket_path())->command(@_)->recv
 }
 
+=head2 workspace_exists($workspace)
+
+Returns true if C<$workspace> is the name of an existing workspace.
+
+  my $old_ws = focused_ws;
+  # switch away from where we currently are
+  fresh_workspace;
+
+  ok(workspace_exists($old_ws), 'old workspace still exists');
+
+=cut
 sub workspace_exists {
     my ($name) = @_;
     ($name ~~ @{get_workspace_names()})
@@ -387,6 +619,9 @@ sub workspace_exists {
 
 Returns the name of the currently focused workspace.
 
+  my $ws = focused_ws;
+  is($ws, '1', 'i3 starts on workspace 1');
+
 =cut
 sub focused_ws {
     my $i3 = i3(get_socket_path());
@@ -398,16 +633,31 @@ sub focused_ws {
     return $first->{name}
 }
 
-#
-# Sends an I3_SYNC ClientMessage with a random value to the root window.
-# i3 will reply with the same value, but, due to the order of events it
-# processes, only after all other events are done.
-#
-# This can be used to ensure the results of a cmd 'focus left' are pushed to
-# X11 and that $x->input_focus returns the correct value afterwards.
-#
-# See also docs/testsuite for a long explanation
-#
+=head2 sync_with_i3([ $args ])
+
+Sends an I3_SYNC ClientMessage with a random value to the root window.
+i3 will reply with the same value, but, due to the order of events it
+processes, only after all other events are done.
+
+This can be used to ensure the results of a cmd 'focus left' are pushed to
+X11 and that C<< $x->input_focus >> returns the correct value afterwards.
+
+See also L<http://build.i3wm.org/docs/testsuite.html> for a longer explanation.
+
+  my $window = open_window;
+  $window->add_hint('urgency');
+  # Ensure i3 picked up the change
+  sync_with_i3;
+
+The only time when you need to use the C<no_cache> argument is when you just
+killed your own X11 connection:
+
+  cmd 'kill client';
+  # We need to re-establish the X11 connection which we just killed :).
+  $x = i3test::X11->new;
+  sync_with_i3(no_cache => 1);
+
+=cut
 sub sync_with_i3 {
     my %args = @_ == 1 ? %{$_[0]} : @_;
 
@@ -458,15 +708,22 @@ sub sync_with_i3 {
     };
 }
 
-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;
-}
+=head2 exit_gracefully($pid, [ $socketpath ])
+
+Tries to exit i3 gracefully (with the 'exit' cmd) or kills the PID if that fails.
+
+If C<$socketpath> is not specified, C<get_socket_path()> will be called.
 
-# Tries to exit i3 gracefully (with the 'exit' cmd) or kills the PID if that fails
+You only need to use this function if you have launched i3 on your own with
+C<launch_with_config>. Otherwise, it will be automatically called when the
+testcase ends.
+
+  use i3test i3_autostart => 0;
+  my $pid = launch_with_config($config);
+  # …
+  exit_gracefully($pid);
+
+=cut
 sub exit_gracefully {
     my ($pid, $socketpath) = @_;
     $socketpath ||= get_socket_path();
@@ -491,7 +748,20 @@ sub exit_gracefully {
     undef $i3_pid;
 }
 
-# Gets the socket path from the I3_SOCKET_PATH atom stored on the X11 root window
+=head2 get_socket_path([ $cache ])
+
+Gets the socket path from the C<I3_SOCKET_PATH> atom stored on the X11 root
+window. After the first call, this function will return a cached version of the
+socket path unless you specify a false value for C<$cache>.
+
+  my $i3 = i3(get_socket_path());
+  $i3->command('nop test example')->recv;
+
+To avoid caching:
+
+  my $i3 = i3(get_socket_path(0));
+
+=cut
 sub get_socket_path {
     my ($cache) = @_;
     $cache ||= 1;
@@ -511,9 +781,26 @@ sub get_socket_path {
     return $socketpath;
 }
 
-#
-# launches a new i3 process with the given string as configuration file.
-# useful for tests which test specific config file directives.
+=head2 launch_with_config($config, [ $args ])
+
+Launches a new i3 process with C<$config> as configuration file. Useful for
+tests which test specific config file directives.
+
+  use i3test i3_autostart => 0;
+
+  my $config = <<EOT;
+  # i3 config file (v4)
+  for_window [class="borderless"] border none
+  for_window [title="special borderless title"] border none
+  EOT
+
+  my $pid = launch_with_config($config);
+
+  # …
+
+  exit_gracefully($pid);
+
+=cut
 sub launch_with_config {
     my ($config, %args) = @_;
 
@@ -546,6 +833,7 @@ sub launch_with_config {
         testname => $ENV{TESTNAME},
         valgrind => $ENV{VALGRIND},
         strace => $ENV{STRACE},
+        xtrace => $ENV{XTRACE},
         restart => $ENV{RESTART},
         cv => $cv,
         dont_create_temp_dir => $args{dont_create_temp_dir},
@@ -563,6 +851,12 @@ sub launch_with_config {
     return $i3_pid;
 }
 
+=head1 AUTHOR
+
+Michael Stapelberg <michael@i3wm.org>
+
+=cut
+
 package i3test::X11;
 use parent 'X11::XCB::Connection';
 
diff --git a/testcases/lib/i3test/Test.pm b/testcases/lib/i3test/Test.pm
new file mode 100644 (file)
index 0000000..0253bc2
--- /dev/null
@@ -0,0 +1,109 @@
+package i3test::Test;
+# vim:ts=4:sw=4:expandtab
+
+use base 'Test::Builder::Module';
+
+our @EXPORT = qw(
+    is_num_children
+    cmp_float
+    does_i3_live
+);
+
+my $CLASS = __PACKAGE__;
+
+=head1 NAME
+
+i3test::Test - Additional test instructions for use in i3 testcases
+
+=head1 SYNOPSIS
+
+  use i3test;
+
+  my $ws = fresh_workspace;
+  is_num_children($ws, 0, 'no containers on this workspace yet');
+  cmd 'open';
+  is_num_children($ws, 1, 'one container after "open"');
+
+  done_testing;
+
+=head1 DESCRIPTION
+
+This module provides convenience methods for i3 testcases. If you notice that a
+certain pattern is present in 5 or more test cases, it should most likely be
+moved into this module.
+
+=head1 EXPORT
+
+=head2 is_num_children($workspace, $expected, $test_name)
+
+Gets the number of children on the given workspace and verifies that they match
+the expected amount of children.
+
+  is_num_children('1', 0, 'no containers on workspace 1 at startup');
+
+=cut
+
+sub is_num_children {
+    my ($workspace, $num_children, $name) = @_;
+    my $tb = $CLASS->builder;
+
+    my $con = i3test::get_ws($workspace);
+    $tb->ok(defined($con), "Workspace $workspace exists");
+    if (!defined($con)) {
+        $tb->skip("Workspace does not exist, skipping is_num_children");
+        return;
+    }
+
+    my $got_num_children = scalar @{$con->{nodes}};
+
+    $tb->is_num($got_num_children, $num_children, $name);
+}
+
+=head2 cmp_float($a, $b)
+
+Compares floating point numbers C<$a> and C<$b> and returns true if they differ
+less then 1e-6.
+
+  $tmp = fresh_workspace;
+
+  open_window for (1..4);
+
+  cmd 'resize grow width 10 px or 25 ppt';
+
+  ($nodes, $focus) = get_ws_content($tmp);
+  ok(cmp_float($nodes->[0]->{percent}, 0.166666666666667), 'first window got 16%');
+  ok(cmp_float($nodes->[1]->{percent}, 0.166666666666667), 'second window got 16%');
+  ok(cmp_float($nodes->[2]->{percent}, 0.166666666666667), 'third window got 16%');
+  ok(cmp_float($nodes->[3]->{percent}, 0.50), 'fourth window got 50%');
+
+=cut
+sub cmp_float {
+  my ($a, $b, $name) = @_;
+  my $tb = $CLASS->builder;
+
+  $tb->cmp_ok(abs($a - $b), '<', 1e-6, $name);
+}
+
+=head2 does_i3_live
+
+Returns true if the layout tree can still be received from i3.
+
+  # i3 used to crash on invalid commands in revision X
+  cmd 'invalid command';
+  does_i3_live;
+
+=cut
+sub does_i3_live {
+    my $tree = i3test::i3(i3test::get_socket_path())->get_tree->recv;
+    my @nodes = @{$tree->{nodes}};
+    my $tb = $CLASS->builder;
+    $tb->ok((@nodes > 0), 'i3 still lives');
+}
+
+=head1 AUTHOR
+
+Michael Stapelberg <michael@i3wm.org>
+
+=cut
+
+1
index c13b87c4f57bef37e8e8ddd45f76c3976b78dfd9..61685a64fb7cfa906146779d84923b35764fc419 100644 (file)
@@ -1,5 +1,18 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
 
 use i3test;
 
index 1377ee942407f8c615273212ea82e563b1134441..7d840426348008670f529f5c0b8b004d2f4fc2dd 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # checks if i3 supports I3_SYNC
 #
 use i3test;
index 34359f20a7e28410378666bbd0f2c3665fa12295..020e19cc7920a61f38c5ea84a5ffe4c7a71761e1 100644 (file)
@@ -1,5 +1,18 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
 
 use i3test;
 
index e998eb46fcddce97ec82634520abff3cad1599a0..cb173ac0701d02cfca708ccd693174eeb31a2d38 100644 (file)
@@ -1,5 +1,18 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
 
 use i3test;
 
index db5fb6db5ed564bf27a35c731724af7a32220134..2a0d91025592dfe01a10733ddad1b655f2c36008 100644 (file)
@@ -1,5 +1,18 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
 
 use i3test;
 
index cee77132593c06e2917eea4fe1179fe09a6a5103..81a97d062156f603b539a1733816e89fff2b6381 100644 (file)
@@ -1,5 +1,18 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
 
 use i3test;
 use List::Util qw(first);
index 8a795c46c0a3d4f8a555fa08f5700c0be00b9cf0..d6ce0fb864ee6eeb1e39f7ffa4f2723249687478 100644 (file)
@@ -1,5 +1,18 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
 
 use i3test;
 
index 20acf49e033ded8426b45b9f54671b06006c618d..1bac40f053e43318b242c0b61b2d1b6175e83b4b 100644 (file)
@@ -1,5 +1,18 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
 
 use i3test;
 use X11::XCB 'PROP_MODE_REPLACE';
index 040faf203edbe05f90e0f72aed1d67d46cda667f..0e01d90b76add45d1e91796573de17a470a86dec 100644 (file)
@@ -1,5 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Beware that this test uses workspace 9 to perform some tests (it expects
 # the workspace to be empty).
 # TODO: skip it by default?
index 3b3fe74d79f5c02a844aa053e005a2a275e327f1..38227635f0fdda7d91c7e223817bddac28910d72 100644 (file)
@@ -1,5 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Checks if the focus is correctly restored, when creating a floating client
 # over an unfocused tiling client and destroying the floating one again.
 
index ec7b8df874ab71d6b33b7742a83fda3d5cbffd12..96c649753c60e8a647dd2d6063cc1f5a37658e93 100644 (file)
@@ -1,5 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Beware that this test uses workspace 9 to perform some tests (it expects
 # the workspace to be empty).
 # TODO: skip it by default?
index 078ab92c071ff8b6a55b25320262f7383997a936..c806486335065a93a85640979b387ac5480bedce 100644 (file)
@@ -1,5 +1,18 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
 
 use i3test;
 use File::Temp;
index 52817d709d0ed8784cfcb98ac51d6a8de41c0612..ec690b5eb4f66d0f03fc442b538c9c81c11d702e 100644 (file)
@@ -1,5 +1,18 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
 
 use i3test;
 
index 04f72c3d8319945422d0f455d043d08b6a8f3c8a..10368532eeae60c3eb8431efcc354e83f195cf45 100644 (file)
@@ -1,5 +1,18 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
 
 use i3test;
 use List::Util qw(first);
index 497bad9e1aa73195bc8eb6d434650032a842fe83..63e92c3ca422c6bf8644efd21e269ba980cc3034 100644 (file)
@@ -1,5 +1,18 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
 
 use i3test;
 
index 4d9a02948fe4e4b22593232bc26d5b1aa65c86d7..ec2ec9d2a242f948ffb105c81e5ba4f9ebce229e 100644 (file)
@@ -1,5 +1,18 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
 
 use i3test;
 
index 3a495e2788d5ee6842c745197fc96977511cb27e..794473861f41af4b70e90fccb3a5c94933b0f0d0 100644 (file)
@@ -1,5 +1,18 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
 
 use i3test;
 use List::Util qw(first);
@@ -39,14 +52,16 @@ my $expected = {
     name => 'root',
     orientation => $ignore,
     type => 0,
+    split => JSON::XS::false,
     id => $ignore,
     rect => $ignore,
     window_rect => $ignore,
     geometry => $ignore,
     swallows => $ignore,
     percent => undef,
-    layout => 'default',
+    layout => 'splith',
     floating => 'auto_off',
+    last_split_layout => 'splith',
     scratchpad_state => 'none',
     focus => $ignore,
     focused => JSON::XS::false,
index 1d8888c78c230c2d753609f42ad54762d2773785..7991abe54458d9ef2a1d6375270414b956ce07c5 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests whether we can switch to a non-existant workspace
 # (necessary for further tests)
 #
index e2a729c5d38def1abf448f4795b76e63b05339be..fa4becc43569252b563e6d9c51df34f21791985f 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests whether opening an empty container and killing it again works
 #
 use List::Util qw(first);
index e6a4e832fdb3daa2a7da0d7abe565d68c9cba049..7ac622c72457777c6610a047532ce7abd1fa5b55 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests all kinds of matching methods
 #
 use i3test;
@@ -26,8 +39,7 @@ my $win = $content->[0];
 cmd q|[class=".*"] kill|;
 cmd q|[con_id="99999"] kill|;
 
-$content = get_ws_content($tmp);
-ok(@{$content} == 1, 'window still there');
+is_num_children($tmp, 1, 'window still there');
 
 # now kill the window
 cmd 'nop now killing the window';
@@ -37,8 +49,7 @@ cmd qq|[con_id="$id"] kill|;
 wait_for_unmap $window;
 
 cmd 'nop checking if its gone';
-$content = get_ws_content($tmp);
-ok(@{$content} == 0, 'window killed');
+is_num_children($tmp, 0, 'window killed');
 
 # TODO: same test, but with pcre expressions
 
@@ -86,15 +97,13 @@ my $right = open_special(name => 'right');
 ok($right->mapped, 'right window mapped');
 
 # two windows should be here
-$content = get_ws_content($tmp);
-ok(@{$content} == 2, 'two windows opened');
+is_num_children($tmp, 2, 'two windows opened');
 
 cmd '[class="special" title="left"] kill';
 
 sync_with_i3;
 
-$content = get_ws_content($tmp);
-is(@{$content}, 1, 'one window still there');
+is_num_children($tmp, 1, 'one window still there');
 
 ######################################################################
 # check that regular expressions work
@@ -104,17 +113,11 @@ $tmp = fresh_workspace;
 
 $left = open_special(name => 'left', wm_class => 'special7');
 ok($left->mapped, 'left window mapped');
-
-# two windows should be here
-$content = get_ws_content($tmp);
-ok(@{$content} == 1, 'window opened');
+is_num_children($tmp, 1, 'window opened');
 
 cmd '[class="^special[0-9]$"] kill';
-
 wait_for_unmap $left;
-
-$content = get_ws_content($tmp);
-is(@{$content}, 0, 'window killed');
+is_num_children($tmp, 0, 'window killed');
 
 ######################################################################
 # check that UTF-8 works when matching
@@ -124,16 +127,10 @@ $tmp = fresh_workspace;
 
 $left = open_special(name => 'ä 3', wm_class => 'special7');
 ok($left->mapped, 'left window mapped');
-
-# two windows should be here
-$content = get_ws_content($tmp);
-ok(@{$content} == 1, 'window opened');
+is_num_children($tmp, 1, 'window opened');
 
 cmd '[title="^\w [3]$"] kill';
-
 wait_for_unmap $left;
-
-$content = get_ws_content($tmp);
-is(@{$content}, 0, 'window killed');
+is_num_children($tmp, 0, 'window killed');
 
 done_testing;
index 088caf7199eb7e7ed44698dd1a0da13490a39c09..2403fe22d32d12e731a476a5ad6f03a6b2ea8e0d 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests multiple commands (using ';') and multiple operations (using ',')
 #
 use i3test;
index 447be31569ff190c827e458688c736a1e78b0fc2..3228b259a46492e257471c2cc13af95a4aa6d568 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests focus switching (next/prev)
 #
 use i3test;
index f672e9d632ebc4391faca496758ca10bb9a9523d..01765e1e92a8dbeb9f09081c8812b0c705d041e4 100644 (file)
@@ -1,9 +1,23 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests splitting
 #
 use i3test;
+use List::Util qw(first);
 
 my $tmp;
 my $ws;
@@ -19,10 +33,10 @@ sub verify_split_layout {
     $tmp = fresh_workspace;
 
     $ws = get_ws($tmp);
-    is($ws->{orientation}, 'horizontal', 'orientation horizontal by default');
+    is($ws->{layout}, 'splith', 'orientation horizontal by default');
     cmd 'split v';
     $ws = get_ws($tmp);
-    is($ws->{orientation}, 'vertical', 'split v changes workspace orientation');
+    is($ws->{layout}, 'splitv', 'split v changes workspace orientation');
 
     cmd 'open';
     cmd 'open';
@@ -47,7 +61,7 @@ sub verify_split_layout {
 
     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->{layout}, 'splith', 'orientation is horizontal');
     is(@{$second->{nodes}}, 2, 'second container has 2 children');
     is($second->{nodes}->[0]->{name}, $old_name, 'found old second container');
 }
@@ -66,10 +80,10 @@ verify_split_layout(split_command => 'split horizontal');
 $tmp = fresh_workspace;
 
 $ws = get_ws($tmp);
-is($ws->{orientation}, 'horizontal', 'orientation horizontal by default');
+is($ws->{layout}, 'splith', 'orientation horizontal by default');
 cmd 'split v';
 $ws = get_ws($tmp);
-is($ws->{orientation}, 'vertical', 'split v changes workspace orientation');
+is($ws->{layout}, 'splitv', 'split v changes workspace orientation');
 
 cmd 'open';
 my @content = @{get_ws_content($tmp)};
@@ -119,4 +133,29 @@ cmd 'open';
 is(scalar @content, 1, 'Still one container on this ws');
 is(scalar @{$content[0]->{nodes}}, 1, 'Stacked con still has one child node');
 
+################################################################################
+# When focusing the workspace, changing the layout should have an effect on the
+# workspace, not on the parent (CT_CONTENT) container.
+################################################################################
+
+sub get_output_content {
+    my $tree = i3(get_socket_path())->get_tree->recv;
+
+    my @outputs = grep { $_->{name} !~ /^__/ } @{$tree->{nodes}};
+    is(scalar @outputs, 1, 'exactly one output (testcase not multi-monitor capable)');
+    my $output = $outputs[0];
+    # get the first (and only) CT_CON
+    return first { $_->{type} == 2 } @{$output->{nodes}};
+}
+
+$tmp = fresh_workspace;
+
+cmd 'open';
+cmd 'split v';
+cmd 'open';
+cmd 'focus parent';
+is(get_output_content()->{layout}, 'splith', 'content container layout ok');
+cmd 'layout stacked';
+is(get_output_content()->{layout}, 'splith', 'content container layout still ok');
+
 done_testing;
index 052cdbff5ce1afee6ac80aec0617be4faeca7ab8..739dc6055d2286d8489bdae67de9dd615b5013a8 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # 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
@@ -100,14 +113,12 @@ 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');
+is_num_children($tmp, 3, 'three containers after moving left');
 
 # 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');
+is_num_children($tmp, 2, 'two containers after moving right (flattening)');
 
 ######################################################################
 # 4) We create two v-split containers on the workspace, then we move
@@ -128,8 +139,7 @@ cmd "move right";
 cmd 'focus left';
 cmd "move right";
 
-$content = get_ws_content($otmp);
-is(@{$content}, 1, 'only one nodes on this workspace');
+is_num_children($otmp, 1, 'only one node on this workspace');
 
 ######################################################################
 # 5) test moving floating containers.
index 8aec87d7251686c63a43e40498bf634cfc6d1ed4..76e7f47f53ef6d984f69516eb09788f8f9bea250 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Regression: closing of floating clients did crash i3 when closing the
 # container which contained this client.
 #
index c83c0809e3c50942704be11a7b04c6f3037c4f1f..40507b51cc102f89a1d8c0f1ae294393bdbcd25f 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Regression: make a container floating, kill its parent, make it tiling again
 #
 use i3test;
index ee58968f7f083166adf4c1c0eccb156670f50374..e6f8069de351bd0695312ba2b27e8d3a560eed15 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Check if new containers are opened after the currently focused one instead
 # of always at the end
 use List::Util qw(first);
index 5fc3786e02292bfdbf988053aad25a56fc95a5a0..df226e84070bf826d9667af576b218df9a13382f 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Check if the focus is correctly restored after closing windows.
 #
 use i3test;
index bf93cc69651dc69bd32e13cafc52459ed3f6c0df..bcc838969cc0f00eb51d4a7ba97bf542e40a21a1 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Check if empty split containers are automatically closed.
 #
 use i3test;
index 9c1e74caa192b86bb3e394368ce76eb71c1cde97..c04f1b09f0ead26dc93feef274ea4a770970267a 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Check if stacking containers can be used independantly of
 # the split mode (horizontal/vertical) of the underlying
 # container.
index 3f00428c035739cabf9f6f21a04b68cb226bb592..ba26c85ff122955de3d27067efba8ac1663d9241 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Checks if the 'move [window/container] to workspace' command works correctly
 #
 use i3test;
@@ -18,20 +31,20 @@ sub move_workspace_test {
     my $tmp2 = get_unused_workspace();
     cmd "workspace $tmp";
 
-    ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+    is_num_children($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');
+    is_num_children($tmp, 2, 'two containers on first ws');
 
     cmd "workspace $tmp2";
-    ok(@{get_ws_content($tmp2)} == 0, 'no containers on second ws yet');
+    is_num_children($tmp2, 0, 'no containers on second ws yet');
 
     cmd "workspace $tmp";
 
     cmd "$movecmd $tmp2";
-    ok(@{get_ws_content($tmp)} == 1, 'one container on first ws anymore');
-    ok(@{get_ws_content($tmp2)} == 1, 'one container on second ws');
+    is_num_children($tmp, 1, 'one container on first ws anymore');
+    is_num_children($tmp2, 1, 'one container on second ws');
     my ($nodes, $focus) = get_ws_content($tmp2);
 
     is($focus->[0], $second, 'same container on different ws');
@@ -53,7 +66,7 @@ move_workspace_test('move container to workspace');
 
 cmd 'workspace 13: meh';
 cmd 'open';
-ok(@{get_ws_content('13: meh')} == 1, 'one container on 13: meh');
+is_num_children('13: meh', 1, 'one container on 13: meh');
 
 ok(!workspace_exists('13'), 'workspace 13 does not exist yet');
 
@@ -61,8 +74,8 @@ cmd 'workspace 12';
 cmd 'open';
 
 cmd 'move to workspace number 13';
-ok(@{get_ws_content('13: meh')} == 2, 'two containers on 13: meh');
-ok(@{get_ws_content('12')} == 0, 'no container on 12 anymore');
+is_num_children('13: meh', 2, 'one container on 13: meh');
+is_num_children('12', 0, 'no container on 12 anymore');
 
 ok(!workspace_exists('13'), 'workspace 13 does still not exist');
 
@@ -76,28 +89,48 @@ ok(!workspace_exists('13'), 'workspace 13 does still not exist');
 my $tmp = get_unused_workspace();
 my $tmp2 = get_unused_workspace();
 cmd "workspace $tmp";
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+is_num_children($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');
+is_num_children($tmp, 2, 'two containers');
 
 cmd "workspace $tmp2";
-ok(@{get_ws_content($tmp2)} == 0, 'no containers yet');
+is_num_children($tmp2, 0, 'no containers yet');
 my $third = open_empty_con($i3);
-ok(@{get_ws_content($tmp2)} == 1, 'one container on second ws');
+is_num_children($tmp2, 1, 'one container on second ws');
 
 # go back to the first workspace, move one of the containers to the next one
 cmd "workspace $tmp";
 cmd 'move workspace next';
-ok(@{get_ws_content($tmp)} == 1, 'one container on first ws');
-ok(@{get_ws_content($tmp2)} == 2, 'two containers on second ws');
+is_num_children($tmp, 1, 'one container on first ws');
+is_num_children($tmp2, 2, 'two containers on second ws');
 
 # go to the second workspace and move two containers to the first one
 cmd "workspace $tmp2";
 cmd 'move workspace prev';
 cmd 'move workspace prev';
-ok(@{get_ws_content($tmp)} == 3, 'three containers on first ws');
-ok(@{get_ws_content($tmp2)} == 0, 'no containers on second ws');
+is_num_children($tmp, 3, 'three containers on first ws');
+is_num_children($tmp2, 0, 'no containers on second ws');
+
+###################################################################
+# check if 'move workspace current' works
+###################################################################
+
+$tmp = get_unused_workspace();
+$tmp2 = get_unused_workspace();
+
+cmd "workspace $tmp";
+$first = open_window(name => 'win-name');
+is_num_children($tmp, 1, 'one container on first ws');
+
+cmd "workspace $tmp2";
+is_num_children($tmp2, 0, 'no containers yet');
+
+cmd qq|[title="win-name"] move workspace $tmp2|;
+is_num_children($tmp2, 1, 'one container on second ws');
+
+cmd qq|[title="win-name"] move workspace $tmp|;
+is_num_children($tmp2, 0, 'no containers on second ws');
 
 ###################################################################
 # check if floating cons are moved to new workspaces properly
@@ -121,4 +154,26 @@ $ws = get_ws($tmp2);
 is(@{$ws->{nodes}}, 0, 'no nodes on workspace');
 is(@{$ws->{floating_nodes}}, 1, 'one floating node on workspace');
 
+################################################################################
+# Check that 'move workspace number' works correctly.
+################################################################################
+
+$tmp = get_unused_workspace();
+cmd 'open';
+
+cmd 'workspace 16';
+cmd 'open';
+is_num_children('16', 1, 'one node on ws 16');
+
+cmd "workspace $tmp";
+cmd 'open';
+cmd 'move workspace number 16';
+is_num_children('16', 2, 'two nodes on ws 16');
+
+ok(!workspace_exists('17'), 'workspace 17 does not exist yet');
+cmd 'open';
+cmd 'move workspace number 17';
+ok(workspace_exists('17'), 'workspace 17 created by moving');
+is(@{get_ws('17')->{nodes}}, 1, 'one node on ws 16');
+
 done_testing;
index d3736e3c35fee0d64db0546b4de63adcdd8da24b..1d2cf4cee8e3f96c60aef6137a8893914febefe3 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Checks if size hints are interpreted correctly.
 #
 use i3test;
index d58985e3608b2f4f2a3e7223331c3df09f0b727b..494cf367c545dee0604ed6acbd3df52f414e1ec1 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
 # 
 #
 use i3test;
index c7218130166f082f22a8dcd2a991df3fcc000fc7..f38a1472f5dc71bf374d63e06b698f5368d75029 100644 (file)
@@ -1,5 +1,18 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
 
 use i3test;
 
index fa747718e012d3a6506b36647dfa773bc9f2ed3e..703707aae0dfa10ee528bd557dec0082e4fb0dbf 100644 (file)
@@ -1,6 +1,21 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
-# Regression test: when only having a floating window on a workspace, it should not be deleted.
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Regression test: when only having a floating window on a workspace, it should
+# not be deleted.
 
 use i3test;
 
index e91870bc35e0ee1fd6bd1a203803f96c1355635a..6861b1f9f6abaa9db783f9409e7eecab21fc7dfe 100644 (file)
@@ -1,5 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Regression test: Floating windows were not correctly unmapped when switching
 # to a different workspace.
 
index db86e1ca71e6e9c2e044ad8c94508697f2628cff..79b0b271e9c2125ae66d255cb8e9722781a1c2ea 100644 (file)
@@ -1,5 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Regression test: New windows were attached to the container of a floating window
 # if only a floating window is present on the workspace.
 
index 78b9191a73f278374d8b5a0a50aae230826434d9..6829a1478cc078d643fc5fe450e8b569b6795383 100644 (file)
@@ -1,5 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Check if numbered workspaces and named workspaces are sorted in the right way
 # in get_workspaces IPC output (necessary for i3bar etc.).
 use i3test;
index 3d78b1bd566a9ee022977e80bee5890cb67dfd00..0609fecb0cfaeb509d700df440c6955117d90130 100644 (file)
@@ -1,5 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Regression: Check if the focus stays the same when switching the layout
 # bug introduced by 77d0d42ed2d7ac8cafe267c92b35a81c1b9491eb
 use i3test;
index 4f84f213a8360a62c9395205f86a9887f15d8e25..e038a87b1647fe41915e6672e34e714a18841f4e 100644 (file)
@@ -1,5 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests resizing tiling containers
 use i3test;
 
@@ -22,8 +36,8 @@ 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%');
+cmp_float($nodes->[0]->{percent}, 0.25, 'top window got only 25%');
+cmp_float($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
 
 
 ############################################################
@@ -34,8 +48,8 @@ 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%');
+cmp_float($nodes->[0]->{percent}, 0.25, 'top window got only 25%');
+cmp_float($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
 
 ############################################################
 # checks that resizing within stacked/tabbed cons works
@@ -52,14 +66,14 @@ cmd 'split h';
 cmd 'layout stacked';
 
 ($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[0]->{percent}, 0.5, 'top window got 50%');
-is($nodes->[1]->{percent}, 0.5, 'bottom window got 50%');
+cmp_float($nodes->[0]->{percent}, 0.5, 'top window got 50%');
+cmp_float($nodes->[1]->{percent}, 0.5, 'bottom window got 50%');
 
 cmd 'resize grow up 10 px or 25 ppt';
 
 ($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[0]->{percent}, 0.25, 'top window got 25%');
-is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
+cmp_float($nodes->[0]->{percent}, 0.25, 'top window got 25%');
+cmp_float($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
 
 ############################################################
 # Checks that resizing in the parent's parent's orientation works.
@@ -79,14 +93,14 @@ $top = open_window;
 $bottom = open_window;
 
 ($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[0]->{percent}, 0.5, 'left window got 50%');
-is($nodes->[1]->{percent}, 0.5, 'right window got 50%');
+cmp_float($nodes->[0]->{percent}, 0.5, 'left window got 50%');
+cmp_float($nodes->[1]->{percent}, 0.5, 'right window got 50%');
 
 cmd 'resize grow left 10 px or 25 ppt';
 
 ($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[0]->{percent}, 0.25, 'left window got 25%');
-is($nodes->[1]->{percent}, 0.75, 'right window got 75%');
+cmp_float($nodes->[0]->{percent}, 0.25, 'left window got 25%');
+cmp_float($nodes->[1]->{percent}, 0.75, 'right window got 75%');
 
 ################################################################################
 # Check that the resize grow/shrink width/height syntax works.
@@ -101,8 +115,8 @@ $right = open_window;
 cmd 'resize grow width 10 px or 25 ppt';
 
 ($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[0]->{percent}, 0.25, 'left window got 25%');
-is($nodes->[1]->{percent}, 0.75, 'right window got 75%');
+cmp_float($nodes->[0]->{percent}, 0.25, 'left window got 25%');
+cmp_float($nodes->[1]->{percent}, 0.75, 'right window got 75%');
 
 # Now test it with four windows
 $tmp = fresh_workspace;
@@ -112,19 +126,19 @@ open_window for (1..4);
 cmd 'resize grow width 10 px or 25 ppt';
 
 ($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[0]->{percent}, 0.166666666666667, 'first window got 16%');
-is($nodes->[1]->{percent}, 0.166666666666667, 'second window got 16%');
-is($nodes->[2]->{percent}, 0.166666666666667, 'third window got 16%');
-is($nodes->[3]->{percent}, 0.50, 'fourth window got 50%');
+cmp_float($nodes->[0]->{percent}, 0.166666666666667, 'first window got 16%');
+cmp_float($nodes->[1]->{percent}, 0.166666666666667, 'second window got 16%');
+cmp_float($nodes->[2]->{percent}, 0.166666666666667, 'third window got 16%');
+cmp_float($nodes->[3]->{percent}, 0.50, 'fourth window got 50%');
 
 # height should be a no-op in this situation
 cmd 'resize grow height 10 px or 25 ppt';
 
 ($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[0]->{percent}, 0.166666666666667, 'first window got 16%');
-is($nodes->[1]->{percent}, 0.166666666666667, 'second window got 16%');
-is($nodes->[2]->{percent}, 0.166666666666667, 'third window got 16%');
-is($nodes->[3]->{percent}, 0.50, 'fourth window got 50%');
+cmp_float($nodes->[0]->{percent}, 0.166666666666667, 'first window got 16%');
+cmp_float($nodes->[1]->{percent}, 0.166666666666667, 'second window got 16%');
+cmp_float($nodes->[2]->{percent}, 0.166666666666667, 'third window got 16%');
+cmp_float($nodes->[3]->{percent}, 0.50, 'fourth window got 50%');
 
 
 ############################################################
index 6b2df806b08cce0632e922244fa324bf8ed49c0e..817b6ae407359332ac086f753611422bd7eefcbb 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Regression: move a floating window to a different workspace crashes i3
 #
 use i3test;
index 03d9ec12a8e3cdd46d44d8da21e146468a970f72..00f0f54153a53344cadba487cc382fd9b67c3f12 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Regression: floating windows are tiling after restarting, closing them crashes i3
 #
 use i3test;
index 03318d7afbf366ea28d216c8c30a2df82f0a8b47..6e42c883aa11982468101ea433eb1c7e6ba1ea5d 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # 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
index 9d22afc3d0f955c49e58096a0d7187bf39ca7932..33d9f1d155ac72871182477bdebc23783f6d6102 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # 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
@@ -22,7 +35,7 @@ cmd 'move up';
 cmd 'move right';
 my $ws = get_ws($tmp);
 
-is($ws->{orientation}, 'horizontal', 'workspace orientation is horizontal');
+is($ws->{layout}, 'splith', 'workspace layout is splith');
 is(@{$ws->{nodes}}, 3, 'all three windows on workspace level');
 
 done_testing;
index ca209e1cadc1a799148aa00b4c4b9e8f0050896a..e6158f861f215e55f1d62473108397f02918d276 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 use i3test;
 
 my $tmp = fresh_workspace;
index ff63711c3a6e258a5f833882bd90f611b9ed3bf2..7166aef2ce6f21589c2c9ee77e055cc6342f6e1d 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Regression test for moving a con outside of a floating con when there are no
 # tiling cons on a workspace
 #
index 3d71b5003387355344c443f2def994da406644de..248a8ffad9f4a533bb5c3c883a31c1cf19cb74d3 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Regression test for correct focus behaviour when moving a floating con to
 # another workspace.
 #
index 3cda60598db0e5aef8550870671e4d8c131886ab..cafbaffbdd80d03de63e35012bbc0cbbf3cc4ded 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Regression test for inplace restarting with dock clients
 #
 use i3test;
index 881ef8c1486728182f4a4306df1cfc90174603cc..c0fb3a7e174d408256cb10eb2997c83a5c4ab7ce 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Regression test for setting a window to floating, tiling and opening a new window
 #
 use i3test;
index 01009133c4d3db08357cb151bbc8d7efe0f7a660..771a9f07dd007a9350ade455ab0e235a5590b356 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Regression test for using level-up to get to the 'content'-container and
 # toggle floating
 #
index 83f3e85d58bf7cac5d9fe823d0c6ed07b57bda1c..d2cf206d0dc359b0f80d1f39c6bfdfbfeb1ff58f 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Test if the requested width/height is set after making the window floating.
 #
 use i3test;
index 76577fb36bb3b7ad6148338a7449098eecf23838..3bb0dd941ae938f16d1afe9304c2ff731cc66ba0 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Regression test for closing one of multiple dock clients
 #
 use i3test;
index 76c31af6b0d1a37a0f1c02ac8ee7f5220b129711..7475f9c7bddd5e69814490978b8b38b9e58124a3 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Test to see if i3 combines the geometry of all children in a split container
 # when setting the split container to floating
 #
index f9dc6dce1fe48e1fe158b10c629578fc5e6b780e..29a410d2295c6aa61fe3115141431cb92a4b6048 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Test if new containers get focused when there is a fullscreen container at
 # the time of launching the new one. Also make sure that focusing containers
 # in other workspaces work even when there is a fullscreen container.
@@ -11,9 +24,9 @@ my $i3 = i3(get_socket_path());
 
 my $tmp = fresh_workspace;
 
-#####################################################################
-# open the left window
-#####################################################################
+################################################################################
+# Open the left window.
+################################################################################
 
 my $left = open_window({ background_color => '#ff0000' });
 
@@ -21,23 +34,26 @@ is($x->input_focus, $left->id, 'left window focused');
 
 diag("left = " . $left->id);
 
-#####################################################################
-# Open the right window
-#####################################################################
+################################################################################
+# Open the right window.
+################################################################################
 
 my $right = open_window({ background_color => '#00ff00' });
 
 diag("right = " . $right->id);
 
-#####################################################################
-# Set the right window to fullscreen
-#####################################################################
+################################################################################
+# Set the right window to fullscreen.
+################################################################################
+
 cmd 'nop setting fullscreen';
 cmd 'fullscreen';
 
-#####################################################################
-# Open a third window
-#####################################################################
+################################################################################
+# Open a third window. Since we're fullscreen, the window won't be # mapped, so
+# don't wait for it to be mapped. Instead, just send the map request and sync
+# with i3 to make sure i3 recognizes it.
+################################################################################
 
 my $third = open_window({
         background_color => '#0000ff',
@@ -51,13 +67,15 @@ sync_with_i3;
 
 diag("third = " . $third->id);
 
-# move the fullscreen window to a different ws
+################################################################################
+# Move the window to a different workspace, and verify that the third window now
+# gets focused in the current workspace.
+################################################################################
 
 my $tmp2 = get_unused_workspace;
 
 cmd "move workspace $tmp2";
 
-# verify that the third window has the focus
 is($x->input_focus, $third->id, 'third window focused');
 
 ################################################################################
@@ -87,20 +105,204 @@ is($nodes->[0]->{id}, $old_id, 'id unchanged');
 is($nodes->[0]->{focused}, 1, 'fullscreen window focused');
 
 ################################################################################
-# Make sure it's possible to focus a container in a different workspace even if
-# we are currently focusing a fullscreen container.
+# Ensure it's possible to change focus if it doesn't escape the fullscreen
+# container with fullscreen global. We can't even focus a container in a
+# different workspace.
 ################################################################################
 
+cmd 'fullscreen';
+
+$tmp = fresh_workspace;
+cmd "workspace $tmp";
+my $diff_ws = open_window;
+
 $tmp2 = fresh_workspace;
-my $focusable_window = open_window;
+cmd "workspace $tmp2";
+cmd 'split h';
+
+$left = open_window;
+my $right1 = open_window;
+cmd 'split v';
+my $right2 = open_window;
+
+cmd 'focus parent';
+cmd 'fullscreen global';
+
+cmd '[id="' . $right1->id . '"] focus';
+is($x->input_focus, $right1->id, 'upper right window focused');
+
+cmd '[id="' . $right2->id . '"] focus';
+is($x->input_focus, $right2->id, 'bottom right window focused');
+
+cmd 'focus parent';
+isnt($x->input_focus, $right2->id, 'bottom right window no longer focused');
+
+cmd 'focus child';
+is($x->input_focus, $right2->id, 'bottom right window focused again');
+
+cmd '[id="' . $left->id . '"] focus';
+is($x->input_focus, $right2->id, 'prevented focus change to left window');
+
+cmd 'focus up';
+is($x->input_focus, $right1->id, 'allowed focus up');
+
+cmd 'focus down';
+is($x->input_focus, $right2->id, 'allowed focus down');
+
+cmd 'focus left';
+is($x->input_focus, $right2->id, 'prevented focus left');
+
+cmd 'focus right';
+is($x->input_focus, $right2->id, 'prevented focus right');
+
+cmd 'focus down';
+is($x->input_focus, $right1->id, 'allowed focus wrap (down)');
+
+cmd 'focus up';
+is($x->input_focus, $right2->id, 'allowed focus wrap (up)');
+
+cmd '[id="' . $diff_ws->id . '"] focus';
+is($x->input_focus, $right2->id, 'prevented focus change to different ws');
+
+################################################################################
+# Same tests when we're in non-global fullscreen mode. It should now be possible
+# to focus a container in a different workspace.
+################################################################################
+
+cmd 'focus parent';
+cmd 'fullscreen global';
+cmd 'fullscreen';
+
+cmd '[id="' . $right1->id . '"] focus';
+is($x->input_focus, $right1->id, 'upper right window focused');
+
+cmd '[id="' . $right2->id . '"] focus';
+is($x->input_focus, $right2->id, 'bottom right window focused');
+
+cmd 'focus parent';
+isnt($x->input_focus, $right2->id, 'bottom right window no longer focused');
+
+cmd 'focus child';
+is($x->input_focus, $right2->id, 'bottom right window focused again');
+
+cmd '[id="' . $left->id . '"] focus';
+is($x->input_focus, $right2->id, 'prevented focus change to left window');
+
+cmd 'focus up';
+is($x->input_focus, $right1->id, 'allowed focus up');
+
+cmd 'focus down';
+is($x->input_focus, $right2->id, 'allowed focus down');
+
+cmd 'focus left';
+is($x->input_focus, $right2->id, 'prevented focus left');
+
+cmd 'focus right';
+is($x->input_focus, $right2->id, 'prevented focus right');
+
+cmd 'focus down';
+is($x->input_focus, $right1->id, 'allowed focus wrap (down)');
+
+cmd 'focus up';
+is($x->input_focus, $right2->id, 'allowed focus wrap (up)');
+
+cmd '[id="' . $diff_ws->id . '"] focus';
+is($x->input_focus, $diff_ws->id, 'allowed focus change to different ws');
+
+################################################################################
+# More testing of the interaction between wrapping and the fullscreen focus
+# restrictions.
+################################################################################
+
+cmd '[id="' . $right1->id . '"] focus';
+is($x->input_focus, $right1->id, 'upper right window focused');
+
+cmd 'focus parent';
+cmd 'fullscreen';
+cmd 'focus child';
+
+cmd 'split v';
+my $right12 = open_window;
+
+cmd 'focus down';
+is($x->input_focus, $right2->id, 'bottom right window focused');
+
+cmd 'split v';
+my $right22 = open_window;
+
+cmd 'focus parent';
+cmd 'fullscreen';
+cmd 'focus child';
+
+cmd 'focus down';
+is($x->input_focus, $right2->id, 'focus did not leave parent container (1)');
+
+cmd 'focus down';
+is($x->input_focus, $right22->id, 'focus did not leave parent container (2)');
+
+cmd 'focus up';
+is($x->input_focus, $right2->id, 'focus did not leave parent container (3)');
+
+cmd 'focus up';
+is($x->input_focus, $right22->id, 'focus did not leave parent container (4)');
+
+################################################################################
+# Ensure that moving in a direction doesn't violate the focus restrictions.
+################################################################################
+
+sub verify_move {
+    my $num = shift;
+    my $msg = shift;
+    my $nodes = get_ws_content($tmp2);
+    my $split = $nodes->[1];
+    my $fs = $split->{nodes}->[1];
+    is(scalar @{$fs->{nodes}}, $num, $msg);
+}
+
+cmd 'move left';
+verify_move(2, 'prevented move left');
+cmd 'move right';
+verify_move(2, 'prevented move right');
+cmd 'move down';
+verify_move(2, 'prevented move down');
+cmd 'move up';
+cmd 'move up';
+verify_move(2, 'prevented move up');
+
+################################################################################
+# Moving to a different workspace is allowed with per-output fullscreen
+# containers.
+################################################################################
+
+cmd "move to workspace $tmp";
+verify_move(1, 'did not prevent move to workspace by name');
 
 cmd "workspace $tmp";
-cmd '[id="' . $focusable_window->id . '"] focus';
+cmd "move to workspace $tmp2";
+cmd "workspace $tmp2";
 
-is(focused_ws(), $tmp2, 'focus went to a different workspace');
+cmd "move to workspace prev";
+verify_move(1, 'did not prevent move to workspace by position');
 
-$nodes = get_ws_content($tmp2);
-is(scalar @$nodes, 1, 'precisely one window');
-is($nodes->[0]->{focused}, 1, 'focusable window focused');
+################################################################################
+# Ensure that is not allowed with global fullscreen containers.
+################################################################################
+
+cmd "workspace $tmp";
+cmd "move to workspace $tmp2";
+cmd "workspace $tmp2";
+
+cmd 'focus parent';
+cmd 'fullscreen';
+cmd 'fullscreen global';
+cmd 'focus child';
+
+cmd "move to workspace $tmp";
+verify_move(2, 'prevented move to workspace by name');
+
+cmd "move to workspace prev";
+verify_move(2, 'prevented move to workspace by position');
+
+# TODO: Tests for "move to output" and "move workspace to output".
 
 done_testing;
diff --git a/testcases/t/157-regress-fullscreen-level-up.t b/testcases/t/157-regress-fullscreen-level-up.t
deleted file mode 100644 (file)
index 316dbca..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression test: level up should be a noop during fullscreen mode
-#
-use i3test;
-
-my $tmp = fresh_workspace;
-
-#####################################################################
-# open a window, verify it’s not in fullscreen mode
-#####################################################################
-
-my $win = open_window;
-
-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';
-
-$nodes = get_ws_content $tmp;
-is($nodes->[0]->{fullscreen_mode}, 1, 'client fullscreen now');
-
-#####################################################################
-# send level up, try to un-fullscreen
-#####################################################################
-cmd 'focus parent';
-cmd 'fullscreen';
-
-$nodes = get_ws_content $tmp;
-is($nodes->[0]->{fullscreen_mode}, 0, 'client not fullscreen any longer');
-
-does_i3_live;
-
-done_testing;
index c4d305750183b9f020cc939f9740e132100a903c..222c93e47176877d91d77588248c2336cc243c4b 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests if the WM_TAKE_FOCUS protocol is correctly handled by i3
 #
 use i3test;
index c63bbbc4e12d6e8efa0cf31292c5b96a04809c7d..d21581d1fe9506b9f3c28bf517290e655752dcb8 100644 (file)
@@ -1,10 +1,24 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests if the various ipc_socket_path options are correctly handled
 #
 use i3test i3_autostart => 0;
 use File::Temp qw(tempfile tempdir);
+use File::Basename;
 use POSIX qw(getuid);
 use v5.10;
 
@@ -20,21 +34,14 @@ EOT
 # ensure XDG_RUNTIME_DIR is not set
 delete $ENV{XDG_RUNTIME_DIR};
 
-# See which files exist in /tmp before to not mistakenly check an already
-# existing tmpdir of another i3 instance.
-my @files_before = </tmp/i3-*>;
 my $pid = launch_with_config($config, dont_add_socket_path => 1, dont_create_temp_dir => 1);
-my @files_after = </tmp/i3-*>;
-@files_after = grep { !($_ ~~ @files_before) } @files_after;
-
-is(@files_after, 1, 'one new temp directory');
-
+my $socketpath = get_socket_path(0);
 my $folder = "/tmp/i3-" . getpwuid(getuid());
-like($files_after[0], qr/^$folder/, 'temp directory matches expected pattern');
-$folder = $files_after[0];
+like(dirname($socketpath), qr/^$folder/, 'temp directory matches expected pattern');
+$folder = dirname($socketpath);
 
 ok(-d $folder, "folder $folder exists");
-my $socketpath = "$folder/ipc-socket." . $pid;
+$socketpath = "$folder/ipc-socket." . $pid;
 ok(-S $socketpath, "file $socketpath exists and is a socket");
 
 exit_gracefully($pid);
index 9ae677e7789e47c8e9a919b639243342824b7a61..1db64575800258e798e826ed3ebd557ef30b02f0 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Regression test to check if borders are correctly restored after an inplace
 # restart.
 # found in eb8ad348b28e243cba1972e802ca8ee636472fc9
index 3562ba7a2708b9b537d6b6d40c4a03afc1b71628..6c349aad684770c3ce862e5c2b0ecddb3bb24a0b 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Regression test for setting the urgent hint on dock clients.
 # found in 4be3178d4d360c2996217d811e61161c84d25898
 #
index 6df2bcbddf0392a5c7d71dec0da1a158818d01f9..a596603019462b8e032d1547fa068f85f491e2b3 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when
 # unmapped.
 #
index bce6b23b1e7e60d2a15c551d514e4bb939ec2aa6..be30ca8fb1f764c4124f5e6329e041d44c06de7c 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when
 # unmapped.
 #
index eb266c2b4d3c59c3c84a4a3483ca6a7b43e8395e..b01de91d3b398a2bc0afc4561178f5f407a256df 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 use i3test i3_autostart => 0;
 use X11::XCB qw(PROP_MODE_REPLACE);
 
index d79c1000e92b59110c2e406d63485071016ef102..a06bb59d75067248a7ba406b7d1a6748135fd097 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests if assignments work
 #
 use i3test i3_autostart => 0;
@@ -57,6 +70,7 @@ my $tmp = fresh_workspace;
 ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
 
 my $window = open_special;
+wait_for_map($window);
 
 ok(@{get_ws_content($tmp)} == 1, 'special window got managed to current (random) workspace');
 
@@ -206,24 +220,16 @@ sub i3nagbar_running {
 $config = <<EOT;
 # i3 config file (v4)
 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-assign "special" → ~
+for_window [title="special"] floating enable
 EOT
 
 $pid = launch_with_config($config);
 
-# Ensure that i3-nagbar is running. It should be started pretty quickly, so we
-# busy-loop with a short delay.
-while (!i3nagbar_running($pid)) {
-    sleep 0.05;
-}
-
 $tmp = fresh_workspace;
 
 ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
 my @docked = get_dock_clients;
-# We expect i3-nagbar as the first dock client due to using the old assign
-# syntax
-is(@docked, 1, 'one dock client yet');
+is(@docked, 0, 'one dock client yet');
 
 $window = open_special(
     window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
@@ -233,7 +239,7 @@ $content = get_ws($tmp);
 ok(@{$content->{nodes}} == 0, 'no tiling cons');
 ok(@{$content->{floating_nodes}} == 0, 'one floating con');
 @docked = get_dock_clients;
-is(@docked, 2, 'two dock clients now');
+is(@docked, 1, 'one dock client now');
 
 $window->destroy;
 
index ee6c970675b2a4004ef3abc98f2aba97f04a20e9..033a31f2665e025ea86188b275c8c832707bcd5a 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests the workspace_layout config option.
 #
 
index ec6d48212daf83902284c25e347512d1b4fc21bc..ec8c41c815271c992d2017a7233983864b1ba550 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Verifies that i3 survives inplace restarts with fullscreen containers
 #
 use i3test;
index aec8df6cf9d9d98e48efa411d5c566782d2bca86..7377194daacb891015bee32acdc9d237c0c1fe7d 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests if the 'border toggle' command works correctly
 #
 use i3test;
index 7949ce665f5ec4cc437c77c6de1bf0e2cc03ddee..fd0865058c14e352770cf3f5cf531e6c99a5eac5 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests if the 'force_focus_wrapping' config directive works correctly.
 #
 use i3test i3_autostart => 0;
index e791bb015cd10315cf306982c5189ab56bda7dc7..a0363ef3df4017238a801433c5e67e7fb935ee69 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests if i3-migrate-config-to-v4 correctly migrates all config file
 # directives and commands
 #
@@ -195,7 +208,7 @@ 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 toggle split$|), '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');
index 42a444592bccb4fef7f90e7b3bea9d5d3f3e3830..9e6806a497046e6397382efea8654c2bda8a78b7 100644 (file)
@@ -1,5 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # checks if i3 starts up on workspace '1' or the first configured named workspace
 #
 use i3test i3_autostart => 0;
index e8964d307b7a8d3a7e57d208558e885d2caebf26..3b97feb1b4eaa0e71540dc1cc76fde026c0b6577 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # checks if the IPC message type get_marks works correctly
 #
 use i3test;
index 22306db60aa9bb11d6323bdb00a8c79e7efee7f2..91d367d1f000fb9b0db99c4d74117300a39e1b85 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Regression: Checks if focus is stolen when a window is managed which is
 # assigned to an invisible workspace
 #
index 2586657b8f6b97f47fde6e0e3531d30b0a2a9f3f..6e837cf09528e07a0f1773dc8e7598bb8cfaafd4 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests the new_window and new_float config option.
 #
 
index 469d1be81886dedafe2a4156296fd2a8e94d750a..192e97537634e4eaaa2ace55a157e305231876e4 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Regression: Checks if i3 still lives after using 'focus mode_toggle' on an
 # empty workspace. This regression was fixed in
 # 0848844f2d41055f6ffc69af1149d7a873460976.
index 3a4dbc81f3ed5d4a305f725acaab5cc516c3bacd..b27a9a70a0296420a191531f190ee18d8a5e58ed 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Test for the startup notification protocol.
 #
 
@@ -58,7 +71,7 @@ END_OF_C_CODE
 
 my $first_ws = fresh_workspace;
 
-is(@{get_ws_content($first_ws)}, 0, 'no containers on this workspace yet');
+is_num_children($first_ws, 0, 'no containers on this workspace yet');
 
 ######################################################################
 # 1) initiate startup, switch workspace, create window
@@ -95,7 +108,7 @@ is(get_startup_id(), $startup_id, 'libstartup-notification returns the same id')
 
 my $second_ws = fresh_workspace;
 
-is(@{get_ws_content($second_ws)}, 0, 'no containers on the second workspace yet');
+is_num_children($second_ws, 0, 'no containers on the second workspace yet');
 
 my $win = open_window({ dont_map => 1 });
 mark_window($win->id);
@@ -105,8 +118,8 @@ $win->map;
 # We sync with i3 here to make sure $x->input_focus is updated.
 sync_with_i3;
 
-is(@{get_ws_content($second_ws)}, 0, 'still no containers on the second workspace');
-is(@{get_ws_content($first_ws)}, 1, 'one container on the first workspace');
+is_num_children($second_ws, 0, 'still no containers on the second workspace');
+is_num_children($first_ws, 1, 'one container on the first workspace');
 
 ######################################################################
 # same thing, but with _NET_STARTUP_ID set on the leader
@@ -119,8 +132,8 @@ $win = open_window({ dont_map => 1, client_leader => $leader });
 $win->map;
 sync_with_i3;
 
-is(@{get_ws_content($second_ws)}, 0, 'still no containers on the second workspace');
-is(@{get_ws_content($first_ws)}, 2, 'two containers on the first workspace');
+is_num_children($second_ws, 0, 'still no containers on the second workspace');
+is_num_children($first_ws, 2, 'two containers on the first workspace');
 
 ######################################################################
 # 2) open another window after the startup process is completed
@@ -131,7 +144,7 @@ complete_startup();
 sync_with_i3;
 
 my $otherwin = open_window;
-is(@{get_ws_content($second_ws)}, 1, 'one container on the second workspace');
+is_num_children($second_ws, 1, 'one container on the second workspace');
 
 ######################################################################
 # 3) test that the --no-startup-id flag for exec leads to no DESKTOP_STARTUP_ID
@@ -166,5 +179,4 @@ unlink($tmp);
 
 is($startup_id, '', 'startup_id empty');
 
-
 done_testing;
index 80b2d47135aad5ef6e668001b9489de07a9501c7..07c3c84a1e8fe8c016ad0e68bb7bf33c2a229921 100644 (file)
@@ -1,5 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Checks if the 'workspace back_and_forth' command and the
 # 'workspace_auto_back_and_forth' config directive work correctly.
 #
@@ -66,12 +80,31 @@ ok(get_ws($second_ws)->{focused}, 'second workspace focused');
 
 cmd 'workspace number 5';
 ok(get_ws('5')->{focused}, 'workspace 5 focused');
+# ensure it stays open
+cmd 'open';
 
 cmd 'workspace number 6';
 ok(get_ws('6')->{focused}, 'workspace 6 focused');
+# ensure it stays open
+cmd 'open';
+
+cmd 'workspace number 6';
+is(focused_ws, '5', 'workspace 5 focused again');
+
+################################################################################
+# Rename the workspaces and see if workspace number still works with BAF.
+################################################################################
+
+cmd 'rename workspace 5 to 5: foo';
+cmd 'rename workspace 6 to 6: baz';
+
+is(focused_ws, '5: foo', 'workspace 5 still focused');
+
+cmd 'workspace number 6';
+is(focused_ws, '6: baz', 'workspace 6 now focused');
 
 cmd 'workspace number 6';
-ok(get_ws('5')->{focused}, 'workspace 5 focused again');
+is(focused_ws, '5: foo', 'workspace 5 focused again');
 
 exit_gracefully($pid);
 
index 3caa6696fe1b4e7fe51e1aa84ff51291a69d403d..762e52b87af6e1598da68583c64cccc8c2b4abde 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Checks that the bar config is parsed correctly.
 #
 
index 25fe7d9ad8de9eda9a00e71286826f0f04bde7c1..53e67bdc0d8c69096987b8d06a6a008216713cc7 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests if empty workspaces are closed when the last child
 # exits, as long as they're not empty.
 #
index 212711708b4b9e8afee95145fdc8bd4107b73b20..ae442023d4800fd5cac693a8f35cdd9c059f206c 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # The command "move workspace prev; workspace prev" will lead to an error.
 # This regression is present in 7f9b65f6a752e454c492447be4e21e2ee8faf8fd
 use i3test;
@@ -15,11 +28,11 @@ my $keep_open_con = open_empty_con($i3);
 my $tmp = fresh_workspace;
 my $con = open_empty_con($i3);
 
-is(@{get_ws_content($tmp)}, 1, 'one container');
-is(@{get_ws_content($old)}, 1, 'one container on old ws');
+is_num_children($tmp, 1, 'one container');
+is_num_children($old, 1, 'one container on old ws');
 
 cmd 'move workspace prev; workspace prev';
 
-is(@{get_ws_content($old)}, 2, 'container moved away');
+is_num_children($old, 2, 'container moved away');
 
 done_testing;
index 487803c4bf9cd2146d6d76f1df75472533ac2fbe..454bfe7d34ef43a375555a9bb6c4cf6a781f2a8d 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Verifies that i3 does not leak any file descriptors in 'exec'.
 #
 use i3test;
index f77f780a461cd9bb7dbd461f552bbdc8fa82559f..c6a054242c94df6018295887f2c419b56b3fd2a3 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Regression test: Changing border style should not have an impact on the size
 # (geometry) of the child window. See ticket http://bugs.i3wm.org/561
 # Wrong behaviour manifested itself up to (including) commit
index 6212a9eab8f26bcefc2c53037003db4c76775f6e..4aaabb83ffcdca2984e061c3e154352485d60c6e 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Regression test: Focusing a dock window should just do nothing, not crash i3.
 # See ticket http://bugs.i3wm.org/575
 # Wrong behaviour manifested itself up to (including) commit
index 1da25a651c34f1cbe87605bc4b705f4e079a94b5..8fbbff70b00c74e17a607d2bd72335fcd2bccbf2 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Checks that variables are parsed correctly by using for_window rules with
 # variables in it.
 #
index 1a21f2b2290a690eb5be85e475defe600ed14f2d..d637baf31b8077dd615f9bf59875350e49763eb9 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Regression: resizing a floating split container leads to a crash.
 # (Ticket #588, present until 4412ccbe5a4fad8a4cd594e6f10f937515a4d37c)
 #
index 06debab3156facf2a6236a4f8e5e0b7012e923f7..87bda5295242c077de5941d5065237b8a09488a7 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests for the scratchpad functionality.
 #
 use i3test;
index 6f2e584ff130822afbe5b16aac8813f2d01e2258..7562ad90e8f82ed1c0cc27b6d25f339fec0c9785 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Regression test: New windows were not opened in the correct place if they
 # matched an assignment.
 # Wrong behaviour manifested itself up to (including) commit
@@ -22,29 +35,21 @@ my $i3 = i3(get_socket_path(0));
 cmd 'workspace targetws';
 
 open_window(name => "testcase");
-
-my $nodes = get_ws_content('targetws');
-is(scalar @$nodes, 1, 'precisely one window');
+is_num_children('targetws', 1, 'precisely one window');
 
 open_window(name => "testcase");
-
-$nodes = get_ws_content('targetws');
-is(scalar @$nodes, 2, 'precisely two windows');
+is_num_children('targetws', 2, 'precisely two windows');
 
 cmd 'split v';
 
 open_window(name => "testcase");
-
-$nodes = get_ws_content('targetws');
-is(scalar @$nodes, 2, 'still two windows');
+is_num_children('targetws', 2, 'still two windows');
 
 # focus parent. the new window should now be opened right next to the last one.
 cmd 'focus parent';
 
 open_window(name => "testcase");
-
-$nodes = get_ws_content('targetws');
-is(scalar @$nodes, 3, 'new window opened next to last one');
+is_num_children('targetws', 3, 'new window opened next to last one');
 
 exit_gracefully($pid);
 
index 8b57a0a1c7c9c4789871353b3e9d0dd5827181eb..37deb94256e7708556d03cb676593fdf49245b37 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests the standalone parser binary to see if it calls the right code when
 # confronted with various commands, if it prints proper error messages for
 # wrong commands and if it terminates in every case.
@@ -12,7 +25,7 @@ sub parser_calls {
 
     # TODO: use a timeout, so that we can error out if it doesn’t terminate
     # TODO: better way of passing arguments
-    my $stdout = qx(../test.commands_parser '$command' 2>&-);
+    my $stdout = qx(../test.commands_parser '$command' 2>&1 >&-);
 
     # Filter out all debugging output.
     my @lines = split("\n", $stdout);
@@ -127,15 +140,15 @@ is(parser_calls("\nworkspace test"),
 ################################################################################
 
 is(parser_calls('unknown_literal'),
-   "Expected one of these tokens: <end>, '[', 'move', 'exec', 'exit', 'restart', 'reload', 'border', 'layout', 'append_layout', 'workspace', 'focus', 'kill', 'open', 'fullscreen', 'split', 'floating', 'mark', 'resize', 'rename', 'nop', 'scratchpad', 'mode'\n" .
-   "Your command: unknown_literal\n" .
-   "              ^^^^^^^^^^^^^^^",
+   "ERROR: Expected one of these tokens: <end>, '[', 'move', 'exec', 'exit', 'restart', 'reload', 'border', 'layout', 'append_layout', 'workspace', 'focus', 'kill', 'open', 'fullscreen', 'split', 'floating', 'mark', 'resize', 'rename', 'nop', 'scratchpad', 'mode'\n" .
+   "ERROR: Your command: unknown_literal\n" .
+   "ERROR:               ^^^^^^^^^^^^^^^",
    'error for unknown literal ok');
 
 is(parser_calls('move something to somewhere'),
-   "Expected one of these tokens: 'window', 'container', 'to', 'workspace', 'output', 'scratchpad', 'left', 'right', 'up', 'down', 'position', 'absolute'\n" .
-   "Your command: move something to somewhere\n" .
-   "                   ^^^^^^^^^^^^^^^^^^^^^^",
+   "ERROR: Expected one of these tokens: 'window', 'container', 'to', 'workspace', 'output', 'scratchpad', 'left', 'right', 'up', 'down', 'position', 'absolute'\n" .
+   "ERROR: Your command: move something to somewhere\n" .
+   "ERROR:                    ^^^^^^^^^^^^^^^^^^^^^^",
    'error for unknown literal ok');
 
 ################################################################################
index 1de9f366109d08cd63806272ae033cf8ddd2cab3..3d602c1172ed6a2fe3344e83896a620f3c0221bd 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Verifies that i3 survives inplace restarts with fullscreen containers
 #
 use i3test;
index 9b6fb15011879dce86db2ebe23b05b3db07aeb40..a3ce84762dc9d8848ffddae3fd1e49b59ab8113d 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests the floating_{minimum,maximum}_size config options.
 #
 # Note that the minimum floating window size is already verified in
index 9b6e6c7a219935a6f4e49dffce2e3679241a7f1f..5451f482272cd2e366dd758c9ae93b54535e2747 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Test for ticket #676: 'scratchpad show' causes a segfault if the scratchpad
 # window is shown on another workspace.
 #
@@ -45,8 +58,7 @@ my $win = open_window;
 my $scratch = open_special;
 cmd '[class="special"] move scratchpad';
 
-my ($nodes, $focus) = get_ws_content($tmp);
-is(scalar @$nodes, 1, 'one window on current ws');
+is_num_children($tmp, 1, 'one window on current ws');
 
 my $otmp = fresh_workspace;
 cmd 'scratchpad show';
diff --git a/testcases/t/191-resize-levels.t b/testcases/t/191-resize-levels.t
new file mode 100644 (file)
index 0000000..559a93e
--- /dev/null
@@ -0,0 +1,43 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Verifies that you can resize across different levels of containers even when
+# they are all of the same orientation.
+# (Ticket #754)
+use i3test;
+
+my $tmp = fresh_workspace;
+
+open_window;
+open_window;
+cmd 'split v';
+my $middle = open_window;
+open_window;
+cmd 'focus parent';
+cmd 'split h';
+open_window;
+
+cmd '[id="' . $middle->id . '"] focus';
+is($x->input_focus, $middle->id, 'middle window focused');
+
+cmd 'resize grow left 10px or 25ppt';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+
+cmp_float($nodes->[0]->{percent}, 0.25, 'left container got only 25%');
+cmp_float($nodes->[1]->{percent}, 0.75, 'right container got 75%');
+
+done_testing;
diff --git a/testcases/t/192-layout.t b/testcases/t/192-layout.t
new file mode 100644 (file)
index 0000000..6fd6eae
--- /dev/null
@@ -0,0 +1,98 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Verifies that switching between the different layouts works as expected.
+use i3test;
+
+my $tmp = fresh_workspace;
+
+open_window;
+open_window;
+cmd 'split v';
+open_window;
+
+my ($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout is splitv currently');
+
+cmd 'layout stacked';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'stacked', 'layout now stacked');
+
+cmd 'layout tabbed';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed');
+
+cmd 'layout toggle split';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv again');
+
+cmd 'layout toggle split';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splith', 'layout now splith');
+
+cmd 'layout toggle split';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv');
+
+cmd 'layout toggle split';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splith', 'layout now splith');
+
+cmd 'layout toggle';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'stacked', 'layout now stacked');
+
+cmd 'layout toggle';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed');
+
+cmd 'layout toggle';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splith', 'layout now splith');
+
+cmd 'layout toggle';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'stacked', 'layout now stacked');
+
+cmd 'layout toggle all';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed');
+
+cmd 'layout toggle all';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splith', 'layout now splith');
+
+cmd 'layout toggle all';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv');
+
+cmd 'layout toggle all';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'stacked', 'layout now stacked');
+
+cmd 'layout toggle all';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed');
+
+cmd 'layout toggle all';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splith', 'layout now splith');
+
+cmd 'layout toggle all';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv');
+
+done_testing;
diff --git a/testcases/t/193-ipc-version.t b/testcases/t/193-ipc-version.t
new file mode 100644 (file)
index 0000000..d2e082e
--- /dev/null
@@ -0,0 +1,37 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Verifies that we can get the version number of i3 via IPC.
+use i3test;
+
+my $i3 = i3(get_socket_path());
+$i3->connect->recv;
+# We explicitly send the version message because AnyEvent::I3’s 'version' sugar
+# method has a fallback which tries to parse the version number from i3
+# --version for older versions, and we want to avoid using that.
+my $version = $i3->message(7, "")->recv;
+
+# We need to change this when the major version changes (but we need to touch a
+# lot of changes then anyways).
+is($version->{major}, 4, 'major version is 4');
+
+cmp_ok($version->{minor}, '>', 0, 'minor version > 0');
+
+is(int($version->{minor}), $version->{minor}, 'minor version is an integer');
+is(int($version->{patch}), $version->{patch}, 'patch version is an integer');
+like($version->{human_readable}, qr/branch/, 'human readable version contains branch name');
+
+done_testing;
diff --git a/testcases/t/194-regress-floating-size.t b/testcases/t/194-regress-floating-size.t
new file mode 100644 (file)
index 0000000..dc6739e
--- /dev/null
@@ -0,0 +1,57 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Verifies that the size requested by floating windows is set by i3, no matter
+# to which value the new_window option is set.
+# ticket #770, bug still present in commit ae88accf6fe3817ff42d0d51be1965071194766e
+use i3test i3_autostart => 0;
+
+sub test_with_new_window_value {
+    my ($value) = @_;
+
+    my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+    if (defined($value)) {
+        $config .= "new_window $value\n";
+        diag("testing with new_window $value");
+    } else {
+        diag("testing without new_window");
+    }
+
+    my $pid = launch_with_config($config);
+
+    my $tmp = fresh_workspace;
+
+    my $window = open_floating_window({ rect => [ 0, 0, 400, 150 ] });
+
+    my ($absolute, $top) = $window->rect;
+
+    ok($window->mapped, 'Window is mapped');
+    cmp_ok($absolute->{width}, '==', 400, 'requested width kept');
+    cmp_ok($absolute->{height}, '==', 150, 'requested height kept');
+
+    exit_gracefully($pid);
+}
+
+test_with_new_window_value(undef);
+test_with_new_window_value('1pixel');
+test_with_new_window_value('normal');
+test_with_new_window_value('none');
+
+done_testing;
diff --git a/testcases/t/195-net-active-window.t b/testcases/t/195-net-active-window.t
new file mode 100644 (file)
index 0000000..c62d4fd
--- /dev/null
@@ -0,0 +1,68 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Verifies that the _NET_ACTIVE_WINDOW message only changes focus when the
+# window is on a visible workspace.
+# ticket #774, bug still present in commit 1e49f1b08a3035c1f238fcd6615e332216ab582e
+use i3test;
+
+sub send_net_active_window {
+    my ($id) = @_;
+
+    my $msg = pack "CCSLLLLLLL",
+        X11::XCB::CLIENT_MESSAGE, # response_type
+        32, # format
+        0, # sequence
+        $id, # destination window
+        $x->atom(name => '_NET_ACTIVE_WINDOW')->id,
+        0,
+        0,
+        0,
+        0,
+        0;
+
+    $x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg);
+}
+
+my $ws1 = fresh_workspace;
+my $win1 = open_window;
+my $win2 = open_window;
+
+################################################################################
+# Ensure that the _NET_ACTIVE_WINDOW ClientMessage works when windows are visible
+################################################################################
+
+is($x->input_focus, $win2->id, 'window 2 has focus');
+
+send_net_active_window($win1->id);
+
+is($x->input_focus, $win1->id, 'window 1 has focus');
+
+################################################################################
+# Switch to a different workspace and ensure sending the _NET_ACTIVE_WINDOW
+# ClientMessage has no effect anymore.
+################################################################################
+
+my $ws2 = fresh_workspace;
+my $win3 = open_window;
+
+is($x->input_focus, $win3->id, 'window 3 has focus');
+
+send_net_active_window($win1->id);
+
+is($x->input_focus, $win3->id, 'window 3 still has focus');
+
+done_testing;
diff --git a/testcases/t/196-randr-output-names.t b/testcases/t/196-randr-output-names.t
new file mode 100644 (file)
index 0000000..e5049eb
--- /dev/null
@@ -0,0 +1,36 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Verify that i3 allows strange RandR output names such as DVI-I_1/digital.
+# Ticket: #785
+# Bug still in: 4.2-256-ga007283
+use i3test i3_autostart => 0;
+use File::Temp qw(tempfile);
+
+my ($fh, $filename) = tempfile(UNLINK => 1);
+print $fh <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+workspace 2 output DVI-I_1/digital
+EOT
+
+my $output = qx(../i3 -C -c $filename);
+unlike($output, qr/ERROR/, 'no errors in i3 -C');
+
+close($fh);
+
+done_testing;
diff --git a/testcases/t/197-regression-move-vanish.t b/testcases/t/197-regression-move-vanish.t
new file mode 100644 (file)
index 0000000..0307769
--- /dev/null
@@ -0,0 +1,35 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Regression test: moving a window to the right out of a splitv container would
+# make it vanish.
+# Ticket: #790
+# Bug still in: 4.2-277-ga598544
+use i3test;
+
+my $ws = fresh_workspace;
+
+my $top = open_window;
+cmd 'split v';
+my $bottom = open_window;
+
+is_num_children($ws, 2, 'two windows on workspace level');
+
+cmd 'move right';
+
+is_num_children($ws, 2, 'still two windows on workspace level');
+
+done_testing;
index 5c341d63be5af6dfb4c157ff4d067f4ff3cdc35b..3df2ba3f779303015731028bed1141d5a367bc29 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests that the provided X-Server to the t/5??-*.t tests is actually providing
 # multiple monitors.
 #
index 97a64a4a3c4014a52954abaea72b5f0e34b1b311..0f9b0df012730f31f8293207c652a89068934e80 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Verifies that scratchpad windows show up on the proper output.
 # ticket #596, bug present until up to commit
 # 89dded044b4fffe78f9d70778748fabb7ac533e9.
@@ -25,18 +38,18 @@ my $i3 = i3(get_socket_path());
 sub verify_scratchpad_on_same_ws {
     my ($ws) = @_;
 
-    is(scalar @{get_ws($ws)->{nodes}}, 0, 'no nodes on this ws');
+    is_num_children($ws, 0, 'no nodes on this ws');
 
     my $window = open_window;
 
-    is(scalar @{get_ws($ws)->{nodes}}, 1, 'one nodes on this ws');
+    is_num_children($ws, 1, 'one nodes on this ws');
 
     cmd 'move scratchpad';
 
-    is(scalar @{get_ws($ws)->{nodes}}, 0, 'no nodes on this ws');
+    is_num_children($ws, 0, 'no nodes on this ws');
 
     cmd 'scratchpad show';
-    is(scalar @{get_ws($ws)->{nodes}}, 0, 'no nodes on this ws');
+    is_num_children($ws, 0, 'no nodes on this ws');
     is(scalar @{get_ws($ws)->{floating_nodes}}, 1, 'one floating node on this ws');
 }
 
@@ -61,21 +74,21 @@ sub verify_scratchpad_switch {
 
     cmd "workspace $first";
 
-    is(scalar @{get_ws($first)->{nodes}}, 0, 'no nodes on this ws');
+    is_num_children($first, 0, 'no nodes on this ws');
 
     my $window = open_window;
 
-    is(scalar @{get_ws($first)->{nodes}}, 1, 'one nodes on this ws');
+    is_num_children($first, 1, 'one nodes on this ws');
 
     cmd 'move scratchpad';
 
-    is(scalar @{get_ws($first)->{nodes}}, 0, 'no nodes on this ws');
+    is_num_children($first, 0, 'no nodes on this ws');
 
     cmd "workspace $second";
 
     cmd 'scratchpad show';
     my $ws = get_ws($second);
-    is(scalar @{$ws->{nodes}}, 0, 'no nodes on this ws');
+    is_num_children($second, 0, 'no nodes on this ws');
     is(scalar @{$ws->{floating_nodes}}, 1, 'one floating node on this ws');
 
     # Verify that the coordinates are within bounds.
index 4e0fedbbf99356335557104f35b8a865398232be..a6c5583fee5614f44951161dba1c9d0e7430e7b8 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Verifies the 'focus output' command works properly.
 
 use i3test i3_autostart => 0;
index 94ba34349295d1a4e62b58c65250bf70ce8e4593..20d4fd2ba1a0abca0c694c874f2de8db6678f355 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests whether 'workspace next_on_output' and the like work correctly.
 #
 use List::Util qw(first);
index 57e5694333a30b7013b981f042b174939c15820f..7a976271c12ce64ed0c4a232ba0890c1cde178ea 100644 (file)
@@ -1,6 +1,19 @@
 #!perl
 # vim:ts=4:sw=4:expandtab
 #
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
 # Tests whether the 'move workspace <ws> to [output] <output>' command works
 #
 use List::Util qw(first);
@@ -9,6 +22,10 @@ use i3test i3_autostart => 0;
 # TODO:
 # introduce 'move workspace 3 to output <output>' with synonym 'move workspace 3 to <output>'
 
+# Ensure the pointer is at (0, 0) so that we really start on the first
+# (the left) workspace.
+$x->root->warp_pointer(0, 0);
+
 my $config = <<EOT;
 # i3 config file (v4)
 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
diff --git a/testcases/t/505-scratchpad-resolution.t b/testcases/t/505-scratchpad-resolution.t
new file mode 100644 (file)
index 0000000..de13a54
--- /dev/null
@@ -0,0 +1,87 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Verifies that scratchpad windows don’t move due to floating point caulcation
+# errors when repeatedly hiding/showing, no matter what display resolution.
+#
+use i3test i3_autostart => 0;
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 683x768+0+0,1024x768+683+0
+EOT
+my $pid = launch_with_config($config);
+
+my $i3 = i3(get_socket_path());
+
+$x->root->warp_pointer(0, 0);
+sync_with_i3;
+
+sub verify_scratchpad_doesnt_move {
+    my ($ws) = @_;
+
+    is_num_children($ws, 0, 'no nodes on this ws');
+
+    my $window = open_window;
+    is_num_children($ws, 1, 'one node on this ws');
+
+    cmd 'move scratchpad';
+    is_num_children($ws, 0, 'no nodes on this ws');
+
+    my $last_x = -1;
+    for (1 .. 20) {
+        cmd 'scratchpad show';
+        is(scalar @{get_ws($ws)->{floating_nodes}}, 1, 'one floating node on this ws');
+
+        # Verify that the coordinates are within bounds.
+        my $content = get_ws($ws);
+        my $srect = $content->{floating_nodes}->[0]->{rect};
+        if ($last_x > -1) {
+            is($srect->{x}, $last_x, 'scratchpad window did not move');
+        }
+        $last_x = $srect->{x};
+        cmd 'scratchpad show';
+    }
+
+    # We need to kill the scratchpad window, otherwise scratchpad show in
+    # subsequent calls of verify_scratchpad_doesnt_move will cycle between all
+    # the windows.
+    cmd 'scratchpad show';
+    cmd 'kill';
+}
+
+################################################################################
+# test it on the left output first (1366x768)
+################################################################################
+
+my $second = fresh_workspace(output => 0);
+verify_scratchpad_doesnt_move($second);
+
+################################################################################
+# now on the right output (1024x768)
+################################################################################
+
+$x->root->warp_pointer(683 + 10, 0);
+sync_with_i3;
+
+my $third = fresh_workspace(output => 1);
+verify_scratchpad_doesnt_move($third);
+
+exit_gracefully($pid);
+
+done_testing;