]> git.sur5r.net Git - i3/i3/commitdiff
Merge branch 'master' into next
authorMichael Stapelberg <michael@stapelberg.de>
Wed, 5 Sep 2012 15:09:43 +0000 (17:09 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Wed, 5 Sep 2012 15:09:43 +0000 (17:09 +0200)
160 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.manpages
docs/Makefile
docs/asciidoc-git.conf
docs/docs.mk [new file with mode: 0644]
docs/hacking-howto
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/116-nestedcons.t
testcases/t/119-match.t
testcases/t/122-split.t
testcases/t/124-move.t
testcases/t/132-move-workspace.t
testcases/t/141-resize.t
testcases/t/145-flattening.t
testcases/t/156-fullscreen-focus.t
testcases/t/157-regress-fullscreen-level-up.t [deleted file]
testcases/t/159-socketpaths.t
testcases/t/166-assign.t
testcases/t/171-config-migrate.t
testcases/t/175-startup-notification.t
testcases/t/176-workspace-baf.t
testcases/t/179-regress-multiple-ws.t
testcases/t/186-regress-assign-focus-parent.t
testcases/t/187-commands-parser.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/501-scratchpad.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..35a03f2593cea5d6c9bc4bfa10ad92c8a22494f8 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/               │
@@ -22,6 +21,8 @@
 │ 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
 
index 065cdbcbe5a1568cfcf161a9415243897f7f1d41..16b444c4e2b63b5058073a43afcbd95f1db772e0 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 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 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..e7153d5
--- /dev/null
@@ -0,0 +1,150 @@
+
+ ┌──────────────────────────────┐
+ │ 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 of the most visible changes is probably that commands which lead to an
+  error will now spawn i3-nagbar. This will make you immediately aware of
+  problems such as typos in your configuration file (such as "bindsym $mod+x exc
+  firefox" instead of "exec"). This is not restricted to parser errors, but all
+  errors (such as when trying to move a window to another workspace without
+  actually having a window focused). If this is annoying to you for some specific
+  key configuration, you can turn it off by replacing a binding like:
+      bindsym $mod+x move absolute position center
+  with something like this:
+      bindsym $mod+x exec --no-startup-id i3-msg move absolute position center >/dev/null 2>&1
+  (Yes, this is somewhat painful, but intended. You should not suppress errors in
+  general, so we don’t want to make it too easy.)
+
+
+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.
+
+ ┌────────────────────────────┐
+ │ 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
+
+ ┌────────────────────────────┐
+ │ 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).
+
+ ┌────────────────────────────┐
+ │ 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, Julius Plenz, Marcel Hellwig, Marcus,
+  Michael Stapelberg, mloskot, Moritz Bandemer, oblique, Ondrej Grover, Pavel
+  Löbl, Philipp Middendorf, prg, Quentin Glidic, somelauw, stfn, tucos,
+  TunnelWicht, Valentin Haenel
+
+-- Michael Stapelberg, 2012-08-18
index 439490596d38ff0b910c6b34896d400fea577af8..8de425d0aabddb545cf80d6f557814ff60ebdf7f 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,31 @@ 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_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..4ec5cbc80be946b7918f59ad1612097252abfc0a 100644 (file)
@@ -2,14 +2,33 @@ 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
 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 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..773c832
--- /dev/null
@@ -0,0 +1,35 @@
+DISTCLEAN_TARGETS += clean-docs
+
+# To pass additional parameters for asciidoc
+ASCIIDOC = asciidoc
+
+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 $@ $<
+
+docs: $(ASCIIDOC_TARGETS)
+
+$(ASCIIDOC_TOC_TARGETS): docs/%.html: docs/%
+       $(ASCIIDOC_TOC_CALL)
+
+$(ASCIIDOC_NOTOC_TARGETS): docs/%.html: docs/%
+       $(ASCIIDOC_CALL)
+
+clean-docs:
+       rm -f $(ASCIIDOC_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.
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..fcc9393a53abebe46062dd0b7790eaf5af6a0100 100644 (file)
@@ -45,6 +45,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..bc8948005b85ce29af726a846e03c7cfe9b0a32e 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]]
@@ -465,6 +480,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
@@ -1010,8 +1041,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 +1052,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 +1221,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 +1244,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 +1359,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 +1377,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 +1404,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..ebb24864cded0cb06548cfaebe11340ca87854be 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
index f4ed9a3ef598250fbb5a0f26471337c36a68f27d..6df3f6fc5a082b10080b068b91803ac330ad065f 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) */
@@ -280,9 +288,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 +299,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 +423,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 +433,6 @@ struct Con {
         CT_WORKSPACE = 4,
         CT_DOCKAREA = 5
     } type;
-    orientation_t orientation;
     struct Con *parent;
 
     struct Rect rect;
@@ -496,7 +497,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..52cde189eef27ed18ee96a4acb06e35c6fcf155e 100644 (file)
@@ -159,7 +159,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}               {
@@ -200,6 +200,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..af7d77cf792cc31c9045d6060d2334b1de0e2989 100644 (file)
@@ -19,6 +19,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 +236,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 +273,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 +302,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 +322,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",
@@ -690,6 +735,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"
@@ -754,6 +801,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
@@ -788,6 +837,7 @@ line:
     | workspace_layout
     | new_window
     | new_float
+    | hide_edge_borders
     | focus_follows_mouse
     | force_focus_wrapping
     | force_xinerama
@@ -1429,6 +1479,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..20a7d67a16848a0106e3037f4f904a8708d83223 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);
@@ -208,7 +206,7 @@ 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 +238,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 +258,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 +302,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 +313,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,9 +378,9 @@ 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);
@@ -414,7 +403,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 +415,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 c24a379f7abc186a1fd2dc5944da7a1e03992be0..cb756b6d4a63252ad49d1996803cb866ba35a073 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);
 
@@ -663,8 +684,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);
@@ -677,14 +702,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);
+    }
 }
 
 /*
@@ -893,12 +936,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};
@@ -908,6 +985,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.
@@ -987,33 +1082,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))) {
@@ -1034,7 +1139,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);
+            }
+        }
+    }
 }
 
 /*
@@ -1111,12 +1281,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 {
@@ -1128,7 +1298,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..c5846279658dcd7426d86c88b814de0041be3e3f 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "config.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -392,7 +394,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..0d087e79896497d7ef7a96ffff38d06e3a458a74 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;
diff --git a/src/i3.mk b/src/i3.mk
new file mode 100644 (file)
index 0000000..b5c7c47
--- /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/readlink 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 readlink -f .))
+
+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}
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..2a578d6
--- /dev/null
@@ -0,0 +1,305 @@
+#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 success_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, success_key %d, nesting_level %d\n", boolval, success_key, current_nesting_level);
+
+    if (success_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
+    success_key = (stringlen >= strlen("success") &&
+                   strncmp((const char*)stringval, "success", strlen("success")) == 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 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) {
+
+    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();
+
+    /* 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;
+    success_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..d7b8cd2872d447808743aeafb68a4dce6266e7e6 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 */
@@ -208,11 +210,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 +291,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];
index 4b3289cdb288f47c21954534ae8bdc7d1d5d0694..b65344a276c5edbefdee04f260e0856d45ca9441 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "resize.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
index 5f65bba8798558be8fda5953d3f29229779ba669..6bba823fa2893512ad1f66924414935b08f0c744 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "scratchpad.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -140,3 +142,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..3a9307e1221efdda46f110ba4875b06853d8f8ab 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "sighandler.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -37,7 +39,7 @@ static int crash_text_longest = 5;
  * 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,8 +51,8 @@ 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,
+    for (int i = 0; crash_text_i3strings[i] != NULL; ++i) {
+        draw_text(crash_text_i3strings[i], pixmap, pixmap_gc,
                 8, 5 + i * font_height, width - 16);
     }
 
@@ -145,10 +147,15 @@ void handle_signal(int sig, siginfo_t *info, void *data) {
     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,7 +179,7 @@ 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);
     }
 
index bcc2415af10a34c1d98b883244fa3f126a17d9f8..b0aa2ca3625bd4bc603172cbb3736c115da3b0a1 100644 (file)
@@ -1,3 +1,5 @@
+#undef I3__FILE__
+#define I3__FILE__ "startup.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
index f29369c607eba2d0e64b64d630a0d6ffe78753c2..2c1c257e0235c569e869a20f8ca4d696b6b6899f 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..853311816538f516caf9200f94c9d354abf78247 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,8 +692,7 @@ 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) {
@@ -694,9 +700,8 @@ void ws_force_orientation(Con *ws, orientation_t orientation) {
     Con *split = con_new(NULL, NULL);
     split->parent = ws;
 
-    /* 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,8 +713,8 @@ 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;
 
     /* 5: attach the new split container to the workspace */
     DLOG("Attaching new split to ws\n");
@@ -744,19 +749,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..52deebc43af8e02318670567952766f7d41fdb4d 100644 (file)
@@ -33,6 +33,7 @@ our @EXPORT = qw(
     open_floating_window
     get_dock_clients
     cmd
+    cmp_float
     sync_with_i3
     does_i3_live
     exit_gracefully
@@ -115,6 +116,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");
@@ -546,6 +548,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 +566,14 @@ sub launch_with_config {
     return $i3_pid;
 }
 
+# compares two floats and return true if they differ less
+# then 1e-6
+sub cmp_float {
+  my ($a, $b) = @_;
+
+  return abs($a - $b) < 1e-6;
+}
+
 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..d83de55
--- /dev/null
@@ -0,0 +1,63 @@
+package i3test::Test;
+
+use base 'Test::Builder::Module';
+
+our @EXPORT = qw(is_num_children);
+
+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);
+}
+
+=head1 AUTHOR
+
+Michael Stapelberg <michael@i3wm.org>
+
+=cut
+
+1
index 3a495e2788d5ee6842c745197fc96977511cb27e..18e210196a3127907e13e16f877159717f08e418 100644 (file)
@@ -39,14 +39,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 e6a4e832fdb3daa2a7da0d7abe565d68c9cba049..b02cc1e0e1527e4f2903aae29869383641ffbd9e 100644 (file)
@@ -26,8 +26,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 +36,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 +84,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 +100,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 +114,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 f672e9d632ebc4391faca496758ca10bb9a9523d..7f8f392d4ab74254830686bec9d943d7639006a5 100644 (file)
@@ -4,6 +4,7 @@
 # Tests splitting
 #
 use i3test;
+use List::Util qw(first);
 
 my $tmp;
 my $ws;
@@ -19,10 +20,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 +48,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 +67,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 +120,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..c093e47b92ed877cef17d06c5cc1bfb368e9705d 100644 (file)
@@ -100,14 +100,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 +126,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 3f00428c035739cabf9f6f21a04b68cb226bb592..c56a17a473399f8c883629cd97f7bdb3b9690085 100644 (file)
@@ -18,20 +18,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 +53,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 +61,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 +76,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 +141,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 4f84f213a8360a62c9395205f86a9887f15d8e25..20dc68f49a29ed356dcf302d18c485c42f33c29b 100644 (file)
@@ -22,8 +22,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%');
+ok(cmp_float($nodes->[0]->{percent}, 0.25), 'top window got only 25%');
+ok(cmp_float($nodes->[1]->{percent}, 0.75), 'bottom window got 75%');
 
 
 ############################################################
@@ -34,8 +34,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%');
+ok(cmp_float($nodes->[0]->{percent}, 0.25), 'top window got only 25%');
+ok(cmp_float($nodes->[1]->{percent}, 0.75), 'bottom window got 75%');
 
 ############################################################
 # checks that resizing within stacked/tabbed cons works
@@ -52,14 +52,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%');
+ok(cmp_float($nodes->[0]->{percent}, 0.5), 'top window got 50%');
+ok(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%');
+ok(cmp_float($nodes->[0]->{percent}, 0.25), 'top window got 25%');
+ok(cmp_float($nodes->[1]->{percent}, 0.75), 'bottom window got 75%');
 
 ############################################################
 # Checks that resizing in the parent's parent's orientation works.
@@ -79,14 +79,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%');
+ok(cmp_float($nodes->[0]->{percent}, 0.5), 'left window got 50%');
+ok(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%');
+ok(cmp_float($nodes->[0]->{percent}, 0.25), 'left window got 25%');
+ok(cmp_float($nodes->[1]->{percent}, 0.75), 'right window got 75%');
 
 ################################################################################
 # Check that the resize grow/shrink width/height syntax works.
@@ -101,8 +101,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%');
+ok(cmp_float($nodes->[0]->{percent}, 0.25), 'left window got 25%');
+ok(cmp_float($nodes->[1]->{percent}, 0.75), 'right window got 75%');
 
 # Now test it with four windows
 $tmp = fresh_workspace;
@@ -112,19 +112,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%');
+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%');
 
 # 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%');
+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%');
 
 
 ############################################################
index 9d22afc3d0f955c49e58096a0d7187bf39ca7932..dbd1f2465dd1038549a5e1e9b5911e8d05a64b96 100644 (file)
@@ -22,7 +22,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 f9dc6dce1fe48e1fe158b10c629578fc5e6b780e..b779ce7d8490286a95088aacd2b4d2619b640c5b 100644 (file)
@@ -11,9 +11,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 +21,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 +54,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 +92,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 c63bbbc4e12d6e8efa0cf31292c5b96a04809c7d..7c3946b4e91c1549e39a4a5cdc584a61f5db19cb 100644 (file)
@@ -5,6 +5,7 @@
 #
 use i3test i3_autostart => 0;
 use File::Temp qw(tempfile tempdir);
+use File::Basename;
 use POSIX qw(getuid);
 use v5.10;
 
@@ -20,21 +21,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 d79c1000e92b59110c2e406d63485071016ef102..3a8a10d5fe166baf27714ab7b55047debd7c3a57 100644 (file)
@@ -57,6 +57,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 +207,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 +226,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 e791bb015cd10315cf306982c5189ab56bda7dc7..dd0cbc6092dbac026cfb18f70c2e7df2cce5305a 100644 (file)
@@ -195,7 +195,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 3a4dbc81f3ed5d4a305f725acaab5cc516c3bacd..2c6ce3537200df9bf58ef7d3356cc77435615435 100644 (file)
@@ -58,7 +58,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 +95,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 +105,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 +119,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 +131,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 +166,4 @@ unlink($tmp);
 
 is($startup_id, '', 'startup_id empty');
 
-
 done_testing;
index 80b2d47135aad5ef6e668001b9489de07a9501c7..806445634b5bc2a5670a17311bc4984a86ff402d 100644 (file)
@@ -66,12 +66,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';
-ok(get_ws('5')->{focused}, 'workspace 5 focused again');
+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';
+is(focused_ws, '5: foo', 'workspace 5 focused again');
 
 exit_gracefully($pid);
 
index 212711708b4b9e8afee95145fdc8bd4107b73b20..be3cc687aa2463403a374e92616a0a41973ba03f 100644 (file)
@@ -15,11 +15,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 6f2e584ff130822afbe5b16aac8813f2d01e2258..4f4db43004cf862a9f6c9c87f9f64757434f2015 100644 (file)
@@ -22,29 +22,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..335c775de346e1b0c9661e2e21e4a608470a0d83 100644 (file)
@@ -12,7 +12,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 +127,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 9b6e6c7a219935a6f4e49dffce2e3679241a7f1f..ed03f210c93cd597de8130b24b25bd28ee31ccdf 100644 (file)
@@ -45,8 +45,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..230a491
--- /dev/null
@@ -0,0 +1,29 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# 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);
+
+ok(cmp_float($nodes->[0]->{percent}, 0.25), 'left container got only 25%');
+ok(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..e410d51
--- /dev/null
@@ -0,0 +1,84 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# 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..172dd8f
--- /dev/null
@@ -0,0 +1,23 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# 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..ccbfaae
--- /dev/null
@@ -0,0 +1,43 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# 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..f392378
--- /dev/null
@@ -0,0 +1,54 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# 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..dfc288d
--- /dev/null
@@ -0,0 +1,22 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+# 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;
index 97a64a4a3c4014a52954abaea72b5f0e34b1b311..9305239abec6f74c8308a8881a70556b48aeaae5 100644 (file)
@@ -25,18 +25,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 +61,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 57e5694333a30b7013b981f042b174939c15820f..8357e0dac6429dc29d1c42df39a579584cefea66 100644 (file)
@@ -9,6 +9,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..ce85cfc
--- /dev/null
@@ -0,0 +1,74 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# 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;