i3-nagbar/i3-nagbar
i3-msg/i3-msg
i3-config-wizard/i3-config-wizard
+libi3/libi3.a
docs/*.html
docs/*.aux
docs/*.out
+++ /dev/null
----------------------
-- Command mode
----------------------
-
-This is the grammar for the 'command mode' (your configuration file
-uses these commands, too).
-
-left := <h> | <cursor-left>
-right := <l> | <cursor-right>
-up := <j> | <cursor-up>
-down := <k> | <cursor-down>
-
-where := <left|right|up|down> | <tag>
-move := <m>
-snap := <s>
-
-cmd := [ <times> ] [ <move> | <snap> ] <where>
-with := <w> { [ <times> ] <where> }+ <space> <cmd>
-jump := [ "<window class>[/<window title>]" | <workspace> [ <column> <row> ] ]
-focus := focus [ <times> | floating | tiling | ft ]
- (travels the focus stack backwards, <times> number of times (by default 1).
- So by specifying "focus 1" it selects the window which last had the focus
- before you focused the current one window.
- The following 3 special values are also valid:
- 'floating' (select the next floating window).
- 'tiling' (select the next tiling window).
- 'ft' (toggle tiling/floating: if the current window is floating,
- select the next tiling window and vice-versa)
-special := [ exec <path> | kill | exit | restart ]
-
-input := [ <cmd> | <with> | <jump> | <focus> | <special> ]
-
-you can cancel command mode by pressing escape anytime.
-
-Some examples:
-
-Select the window on the left:
-h
-
-Select the window two places on the left:
-2h
-
-Move window to the right:
-ml
-
-Move window and window on the bottom to the right:
-wk ml
│ xcb-proto │ 1.3 │ 1.6 │ http://xcb.freedesktop.org/dist/ │
│ libxcb │ 1.1.93 │ 1.7 │ http://xcb.freedesktop.org/dist/ │
│ xcb-util │ 0.3.3 │ 0.3.8 │ http://xcb.freedesktop.org/dist/ │
-│ libev │ 3.0 │ 4.04 │ http://libev.schmorp.de/ │
+│ libev │ 4.0 │ 4.04 │ http://libev.schmorp.de/ │
│ flex │ 2.5.35 │ 2.5.35 │ http://flex.sourceforge.net/ │
│ bison │ 2.4.1 │ 2.4.1 │ http://www.gnu.org/software/bison/ │
│ yajl │ 1.0.8 │ 2.0.1 │ http://lloyd.github.com/yajl/ │
│ docbook-xml │ 4.5 │ 4.5 │ http://www.methods.co.nz/asciidoc/ │
│ libxcursor │ 1.1.11 │ 1.1.11 │ http://ftp.x.org/pub/current/src/lib/ │
│ Xlib │ 1.3.3 │ 1.4.3 │ http://ftp.x.org/pub/current/src/lib/ │
+│ PCRE │ 8.12 │ 8.12 │ http://www.pcre.org/ │
+│ libsn¹ │ 0.10 │ 0.12 │ http://freedesktop.org/wiki/Software/startup-notification
└─────────────┴────────┴────────┴────────────────────────────────────────┘
+ ¹ libsn = libstartup-notification
- i3-msg, i3-input, i3-nagbar and i3-config-wizard do not introduce any new
- dependencies.
-
- i3-wsbar is implemented in Perl and has the following dependencies:
-
- • IPC::Run
- • Try::Tiny
- • AnyEvent
- • AnyEvent::I3
-
- All of them are available at CPAN, see http://search.cpan.org/
- Use your distribution’s packages or cpan(1) to install them.
+ i3bar, i3-msg, i3-input, i3-nagbar and i3-config-wizard do not introduce any
+ new dependencies.
i3-migrate-config-to-v4 is implemented in Perl, but it has no dependencies
besides Perl 5.10.
-Copyright (c) 2009, Michael Stapelberg
+Copyright © 2009-2011, Michael Stapelberg and contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
UNUSED:=$(shell $(MAKE) loglevels.h)
endif
-SUBDIRS=i3-msg i3-input i3-nagbar i3-config-wizard i3bar
+SUBDIRS:=i3-msg i3-input i3-nagbar i3-config-wizard i3bar
# Depend on the specific file (.c for each .o) and on all headers
src/%.o: src/%.c ${HEADERS}
- echo "CC $<"
+ echo "[i3] CC $<"
$(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR; exit 0; }' loglevels.tmp))" -c -o $@ $<
all: i3 subdirs
-i3: src/cfgparse.y.o src/cfgparse.yy.o src/cmdparse.y.o src/cmdparse.yy.o ${FILES}
- echo "LINK i3"
- $(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+i3: libi3/libi3.a src/cfgparse.y.o src/cfgparse.yy.o src/cmdparse.y.o src/cmdparse.yy.o ${FILES}
+ echo "[i3] LINK i3"
+ $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS)
+
+libi3/%.a: libi3/*.c
+ $(MAKE) -C libi3
subdirs:
for dir in $(SUBDIRS); do \
done
loglevels.h:
- echo "LOGLEVELS"
+ echo "[i3] LOGLEVELS"
for file in $$(ls src/*.c src/*.y src/*.l | grep -v 'cfgparse.\(tab\|yy\).c'); \
do \
echo $$(basename $$file .c); \
echo "};") > include/loglevels.h;
src/cfgparse.yy.o: src/cfgparse.l src/cfgparse.y.o ${HEADERS}
- echo "LEX $<"
+ echo "[i3] LEX $<"
flex -i -o$(@:.o=.c) $<
$(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c)
src/cmdparse.yy.o: src/cmdparse.l src/cmdparse.y.o ${HEADERS}
- echo "LEX $<"
+ echo "[i3] LEX $<"
flex -Pcmdyy -i -o$(@:.o=.c) $<
$(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c)
src/cfgparse.y.o: src/cfgparse.y ${HEADERS}
- echo "YACC $<"
+ echo "[i3] YACC $<"
bison --debug --verbose -b $(basename $< .y) -d $<
$(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c)
src/cmdparse.y.o: src/cmdparse.y ${HEADERS}
- echo "YACC $<"
+ echo "[i3] YACC $<"
bison -p cmdyy --debug --verbose -b $(basename $< .y) -d $<
$(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c)
install: all
- echo "INSTALL"
+ echo "[i3] INSTALL"
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
$(INSTALL) -d -m 0755 $(DESTDIR)$(SYSCONFDIR)/i3
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/include/i3
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/xsessions
$(INSTALL) -m 0755 i3 $(DESTDIR)$(PREFIX)/bin/
$(INSTALL) -m 0755 i3-migrate-config-to-v4 $(DESTDIR)$(PREFIX)/bin/
+ $(INSTALL) -m 0755 i3-sensible-editor $(DESTDIR)$(PREFIX)/bin/
+ $(INSTALL) -m 0755 i3-sensible-pager $(DESTDIR)$(PREFIX)/bin/
+ $(INSTALL) -m 0755 i3-sensible-terminal $(DESTDIR)$(PREFIX)/bin/
test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config
test -e $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes || $(INSTALL) -m 0644 i3.config.keycodes $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes
$(INSTALL) -m 0644 i3.welcome $(DESTDIR)$(SYSCONFDIR)/i3/welcome
[ ! -d i3-${VERSION} ] || rm -rf i3-${VERSION}
[ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2
mkdir i3-${VERSION}
- cp i3-migrate-config-to-v4 i3.config.keycodes DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome pseudo-doc.doxygen i3-wsbar Makefile i3-${VERSION}
- cp -r src i3-msg i3-nagbar i3-config-wizard i3bar yajl-fallback include man i3-${VERSION}
+ cp i3-migrate-config-to-v4 i3-sensible-* i3.config.keycodes DEPENDS GOALS LICENSE PACKAGE-MAINTAINER RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome pseudo-doc.doxygen i3-wsbar Makefile i3-${VERSION}
+ cp -r src libi3 i3-msg i3-nagbar i3-config-wizard i3bar yajl-fallback include man i3-${VERSION}
# Only copy toplevel documentation (important stuff)
mkdir i3-${VERSION}/docs
# Pre-generate documentation
- make -C docs
- make -C i3bar/doc
+ $(MAKE) -C docs
+ $(MAKE) -C i3bar/doc
# Cleanup τεχ output files
find docs -regex ".*\.\(aux\|out\|log\|toc\|bm\|dvi\|log\)" -exec rm '{}' \;
find docs -maxdepth 1 -type f ! \( -name "*.xcf" -or -name "*.svg" \) -exec cp '{}' i3-${VERSION}/docs \;
# Only copy source code from i3-input
mkdir i3-${VERSION}/i3-input
find i3-input -maxdepth 1 -type f \( -name "*.c" -or -name "*.h" -or -name "Makefile" \) -exec cp '{}' i3-${VERSION}/i3-input \;
- sed -e 's/^GIT_VERSION:=\(.*\)/GIT_VERSION:=$(shell echo '${GIT_VERSION}' | sed 's/\\/\\\\/g')/g;s/^VERSION:=\(.*\)/VERSION:=${VERSION}/g' common.mk > i3-${VERSION}/common.mk
+ sed -e 's/^GIT_VERSION:=\(.*\)/GIT_VERSION:=$(shell /bin/echo '${GIT_VERSION}' | sed 's/\\/\\\\/g')/g;s/^VERSION:=\(.*\)/VERSION:=${VERSION}/g' common.mk > i3-${VERSION}/common.mk
# Pre-generate a manpage to allow distributors to skip this step and save some dependencies
$(MAKE) -C man
cp man/*.1 i3-${VERSION}/man/
clean:
rm -f src/*.o src/*.gcno src/cfgparse.tab.{c,h} src/cfgparse.yy.c src/cfgparse.{output,dot} src/cmdparse.tab.{c,h} src/cmdparse.yy.c src/cmdparse.{output,dot} loglevels.tmp include/loglevels.h
(which lcov >/dev/null 2>&1 && lcov -d . --zerocounters) || true
+ $(MAKE) -C libi3 clean
$(MAKE) -C docs clean
$(MAKE) -C man clean
for dir in $(SUBDIRS); do \
Please make sure the manpage for i3 will be properly created and installed
in your package.
-Also please provide the path to a suitable terminal emulator which is installed
-as a dependency of your package (e.g. urxvt). On systems which have a special
-commend to launch the best available terminal emulator, please use this one
-(e.g. x-terminal-emulator on debian).
+Please check the i3-sensible-{editor,pager,terminal} scripts and modify them if
+necessary. The former two provide fallbacks in case $PAGER or $EDITOR is not
+set (which might be more common than you think, because they have to be set in
+~/.xsession, not in the shell configuration!) while the latter tries to launch
+a terminal emulator. The scripts are most prominently used in i3-nagbar, which
+alerts the user when the configuration is broken for some reason. Also,
+i3-sensible-terminal is used in the default configuration.
-On debian, this looks like this:
+If your distribution has a mechanism to get the preferred terminal, such as the
+x-terminal-emulator symlink in Debian, please use it in i3-sensible-terminal.
+
+On debian, compilation and installing the manpages looks like this:
# Compilation
- $(MAKE) TERM_EMU=x-terminal-emulator
+ $(MAKE)
$(MAKE) -C man
# Installation
--- /dev/null
+
+ ┌────────────────────────────┐
+ │ Release notes for i3 v4.1 │
+ └────────────────────────────┘
+
+This is the second release of the new major version of i3, v4.1. It brings some
+new (and long-awaited) features, the most prominent being tray support in i3bar
+(for NetworkManager, Skype, etc.).
+
+The assign syntax has changed to support criteria now. Also, criteria support
+regular expressions (using PCRE) now. Check the userguide for the new syntax.
+i3-nagbar will automatically display a warning when you use the old syntax.
+
+i3 now supports startup notifications, meaning that during an application
+starts up, the mouse cursor will change to 'watch' on the root window. Also,
+the application window will appear on the workspace on which it was launched
+(not on the currently focused workspace). Some applications don’t support
+startup notifications. If the cursor change bothers you, turn it off by using
+the --no-startup-id flag (see the userguide).
+
+This release has been in use by many users and is considered stable. Please
+upgrade.
+
+
+ ┌────────────────────────────┐
+ │ New features │
+ └────────────────────────────┘
+
+ • Switch to dpkg-source 3.0 (quilt) and compat level 7
+ • Implement system tray support in i3bar (for NetworkManager, Skype, …)
+ • i3bar is now configurable in the i3 configfile
+ • Implement support for PCRE regular expressions in criteria
+ • Implement a new assign syntax which uses criteria
+ • Sort named workspaces whose name starts with a number accordingly
+ • Warn on duplicate bindings for the same key
+ • Restrict 'resize' command to left/right for horizontal containers, up/down
+ for vertical containers
+ • Implement support for startup notifications (cursor will change to 'watch',
+ started applications show up on the workspace they have been launched on)
+ • Implement the GET_MARKS IPC request to get all marks
+ • Implement the new_float config option (border style for floating windows)
+ • Implement passing IPC sockets to i3 (systemd-style socket activation)
+ • Implement the 'move output' command to move containers to a specific output
+ • Implement focus switching for floating windows
+ • Implement the window_role criterion (for matching multi-window apps)
+ • Implement a force_xinerama configuration directive
+ • Implement the --get-socketpath, useful for scripts using the IPC interface
+ • Implement the 'move workspace next' and 'move workspace prev' commands
+ • Implement the 'workspace back_and_forth' command and related configuration
+ option
+ • Implement the move command for floating windows
+ • i3 will now handle arbitrary text arguments by sending them as an IPC
+ command, like i3-msg: 'i3 reload' or 'i3 move workspace 3'
+ • Introduce the i3-sensible-{pager,editor,terminal} scripts to execute
+ $PAGER, $EDITOR or an available terminal emulator
+ • i3-input: implement -F (format) option
+
+
+ ┌────────────────────────────┐
+ │ Bugfixes │
+ └────────────────────────────┘
+
+ • Bugfix: Preserve marks when restarting
+ • Bugfix: Correctly free old assignments when reloading
+ • Bugfix: Fix flickering when moving floating windows between monitors
+ • Bugfix: Correctly handle ConfigureRequests for floating windows in a
+ multi-monitor environment.
+ • Bugfix: Fix size of floating windows with X11 borders
+ • Bugfix: Always adjust floating window position when moving to another
+ output
+ • Bugfix: Avoid out-of-bounds coordinates when moving floating windows
+ • Bugfix: Don’t steal focus when a window gets destroyed
+ • Bugfix: Correctly split key/value when parsing variables
+ • Bugfix: Correctly revert focus to other floating windows when closing a
+ floating window
+ • Bugfix: Don’t leak the error logfile file descriptor
+ • Bugfix: Don’t steal focus when a window opens on an invisible workspace due
+ to assignments
+ • Bugfix: Fix handling of Mode_switch in i3-input
+ • Bugfix: Close invisible workspaces when they become empty
+ • Bugfix: Don’t invoke interactive resizing when clicking on the decoration
+ of a split container with more than one child (switch focus instead)
+ • Bugfix: Make named workspace assignments work again
+ • Bugfix: RandR: Correctly keep focus on the focused workspace when an output
+ disappears
+ • Bugfix: Insert container at the correct position on workspace level when
+ workspace_layout == default
+
+
+ ┌────────────────────────────┐
+ │ Thanks! │
+ └────────────────────────────┘
+
+Thanks for testing, bugfixes, discussions and everything I forgot go out to:
+
+ aksr, alexanderb, atsutane, bacardi55, bjonnh, brian, cls, don, donald,
+ eeemsi, f8l, fernandotcl, isolnchip, julien, motif, mw, mxf, phnom, pl,
+ pnutzh4x0r, raphael, sardemff7, stfn, thomasba, xeen
+
+-- Michael Stapelberg, 2011-11-11
+++ /dev/null
-
-Please see http://i3wm.org/ for our bugtracker.
-
-Some old notes, just to not lose them:
- * was passiert, wenn zwei fenster fullscreen wollen auf dem selben workspace?
- * OpenOffice
- * funktioniert xinerama etc. mit --below? insbesondere das fokus-verschieben in andere screens
SYSCONFDIR=$(PREFIX)/etc
endif
endif
-TERM_EMU=xterm
# The escaping is absurd, but we need to escape for shell, sed, make, define
GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch $(shell [ -f $(TOPDIR)/.git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' $(TOPDIR)/.git/HEAD || echo 'unknown'))"
VERSION:=$(shell git describe --tags --abbrev=0)
endif
# An easier way to get CFLAGS and LDFLAGS falling back in case there's
-# no pkg-config support for certain libraries
-cflags_for_lib = $(shell pkg-config --silence-errors --cflags $(1))
-ldflags_for_lib = $(shell pkg-config --exists $(1) && pkg-config --libs $(1) || echo -l$(2))
+# no pkg-config support for certain libraries.
+#
+# NOTE that you must not use a blank after comma when calling this:
+# $(call ldflags_for_lib name, fallback) # bad
+# $(call ldflags_for_lib name,fallback) # good
+# Otherwise, the compiler will get -l foo instead of -lfoo
+#
+# We redirect stderr to /dev/null because pkg-config prints an error if support
+# for gnome-config was enabled but gnome-config is not actually installed.
+cflags_for_lib = $(shell pkg-config --silence-errors --cflags $(1) 2>/dev/null)
+ldflags_for_lib = $(shell pkg-config --exists 2>/dev/null $(1) && pkg-config --libs $(1) 2>/dev/null || echo -l$(2))
CFLAGS += -std=c99
CFLAGS += -pipe
CFLAGS += -Wunused-value
CFLAGS += -Iinclude
CFLAGS += $(call cflags_for_lib, xcb-keysyms)
-ifeq ($(shell pkg-config --exists xcb-util || echo 1),1)
+ifeq ($(shell pkg-config --exists xcb-util 2>/dev/null || echo 1),1)
CPPFLAGS += -DXCB_COMPAT
CFLAGS += $(call cflags_for_lib, xcb-atom)
CFLAGS += $(call cflags_for_lib, xcb-aux)
CFLAGS += $(call cflags_for_lib, x11)
CFLAGS += $(call cflags_for_lib, yajl)
CFLAGS += $(call cflags_for_lib, libev)
+CFLAGS += $(call cflags_for_lib, libpcre)
+CFLAGS += $(call cflags_for_lib, libstartup-notification-1.0)
CPPFLAGS += -DI3_VERSION=\"${GIT_VERSION}\"
CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\"
-CPPFLAGS += -DTERM_EMU=\"$(TERM_EMU)\"
+
+ifeq ($(shell pkg-config --atleast-version=8.10 libpcre 2>/dev/null && echo 1),1)
+CPPFLAGS += -DPCRE_HAS_UCP=1
+endif
LIBS += -lm
-LIBS += $(call ldflags_for_lib, xcb-event, xcb-event)
-LIBS += $(call ldflags_for_lib, xcb-keysyms, xcb-keysyms)
-ifeq ($(shell pkg-config --exists xcb-util || echo 1),1)
-LIBS += $(call ldflags_for_lib, xcb-atom, xcb-atom)
-LIBS += $(call ldflags_for_lib, xcb-aux, xcb-aux)
+LIBS += -L $(TOPDIR)/libi3 -li3
+LIBS += $(call ldflags_for_lib, xcb-event,xcb-event)
+LIBS += $(call ldflags_for_lib, xcb-keysyms,xcb-keysyms)
+ifeq ($(shell pkg-config --exists xcb-util 2>/dev/null || echo 1),1)
+LIBS += $(call ldflags_for_lib, xcb-atom,xcb-atom)
+LIBS += $(call ldflags_for_lib, xcb-aux,xcb-aux)
else
LIBS += $(call ldflags_for_lib, xcb-util)
endif
-LIBS += $(call ldflags_for_lib, xcb-icccm, xcb-icccm)
-LIBS += $(call ldflags_for_lib, xcb-xinerama, xcb-xinerama)
-LIBS += $(call ldflags_for_lib, xcb-randr, xcb-randr)
-LIBS += $(call ldflags_for_lib, xcb, xcb)
-LIBS += $(call ldflags_for_lib, xcursor, Xcursor)
-LIBS += $(call ldflags_for_lib, x11, X11)
-LIBS += $(call ldflags_for_lib, yajl, yajl)
-LIBS += $(call ldflags_for_lib, libev, ev)
+LIBS += $(call ldflags_for_lib, xcb-icccm,xcb-icccm)
+LIBS += $(call ldflags_for_lib, xcb-xinerama,xcb-xinerama)
+LIBS += $(call ldflags_for_lib, xcb-randr,xcb-randr)
+LIBS += $(call ldflags_for_lib, xcb,xcb)
+LIBS += $(call ldflags_for_lib, xcursor,Xcursor)
+LIBS += $(call ldflags_for_lib, x11,X11)
+LIBS += $(call ldflags_for_lib, yajl,yajl)
+LIBS += $(call ldflags_for_lib, libev,ev)
+LIBS += $(call ldflags_for_lib, libpcre,pcre)
+LIBS += $(call ldflags_for_lib, libstartup-notification-1.0,startup-notification-1)
# Please test if -Wl,--as-needed works on your platform and send me a patch.
# it is known not to work on Darwin (Mac OS X)
-i3-wm (4.0.3-0) unstable; urgency=low
-
- * NOT YET RELEASED!
-
- -- Michael Stapelberg <michael@stapelberg.de> Sun, 28 Aug 2011 20:17:31 +0200
+i3-wm (4.1-1) unstable; urgency=low
+
+ * Switch to dpkg-source 3.0 (quilt) and compat level 7
+ * Implement system tray support in i3bar (for NetworkManager, Skype, …)
+ * i3bar is now configurable in the i3 configfile
+ * Implement support for PCRE regular expressions in criteria
+ * Implement a new assign syntax which uses criteria
+ * Sort named workspaces whose name starts with a number accordingly
+ * Warn on duplicate bindings for the same key
+ * Restrict 'resize' command to left/right for horizontal containers, up/down
+ for vertical containers
+ * Implement support for startup notifications (cursor will change to 'watch',
+ started applications show up on the workspace they have been launched on)
+ * Implement the GET_MARKS IPC request to get all marks
+ * Implement the new_float config option (border style for floating windows)
+ * Implement passing IPC sockets to i3 (systemd-style socket activation)
+ * Implement the 'move output' command to move containers to a specific output
+ * Implement focus switching for floating windows
+ * Implement the window_role criterion (for matching multi-window apps)
+ * Implement a force_xinerama configuration directive
+ * Implement the --get-socketpath, useful for scripts using the IPC interface
+ * Implement the 'move workspace next' and 'move workspace prev' commands
+ * Implement the 'workspace back_and_forth' command and related configuration
+ option
+ * Implement the move command for floating windows
+ * i3 will now handle arbitrary text arguments by sending them as an IPC
+ command, like i3-msg: 'i3 reload' or 'i3 move workspace 3'
+ * Introduce the i3-sensible-{pager,editor,terminal} scripts to execute
+ $PAGER, $EDITOR or an available terminal emulator
+ * i3-input: implement -F (format) option
+ * Bugfix: Preserve marks when restarting
+ * Bugfix: Correctly free old assignments when reloading
+ * Bugfix: Fix flickering when moving floating windows between monitors
+ * Bugfix: Correctly handle ConfigureRequests for floating windows in a
+ multi-monitor environment.
+ * Bugfix: Fix size of floating windows with X11 borders
+ * Bugfix: Always adjust floating window position when moving to another
+ output
+ * Bugfix: Avoid out-of-bounds coordinates when moving floating windows
+ * Bugfix: Don’t steal focus when a window gets destroyed
+ * Bugfix: Correctly split key/value when parsing variables
+ * Bugfix: Correctly revert focus to other floating windows when closing a
+ floating window
+ * Bugfix: Don’t leak the error logfile file descriptor
+ * Bugfix: Don’t steal focus when a window opens on an invisible workspace due
+ to assignments
+ * Bugfix: Fix handling of Mode_switch in i3-input
+ * Bugfix: Close invisible workspaces when they become empty
+ * Bugfix: Don’t invoke interactive resizing when clicking on the decoration
+ of a split container with more than one child (switch focus instead)
+ * Bugfix: Make named workspace assignments work again
+ * Bugfix: RandR: Correctly keep focus on the focused workspace when an output
+ disappears
+ * Bugfix: Insert container at the correct position on workspace level when
+ workspace_layout == default
+
+ -- Michael Stapelberg <michael@stapelberg.de> Fri, 11 Nov 2011 21:28:15 +0000
i3-wm (4.0.2-1) unstable; urgency=low
Priority: extra
Maintainer: Michael Stapelberg <michael@stapelberg.de>
DM-Upload-Allowed: yes
-Build-Depends: debhelper (>= 6), libx11-dev, libxcb-util0-dev (>= 0.3.8), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-icccm4-dev, libxcursor-dev, asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, perl, texlive-latex-base, texlive-latex-recommended, texlive-latex-extra
+Build-Depends: debhelper (>= 7.0.50~), libx11-dev, libxcb-util0-dev (>= 0.3.8), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-icccm4-dev, libxcursor-dev, asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, texlive-latex-base, texlive-latex-recommended, texlive-latex-extra, libpcre3-dev, libstartup-notification0-dev (>= 0.10)
Standards-Version: 3.9.2
Homepage: http://i3wm.org/
Recommends: i3lock, suckless-tools, i3status
Description: metapackage (i3 window manager, screen locker, menu, statusbar)
This metapackage installs the i3 window manager (i3-wm), the i3lock screen
- locker (slightly improved version of slock), suckless-tools which contains
- dmenu and i3status, which displays useful information about your system in
- combination with dzen2. These are all the tools you need to use the i3 window
- manager efficiently.
+ locker, i3status (for system information) and suckless-tools (for dmenu).
+ These are all the tools you need to use the i3 window manager efficiently.
Package: i3-wm
Architecture: any
Section: x11
-Depends: ${shlibs:Depends}, ${misc:Depends}, x11-utils
+Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, x11-utils
Provides: x-window-manager
Suggests: rxvt-unicode | x-terminal-emulator
-Recommends: xfonts-base, libanyevent-i3-perl, libanyevent-perl, libipc-run-perl
+Recommends: xfonts-base
Description: improved dynamic tiling window manager
- Key features of i3 are correct implementation of Xinerama (workspaces are
- assigned to virtual screens, i3 does the right thing when attaching new
- monitors), XrandR support (not done yet), horizontal and vertical columns
- (think of a table) in tiling. Also, special focus is on writing clean,
- readable and well documented code. i3 uses xcb for asynchronous
- communication with X11, and has several measures to be very fast.
+ Key features of i3 are good documentation, reasonable defaults (changeable in
+ a simple configuration file) and good multi-monitor support. The user
+ interface is designed for power users and emphasizes keyboard usage. i3 uses
+ XCB for asynchronous communication with X11 and aims to be fast and
+ light-weight.
.
Please be aware i3 is primarily targeted at advanced users and developers.
docs/wsbar.png
docs/keyboard-layer1.png
docs/keyboard-layer2.png
+docs/testsuite.html
+docs/i3-sync-working.png
+docs/i3-sync.png
--- /dev/null
+man/i3.1
+man/i3-msg.1
+man/i3-input.1
+man/i3-nagbar.1
+man/i3-config-wizard.1
+man/i3-migrate-config-to-v4.1
+man/i3-sensible-pager.1
+man/i3-sensible-editor.1
+man/i3-sensible-terminal.1
+i3bar/doc/i3bar.1
--- /dev/null
+## Description: Document Debian-specific x-terminal-emulator in the manpage.
+## Origin/Author: Michael Stapelberg
+Index: i3-4.1/man/i3-sensible-terminal.man
+===================================================================
+--- i3-4.1.orig/man/i3-sensible-terminal.man 2011-11-11 22:38:06.508025537 +0000
++++ i3-4.1/man/i3-sensible-terminal.man 2011-11-11 22:38:04.752994892 +0000
+@@ -22,6 +22,7 @@
+ It tries to start one of the following (in that order):
+
+ * $TERMINAL (this is a non-standard variable)
++* x-terminal-emulator (only on Debian)
+ * xterm
+ * urxvt
+ * rxvt
--- /dev/null
+use-x-terminal-emulator.patch
+manpage-x-terminal-emulator.patch
--- /dev/null
+## Description: Use Debian-specific x-terminal-emulator in i3-sensible-terminal
+## Origin/Author: Michael Stapelberg
+--- a/i3-sensible-terminal.O 2011-11-11 22:03:52.414218386 +0000
++++ b/i3-sensible-terminal 2011-11-11 22:04:38.372020210 +0000
+@@ -1,13 +1,11 @@
+ #!/bin/sh
+ # This script tries to exec a terminal emulator by trying some known terminal
+ # emulators.
+-#
+-# Distributions/packagers should enhance this script with a
+-# distribution-specific mechanism to find the preferred terminal emulator. On
+-# Debian, there is the x-terminal-emulator symlink for example.
+-# Please don't touch the first line, though:
+ which $TERMINAL >/dev/null && exec $TERMINAL "$@"
+
++# Debian-specific: use x-terminal-emulator
++which x-terminal-emulator >/dev/null && exec x-terminal-emulator "$@"
++
+ # Hopefully one of these is installed:
+ which xterm >/dev/null && exec xterm "$@"
+ which urxvt >/dev/null && exec urxvt "$@"
#!/usr/bin/make -f
-# -*- makefile -*-
-# Sample debian/rules that uses debhelper.
-# This file was originally written by Joey Hess and Craig Small.
-# As a special exception, when this file is copied by dh-make into a
-# dh-make output file, you may use that output file without restriction.
-# This special exception was added by Craig Small in version 0.37 of dh-make.
+# vi: ts=8 sw=8 noet
-# Uncomment this to turn on verbose mode.
-#export DH_VERBOSE=1
-
-config.status: configure
- dh_testdir
- touch $@
-
-build: build-arch build-indep
-
-build-arch: build-stamp
-build-indep: build-stamp
+DPKG_EXPORT_BUILDFLAGS = 1
+-include /usr/share/dpkg/buildflags.mk
+build: build-stamp
build-stamp:
- dh_testdir
+ dh build
+ touch build-stamp
- # Add here commands to compile the package.
- $(MAKE) TERM_EMU=x-terminal-emulator
- $(MAKE) -C man
- $(MAKE) -C docs
+clean:
+ dh clean
- touch $@
+install: build install-stamp
+install-stamp:
+ dh install
+ touch install-stamp
-clean:
- dh_testdir
- dh_testroot
- rm -f build-stamp
+binary-arch: install
+ dh binary-arch
- # Add here commands to clean up after the build process.
- [ ! -f Makefile ] || $(MAKE) distclean
+binary-indep: install
+ dh binary-indep
- dh_clean
+binary: binary-arch binary-indep
-install: build
- dh_testdir
- dh_testroot
- dh_clean -k
- dh_installdirs
+override_dh_auto_build:
+ $(MAKE)
+ $(MAKE) -C man
+ $(MAKE) -C docs
- # Add here commands to install the package into debian/i3-wm
+override_dh_install:
$(MAKE) DESTDIR=$(CURDIR)/debian/i3-wm/ install
- mkdir -p $(CURDIR)/debian/i3-wm/usr/share/man/man1
- cp man/i3.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
- cp man/i3-msg.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
- cp man/i3-input.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
- cp man/i3-nagbar.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
- cp man/i3-config-wizard.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
- cp man/i3-migrate-config-to-v4.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
- cp i3bar/doc/i3bar.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
-
-# Build architecture-independent files here.
-binary-indep: build install
-# We have nothing to do by default.
-
-# Build architecture-dependent files here.
-binary-arch: build install
- dh_testdir
- dh_testroot
- dh_installchangelogs
- dh_installdocs
- dh_installexamples
- dh_installdebconf
- dh_installinit
- dh_installman
- dh_installwm
- dh_link
+override_dh_strip:
dh_strip --dbg-package=i3-wm-dbg
- dh_compress
- dh_fixperms
- dh_installdeb
- dh_shlibdeps
- dh_gencontrol
- dh_md5sums
- dh_builddeb
-
-binary: binary-indep binary-arch
-.PHONY: build clean binary-indep binary-arch binary install
+# To pass additional parameters for asciidoc
+ASCIIDOC=asciidoc
-all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html wsbar.html refcard.pdf
+all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html wsbar.html refcard.pdf testsuite.html
hacking-howto.html: hacking-howto
- asciidoc -a toc -n $<
+ $(ASCIIDOC) -a toc -n $<
debugging.html: debugging
- asciidoc -n $<
+ $(ASCIIDOC) -n $<
userguide.html: userguide
- asciidoc -a toc -n $<
+ $(ASCIIDOC) -a toc -n $<
+
+testsuite.html: testsuite
+ $(ASCIIDOC) -a toc -n $<
ipc.html: ipc
- asciidoc -a toc -n $<
+ $(ASCIIDOC) -a toc -n $<
multi-monitor.html: multi-monitor
- asciidoc -a toc -n $<
+ $(ASCIIDOC) -a toc -n $<
wsbar.html: wsbar
- asciidoc -a toc -n $<
+ $(ASCIIDOC) -a toc -n $<
refcard.pdf: refcard.tex
pdflatex refcard.tex && pdflatex refcard.tex
--- /dev/null
+#\r
+# xhtml11.conf\r
+#\r
+# Asciidoc configuration file.\r
+# xhtml11 backend, generates XHTML 1.1 conformant markup.\r
+#\r
+\r
+[miscellaneous]\r
+outfilesuffix=.html\r
+\r
+[attributes]\r
+basebackend=html\r
+basebackend-html=\r
+basebackend-xhtml11=\r
+\r
+[replacements2]\r
+# Line break.\r
+(?m)^(.*)\s\+$=\1<br />\r
+\r
+[replacements]\r
+ifdef::asciidoc7compatible[]\r
+# Superscripts.\r
+\^(.+?)\^=<sup>\1</sup>\r
+# Subscripts.\r
+~(.+?)~=<sub>\1</sub>\r
+endif::asciidoc7compatible[]\r
+\r
+[ruler-blockmacro]\r
+<hr />\r
+\r
+[pagebreak-blockmacro]\r
+<div style="page-break-after:always"></div>\r
+\r
+[blockdef-pass]\r
+asciimath-style=template="asciimathblock",subs=[]\r
+latexmath-style=template="latexmathblock",subs=[]\r
+\r
+[macros]\r
+# math macros.\r
+# Special characters are escaped in HTML math markup.\r
+(?su)[\\]?(?P<name>asciimath|latexmath):(?P<subslist>\S*?)\[(?P<passtext>.*?)(?<!\\)\]=[specialcharacters]\r
+(?u)^(?P<name>asciimath|latexmath)::(?P<subslist>\S*?)(\[(?P<passtext>.*?)\])$=#[specialcharacters]\r
+\r
+[asciimath-inlinemacro]\r
+`{passtext}`\r
+\r
+[asciimath-blockmacro]\r
+<div class="mathblock{role? {role}}"{id? id="{id}"}>\r
+<div class="content">\r
+<div class="title">{title}</div>\r
+`{passtext}`\r
+</div></div>\r
+\r
+[asciimathblock]\r
+<div class="mathblock{role? {role}}"{id? id="{id}"}>\r
+<div class="content">\r
+<div class="title">{title}</div>\r
+`|`\r
+</div></div>\r
+\r
+[latexmath-inlinemacro]\r
+{passtext}\r
+\r
+[latexmath-blockmacro]\r
+<div class="mathblock{role? {role}}"{id? id="{id}"}>\r
+<div class="content">\r
+<div class="title">{title}</div>\r
+{passtext}\r
+</div></div>\r
+\r
+[latexmathblock]\r
+<div class="mathblock{role? {role}}"{id? id="{id}"}>\r
+<div class="content">\r
+<div class="title">{title}</div>\r
+|\r
+</div></div>\r
+\r
+[image-inlinemacro]\r
+<span class="image{role? {role}}">\r
+<a class="image" href="{link}">\r
+{data-uri%}<img src="{imagesdir=}{imagesdir?/}{target}" alt="{alt={target}}"{width? width="{width}"}{height? height="{height}"}{title? title="{title}"} />\r
+{data-uri#}<img alt="{alt={target}}"{width? width="{width}"}{height? height="{height}"}{title? title="{title}"} src="data:image/{eval:os.path.splitext('{target}')[1][1:]};base64,\r
+{data-uri#}{sys3:python -uc "import base64,sys; base64.encode(sys.stdin,sys.stdout)" < "{eval:os.path.join("{indir={outdir}}","{imagesdir=}","{target}")}"}" />\r
+{link#}</a>\r
+</span>\r
+\r
+[image-blockmacro]\r
+<div class="imageblock{style? {style}}{role? {role}}"{id? id="{id}"}{align? style="text-align:{align};"}{float? style="float:{float};"}>\r
+<div class="content">\r
+<a class="image" href="{link}">\r
+{data-uri%}<img src="{imagesdir=}{imagesdir?/}{target}" alt="{alt={target}}"{width? width="{width}"}{height? height="{height}"} />\r
+{data-uri#}<img alt="{alt={target}}"{width? width="{width}"}{height? height="{height}"} src="data:image/{eval:os.path.splitext('{target}')[1][1:]};base64,\r
+{data-uri#}{sys:python -uc "import base64,sys; base64.encode(sys.stdin,sys.stdout)" < "{eval:os.path.join("{indir={outdir}}","{imagesdir=}","{target}")}"}" />\r
+{link#}</a>\r
+</div>\r
+<div class="title">{caption={figure-caption} {counter:figure-number}. }{title}</div>\r
+</div>\r
+\r
+[unfloat-blockmacro]\r
+<div style="clear:both;"></div>\r
+\r
+[indexterm-inlinemacro]\r
+# Index term.\r
+{empty}\r
+\r
+[indexterm2-inlinemacro]\r
+# Index term.\r
+# Single entry index term that is visible in the primary text flow.\r
+{1}\r
+\r
+[footnote-inlinemacro]\r
+# footnote:[<text>].\r
+<span class="footnote"><br />[{0}]<br /></span>\r
+\r
+[footnoteref-inlinemacro]\r
+# footnoteref:[<id>], create reference to footnote.\r
+{2%}<span class="footnoteref"><br /><a href="#_footnote_{1}">[{1}]</a><br /></span>\r
+# footnoteref:[<id>,<text>], create footnote with ID.\r
+{2#}<span class="footnote" id="_footnote_{1}"><br />[{2}]<br /></span>\r
+\r
+[callout-inlinemacro]\r
+ifndef::icons[]\r
+<b><{index}></b>\r
+endif::icons[]\r
+ifdef::icons[]\r
+ifndef::data-uri[]\r
+<img src="{icon={iconsdir}/callouts/{index}.png}" alt="{index}" />\r
+endif::data-uri[]\r
+ifdef::data-uri[]\r
+<img alt="{index}" src="data:image/png;base64,\r
+{sys:python -uc "import base64,sys; base64.encode(sys.stdin,sys.stdout)" < "{eval:os.path.join("{indir={outdir}}","{icon={iconsdir}/callouts/{index}.png}")}"}" />\r
+endif::data-uri[]\r
+endif::icons[]\r
+\r
+# Comment line macros.\r
+[comment-inlinemacro]\r
+{showcomments#}<br /><span class="comment">{passtext}</span><br />\r
+\r
+[comment-blockmacro]\r
+{showcomments#}<p><span class="comment">{passtext}</span></p>\r
+\r
+[literal-inlinemacro]\r
+# Inline literal.\r
+<tt>{passtext}</tt>\r
+\r
+# List tags.\r
+[listtags-bulleted]\r
+list=<div class="ulist{style? {style}}{compact-option? compact}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<ul>|</ul></div>\r
+item=<li>|</li>\r
+text=<p>|</p>\r
+\r
+[listtags-numbered]\r
+# The start attribute is not valid XHTML 1.1 but all browsers support it.\r
+list=<div class="olist{style? {style}}{compact-option? compact}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<ol class="{style}"{start? start="{start}"}>|</ol></div>\r
+item=<li>|</li>\r
+text=<p>|</p>\r
+\r
+[listtags-labeled]\r
+list=<div class="dlist{compact-option? compact}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<dl>|</dl></div>\r
+entry=\r
+label=\r
+term=<dt class="hdlist1{strong-option? strong}">|</dt>\r
+item=<dd>|</dd>\r
+text=<p>|</p>\r
+\r
+[listtags-horizontal]\r
+list=<div class="hdlist{compact-option? compact}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<table>{labelwidth?<col width="{labelwidth}%" />}{itemwidth?<col width="{itemwidth}%" />}|</table></div>\r
+label=<td class="hdlist1{strong-option? strong}">|</td>\r
+term=|<br />\r
+entry=<tr>|</tr>\r
+item=<td class="hdlist2">|</td>\r
+text=<p style="margin-top: 0;">|</p>\r
+\r
+[listtags-qanda]\r
+list=<div class="qlist{style? {style}}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<ol>|</ol></div>\r
+entry=<li>|</li>\r
+label=\r
+term=<p><em>|</em></p>\r
+item=\r
+text=<p>|</p>\r
+\r
+[listtags-callout]\r
+ifndef::icons[]\r
+list=<div class="colist{style? {style}}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<ol>|</ol></div>\r
+item=<li>|</li>\r
+text=<p>|</p>\r
+endif::icons[]\r
+ifdef::icons[]\r
+list=<div class="colist{style? {style}}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<table>|</table></div>\r
+ifndef::data-uri[]\r
+item=<tr><td><img src="{iconsdir}/callouts/{listindex}.png" alt="{listindex}" /></td><td>|</td></tr>\r
+endif::data-uri[]\r
+ifdef::data-uri[]\r
+item=<tr><td><img alt="{listindex}" src="data:image/png;base64, {sys:python -uc "import base64,sys; base64.encode(sys.stdin,sys.stdout)" < "{eval:os.path.join("{indir={outdir}}","{icon={iconsdir}/callouts/{listindex}.png}")}"}" /></td><td>|</td></tr>\r
+endif::data-uri[]\r
+text=|\r
+endif::icons[]\r
+\r
+[listtags-glossary]\r
+list=<div class="dlist{style? {style}}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<dl>|</dl></div>\r
+label=\r
+entry=\r
+term=<dt>|</dt>\r
+item=<dd>|</dd>\r
+text=<p>|</p>\r
+\r
+[listtags-bibliography]\r
+list=<div class="ulist{style? {style}}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<ul>|</ul></div>\r
+item=<li>|</li>\r
+text=<p>|</p>\r
+\r
+[tags]\r
+# Quoted text.\r
+emphasis=<em>{1?<span class="{1}">}|{1?</span>}</em>\r
+strong=<strong>{1?<span class="{1}">}|{1?</span>}</strong>\r
+monospaced=<tt>{1?<span class="{1}">}|{1?</span>}</tt>\r
+singlequoted={lsquo}{1?<span class="{1}">}|{1?</span>}{rsquo}\r
+doublequoted={ldquo}{1?<span class="{1}">}|{1?</span>}{rdquo}\r
+unquoted={1?<span class="{1}">}|{1?</span>}\r
+superscript=<sup>{1?<span class="{1}">}|{1?</span>}</sup>\r
+subscript=<sub>{1?<span class="{1}">}|{1?</span>}</sub>\r
+\r
+ifdef::deprecated-quotes[]\r
+# Override with deprecated quote attributes.\r
+emphasis={role?<span class="{role}">}<em{1,2,3? style="}{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}{1,2,3?"}>|</em>{role?</span>}\r
+strong={role?<span class="{role}">}<strong{1,2,3? style="}{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}{1,2,3?"}>|</strong>{role?</span>}\r
+monospaced={role?<span class="{role}">}<tt{1,2,3? style="}{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}{1,2,3?"}>|</tt>{role?</span>}\r
+singlequoted={role?<span class="{role}">}{1,2,3?<span style="}{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}{1,2,3?">}{amp}#8216;|{amp}#8217;{1,2,3?</span>}{role?</span>}\r
+doublequoted={role?<span class="{role}">}{1,2,3?<span style="}{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}{1,2,3?">}{amp}#8220;|{amp}#8221;{1,2,3?</span>}{role?</span>}\r
+unquoted={role?<span class="{role}">}{1,2,3?<span style="{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}">}|{1,2,3?</span>}{role?</span>}\r
+superscript={role?<span class="{role}">}<sup{1,2,3? style="}{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}{1,2,3?"}>|</sup>{role?</span>}\r
+subscript={role?<span class="{role}">}<sub{1,2,3? style="}{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}{1,2,3?"}>|</sub>{role?</span>}\r
+endif::deprecated-quotes[]\r
+\r
+# Inline macros\r
+[http-inlinemacro]\r
+<a href="{name}:{target}">{0={name}:{target}}</a>\r
+[https-inlinemacro]\r
+<a href="{name}:{target}">{0={name}:{target}}</a>\r
+[ftp-inlinemacro]\r
+<a href="{name}:{target}">{0={name}:{target}}</a>\r
+[file-inlinemacro]\r
+<a href="{name}:{target}">{0={name}:{target}}</a>\r
+[irc-inlinemacro]\r
+<a href="{name}:{target}">{0={name}:{target}}</a>\r
+[mailto-inlinemacro]\r
+<a href="mailto:{target}">{0={target}}</a>\r
+[link-inlinemacro]\r
+<a href="{target}">{0={target}}</a>\r
+[callto-inlinemacro]\r
+<a href="{name}:{target}">{0={target}}</a>\r
+# anchor:id[text]\r
+[anchor-inlinemacro]\r
+<a id="{target}"></a>\r
+# [[id,text]]\r
+[anchor2-inlinemacro]\r
+<a id="{1}"></a>\r
+# [[[id]]]\r
+[anchor3-inlinemacro]\r
+<a id="{1}"></a>[{1}]\r
+# xref:id[text]\r
+[xref-inlinemacro]\r
+<a href="#{target}">{0=[{target}]}</a>\r
+# <<id,text>>\r
+[xref2-inlinemacro]\r
+<a href="#{1}">{2=[{1}]}</a>\r
+\r
+# Special word substitution.\r
+[emphasizedwords]\r
+<em>{words}</em>\r
+[monospacedwords]\r
+<tt>{words}</tt>\r
+[strongwords]\r
+<strong>{words}</strong>\r
+\r
+# Paragraph substitution.\r
+[paragraph]\r
+<div class="paragraph{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<p>\r
+|\r
+</p></div>\r
+\r
+[admonitionparagraph]\r
+template::[admonitionblock]\r
+\r
+# Delimited blocks.\r
+[listingblock]\r
+<div class="listingblock{role? {role}}"{id? id="{id}"}>\r
+<div class="title">{caption=}{title}</div>\r
+<div class="content">\r
+<pre><tt>\r
+|\r
+</tt></pre>\r
+</div></div>\r
+\r
+[literalblock]\r
+<div class="literalblock{role? {role}}"{id? id="{id}"}>\r
+<div class="title">{title}</div>\r
+<div class="content">\r
+<pre><tt>\r
+|\r
+</tt></pre>\r
+</div></div>\r
+\r
+[sidebarblock]\r
+<div class="sidebarblock{role? {role}}"{id? id="{id}"}>\r
+<div class="content">\r
+<div class="title">{title}</div>\r
+|\r
+</div></div>\r
+\r
+[openblock]\r
+<div class="openblock{role? {role}}"{id? id="{id}"}>\r
+<div class="title">{title}</div>\r
+<div class="content">\r
+|\r
+</div></div>\r
+\r
+[partintroblock]\r
+template::[openblock]\r
+\r
+[abstractblock]\r
+template::[quoteblock]\r
+\r
+[quoteblock]\r
+<div class="quoteblock{role? {role}}"{id? id="{id}"}>\r
+<div class="title">{title}</div>\r
+<div class="content">\r
+|\r
+</div>\r
+<div class="attribution">\r
+<em>{citetitle}</em>{attribution?<br />}\r
+— {attribution}\r
+</div></div>\r
+\r
+[verseblock]\r
+<div class="verseblock{role? {role}}"{id? id="{id}"}>\r
+<div class="title">{title}</div>\r
+<pre class="content">\r
+|\r
+</pre>\r
+<div class="attribution">\r
+<em>{citetitle}</em>{attribution?<br />}\r
+— {attribution}\r
+</div></div>\r
+\r
+[exampleblock]\r
+<div class="exampleblock{role? {role}}"{id? id="{id}"}>\r
+<div class="title">{caption={example-caption} {counter:example-number}. }{title}</div>\r
+<div class="content">\r
+|\r
+</div></div>\r
+\r
+[admonitionblock]\r
+<div class="admonitionblock{role? {role}}"{id? id="{id}"}>\r
+<table><tr>\r
+<td class="icon">\r
+{data-uri%}{icons#}<img src="{icon={iconsdir}/{name}.png}" alt="{caption}" />\r
+{data-uri#}{icons#}<img alt="{caption}" src="data:image/png;base64,\r
+{data-uri#}{icons#}{sys:python -uc "import base64,sys; base64.encode(sys.stdin,sys.stdout)" < "{eval:os.path.join("{indir={outdir}}","{icon={iconsdir}/{name}.png}")}"}" />\r
+{icons%}<div class="title">{caption}</div>\r
+</td>\r
+<td class="content">\r
+<div class="title">{title}</div>\r
+|\r
+</td>\r
+</tr></table>\r
+</div>\r
+\r
+# Tables.\r
+[tabletags-default]\r
+colspec=<col{autowidth-option! width="{colpcwidth}%"} />\r
+bodyrow=<tr>|</tr>\r
+headdata=<th {colspan@1::colspan="{colspan}" }{rowspan@1::rowspan="{rowspan}" }align="{halign}" valign="{valign}">|</th>\r
+bodydata=<td {colspan@1::colspan="{colspan}" }{rowspan@1::rowspan="{rowspan}" }align="{halign}" valign="{valign}">|</td>\r
+paragraph=<p class="table">|</p>\r
+\r
+[tabletags-header]\r
+paragraph=<p class="table header">|</p>\r
+\r
+[tabletags-emphasis]\r
+paragraph=<p class="table"><em>|</em></p>\r
+\r
+[tabletags-strong]\r
+paragraph=<p class="table"><strong>|</strong></p>\r
+\r
+[tabletags-monospaced]\r
+paragraph=<p class="table"><tt>|</tt></p>\r
+\r
+[tabletags-verse]\r
+bodydata=<td {colspan@1::colspan="{colspan}" }{rowspan@1::rowspan="{rowspan}" }align="{halign}" valign="{valign}"><div class="verse">|</div></td>\r
+paragraph=\r
+\r
+[tabletags-literal]\r
+bodydata=<td {colspan@1::colspan="{colspan}" }{rowspan@1::rowspan="{rowspan}" }align="{halign}" valign="{valign}"><div class="literal"><pre><tt>|</tt></pre></div></td>\r
+paragraph=\r
+\r
+[tabletags-asciidoc]\r
+bodydata=<td {colspan@1::colspan="{colspan}" }{rowspan@1::rowspan="{rowspan}" }align="{halign}" valign="{valign}"><div>|</div></td>\r
+paragraph=\r
+\r
+[table]\r
+<div class="tableblock{role? {role}}"{id? id="{id}"}>\r
+<table rules="{grid=all}"\r
+style="margin-left:{align@left:0}{align@center|right:auto}; margin-right:{align@left|center:auto}{align@right:0};"\r
+style="float:{float};"\r
+{autowidth-option%}width="{tablepcwidth}%"\r
+{autowidth-option#}{width#width="{tablepcwidth}%"}\r
+frame="{frame%border}"\r
+frame="{frame@topbot:hsides}{frame@all:border}{frame@none:void}{frame@sides:vsides}"\r
+cellspacing="0" cellpadding="4">\r
+<caption class="title">{caption={table-caption} {counter:table-number}. }{title}</caption>\r
+{colspecs}\r
+{headrows#}<thead>\r
+{headrows}\r
+{headrows#}</thead>\r
+{footrows#}<tfoot>\r
+{footrows}\r
+{footrows#}</tfoot>\r
+<tbody>\r
+{bodyrows}\r
+</tbody>\r
+</table>\r
+</div>\r
+\r
+#--------------------------------------------------------------------\r
+# Deprecated old table definitions.\r
+#\r
+\r
+[miscellaneous]\r
+# Screen width in pixels.\r
+pagewidth=800\r
+pageunits=\r
+\r
+[old_tabledef-default]\r
+template=old_table\r
+colspec=<col width="{colwidth}{pageunits}" />\r
+bodyrow=<tr>|</tr>\r
+headdata=<th align="{colalign}">|</th>\r
+footdata=<td align="{colalign}">|</td>\r
+bodydata=<td align="{colalign}">|</td>\r
+\r
+[old_table]\r
+<div class="tableblock"{id? id="{id}"}>\r
+<table rules="{grid=none}"\r
+frame="{frame%hsides}"\r
+frame="{frame@topbot:hsides}{frame@all:border}{frame@none:void}{frame@sides:vsides}"\r
+cellspacing="0" cellpadding="4">\r
+<caption class="title">{caption={table-caption}}{title}</caption>\r
+{colspecs}\r
+{headrows#}<thead>\r
+{headrows}\r
+{headrows#}</thead>\r
+{footrows#}<tfoot>\r
+{footrows}\r
+{footrows#}</tfoot>\r
+<tbody valign="top">\r
+{bodyrows}\r
+</tbody>\r
+</table>\r
+</div>\r
+\r
+# End of deprecated old table definitions.\r
+#--------------------------------------------------------------------\r
+\r
+[floatingtitle]\r
+<h{level@0:1}{level@1:2}{level@2:3}{level@3:4}{level@4:5}{id? id="{id}"} class="float">{title}</h{level@0:1}{level@1:2}{level@2:3}{level@3:4}{level@4:5}>\r
+\r
+[preamble]\r
+# Untitled elements between header and first section title.\r
+<div id="preamble">\r
+<div class="sectionbody">\r
+|\r
+</div>\r
+</div>\r
+\r
+# Document sections.\r
+[sect0]\r
+<h1{id? id="{id}"}>{title}</h1>\r
+|\r
+\r
+[sect1]\r
+<div class="sect1{style? {style}}{role? {role}}">\r
+<h2{id? id="{id}"}>{numbered?{sectnum} }{title}</h2>\r
+<div class="sectionbody">\r
+|\r
+</div>\r
+</div>\r
+\r
+[sect2]\r
+<div class="sect2{style? {style}}{role? {role}}">\r
+<h3{id? id="{id}"}>{numbered?{sectnum} }{title}</h3>\r
+|\r
+</div>\r
+\r
+[sect3]\r
+<div class="sect3{style? {style}}{role? {role}}">\r
+<h4{id? id="{id}"}>{numbered?{sectnum} }{title}</h4>\r
+|\r
+</div>\r
+\r
+[sect4]\r
+<div class="sect4{style? {style}}{role? {role}}">\r
+<h5{id? id="{id}"}>{title}</h5>\r
+|\r
+</div>\r
+\r
+[appendix]\r
+<div class="sect1{style? {style}}{role? {role}}">\r
+<h2{id? id="{id}"}>{numbered?{sectnum} }{appendix-caption} {counter:appendix-number:A}: {title}</h2>\r
+<div class="sectionbody">\r
+|\r
+</div>\r
+</div>\r
+\r
+[toc]\r
+<div id="toc">\r
+ <div id="toctitle">{toc-title}</div>\r
+ <noscript><p><b>JavaScript must be enabled in your browser to display the table of contents.</b></p></noscript>\r
+</div>\r
+\r
+[header]\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"\r
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="{lang=en}">\r
+<head>\r
+<link rel="icon" type="image/png" href="/favicon.png">\r
+<meta http-equiv="Content-Type" content="{quirks=application/xhtml+xml}{quirks?text/html}; charset={encoding}" />\r
+<meta name="generator" content="AsciiDoc {asciidoc-version}" />\r
+<meta name="description" content="{description}" />\r
+<meta name="keywords" content="{keywords}" />\r
+<title>i3: {title}</title>\r
+{title%}<title>i3: {doctitle=}</title>\r
+<link rel="stylesheet" href="{stylesdir=.}/style.css" type="text/css" />\r
+ifdef::linkcss[]\r
+<link rel="stylesheet" href="{stylesdir=.}/{theme={backend}}.css" type="text/css" />\r
+{doctype-manpage}<link rel="stylesheet" href="{stylesdir=.}/{theme={backend}}-manpage.css" type="text/css" />\r
+ifdef::quirks[]\r
+<link rel="stylesheet" href="{stylesdir=.}/{theme={backend}}-quirks.css" type="text/css" />\r
+endif::quirks[]\r
+<link rel="stylesheet" href="{stylesdir=.}/{stylesheet}" type="text/css" />\r
+ifdef::pygments[<link rel="stylesheet" href="{stylesdir=.}/pygments.css" type="text/css" />]\r
+endif::linkcss[]\r
+ifndef::linkcss[]\r
+<style type="text/css">\r
+include1::{stylesdir=./stylesheets}/{theme={backend}}.css[]\r
+ifdef::doctype-manpage[]\r
+include1::{stylesdir=./stylesheets}/{theme={backend}}-manpage.css[]\r
+endif::doctype-manpage[]\r
+ifdef::quirks[]\r
+include1::{stylesdir=./stylesheets}/{theme={backend}}-quirks.css[]\r
+endif::quirks[]\r
+include1::{stylesheet}[]\r
+ifdef::pygments[]\r
+include1::{stylesdir=./stylesheets}/pygments.css[]\r
+endif::pygments[]\r
+</style>\r
+endif::linkcss[]\r
+ifndef::disable-javascript[]\r
+ifdef::linkcss[]\r
+<script type="text/javascript">\r
+# Escape as CDATA to pass validators.\r
+/*<![CDATA[*/\r
+window.onload = function()\{asciidoc.footnotes();{toc? asciidoc.toc({toclevels});}\}\r
+/*]]>*/\r
+</script>\r
+<script type="text/javascript" src="{scriptsdir=.}/asciidoc-xhtml11.js"></script>\r
+endif::linkcss[]\r
+ifndef::linkcss[]\r
+<script type="text/javascript">\r
+# Escape as CDATA to pass validators.\r
+/*<![CDATA[*/\r
+window.onload = function()\{asciidoc.footnotes();{toc? asciidoc.toc({toclevels});}\}\r
+include1::{scriptsdir=./javascripts}/asciidoc-xhtml11.js[]\r
+/*]]>*/\r
+</script>\r
+endif::linkcss[]\r
+endif::disable-javascript[]\r
+ifdef::asciimath[]\r
+ifdef::linkcss[]\r
+<script type="text/javascript" src="{scriptsdir=.}/ASCIIMathML.js"></script>\r
+endif::linkcss[]\r
+ifndef::linkcss[]\r
+<script type="text/javascript">\r
+# Escape as CDATA to pass validators.\r
+/*<![CDATA[*/\r
+include1::{scriptsdir=./javascripts}/ASCIIMathML.js[]\r
+/*]]>*/\r
+</script>\r
+endif::linkcss[]\r
+endif::asciimath[]\r
+ifdef::latexmath[]\r
+ifdef::linkcss[]\r
+<script type="text/javascript" src="{scriptsdir=.}/LaTeXMathML.js"></script>\r
+endif::linkcss[]\r
+ifndef::linkcss[]\r
+<script type="text/javascript">\r
+# Escape as CDATA to pass validators.\r
+/*<![CDATA[*/\r
+include1::{scriptsdir=./javascripts}/LaTeXMathML.js[]\r
+/*]]>*/\r
+</script>\r
+endif::linkcss[]\r
+endif::latexmath[]\r
+{docinfo1,docinfo2#}{include:{docdir}/docinfo.html}\r
+{docinfo,docinfo2#}{include:{docdir}/{docname}-docinfo.html}\r
+</head>\r
+<body class="{doctype}"{max-width? style="max-width:{max-width}"}>\r
+\r
+ <div id="main">\r
+ <a href="/"><h1 id="title">i3 - improved tiling WM</h1></a>\r
+ <ul id="nav">\r
+ <li style=" background-color: #FFD000; font-size: 2em;padding: 0.25em;-webkit-border-radius: 0.25em;border: 4px dashed black;color: #000000;">latest git docs</li>\r
+ </ul>\r
+ <br style="clear: both">\r
+<div id="content">\r
+# Article, book header.\r
+ifndef::doctype-manpage[]\r
+<div id="header">\r
+ifndef::notitle[<h1>{doctitle}</h1>]\r
+ifdef::doctitle[]\r
+<span id="author">{author}</span><br />\r
+<span id="email"><tt><<a href="mailto:{email}">{email}</a>></tt></span><br />\r
+<span id="revnumber">version {revnumber}{revdate?,}</span>\r
+<span id="revdate">{revdate}</span>\r
+<br /><span id="revremark">{revremark}</span>\r
+endif::doctitle[]\r
+ifdef::toc[{template:toc}]\r
+</div>\r
+endif::doctype-manpage[]\r
+# Man page header.\r
+ifdef::doctype-manpage[]\r
+<div id="header">\r
+<h1>\r
+{doctitle} Manual Page\r
+</h1>\r
+ifdef::toc[{template:toc}]\r
+<h2>{manname-title}</h2>\r
+<div class="sectionbody">\r
+<p>{manname} -\r
+ {manpurpose}\r
+</p>\r
+</div>\r
+</div>\r
+endif::doctype-manpage[]\r
+\r
+[footer]\r
+</div>\r
+{disable-javascript%<div id="footnotes"><hr /></div>}\r
+<div id="footer" lang="de">\r
+© 2009-2011 Michael Stapelberg, <a href="/impress.html">Impressum</a>\r
+</div>\r
+</body>\r
+</html>\r
+\r
+ifdef::doctype-manpage[]\r
+[synopsis]\r
+template::[sect1]\r
+endif::doctype-manpage[]\r
+\r
+ifdef::quirks[]\r
+include::{backend}-quirks.conf[]\r
+endif::quirks[]\r
IPC interface (interprocess communication)
==========================================
Michael Stapelberg <michael+i3@stapelberg.de>
-March 2010
+October 2011
This document describes how to interface with i3 from a separate process. This
is useful for example to remote-control i3 (to write test cases for example) or
little overhead on both sides and is usually available without headaches in
most languages. In the default configuration file, the ipc-socket gets created
in +/tmp/i3-%u/ipc-socket.%p+ where +%u+ is your UNIX username and +%p+ is the
-PID of i3.
+PID of i3. You can get the socketpath from i3 by calling +i3 --get-socketpath+.
All i3 utilities, like +i3-msg+ and +i3-input+ will read the +I3_SOCKET_PATH+
X11 property, stored on the X11 root window.
-------------------------------------------------------------
use IO::Socket::UNIX;
-my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
+chomp(my $path = qx(i3 --get-socketpath));
+my $sock = IO::Socket::UNIX->new(Peer => $path);
-------------------------------------------------------------
== Sending messages to i3
Gets the layout tree. i3 uses a tree as data structure which includes
every container. The reply will be the JSON-encoded tree (see the reply
section).
+GET_MARKS (5)::
+ Gets a list of marks (identifiers for containers to easily jump to them
+ later). The reply will be a JSON-encoded list of window marks (see
+ reply section).
+GET_BAR_CONFIG (6)::
+ Gets the configuration (as JSON map) of the workspace bar with the
+ given ID. If no ID is provided, an array with all configured bar IDs is
+ returned instead.
So, a typical message could look like this:
--------------------------------------------------
Reply to the GET_OUTPUTS message.
GET_TREE (4)::
Reply to the GET_TREE message.
+GET_MARKS (5)::
+ Reply to the GET_MARKS message.
+GET_BAR_CONFIG (6)::
+ Reply to the GET_BAR_CONFIG message.
=== COMMAND reply
}
------------------------
+=== GET_MARKS reply
+
+The reply consists of a single array of strings for each container that has a
+mark. The order of that array is undefined. If more than one container has the
+same mark, it will be represented multiple times in the reply (the array
+contents are not unique).
+
+If no window has a mark the response will be the empty array [].
+
+=== GET_BAR_CONFIG reply
+
+This can be used by third-party workspace bars (especially i3bar, but others
+are free to implement compatible alternatives) to get the +bar+ block
+configuration from i3.
+
+Depending on the input, the reply is either:
+
+empty input::
+ An array of configured bar IDs
+Bar ID::
+ A JSON map containing the configuration for the specified bar.
+
+Each bar configuration has the following properties:
+
+id (string)::
+ The ID for this bar. Included in case you request multiple
+ configurations and want to differentiate the different replies.
+mode (string)::
+ Either +dock+ (the bar sets the dock window type) or +hide+ (the bar
+ does not show unless a specific key is pressed).
+position (string)::
+ Either +bottom+ or +top+ at the moment.
+status_command (string)::
+ Command which will be run to generate a statusline. Each line on stdout
+ of this command will be displayed in the bar. At the moment, no
+ formatting is supported.
+font (string)::
+ The font to use for text on the bar.
+workspace_buttons (boolean)::
+ Display workspace buttons or not? Defaults to true.
+verbose (boolean)::
+ Should the bar enable verbose output for debugging? Defaults to false.
+colors (map)::
+ Contains key/value pairs of colors. Each value is a color code in hex,
+ formatted #rrggbb (like in HTML).
+
+The following colors can be configured at the moment:
+
+background::
+ Background color of the bar.
+statusline::
+ Text color to be used for the statusline.
+focused_workspace_text/focused_workspace_bg::
+ Text color/background color for a workspace button when the workspace
+ has focus.
+active_workspace_text/active_workspace_bg::
+ Text color/background color for a workspace button when the workspace
+ is active (visible) on some output, but the focus is on another one.
+ You can only tell this apart from the focused workspace when you are
+ using multiple monitors.
+inactive_workspace_text/inactive_workspace_bg::
+ Text color/background color for a workspace button when the workspace
+ does not have focus and is not active (visible) on any output. This
+ will be the case for most workspaces.
+urgent_workspace_text/urgent_workspace_bar::
+ Text color/background color for workspaces which contain at least one
+ window with the urgency hint set.
+
+
+*Example of configured bars:*
+--------------
+["bar-bxuqzf"]
+--------------
+
+*Example of bar configuration:*
+--------------
+{
+ "id": "bar-bxuqzf",
+ "mode": "dock",
+ "position": "bottom",
+ "status_command": "i3status",
+ "font": "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1",
+ "workspace_buttons": true,
+ "verbose": false,
+ "colors": {
+ "background": "#c0c0c0",
+ "statusline": "#00ff00",
+ "focused_workspace_text": "#ffffff",
+ "focused_workspace_bg": "#000000"
+ }
+}
+--------------
== Events
The multi-monitor situation
===========================
Michael Stapelberg <michael+i3@stapelberg.de>
-March 2010
+September 2011
…or: oh no, I have an nVidia graphics card!
exec i3 --force-xinerama -V >>~/.i3/i3log 2>&1
----------------------------------------------
+…or use +force_xinerama yes+ in your configuration file.
+
== The explanation
Starting with version 3.ε, i3 uses the RandR (Rotate and Resize) API instead
For this very reason, we decided to implement the following workaround: As
long as the nVidia driver does not support RandR, an option called
-+--force-xinerama+ is available in i3. This option gets the list of screens
-*once* when starting, and never updates it. As the nVidia driver cannot do
-dynamic configuration anyways, this is not a big deal.
++--force-xinerama+ is available in i3 (alternatively, you can use the
++force_xinerama+ configuration file directive). This option gets the list of
+screens *once* when starting, and never updates it. As the nVidia driver cannot
+do dynamic configuration anyways, this is not a big deal.
+
+Also note that your output names are not descriptive (like +HDMI1+) when using
+Xinerama, instead they are counted up, starting at 0: +xinerama-0+, +xinerama-1+, …
== See also
--- /dev/null
+i3 testsuite
+============
+Michael Stapelberg <michael+i3@stapelberg.de>
+September 2011
+
+This document explains how the i3 testsuite works, how to use it and extend it.
+It is targeted at developers who not necessarily have been doing testing before
+or have not been testing in Perl before. In general, the testsuite is not of
+interest for end users.
+
+
+== Introduction
+
+The i3 testsuite is a collection of files which contain testcases for various
+i3 features. Some of them test if a certain workflow works correctly (moving
+windows, focus behaviour, …). Others are regression tests and contain code
+which previously made i3 crash or lead to unexpected behaviour. They then check
+if i3 still runs (meaning it did not crash) and if it handled everything
+correctly.
+
+The goal of having these tests is to automatically find problems and to
+automatically get a feel for whether a change in the source code breaks any
+existing feature. After every modification of the i3 sourcecode, the developer
+should run the full testsuite. If one of the tests fails, the corresponding
+problem should be fixed (or, in some cases, the testcase has to be modified).
+For every bugreport, a testcase should be written to test the correct
+behaviour. Initially, it will fail, but after fixing the bug, it will pass.
+This ensures (or increases the chance) that bugs which have been fixed once
+will never be found again.
+
+Also, when implementing a new feature, a testcase might be a good way to be
+able to easily test if the feature is working correctly. Many developers will
+test manually if everything works. Having a testcase not only helps you with
+that, but it will also be useful for every future change.
+
+== Implementation
+
+For several reasons, the i3 testsuite has been implemented in Perl:
+
+1. Perl has a long tradition of testing. Every popular/bigger Perl module which
+ you can find on CPAN will not only come with documentation, but also with
+ tests. Therefore, the available infrastructure for tests is comprehensive.
+ See for example the excellent http://search.cpan.org/perldoc?Test::More
+ and the referenced http://search.cpan.org/perldoc?Test::Tutorial.
+
+2. Perl is widely available and has a well-working package infrastructure.
+3. The author is familiar with Perl :).
+
+Please do not start programming language flamewars at this point.
+
+=== Mechanisms
+
+==== Script: complete-run
+
+The testcases are run by a script called +complete-run.pl+. It runs all
+testcases by default, but you can be more specific and let it only run one or
+more testcases. Also, it takes care of starting up a separate instance of i3
+with an appropriate configuration file and creates a folder for each run
+containing the appropriate i3 logfile for each testcase. The latest folder can
+always be found under the symlink +latest/+. Unless told differently, it will
+run the tests on a separate X server instance (using the Xdummy script).
+
+.Example invocation of complete-run.pl+
+---------------------------------------
+$ cd ~/i3/testcases
+
+$ ./complete-run.pl
+# output omitted because it is very long
+All tests successful.
+Files=78, Tests=734, 27 wallclock secs ( 0.38 usr 0.48 sys + 17.65 cusr 3.21 csys = 21.72 CPU)
+Result: PASS
+
+$ ./complete-run.pl t/04-floating.t
+[:3] i3 startup: took 0.07s, status = 1
+[:3] Running t/04-floating.t with logfile testsuite-2011-09-24-16-06-04-4.0.2-226-g1eb011a/i3-log-for-04-floating.t
+[:3] t/04-floating.t finished
+[:3] killing i3
+output for t/04-floating.t:
+ok 1 - use X11::XCB::Window;
+ok 2 - The object isa X11::XCB::Window
+ok 3 - Window is mapped
+ok 4 - i3 raised the width to 75
+ok 5 - i3 raised the height to 50
+ok 6 - i3 did not map it to (0x0)
+ok 7 - The object isa X11::XCB::Window
+ok 8 - i3 let the width at 80
+ok 9 - i3 let the height at 90
+ok 10 - i3 mapped it to x=1
+ok 11 - i3 mapped it to y=18
+ok 12 - The object isa X11::XCB::Window
+ok 13 - i3 let the width at 80
+ok 14 - i3 let the height at 90
+1..14
+
+All tests successful.
+Files=1, Tests=14, 0 wallclock secs ( 0.01 usr 0.00 sys + 0.19 cusr 0.03 csys = 0.23 CPU)
+Result: PASS
+
+$ less latest/i3-log-for-04-floating.t
+----------------------------------------
+
+==== IPC interface
+
+The testsuite makes extensive use of the IPC (Inter-Process Communication)
+interface which i3 provides. It is used for the startup process of i3, for
+terminating it cleanly and (most importantly) for modifying and getting the
+current state (layout tree).
+
+See [http://i3wm.org/docs/ipc.html] for documentation on the IPC interface.
+
+==== X11::XCB
+
+In order to open new windows, change attributes, get events, etc., the
+testsuite uses X11::XCB, a new (and quite specific to i3 at the moment) Perl
+module which uses the XCB protocol description to generate Perl bindings to
+X11. They work in a very similar way to libxcb (which i3 uses) and provide
+relatively high-level interfaces (objects such as +X11::XCB::Window+) aswell as
+access to the low-level interface, which is very useful when testing a window
+manager.
+
+=== Filesystem structure
+
+In the git root of i3, the testcases live in the folder +testcases+. This
+folder contains the +complete-run.pl+ and +Xdummy+ scripts and a base
+configuration file which will be used for the tests. The different testcases
+(their file extension is .t, not .pl) themselves can be found in the
+conventionally named subfolder +t+:
+
+.Filesystem structure
+--------------------------------------------
+├── testcases
+│ ├── complete-run.pl
+│ ├── i3-test.config
+│ ├── lib
+│ │ ├── i3test.pm
+│ │ ├── SocketActivation.pm
+│ │ └── StartXDummy.pm
+│ ├── t
+│ │ ├── 00-load.t
+│ │ ├── 01-tile.t
+│ │ ├── 02-fullscreen.t
+│ │ ├── ...
+│ │ ├── omitted for brevity
+│ │ ├── ...
+│ │ └── 74-regress-focus-toggle.t
+│ └── Xdummy
+--------------------------------------------
+
+== Anatomy of a testcase
+
+Learning by example is definitely a good strategy when you are wondering how to
+write a testcase. Let's take +t/11-goto.t+ as an easy example and go through it
+step by step:
+
+.t/11-goto.t: Boilerplate
+----------------------
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use File::Temp;
+
+my $x = X11::XCB::Connection->new;
+-----------------------
+
+This is what we call boilerplate. It exists at the top of every test file (to
+some extent). The first line is the shebang, which specifies that this file is
+a Perl script. The second line contains VIM specific settings on how to
+edit/format this file (use spaces instead of tabs, indent using 4 spaces).
+Afterwards, the +i3test+ module is used. This module contains i3 testsuite
+specific functions which you are strongly encouraged to use. They make writing
+testcases a lot easier and will make it easier for other people to read your
+tests.
+
+The next line uses the +File::Temp+ module. This is specific to this testcase,
+because it needs to generate a temporary name during the test. Many testcases
+use only the +i3test+ module.
+
+The last line opens a connection to X11. You might or might not need this in
+your testcase, depending on whether you are going to open windows (etc.) or
+only use i3 commands.
+
+.t/11-goto.t: Setup
+----------------------
+my $tmp = fresh_workspace;
+
+cmd 'split h';
+----------------------
+
+The first line calls i3test's +fresh_workspace+ function which looks for a
+currently unused workspace, switches to it, and returns its name. The variable
++$tmp+ will end up having a value such as +"/tmp/87kBVcHbA9"+. Note that this
+is not (necessarily) a valid path, it's just a random workspace name.
+
+So, now that we are on a new workspace, we ensure that the workspace uses
+horizontal orientation by issuing the +split h+ command (see the i3 User's
+Guide for a list of commands). This is not strictly necessary, but good style.
+In general, the +cmd+ function executes the specified i3 command by using the
+IPC interface and returns once i3 acknowledged the command.
+
+.t/11-goto.t: Setup
+----------------------
+#####################################################################
+# Create two windows and make sure focus switching works
+#####################################################################
+
+my $top = open_window($x);
+my $mid = open_window($x);
+my $bottom = open_window($x);
+----------------------
+
+In every major section of a testcase, you should put a comment like the one
+above. This makes it immediately clear how the file is structured.
+
+The +open_window+ function opens a standard window, which will then be put into
+tiling mode by i3. If you want a floating window, use the
++open_floating_window+ function. These functions accept the same parameters as
++X11::XCB::Window->new+, see the i3test documentation at TODO.
+
+.t/11-goto.t: Helper function
+----------------------
+#
+# Returns the input focus after sending the given command to i3 via IPC
+# and syncing with i3
+#
+sub focus_after {
+ my $msg = shift;
+
+ cmd $msg;
+ sync_with_i3 $x;
+ return $x->input_focus;
+}
+----------------------
+
+This section defines a helper function which will be used over and over in this
+testcase. If you have code which gets executed more than once or twice
+(depending on the length of your test, use your best judgement), please put it
+in a function. Tests should be short, concise and clear.
+
+The +focus_after+ function executes a command and returns the X11 focus after
+the command was executed. The +sync_with_i3+ command makes sure that i3 could
+push its state to X11. See <<i3_sync>> to learn how this works exactly.
+
+.t/11-goto.t: Test assumptions
+----------------------
+$focus = $x->input_focus;
+is($focus, $bottom->id, "Latest window focused");
+
+$focus = focus_after('focus left');
+is($focus, $mid->id, "Middle window focused");
+----------------------
+
+Now, we run the first two real tests. They use +Test::More+'s +is+ function,
+which compares two values and prints the differences if they are not the same.
+After the arguments, we supply a short comment to indicate what we are testing
+here. This makes it vastly more easy for the developer to spot which testcase
+is the problem in case one fails.
+
+The first test checks that the most recently opened window is focused.
+Afterwards, the command +focus left+ is issued and it is verified that the
+middle window now has focus.
+
+Note that this is not a comprehensive test of the +focus+ command -- we would
+have to test wrapping, focus when using a more complex layout, focusing the
+parent/child containers, etc. But that is not the point of this testcase.
+Instead, we just want to know if +$x->input_focus+ corresponds with what we are
+expecting. If not, something is completely wrong with the test environment and
+this trivial test will fail.
+
+.t/11-goto.t: Test that the feature does not work (yet)
+----------------------
+#####################################################################
+# Now goto a mark which does not exist
+#####################################################################
+
+my $random_mark = mktemp('mark.XXXXXX');
+
+$focus = focus_after(qq|[con_mark="$random_mark"] focus|);
+is($focus, $mid->id, "focus unchanged");
+----------------------
+
+Syntax hint: The qq keyword is the interpolating quote operator. It lets you
+chose a quote character (in this case the +|+ character, a pipe). This makes
+having double quotes in our string easy.
+
+In this new major section, a random mark (mark is an identifier for a window,
+see "VIM-like marks" in the i3 User’s Guide) will be generated. Afterwards, we
+test that trying to focus that mark will not do anything. This is important: Do
+not only test that using a feature has the expected outcome, but also test that
+using it without properly initializing it does no harm. This command could for
+example have changed focus anyways (a bug) or crash i3 (obviously a bug).
+
+.t/11-goto.t: Test that the feature does work
+----------------------
+cmd "mark $random_mark";
+
+$focus = focus_after('focus left');
+is($focus, $top->id, "Top window focused");
+
+$focus = focus_after(qq|[con_mark="$random_mark"] focus|);
+is($focus, $mid->id, "goto worked");
+----------------------
+
+Remember: Focus was on the middle window (we verified that earlier in "Test
+assumptions"). We now mark the middle window with our randomly generated mark.
+Afterwards, we switch focus away from the middle window to be able to tell if
+focusing it via its mark will work. If the test works, the goto command seems
+to be working.
+
+.t/11-goto.t: Test corner case
+----------------------
+# check that we can specify multiple criteria
+
+$focus = focus_after('focus left');
+is($focus, $top->id, "Top window focused");
+
+$focus = focus_after(qq|[con_mark="$random_mark" con_mark="$random_mark"] focus|);
+is($focus, $mid->id, "goto worked");
+----------------------
+
+Now we test the same feature, but specifying the mark twice in the command.
+This should have no effect, but let’s be sure: test it and see if things go
+wrong.
+
+.t/11-goto.t: Test second code path
+----------------------
+#####################################################################
+# Check whether the focus command will switch to a different
+# workspace if necessary
+#####################################################################
+
+my $tmp2 = fresh_workspace;
+
+is(focused_ws(), $tmp2, 'tmp2 now focused');
+
+cmd qq|[con_mark="$random_mark"] focus|;
+
+is(focused_ws(), $tmp, 'tmp now focused');
+----------------------
+
+This part of the test checks that focusing windows by mark works across
+workspaces. It uses i3test's +focused_ws+ function to get the current
+workspace.
+
+.t/11-goto.t: Test second code path
+----------------------
+done_testing;
+----------------------
+
+The end of every testcase has to contain the +done_testing+ line. This tells
++complete-run.pl+ that the test was finished successfully. If it does not
+occur, the test might have crashed during execution -- some of the reasons why
+that could happen are bugs in the used modules, bugs in the testcase itself or
+an i3 crash resulting in the testcase being unable to communicate with i3 via
+IPC anymore.
+
+[[i3_sync]]
+== Appendix A: The i3 sync protocol
+
+Consider the following situation: You open two windows in your testcase, then
+you use +focus left+ and want to verify that the X11 focus has been updated
+properly. Sounds simple, right? Let’s assume you use this straight-forward
+implementation:
+
+.Racey focus testcase
+-----------
+my $left = open_window($x);
+my $right = open_window($x);
+cmd 'focus left';
+is($x->input_focus, $left->id, 'left window focused');
+----------
+
+However, the test fails. Sometimes. Apparantly, there is a race condition in
+your test. If you think about it, this is because you are using two different
+pieces of software: You tell i3 to update focus, i3 confirms that, and then you
+ask X11 to give you the current focus. There is a certain time i3 needs to
+update the X11 state. If the testcase gets CPU time before X11 processed i3's
+requests, the test will fail.
+
+image::i3-sync.png["Diagram of the race condition", title="Diagram of the race condition"]
+
+One way to "solve" this would be to add +sleep 0.5;+ after the +cmd+ call.
+After 0.5 seconds it should be safe to assume that focus has been updated,
+right?
+
+In practice, this usually works. However, it has several problems:
+
+1. This is obviously not a clean solution, but a workaround. Ugly.
+2. On very slow machines, this might not work. Unlikely, but in different
+ situations (a delay to wait for i3 to startup) the necessary time is much
+ harder to guess, even for fast machines.
+3. This *wastes a lot of time*. Usually, your computer is much faster than 0.5s
+ to update the status. However, sometimes, it might take 0.4s, so we can’t
+ make it +sleep 0.1+.
+
+To illustrate how grave the problem with wasting time actually is: Before
+removing all sleeps from the testsuite, a typical run using 4 separate X
+servers took around 50 seconds on my machine. After removing all the sleeps,
+we achieved times of about 25 seconds. This is very significant and influences
+the way you think about tests -- the faster they are, the more likely you are
+to check whether everything still works quite often (which you should).
+
+What I am trying to say is: Delays adds up quickly and make the test suite
+less robust.
+
+The real solution for this problem is a mechanism which I call "the i3 sync
+protocol". The idea is to send a request (which does not modify state) via X11
+to i3 which will then be answered. Due to the request's position in the event
+queue (*after* all previous events), you can be sure that by the time you
+receive the reply, all other events have been dealt with by i3 (and, more
+importantly, X11).
+
+image::i3-sync-working.png["Diagram of the i3 sync solution", title="Diagram of the i3 sync solution"]
+
+=== Implementation details
+
+The client which wants to sync with i3 initiates the protocol by sending a
+ClientMessage to the X11 root window:
+
+.Send ClientMessage
+-------------------
+# Generate a ClientMessage, see xcb_client_message_t
+my $msg = pack "CCSLLLLLLL",
+ CLIENT_MESSAGE, # response_type
+ 32, # format
+ 0, # sequence
+ $root, # destination window
+ $x->atom(name => 'I3_SYNC')->id,
+
+ $_sync_window->id, # data[0]: our own window id
+ $myrnd, # data[1]: a random value to identify the request
+ 0,
+ 0,
+ 0;
+
+# Send it to the root window -- since i3 uses the SubstructureRedirect
+# event mask, it will get the ClientMessage.
+$x->send_event(0, $root, EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg);
+-------------------
+
+i3 will then reply with the same ClientMessage, sent to the window specified in
++data[0]+. In the reply, +data[0]+ and +data[1]+ are exactly the same as in the
+request. You should use a random value in +data[1]+ and check that you received
+the same one when getting the reply.
+
+== Appendix B: Socket activation
+
+Socket activation is a mechanism which was made popular by systemd, an init
+replacement. It basically describes creating a listening socket before starting
+a program. systemd will invoke the program only when an actual connection to
+the socket is made, hence the term socket activation.
+
+The interesting part of this (in the i3 context) is that you can very precisely
+detect when the program is ready (finished its initialization).
+
+=== Preparing the listening socket
+
++complete-run.pl+ will create a listening UNIX socket which it will then pass
+to i3. This socket will be used by i3 as an additional IPC socket, just like
+the one it will create on its own. Passing the socket happens implicitly
+because children will inherit the parent’s sockets when fork()ing and sockets
+will continue to exist after an exec() call (unless CLOEXEC is set of course).
+
+The only explicit things +complete-run.pl+ has to do is setting the +LISTEN_FDS+
+environment variable to the number of sockets which exist (1 in our case) and
+setting the +LISTEN_PID+ environment variable to the current process ID. Both
+variables are necessary so that the program (i3) knows how many sockets it
+should use and if the environment variable is actually intended for it. i3 will
+then start looking for sockets at file descriptor 3 (since 0, 1 and 2 are used
+for stdin, stdout and stderr, respectively).
+
+The actual Perl code which sets up the socket, fork()s, makes sure the socket
+has file descriptor 3 and sets up the environment variables follows (shortened
+a bit):
+
+
+.Setup socket and environment
+-----------------------------
+my $socket = IO::Socket::UNIX->new(
+ Listen => 1,
+ Local => $args{unix_socket_path},
+);
+
+my $pid = fork;
+if ($pid == 0) {
+ $ENV{LISTEN_PID} = $$;
+ $ENV{LISTEN_FDS} = 1;
+
+ # Only pass file descriptors 0 (stdin), 1 (stdout),
+ # 2 (stderr) and 3 (socket) to the child.
+ $^F = 3;
+
+ # If the socket does not use file descriptor 3 by chance
+ # already, we close fd 3 and dup2() the socket to 3.
+ if (fileno($socket) != 3) {
+ POSIX::close(3);
+ POSIX::dup2(fileno($socket), 3);
+ }
+
+ exec "/usr/bin/i3";
+}
+-----------------------------
+
+=== Waiting for a reply
+
+In the parent process, we want to know when i3 is ready to answer our IPC
+requests and handle our windows. Therefore, after forking, we immediately close
+the listening socket (i3 will handle this side of the socket) and connect to it
+(remember, we are talking about a named UNIX socket) as a client. This connect
+call will immediately succeed because the kernel buffers it. Then, we send a
+request (of type GET_TREE, but that is not really relevant). Writing data to
+the socket will also succeed immediately because, again, the kernel buffers it
+(only up to a certain amount of data of course).
+
+Afterwards, we just blockingly wait until we get an answer. In the child
+process, i3 will setup the listening socket in its event loop. Immediately
+after actually starting the event loop, it will notice a new client connecting
+(the parent process) and handle its request. Since all initialization has been
+completed successfully by the time the event loop is entered, we can now assume
+that i3 is ready.
+
+=== Timing and conclusion
+
+A beautiful feature of this mechanism is that it does not depend on timing. It
+does not matter when the child process gets CPU time or when the parent process
+gets CPU time. On heavily loaded machines (or machines with multiple CPUs,
+cores or unreliable schedulers), this makes waiting for i3 much more robust.
+
+Before using socket activation, we typically used a +sleep(1)+ and hoped that
+i3 was initialized by that time. Of course, this breaks on some (slow)
+computers and wastes a lot of time on faster computers. By using socket
+activation, we decreased the total amount of time necessary to run all tests
+(72 files at the time of writing) from > 100 seconds to 16 seconds. This makes
+it significantly more attractive to run the test suite more often (or at all)
+during development.
+
+An alternative approach to using socket activation is polling for the existance
+of the IPC socket and connecting to it. While this might be slightly easier to
+implement, it wastes CPU time and is considerably uglier than this solution
+:). After all, +lib/SocketActivation.pm+ contains only 54 SLOC.
i3 User’s Guide
===============
Michael Stapelberg <michael+i3@stapelberg.de>
-August 2011
+October 2011
This document contains all the information you need to configure and use the i3
window manager. If it does not, please contact us on IRC (preferred) or post your
When holding the floating modifier, you can resize a floating window by
pressing the right mouse button on it and moving around while holding it. If
-you hold the shift button as well, the resize will be proportional.
+you hold the shift button as well, the resize will be proportional (the aspect
+ratio will be preserved).
*Syntax*:
--------------------------------
*Syntax*:
-----------------------------
-for_window [criteria] command
+for_window <criteria> command
-----------------------------
*Examples*:
[[assign_workspace]]
-Specific windows can be matched by window class and/or window title. It is
-recommended that you match on window classes instead of window titles whenever
-possible because some applications first create their window, and then worry
-about setting the correct title. Firefox with Vimperator comes to mind. The
-window starts up being named Firefox, and only when Vimperator is loaded does
-the title change. As i3 will get the title as soon as the application maps the
+To automatically make a specific window show up on a specific workspace, you
+can use an *assignment*. You can match windows by using any criteria,
+see <<command_criteria>>. It is recommended that you match on window classes
+(and instances, when appropriate) instead of window titles whenever possible
+because some applications first create their window, and then worry about
+setting the correct title. Firefox with Vimperator comes to mind. The window
+starts up being named Firefox, and only when Vimperator is loaded does the
+title change. As i3 will get the title as soon as the application maps the
window (mapping means actually displaying it on the screen), you’d need to have
to match on 'Firefox' in this case.
-You can prefix or suffix workspaces with a `~` to specify that matching clients
-should be put into floating mode. If you specify only a `~`, the client will
-not be put onto any workspace, but will be set floating on the current one.
-
*Syntax*:
------------------------------------------------------------
-assign ["]window class[/window title]["] [→] [workspace]
+assign <criteria> [→] workspace
------------------------------------------------------------
*Examples*:
----------------------
-assign urxvt 2
-assign urxvt → 2
-assign urxvt → work
-assign "urxvt" → 2
-assign "urxvt/VIM" → 3
-assign "gecko" → 4
+# Assign URxvt terminals to workspace 2
+assign [class="URxvt"] 2
+
+# Same thing, but more precise (exact match instead of substring)
+assign [class="^URxvt$"] 2
+
+# Same thing, but with a beautiful arrow :)
+assign [class="^URxvt$"] → 2
+
+# Assignment to a named workspace
+assign [class="^URxvt$"] → work
+
+# Start urxvt -name irssi
+assign [class="^URxvt$" instance="^irssi$"] → 3
----------------------
Note that the arrow is not required, it just looks good :-). If you decide to
use it, it has to be a UTF-8 encoded arrow, not `->` or something like that.
+To get the class and instance, you can use +xprop+. After clicking on the
+window, you will see the following output:
+
+*xwininfo*:
+-----------------------------------
+WM_CLASS(STRING) = "irssi", "URxvt"
+-----------------------------------
+
+The first part of the WM_CLASS is the instance ("irssi" in this example), the
+second part is the class ("URxvt" in this example).
+
+Should you have any problems with assignments, make sure to check the i3
+logfile first (see http://i3wm.org/docs/debugging.html). It includes more
+details about the matching process and the window’s actual class, instance and
+title when starting up.
+
=== Automatically starting applications on i3 startup
By using the +exec+ keyword outside a keybinding, you can configure
*Syntax*:
-------------------
-exec command
-exec_always command
+exec [--no-startup-id] command
+exec_always [--no-startup-id] command
-------------------
*Examples*:
--------------------------------
-exec i3status | i3bar -d
+exec chromium
exec_always ~/my_script.sh
+
+# Execute the terminal emulator urxvt, which is not yet startup-notification aware.
+exec --no-startup-id urxvt
--------------------------------
+The flag --no-startup-id is explained in <<exec>>.
+
[[workspace_screen]]
=== Automatically putting workspaces on specific screens
*Syntax*:
----------------------------------
-workspace <number> output <output>
+workspace <workspace> output <output>
----------------------------------
The 'output' is the name of the RandR output you attach your screen to. On a
laptop, you might have VGA1 and LVDS1 as output names. You can see the
available outputs by running +xrandr --current+.
+If you use named workspaces, they must be quoted:
+
*Examples*:
---------------------------
workspace 1 output LVDS1
workspace 5 output VGA1
+workspace "2: vim" output VGA1
---------------------------
=== Changing colors
force_focus_wrapping yes
------------------------
+=== Forcing Xinerama
+
+As explained in-depth in <http://i3wm.org/docs/multi-monitor.html>, some X11
+video drivers (especially the nVidia binary driver) only provide support for
+Xinerama instead of RandR. In such a situation, i3 must be told to use the
+inferior Xinerama API explicitly and therefore don’t provide support for
+reconfiguring your screens on the fly (they are read only once on startup and
+that’s it).
+
+For people who do cannot modify their +~/.xsession+ to add the
++--force-xinerama+ commandline parameter, a configuration option is provided:
+
+*Syntax*:
+-----------------------
+force_xinerama <yes|no>
+-----------------------
+
+*Example*:
+------------------
+force_xinerama yes
+------------------
+
+Also note that your output names are not descriptive (like +HDMI1+) when using
+Xinerama, instead they are counted up, starting at 0: +xinerama-0+, +xinerama-1+, …
+
+=== Automatic back-and-forth when switching to the current workspace
+
+This configuration directive enables automatic +workspace back_and_forth+ (see
+<<back_and_forth>>) when switching to the workspace that is currently focused.
+
+For instance: Assume you are on workspace "1: www" and switch to "2: IM" using
+mod+2 because somebody sent you a message. You don’t need to remember where you
+came from now, you can just press mod+2 again to switch back to "1: www".
+
+*Syntax*:
+--------------------------------------
+workspace_auto_back_and_forth <yes|no>
+--------------------------------------
+
+*Example*:
+---------------------------------
+workspace_auto_back_and_forth yes
+---------------------------------
+
+== Configuring i3bar
+
+The bar at the bottom of your monitor is drawn by a separate process called
+i3bar. Having this part of "the i3 user interface" in a separate process has
+several advantages:
+
+1. It is a modular approach. If you don’t need a workspace bar at all, or if
+ you prefer a different one (dzen2, xmobar, maybe even gnome-panel?), you can
+ just remove the i3bar configuration and start your favorite bar instead.
+2. It follows the UNIX philosophy of "Make each program do one thing well".
+ While i3 manages your windows well, i3bar is good at displaying a bar on
+ each monitor (unless you configure it otherwise).
+3. It leads to two separate, clean codebases. If you want to understand i3, you
+ don’t need to bother with the details of i3bar and vice versa.
+
+That said, i3bar is configured in the same configuration file as i3. This is
+because it is tightly coupled with i3 (in contrary to i3lock or i3status which
+are useful for people using other window managers). Therefore, it makes no
+sense to use a different configuration place when we already have a good
+configuration infrastructure in place.
+
+Configuring your workspace bar starts with opening a +bar+ block. You can have
+multiple bar blocks to use different settings for different outputs (monitors):
+
+*Example*:
+---------------------------
+bar {
+ status_command i3status
+}
+---------------------------
+
+=== Statusline command
+
+i3bar can run a program and display every line of its +stdout+ output on the
+right hand side of the bar. This is useful to display system information like
+your current IP address, battery status or date/time.
+
+The specified command will be passed to +sh -c+, so you can use globbing and
+have to have correct quoting etc.
+
+*Syntax*:
+----------------------
+status_command command
+----------------------
+
+*Example*:
+-------------------------------------------------
+status_command i3status --config ~/.i3status.conf
+-------------------------------------------------
+
+=== Display mode
+
+You can have i3bar either be visible permanently at one edge of the screen
+(+dock+ mode) or make it show up when you press your modifier key (+hide+
+mode).
+
+The hide mode maximizes screen space that can be used for actual windows. Also,
+i3bar sends the +SIGSTOP+ and +SIGCONT+ signals to the statusline process to
+save battery power.
+
+The default is dock mode.
+
+*Syntax*:
+----------------
+mode <dock|hide>
+----------------
+
+*Example*:
+----------------
+mode hide
+----------------
+
+=== Position
+
+This option determines in which edge of the screen i3bar should show up.
+
+The default is bottom.
+
+*Syntax*:
+---------------------
+position <top|bottom>
+---------------------
+
+*Example*:
+---------------------
+position top
+---------------------
+
+=== Output(s)
+
+You can restrict i3bar to one or more outputs (monitors). The default is to
+handle all outputs. Restricting the outputs is useful for using different
+options for different outputs by using multiple 'bar' blocks.
+
+*Syntax*:
+---------------
+output <output>
+---------------
+
+*Example*:
+-------------------------------
+# big monitor: everything
+bar {
+ output HDMI2
+ status_command i3status
+}
+
+# laptop monitor: bright colors and i3status with less modules.
+bar {
+ output LVDS1
+ status_command i3status --config ~/.i3status-small.conf
+ colors {
+ background #000000
+ statusline #ffffff
+ }
+}
+-------------------------------
+
+=== Tray output
+
+i3bar by default provides a system tray area where programs such as
+NetworkManager, VLC, Pidgin, etc. can place little icons.
+
+You can configure on which output (monitor) the icons should be displayed or
+you can turn off the functionality entirely.
+
+*Syntax*:
+-------------------------
+tray_output <none|output>
+-------------------------
+
+*Example*:
+-------------------------
+# disable system tray
+tray_output none
+
+# show tray icons on the big monitor
+tray_output HDMI2
+-------------------------
+
+=== Font
+
+Specifies the font (again, X core font, not Xft, just like in i3) to be used in
+the bar.
+
+*Syntax*:
+---------------------
+font <font>
+---------------------
+
+*Example*:
+--------------------------------------------------------------
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+--------------------------------------------------------------
+
+=== Workspace buttons
+
+Specifies whether workspace buttons should be shown or not. This is useful if
+you want to display a statusline-only bar containing additional information.
+
+The default is to show workspace buttons.
+
+*Syntax*:
+--------------------------
+workspace_buttons <yes|no>
+--------------------------
+
+*Example*:
+--------------------
+workspace_buttons no
+--------------------
+
+=== Colors
+
+As with i3, colors are in HTML hex format (#rrggbb). The following colors can
+be configured at the moment:
+
+background::
+ Background color of the bar.
+statusline::
+ Text color to be used for the statusline.
+focused_workspace::
+ Text color/background color for a workspace button when the workspace
+ has focus.
+active_workspace::
+ Text color/background color for a workspace button when the workspace
+ is active (visible) on some output, but the focus is on another one.
+ You can only tell this apart from the focused workspace when you are
+ using multiple monitors.
+inactive_workspace::
+ Text color/background color for a workspace button when the workspace
+ does not have focus and is not active (visible) on any output. This
+ will be the case for most workspaces.
+urgent_workspace::
+ Text color/background color for workspaces which contain at least one
+ window with the urgency hint set.
+
+*Syntax*:
+----------------------------------------
+colors {
+ background <color>
+ statusline <color>
+
+ colorclass <foreground> <background>
+}
+----------------------------------------
+
+*Example*:
+--------------------------------------
+colors {
+ background #000000
+ statusline #ffffff
+
+ focused_workspace #ffffff #285577
+ active_workspace #ffffff #333333
+ inactive_workspace #888888 #222222
+ urgent_workspace #ffffff #900000
+}
+--------------------------------------
+
== List of commands
Commands are what you bind to specific keypresses. You can also issue commands
*Example*:
------------------------------------
bindsym mod+x [class="Firefox"] kill
+
+# same thing, but case-insensitive
+bindsym mod+x [class="(?i)firefox"] kill
------------------------------------
The criteria which are currently implemented are:
Compares the window class (the second part of WM_CLASS)
instance::
Compares the window instance (the first part of WM_CLASS)
+window_role::
+ Compares the window role (WM_WINDOW_ROLE).
id::
Compares the X11 window ID, which you can get via +xwininfo+ for example.
title::
Compares the i3-internal container ID, which you can get via the IPC
interface. Handy for scripting.
-Note that currently all criteria are compared case-insensitive and do not
-support regular expressions. This is planned to change in the future.
+The criteria +class+, +instance+, +role+, +title+ and +mark+ are actually
+regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for
+information on how to use them.
+
+[[exec]]
+
+=== Executing applications (exec)
+
+What good is a window manager if you can’t actually start any applications?
+The exec command starts an application by passing the command you specify to a
+shell. This implies that you can use globbing (wildcards) and programs will be
+searched in your $PATH.
+
+*Syntax*:
+------------------------------
+exec [--no-startup-id] command
+------------------------------
+
+*Example*:
+------------------------------
+# Start the GIMP
+bindsym mod+g exec gimp
+
+# Start the terminal emulator urxvt which is not yet startup-notification-aware
+bindsym mod+enter exec --no-startup-id urxvt
+------------------------------
+
+The +--no-startup-id+ parameter disables startup-notification support for this
+particular exec command. With startup-notification, i3 can make sure that a
+window appears on the workspace on which you used the exec command. Also, it
+will change the X11 cursor to +watch+ (a clock) while the application is
+launching. So, if an application is not startup-notification aware (most GTK
+and Qt using applications seem to be, though), you will end up with a watch
+cursor for 60 seconds.
=== Splitting containers
For moving, use +move left+, +move right+, +move down+ and +move up+.
+*Syntax*:
+-----------------------------------
+focus <left|right|down|up>
+focus <parent|child|floating|tiling|mode_toggle>
+move <left|right|down|up> [<px> px]
+-----------------------------------
+
+Note that the amount of pixels you can specify for the +move+ command is only
+relevant for floating containers. The default amount is 10 pixels.
+
*Examples*:
----------------------
-# Focus clients on the left, bottom, top, right:
+# Focus container on the left, bottom, top, right:
bindsym mod+j focus left
bindsym mod+k focus down
bindsym mod+l focus up
# Focus last floating/tiling container
bindsym mod+g focus mode_toggle
-# Move client to the left, bottom, top, right:
+# Move container to the left, bottom, top, right:
bindsym mod+j move left
bindsym mod+k move down
bindsym mod+l move up
bindsym mod+semicolon move right
+
+# Move container, but make floating containers
+# move more than the default
+bindsym mod+j move left 20 px
----------------------
=== Changing (named) workspaces/moving to workspaces
You can also switch to the next and previous workspace with the commands
+workspace next+ and +workspace prev+, which is handy, for example, if you have
workspace 1, 3, 4 and 9 and you want to cycle through them with a single key
-combination.
+combination. Similarily, you can use +move workspace next+ and +move workspace
+prev+ to move a container to the next/previous workspace.
+
+[[back_and_forth]]
+To switch back to the previously focused workspace, use +workspace
+back_and_forth+.
+
+To move a container to another xrandr output such as +LVDS1+ or +VGA1+, you can
+use the +move output+ command followed by the name of the target output. You
+may also use +left+, +right+, +up+, +down+ instead of the xrandr output name to
+move to the the next output in the specified direction.
*Examples*:
-------------------------
bindsym mod+Shift+1 move workspace 1
bindsym mod+Shift+2 move workspace 2
...
+
+# switch between the current and the previously focused one
+bindsym mod+b workspace back_and_forth
-------------------------
==== Named workspaces
Direction can be one of +up+, +down+, +left+ or +right+. The optional pixel
argument specifies by how many pixels a *floating container* should be grown or
-shrinked (the default is 10 pixels). The ppt argument means percentage points
+shrunk (the default is 10 pixels). The ppt argument means percentage points
and specifies by how many percentage points a *tiling container* should be
-grown or shrinked (the default is 10 percentage points).
+grown or shrunk (the default is 10 percentage points).
I recommend using the resize command inside a so called +mode+:
FILES:=$(patsubst %.c,%.o,$(filter-out $(AUTOGENERATED),$(wildcard *.c)))
HEADERS:=$(wildcard *.h)
+CPPFLAGS += -I$(TOPDIR)/include
+
# Depend on the specific file (.c for each .o) and on all headers
%.o: %.c ${HEADERS}
- echo "CC $<"
+ echo "[i3-config-wizard] CC $<"
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
all: i3-config-wizard
-i3-config-wizard: cfgparse.y.o cfgparse.yy.o ${FILES}
- echo "LINK i3-config-wizard"
- $(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+i3-config-wizard: $(TOPDIR)/libi3/libi3.a cfgparse.y.o cfgparse.yy.o ${FILES}
+ echo "[i3-config-wizard] LINK i3-config-wizard"
+ $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS)
+
+$(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c
+ $(MAKE) -C $(TOPDIR)/libi3
cfgparse.yy.o: cfgparse.l cfgparse.y.o ${HEADERS}
- echo "LEX $<"
+ echo "[i3-config-wizard] LEX $<"
flex -i -o$(@:.o=.c) $<
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(@:.o=.c)
cfgparse.y.o: cfgparse.y ${HEADERS}
- echo "YACC $<"
+ echo "[i3-config-wizard] YACC $<"
bison --debug --verbose -b $(basename $< .y) -d $<
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(<:.y=.tab.c)
install: all
- echo "INSTALL"
+ echo "[i3-config-wizard] INSTALL"
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
$(INSTALL) -m 0755 i3-config-wizard $(DESTDIR)$(PREFIX)/bin/
#include <X11/Xlib.h>
+#include "libi3.h"
+
extern Display *dpy;
struct context {
char *str = XKeysymToString(sym);
char *modifiers = modifier_to_string($<number>3);
// TODO: modifier to string
- asprintf(&(context->result), "bindsym %s%s %s\n", modifiers, str, $<string>6);
+ sasprintf(&(context->result), "bindsym %s%s %s\n", modifiers, str, $<string>6);
free(modifiers);
}
;
+++ /dev/null
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- */
-#include <stdint.h>
-#include <string.h>
-#include <stdlib.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <unistd.h>
-#include <err.h>
-
-/*
- * Formats a message (payload) of the given size and type and sends it to i3 via
- * the given socket file descriptor.
- *
- */
-void ipc_send_message(int sockfd, uint32_t message_size,
- uint32_t message_type, uint8_t *payload) {
- int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + sizeof(uint32_t) + message_size;
- char msg[buffer_size];
- char *walk = msg;
-
- strcpy(walk, "i3-ipc");
- walk += strlen("i3-ipc");
- memcpy(walk, &message_size, sizeof(uint32_t));
- walk += sizeof(uint32_t);
- memcpy(walk, &message_type, sizeof(uint32_t));
- walk += sizeof(uint32_t);
- memcpy(walk, payload, message_size);
-
- int sent_bytes = 0;
- int bytes_to_go = buffer_size;
- while (sent_bytes < bytes_to_go) {
- int n = write(sockfd, msg + sent_bytes, bytes_to_go);
- if (n == -1)
- err(EXIT_FAILURE, "write() failed");
-
- sent_bytes += n;
- bytes_to_go -= n;
- }
-}
-
-/*
- * Connects to the i3 IPC socket and returns the file descriptor for the
- * socket. die()s if anything goes wrong.
- *
- */
-int connect_ipc(char *socket_path) {
- int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
- if (sockfd == -1)
- err(EXIT_FAILURE, "Could not create socket");
-
- struct sockaddr_un addr;
- memset(&addr, 0, sizeof(struct sockaddr_un));
- addr.sun_family = AF_LOCAL;
- strcpy(addr.sun_path, socket_path);
- if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
- err(EXIT_FAILURE, "Could not connect to i3");
-
- return sockfd;
-}
+++ /dev/null
-#ifndef _IPC_H
-#define _IPC_H
-
-void ipc_send_message(int sockfd, uint32_t message_size,
- uint32_t message_type, uint8_t *payload);
-
-int connect_ipc(char *socket_path);
-
-#endif
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
- *
- * © 2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
* i3-config-wizard: Program to convert configs using keycodes to configs using
- * keysyms.
+ * keysyms.
*
*/
#include <ev.h>
while (0)
#include "xcb.h"
-#include "ipc.h"
+#include "libi3.h"
enum { STEP_WELCOME, STEP_GENERATE } current_step = STEP_WELCOME;
enum { MOD_Mod1, MOD_Mod4 } modifier = MOD_Mod4;
static char *config_path;
-static xcb_connection_t *conn;
+static uint32_t xcb_numlock_mask;
+xcb_connection_t *conn;
static xcb_get_modifier_mapping_reply_t *modmap_reply;
-static uint32_t font_id;
-static uint32_t font_bold_id;
+static i3Font font;
+static i3Font bold_font;
static char *socket_path;
-static int font_height;
-static int font_bold_height;
static xcb_window_t win;
static xcb_pixmap_t pixmap;
static xcb_gcontext_t pixmap_gc;
char *rewrite_binding(const char *bindingline);
static void finish();
-#if defined(__APPLE__)
-
-/*
- * Taken from FreeBSD
- * Returns a pointer to a new string which is a duplicate of the
- * string, but only copies at most n characters.
- *
- */
-char *strndup(const char *str, size_t n) {
- size_t len;
- char *copy;
-
- for (len = 0; len < n && str[len]; len++)
- continue;
-
- if ((copy = malloc(len + 1)) == NULL)
- return (NULL);
- memcpy(copy, str, len);
- copy[len] = '\0';
- return (copy);
-}
-
-#endif
-
/*
* This function resolves ~ in pathnames.
* It may resolve wildcards in the first part of the path, but if no match
return result;
}
-/*
- * Try to get the socket path from X11 and return NULL if it doesn’t work.
- * As i3-msg is a short-running tool, we don’t bother with cleaning up the
- * connection and leave it up to the operating system on exit.
- *
- */
-static char *socket_path_from_x11() {
- xcb_connection_t *conn;
- int screen;
- if ((conn = xcb_connect(NULL, &screen)) == NULL ||
- xcb_connection_has_error(conn))
- return NULL;
- xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen);
- xcb_window_t root = root_screen->root;
-
- xcb_intern_atom_cookie_t atom_cookie;
- xcb_intern_atom_reply_t *atom_reply;
-
- atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH");
- atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL);
- if (atom_reply == NULL)
- return NULL;
-
- xcb_get_property_cookie_t prop_cookie;
- xcb_get_property_reply_t *prop_reply;
- prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom,
- XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX);
- prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL);
- if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0)
- return NULL;
- if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply),
- (char*)xcb_get_property_value(prop_reply)) == -1)
- return NULL;
- return socket_path;
-}
-
/*
* Handles expose events, that is, draws the window contents.
*
*/
static int handle_expose() {
/* re-draw the background */
- xcb_rectangle_t border = {0, 0, 300, (15*font_height) + 8};
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
+ xcb_rectangle_t border = {0, 0, 300, (15 * font.height) + 8};
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#000000") });
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ font.id });
-#define txt(x, row, text) xcb_image_text_8(conn, strlen(text), pixmap, pixmap_gc, x, (row * font_height) + 2, text)
+#define txt(x, row, text) xcb_image_text_8(conn, strlen(text), pixmap, pixmap_gc, x, (row * font.height) + 2, text)
if (current_step == STEP_WELCOME) {
/* restore font color */
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FFFFFF") });
txt(10, 2, "You have not configured i3 yet.");
txt(10, 3, "Do you want me to generate ~/.i3/config?");
txt(85, 7, "No, I will use the defaults");
/* green */
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#00FF00"));
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#00FF00") });
txt(25, 5, "<Enter>");
/* red */
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000"));
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FF0000") });
txt(31, 7, "<ESC>");
}
if (current_step == STEP_GENERATE) {
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FFFFFF") });
txt(10, 2, "Please choose either:");
txt(85, 4, "Win as default modifier");
else txt(31, 4, "<Win>");
/* the selected modifier */
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_bold_id);
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ bold_font.id });
if (modifier == MOD_Mod4)
txt(31, 4, "<Win>");
else txt(31, 5, "<Alt>");
/* green */
- uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_FONT;
- uint32_t values[] = { get_colorpixel(conn, "#00FF00"), font_id };
- xcb_change_gc(conn, pixmap_gc, mask, values);
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_FONT,
+ (uint32_t[]) { get_colorpixel("#00FF00"), font.id });
txt(25, 9, "<Enter>");
/* red */
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000"));
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FF0000") });
txt(31, 10, "<ESC>");
}
fclose(ks_config);
/* tell i3 to reload the config file */
- int sockfd = connect_ipc(socket_path);
+ int sockfd = ipc_connect(socket_path);
ipc_send_message(sockfd, strlen("reload"), 0, (uint8_t*)"reload");
close(sockfd);
xcb_get_modifier_mapping_cookie_t modmap_cookie;
modmap_cookie = xcb_get_modifier_mapping(conn);
+ symbols = xcb_key_symbols_alloc(conn);
/* Place requests for the atoms we need as soon as possible */
#define xmacro(atom) \
if (!(modmap_reply = xcb_get_modifier_mapping_reply(conn, modmap_cookie, NULL)))
errx(EXIT_FAILURE, "Could not get modifier mapping\n");
- /* XXX: we should refactor xcb_get_numlock_mask so that it uses the
- * modifier mapping we already have */
- xcb_get_numlock_mask(conn);
-
- symbols = xcb_key_symbols_alloc(conn);
+ xcb_numlock_mask = get_mod_mask_for(XCB_NUM_LOCK, symbols, modmap_reply);
- font_id = get_font_id(conn, pattern, &font_height);
- font_bold_id = get_font_id(conn, patternbold, &font_bold_height);
+ font = load_font(pattern, true);
+ bold_font = load_font(patternbold, true);
/* Open an input window */
- win = open_input_window(conn, 300, 205);
+ win = xcb_generate_id(conn);
+ xcb_create_window(
+ conn,
+ XCB_COPY_FROM_PARENT,
+ win, /* the window id */
+ root, /* parent == root */
+ 490, 297, 300, 205, /* dimensions */
+ 0, /* X11 border = 0, we draw our own */
+ XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
+ XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
+ (uint32_t[]){
+ 0, /* back pixel: black */
+ XCB_EVENT_MASK_EXPOSURE |
+ XCB_EVENT_MASK_BUTTON_PRESS
+ });
+
+ /* Map the window (make it visible) */
+ xcb_map_window(conn, win);
/* Setup NetWM atoms */
#define xmacro(name) \
+++ /dev/null
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- */
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <err.h>
-
-#include <xcb/xcb.h>
-#include <xcb/xcb_keysyms.h>
-
-#include <X11/keysym.h>
-
-#include "xcb.h"
-
-extern xcb_window_t root;
-unsigned int xcb_numlock_mask;
-
-/*
- * Convenience-wrapper around xcb_change_gc which saves us declaring a variable
- *
- */
-void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value) {
- xcb_change_gc(conn, gc, mask, &value);
-}
-
-/*
- * Returns the colorpixel to use for the given hex color (think of HTML).
- *
- * The hex_color has to start with #, for example #FF00FF.
- *
- * NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
- * This has to be done by the caller.
- *
- */
-uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
- char strgroups[3][3] = {{hex[1], hex[2], '\0'},
- {hex[3], hex[4], '\0'},
- {hex[5], hex[6], '\0'}};
- uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
- (strtol(strgroups[1], NULL, 16)),
- (strtol(strgroups[2], NULL, 16))};
-
- return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
-}
-
-/*
- * Returns the mask for Mode_switch (to be used for looking up keysymbols by
- * keycode).
- *
- */
-uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode) {
- xcb_key_symbols_t *symbols = xcb_key_symbols_alloc(conn);
-
- xcb_get_modifier_mapping_reply_t *modmap_r;
- xcb_keycode_t *modmap, kc;
- xcb_keycode_t *modeswitchcodes = xcb_key_symbols_get_keycode(symbols, keycode);
- if (modeswitchcodes == NULL)
- return 0;
-
- modmap_r = xcb_get_modifier_mapping_reply(conn, xcb_get_modifier_mapping(conn), NULL);
- modmap = xcb_get_modifier_mapping_keycodes(modmap_r);
-
- for (int i = 0; i < 8; i++)
- for (int j = 0; j < modmap_r->keycodes_per_modifier; j++) {
- kc = modmap[i * modmap_r->keycodes_per_modifier + j];
- for (xcb_keycode_t *ktest = modeswitchcodes; *ktest; ktest++) {
- if (*ktest != kc)
- continue;
-
- free(modeswitchcodes);
- free(modmap_r);
- return (1 << i);
- }
- }
-
- return 0;
-}
-
-/*
- * Opens the window we use for input/output and maps it
- *
- */
-xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height) {
- xcb_window_t win = xcb_generate_id(conn);
- //xcb_cursor_t cursor_id = xcb_generate_id(conn);
-
-#if 0
- /* Use the default cursor (left pointer) */
- if (cursor > -1) {
- i3Font *cursor_font = load_font(conn, "cursor");
- xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id,
- XCB_CURSOR_LEFT_PTR, XCB_CURSOR_LEFT_PTR + 1,
- 0, 0, 0, 65535, 65535, 65535);
- }
-#endif
-
- uint32_t mask = 0;
- uint32_t values[3];
-
- mask |= XCB_CW_BACK_PIXEL;
- values[0] = 0;
-
- mask |= XCB_CW_EVENT_MASK;
- values[1] = XCB_EVENT_MASK_EXPOSURE |
- XCB_EVENT_MASK_BUTTON_PRESS;
-
- xcb_create_window(conn,
- XCB_COPY_FROM_PARENT,
- win, /* the window id */
- root, /* parent == root */
- 490, 297, width, height, /* dimensions */
- 0, /* border = 0, we draw our own */
- XCB_WINDOW_CLASS_INPUT_OUTPUT,
- XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
- mask,
- values);
-
-#if 0
- if (cursor > -1)
- xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
-#endif
-
- /* Map the window (= make it visible) */
- xcb_map_window(conn, win);
-
- return win;
-}
-
-/*
- * Returns the ID of the font matching the given pattern and stores the height
- * of the font (in pixels) in *font_height. die()s if no font matches.
- *
- */
-int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height) {
- xcb_void_cookie_t font_cookie;
- xcb_list_fonts_with_info_cookie_t info_cookie;
-
- /* Send all our requests first */
- int result;
- result = xcb_generate_id(conn);
- font_cookie = xcb_open_font_checked(conn, result, strlen(pattern), pattern);
- info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
-
- xcb_generic_error_t *error = xcb_request_check(conn, font_cookie);
- if (error != NULL) {
- fprintf(stderr, "ERROR: Could not open font: %d\n", error->error_code);
- exit(1);
- }
-
- /* Get information (height/name) for this font */
- xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL);
- if (reply == NULL)
- errx(1, "Could not load font \"%s\"\n", pattern);
-
- *font_height = reply->font_ascent + reply->font_descent;
-
- return result;
-}
-
-/*
- * Finds out which modifier mask is the one for numlock, as the user may change this.
- *
- */
-void xcb_get_numlock_mask(xcb_connection_t *conn) {
- xcb_key_symbols_t *keysyms;
- xcb_get_modifier_mapping_cookie_t cookie;
- xcb_get_modifier_mapping_reply_t *reply;
- xcb_keycode_t *modmap;
- int mask, i;
- const int masks[8] = { XCB_MOD_MASK_SHIFT,
- XCB_MOD_MASK_LOCK,
- XCB_MOD_MASK_CONTROL,
- XCB_MOD_MASK_1,
- XCB_MOD_MASK_2,
- XCB_MOD_MASK_3,
- XCB_MOD_MASK_4,
- XCB_MOD_MASK_5 };
-
- /* Request the modifier map */
- cookie = xcb_get_modifier_mapping_unchecked(conn);
-
- /* Get the keysymbols */
- keysyms = xcb_key_symbols_alloc(conn);
-
- if ((reply = xcb_get_modifier_mapping_reply(conn, cookie, NULL)) == NULL) {
- xcb_key_symbols_free(keysyms);
- return;
- }
-
- modmap = xcb_get_modifier_mapping_keycodes(reply);
-
- /* Get the keycode for numlock */
-#ifdef OLD_XCB_KEYSYMS_API
- xcb_keycode_t numlock = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
-#else
- /* For now, we only use the first keysymbol. */
- xcb_keycode_t *numlock_syms = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
- if (numlock_syms == NULL)
- return;
- xcb_keycode_t numlock = *numlock_syms;
- free(numlock_syms);
-#endif
-
- /* Check all modifiers (Mod1-Mod5, Shift, Control, Lock) */
- for (mask = 0; mask < 8; mask++)
- for (i = 0; i < reply->keycodes_per_modifier; i++)
- if (modmap[(mask * reply->keycodes_per_modifier) + i] == numlock)
- xcb_numlock_mask = masks[mask];
-
- xcb_key_symbols_free(keysyms);
- free(reply);
-}
-
#include "atoms.xmacro"
#undef xmacro
-extern unsigned int xcb_numlock_mask;
-
-void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value);
-uint32_t get_colorpixel(xcb_connection_t *conn, char *hex);
-uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode);
-xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height);
-int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height);
-/**
- * Finds out which modifier mask is the one for numlock, as the user may change this.
- *
- */
-void xcb_get_numlock_mask(xcb_connection_t *conn);
-
#endif
include $(TOPDIR)/common.mk
+CPPFLAGS += -I$(TOPDIR)/include
+
# Depend on the object files of all source-files in src/*.c and on all header files
FILES=$(patsubst %.c,%.o,$(wildcard *.c))
HEADERS=$(wildcard *.h)
# Depend on the specific file (.c for each .o) and on all headers
%.o: %.c ${HEADERS}
- echo "CC $<"
+ echo "[i3-input] CC $<"
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
all: i3-input
-i3-input: ${FILES}
- echo "LINK i3-input"
- $(CC) $(LDFLAGS) -o $@ ${FILES} $(LIBS)
+i3-input: $(TOPDIR)/libi3/libi3.a ${FILES}
+ echo "[i3-input] LINK i3-input"
+ $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS)
+
+$(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c
+ $(MAKE) -C $(TOPDIR)/libi3
install: all
- echo "INSTALL"
+ echo "[i3-input] INSTALL"
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
$(INSTALL) -m 0755 i3-input $(DESTDIR)$(PREFIX)/bin/
char *convert_ucs_to_utf8(char *input);
char *convert_utf8_to_ucs2(char *input, int *real_strlen);
-uint32_t get_colorpixel(xcb_connection_t *conn, char *hex);
-uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode);
-int connect_ipc(char *socket_path);
-void ipc_send_message(int sockfd, uint32_t message_size,
- uint32_t message_type, uint8_t *payload);
-xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height);
-int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height);
-void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value);
#endif
+++ /dev/null
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- */
-#include <stdint.h>
-#include <string.h>
-#include <stdlib.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <unistd.h>
-#include <err.h>
-
-/*
- * Formats a message (payload) of the given size and type and sends it to i3 via
- * the given socket file descriptor.
- *
- */
-void ipc_send_message(int sockfd, uint32_t message_size,
- uint32_t message_type, uint8_t *payload) {
- int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + sizeof(uint32_t) + message_size;
- char msg[buffer_size];
- char *walk = msg;
-
- strcpy(walk, "i3-ipc");
- walk += strlen("i3-ipc");
- memcpy(walk, &message_size, sizeof(uint32_t));
- walk += sizeof(uint32_t);
- memcpy(walk, &message_type, sizeof(uint32_t));
- walk += sizeof(uint32_t);
- memcpy(walk, payload, message_size);
-
- int sent_bytes = 0;
- int bytes_to_go = buffer_size;
- while (sent_bytes < bytes_to_go) {
- int n = write(sockfd, msg + sent_bytes, bytes_to_go);
- if (n == -1)
- err(EXIT_FAILURE, "write() failed");
-
- sent_bytes += n;
- bytes_to_go -= n;
- }
-}
-
-/*
- * Connects to the i3 IPC socket and returns the file descriptor for the
- * socket. die()s if anything goes wrong.
- *
- */
-int connect_ipc(char *socket_path) {
- int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
- if (sockfd == -1)
- err(EXIT_FAILURE, "Could not create socket");
-
- struct sockaddr_un addr;
- memset(&addr, 0, sizeof(struct sockaddr_un));
- addr.sun_family = AF_LOCAL;
- strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
- if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
- err(EXIT_FAILURE, "Could not connect to i3");
-
- return sockfd;
-}
/*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
- *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
* i3-input/main.c: Utility which lets the user input commands and sends them
- * to i3.
+ * to i3.
*
*/
#include <ev.h>
#include "i3-input.h"
+#include "libi3.h"
+
+/* IPC format string. %s will be replaced with what the user entered, then
+ * the command will be sent to i3 */
+static char *format;
+
static char *socket_path;
static int sockfd;
static xcb_key_symbols_t *symbols;
-static int modeswitchmask;
-static int numlockmask;
static bool modeswitch_active = false;
static xcb_window_t win;
static xcb_pixmap_t pixmap;
static char *glyphs_ucs[512];
static char *glyphs_utf8[512];
static int input_position;
-static int font_height;
-static char *command_prefix;
+static i3Font font;
static char *prompt;
static int prompt_len;
static int limit;
xcb_window_t root;
-
-/*
- * Try to get the socket path from X11 and return NULL if it doesn’t work.
- * As i3-msg is a short-running tool, we don’t bother with cleaning up the
- * connection and leave it up to the operating system on exit.
- *
- */
-static char *socket_path_from_x11() {
- xcb_connection_t *conn;
- int screen;
- if ((conn = xcb_connect(NULL, &screen)) == NULL ||
- xcb_connection_has_error(conn))
- return NULL;
- xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen);
- xcb_window_t root = root_screen->root;
-
- xcb_intern_atom_cookie_t atom_cookie;
- xcb_intern_atom_reply_t *atom_reply;
-
- atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH");
- atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL);
- if (atom_reply == NULL)
- return NULL;
-
- xcb_get_property_cookie_t prop_cookie;
- xcb_get_property_reply_t *prop_reply;
- prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom,
- XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX);
- prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL);
- if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0)
- return NULL;
- if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply),
- (char*)xcb_get_property_value(prop_reply)) == -1)
- return NULL;
- return socket_path;
-}
+xcb_connection_t *conn;
/*
* Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for
*
*/
static uint8_t *concat_strings(char **glyphs, int max) {
- uint8_t *output = calloc(max+1, 4);
- uint8_t *walk = output;
- for (int c = 0; c < max; c++) {
- printf("at %c\n", glyphs[c][0]);
- /* if the first byte is 0, this has to be UCS2 */
- if (glyphs[c][0] == '\0') {
- memcpy(walk, glyphs[c], 2);
- walk += 2;
- } else {
- strcpy((char*)walk, glyphs[c]);
- walk += strlen(glyphs[c]);
- }
+ uint8_t *output = calloc(max+1, 4);
+ uint8_t *walk = output;
+ for (int c = 0; c < max; c++) {
+ printf("at %c\n", glyphs[c][0]);
+ /* if the first byte is 0, this has to be UCS2 */
+ if (glyphs[c][0] == '\0') {
+ memcpy(walk, glyphs[c], 2);
+ walk += 2;
+ } else {
+ strcpy((char*)walk, glyphs[c]);
+ walk += strlen(glyphs[c]);
}
- printf("output = %s\n", output);
- return output;
+ }
+ printf("output = %s\n", output);
+ return output;
}
/*
*
*/
static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t *event) {
- printf("expose!\n");
-
- /* re-draw the background */
- xcb_rectangle_t border = {0, 0, 500, font_height + 8}, inner = {2, 2, 496, font_height + 8 - 4};
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000"));
- xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
- xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
-
- /* restore font color */
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
- uint8_t *con = concat_strings(glyphs_ucs, input_position);
- char *full_text = (char*)con;
- if (prompt != NULL) {
- full_text = malloc((prompt_len + input_position) * 2 + 1);
- if (full_text == NULL)
- err(EXIT_FAILURE, "malloc() failed\n");
- memcpy(full_text, prompt, prompt_len * 2);
- memcpy(full_text + (prompt_len * 2), con, input_position * 2);
- }
- xcb_image_text_16(conn, input_position + prompt_len, pixmap, pixmap_gc, 4 /* X */,
- font_height + 2 /* Y = baseline of font */, (xcb_char2b_t*)full_text);
-
- /* Copy the contents of the pixmap to the real window */
- xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, font_height + 8);
- xcb_flush(conn);
- free(con);
- if (prompt != NULL)
- free(full_text);
-
- return 1;
+ printf("expose!\n");
+
+ /* re-draw the background */
+ xcb_rectangle_t border = {0, 0, 500, font.height + 8}, inner = {2, 2, 496, font.height + 8 - 4};
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FF0000") });
+ xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#000000") });
+ xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
+
+ /* restore font color */
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FFFFFF") });
+ uint8_t *con = concat_strings(glyphs_ucs, input_position);
+ char *full_text = (char*)con;
+ if (prompt != NULL) {
+ full_text = malloc((prompt_len + input_position) * 2 + 1);
+ if (full_text == NULL)
+ err(EXIT_FAILURE, "malloc() failed\n");
+ memcpy(full_text, prompt, prompt_len * 2);
+ memcpy(full_text + (prompt_len * 2), con, input_position * 2);
+ }
+ xcb_image_text_16(conn, input_position + prompt_len, pixmap, pixmap_gc, 4 /* X */,
+ font.height + 2 /* Y = baseline of font */, (xcb_char2b_t*)full_text);
+
+ /* Copy the contents of the pixmap to the real window */
+ xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, font.height + 8);
+ xcb_flush(conn);
+ free(con);
+ if (prompt != NULL)
+ free(full_text);
+
+ return 1;
}
/*
*
*/
static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event) {
- printf("releasing %d, state raw = %d\n", event->detail, event->state);
+ printf("releasing %d, state raw = %d\n", event->detail, event->state);
- /* fix state */
- event->state &= ~numlockmask;
+ /* See the documentation of xcb_key_symbols_get_keysym for this one.
+ * Basically: We get either col 0 or col 1, depending on whether shift is
+ * pressed. */
+ int col = (event->state & XCB_MOD_MASK_SHIFT);
- xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state);
- if (sym == XK_Mode_switch) {
- printf("Mode switch disabled\n");
- modeswitch_active = false;
- }
+ /* If modeswitch is currently active, we need to look in group 2 or 3,
+ * respectively. */
+ if (modeswitch_active)
+ col += 2;
- return 1;
+ xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state);
+ if (sym == XK_Mode_switch) {
+ printf("Mode switch disabled\n");
+ modeswitch_active = false;
+ }
+
+ return 1;
}
static void finish_input() {
- uint8_t *command = concat_strings(glyphs_utf8, input_position);
- char *full_command = (char*)command;
- /* prefix the command if a prefix was specified on commandline */
- if (command_prefix != NULL) {
- if (asprintf(&full_command, "%s%s", command_prefix, command) == -1)
- err(EXIT_FAILURE, "asprintf() failed\n");
+ char *command = (char*)concat_strings(glyphs_utf8, input_position);
+
+ /* count the occurences of %s in the string */
+ int c;
+ int len = strlen(format);
+ int cnt = 0;
+ for (c = 0; c < (len-1); c++)
+ if (format[c] == '%' && format[c+1] == 's')
+ cnt++;
+ printf("occurences = %d\n", cnt);
+
+ /* allocate space for the output */
+ int inputlen = strlen(command);
+ char *full = calloc(1,
+ strlen(format) - (2 * cnt) /* format without all %s */
+ + (inputlen * cnt) /* replaced %s */
+ + 1); /* trailing NUL */
+ char *dest = full;
+ for (c = 0; c < len; c++) {
+ /* if this is not % or it is % but without a following 's',
+ * just copy the character */
+ if (format[c] != '%' || (c == (len-1)) || format[c+1] != 's')
+ *(dest++) = format[c];
+ else {
+ strncat(dest, command, inputlen);
+ dest += inputlen;
+ /* skip the following 's' of '%s' */
+ c++;
}
- printf("command = %s\n", full_command);
+ }
+
+ /* prefix the command if a prefix was specified on commandline */
+ printf("command = %s\n", full);
- ipc_send_message(sockfd, strlen(full_command), 0, (uint8_t*)full_command);
+ ipc_send_message(sockfd, strlen(full), 0, (uint8_t*)full);
#if 0
- free(command);
- return 1;
+ free(command);
+ return 1;
#endif
- exit(0);
+ exit(0);
}
/*
*
*/
static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
- printf("Keypress %d, state raw = %d\n", event->detail, event->state);
-
- /* fix state */
- if (modeswitch_active)
- event->state |= modeswitchmask;
-
- /* Apparantly, after activating numlock once, the numlock modifier
- * stays turned on (use xev(1) to verify). So, to resolve useful
- * keysyms, we remove the numlock flag from the event state */
- event->state &= ~numlockmask;
-
- xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state);
- if (sym == XK_Mode_switch) {
- printf("Mode switch enabled\n");
- modeswitch_active = true;
- return 1;
- }
-
- if (sym == XK_Return)
- finish_input();
-
- if (sym == XK_BackSpace) {
- if (input_position == 0)
- return 1;
-
- input_position--;
- free(glyphs_ucs[input_position]);
- free(glyphs_utf8[input_position]);
-
- handle_expose(NULL, conn, NULL);
- return 1;
- }
- if (sym == XK_Escape) {
- exit(0);
- }
-
- /* TODO: handle all of these? */
- printf("is_keypad_key = %d\n", xcb_is_keypad_key(sym));
- printf("is_private_keypad_key = %d\n", xcb_is_private_keypad_key(sym));
- printf("xcb_is_cursor_key = %d\n", xcb_is_cursor_key(sym));
- printf("xcb_is_pf_key = %d\n", xcb_is_pf_key(sym));
- printf("xcb_is_function_key = %d\n", xcb_is_function_key(sym));
- printf("xcb_is_misc_function_key = %d\n", xcb_is_misc_function_key(sym));
- printf("xcb_is_modifier_key = %d\n", xcb_is_modifier_key(sym));
-
- if (xcb_is_modifier_key(sym) || xcb_is_cursor_key(sym))
- return 1;
-
- printf("sym = %c (%d)\n", sym, sym);
-
- /* convert the keysym to UCS */
- uint16_t ucs = keysym2ucs(sym);
- if ((int16_t)ucs == -1) {
- fprintf(stderr, "Keysym could not be converted to UCS, skipping\n");
- return 1;
- }
-
- /* store the UCS into a string */
- uint8_t inp[3] = {(ucs & 0xFF00) >> 8, (ucs & 0xFF), 0};
+ printf("Keypress %d, state raw = %d\n", event->detail, event->state);
+
+ /* See the documentation of xcb_key_symbols_get_keysym for this one.
+ * Basically: We get either col 0 or col 1, depending on whether shift is
+ * pressed. */
+ int col = (event->state & XCB_MOD_MASK_SHIFT);
+
+ /* If modeswitch is currently active, we need to look in group 2 or 3,
+ * respectively. */
+ if (modeswitch_active)
+ col += 2;
+
+ xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, col);
+ if (sym == XK_Mode_switch) {
+ printf("Mode switch enabled\n");
+ modeswitch_active = true;
+ return 1;
+ }
- printf("inp[0] = %02x, inp[1] = %02x, inp[2] = %02x\n", inp[0], inp[1], inp[2]);
- /* convert it to UTF-8 */
- char *out = convert_ucs_to_utf8((char*)inp);
- printf("converted to %s\n", out);
+ if (sym == XK_Return)
+ finish_input();
- glyphs_ucs[input_position] = malloc(3 * sizeof(uint8_t));
- if (glyphs_ucs[input_position] == NULL)
- err(EXIT_FAILURE, "malloc() failed\n");
- memcpy(glyphs_ucs[input_position], inp, 3);
- glyphs_utf8[input_position] = strdup(out);
- input_position++;
+ if (sym == XK_BackSpace) {
+ if (input_position == 0)
+ return 1;
- if (input_position == limit)
- finish_input();
+ input_position--;
+ free(glyphs_ucs[input_position]);
+ free(glyphs_utf8[input_position]);
handle_expose(NULL, conn, NULL);
return 1;
-}
-
-int main(int argc, char *argv[]) {
- socket_path = getenv("I3SOCK");
- char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
- int o, option_index = 0;
-
- static struct option long_options[] = {
- {"socket", required_argument, 0, 's'},
- {"version", no_argument, 0, 'v'},
- {"limit", required_argument, 0, 'l'},
- {"prompt", required_argument, 0, 'P'},
- {"prefix", required_argument, 0, 'p'},
- {"font", required_argument, 0, 'f'},
- {"help", no_argument, 0, 'h'},
- {0, 0, 0, 0}
- };
-
- char *options_string = "s:p:P:f:l:vh";
-
- while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
- switch (o) {
- case 's':
- FREE(socket_path);
- socket_path = strdup(optarg);
- break;
- case 'v':
- printf("i3-input " I3_VERSION);
- return 0;
- case 'p':
- FREE(command_prefix);
- command_prefix = strdup(optarg);
- break;
- case 'l':
- limit = atoi(optarg);
- break;
- case 'P':
- FREE(prompt);
- prompt = strdup(optarg);
- break;
- case 'f':
- FREE(pattern);
- pattern = strdup(optarg);
- break;
- case 'h':
- printf("i3-input " I3_VERSION);
- printf("i3-input [-s <socket>] [-p <prefix>] [-l <limit>] [-P <prompt>] [-f <font>] [-v]\n");
- return 0;
- }
- }
-
- if (socket_path == NULL)
- socket_path = socket_path_from_x11();
-
- if (socket_path == NULL)
- socket_path = "/tmp/i3-ipc.sock";
-
- sockfd = connect_ipc(socket_path);
-
- if (prompt != NULL)
- prompt = convert_utf8_to_ucs2(prompt, &prompt_len);
-
- int screens;
- xcb_connection_t *conn = xcb_connect(NULL, &screens);
- if (xcb_connection_has_error(conn))
- die("Cannot open display\n");
-
- xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
- root = root_screen->root;
-
- modeswitchmask = get_mod_mask(conn, XK_Mode_switch);
- numlockmask = get_mod_mask(conn, XK_Num_Lock);
- symbols = xcb_key_symbols_alloc(conn);
+ }
+ if (sym == XK_Escape) {
+ exit(0);
+ }
+
+ /* TODO: handle all of these? */
+ printf("is_keypad_key = %d\n", xcb_is_keypad_key(sym));
+ printf("is_private_keypad_key = %d\n", xcb_is_private_keypad_key(sym));
+ printf("xcb_is_cursor_key = %d\n", xcb_is_cursor_key(sym));
+ printf("xcb_is_pf_key = %d\n", xcb_is_pf_key(sym));
+ printf("xcb_is_function_key = %d\n", xcb_is_function_key(sym));
+ printf("xcb_is_misc_function_key = %d\n", xcb_is_misc_function_key(sym));
+ printf("xcb_is_modifier_key = %d\n", xcb_is_modifier_key(sym));
+
+ if (xcb_is_modifier_key(sym) || xcb_is_cursor_key(sym))
+ return 1;
- uint32_t font_id = get_font_id(conn, pattern, &font_height);
+ printf("sym = %c (%d)\n", sym, sym);
- /* Open an input window */
- win = open_input_window(conn, 500, font_height + 8);
+ /* convert the keysym to UCS */
+ uint16_t ucs = keysym2ucs(sym);
+ if ((int16_t)ucs == -1) {
+ fprintf(stderr, "Keysym could not be converted to UCS, skipping\n");
+ return 1;
+ }
- /* Create pixmap */
- pixmap = xcb_generate_id(conn);
- pixmap_gc = xcb_generate_id(conn);
- xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font_height + 8);
- xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
+ /* store the UCS into a string */
+ uint8_t inp[3] = {(ucs & 0xFF00) >> 8, (ucs & 0xFF), 0};
- /* Set input focus (we have override_redirect=1, so the wm will not do
- * this for us) */
- xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME);
+ printf("inp[0] = %02x, inp[1] = %02x, inp[2] = %02x\n", inp[0], inp[1], inp[2]);
+ /* convert it to UTF-8 */
+ char *out = convert_ucs_to_utf8((char*)inp);
+ printf("converted to %s\n", out);
- /* Create graphics context */
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
+ glyphs_ucs[input_position] = malloc(3 * sizeof(uint8_t));
+ if (glyphs_ucs[input_position] == NULL)
+ err(EXIT_FAILURE, "malloc() failed\n");
+ memcpy(glyphs_ucs[input_position], inp, 3);
+ glyphs_utf8[input_position] = strdup(out);
+ input_position++;
- /* Grab the keyboard to get all input */
- xcb_flush(conn);
+ if (input_position == limit)
+ finish_input();
- /* Try (repeatedly, if necessary) to grab the keyboard. We might not
- * get the keyboard at the first attempt because of the keybinding
- * still being active when started via a wm’s keybinding. */
- xcb_grab_keyboard_cookie_t cookie;
- xcb_grab_keyboard_reply_t *reply = NULL;
+ handle_expose(NULL, conn, NULL);
+ return 1;
+}
- int count = 0;
- while ((reply == NULL || reply->status != XCB_GRAB_STATUS_SUCCESS) && (count++ < 500)) {
- cookie = xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
- reply = xcb_grab_keyboard_reply(conn, cookie, NULL);
- usleep(1000);
+int main(int argc, char *argv[]) {
+ format = strdup("%s");
+ socket_path = getenv("I3SOCK");
+ char *pattern = sstrdup("-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1");
+ int o, option_index = 0;
+
+ static struct option long_options[] = {
+ {"socket", required_argument, 0, 's'},
+ {"version", no_argument, 0, 'v'},
+ {"limit", required_argument, 0, 'l'},
+ {"prompt", required_argument, 0, 'P'},
+ {"prefix", required_argument, 0, 'p'},
+ {"format", required_argument, 0, 'F'},
+ {"font", required_argument, 0, 'f'},
+ {"help", no_argument, 0, 'h'},
+ {0, 0, 0, 0}
+ };
+
+ char *options_string = "s:p:P:f:l:F:vh";
+
+ while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
+ switch (o) {
+ case 's':
+ FREE(socket_path);
+ socket_path = strdup(optarg);
+ break;
+ case 'v':
+ printf("i3-input " I3_VERSION);
+ return 0;
+ case 'p':
+ /* This option is deprecated, but will still work in i3 v4.1, 4.2 and 4.3 */
+ fprintf(stderr, "i3-input: WARNING: the -p option is DEPRECATED in favor of the -F (format) option\n");
+ FREE(format);
+ sasprintf(&format, "%s%%s", optarg);
+ break;
+ case 'l':
+ limit = atoi(optarg);
+ break;
+ case 'P':
+ FREE(prompt);
+ prompt = strdup(optarg);
+ break;
+ case 'f':
+ FREE(pattern);
+ pattern = strdup(optarg);
+ break;
+ case 'F':
+ FREE(format);
+ format = strdup(optarg);
+ break;
+ case 'h':
+ printf("i3-input " I3_VERSION "\n");
+ printf("i3-input [-s <socket>] [-F <format>] [-l <limit>] [-P <prompt>] [-f <font>] [-v]\n");
+ printf("\n");
+ printf("Example:\n");
+ printf(" i3-input -F 'workspace \"%%s\"' -P 'Switch to workspace: '\n");
+ return 0;
}
-
- if (reply->status != XCB_GRAB_STATUS_SUCCESS) {
- fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status);
- exit(-1);
+ }
+
+ printf("using format \"%s\"\n", format);
+
+ if (socket_path == NULL)
+ socket_path = socket_path_from_x11();
+
+ if (socket_path == NULL)
+ socket_path = "/tmp/i3-ipc.sock";
+
+ sockfd = ipc_connect(socket_path);
+
+ if (prompt != NULL)
+ prompt = convert_utf8_to_ucs2(prompt, &prompt_len);
+
+ int screens;
+ conn = xcb_connect(NULL, &screens);
+ if (!conn || xcb_connection_has_error(conn))
+ die("Cannot open display\n");
+
+ xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
+ root = root_screen->root;
+
+ symbols = xcb_key_symbols_alloc(conn);
+
+ font = load_font(pattern, true);
+
+ /* Open an input window */
+ win = xcb_generate_id(conn);
+ xcb_create_window(
+ conn,
+ XCB_COPY_FROM_PARENT,
+ win, /* the window id */
+ root, /* parent == root */
+ 50, 50, 500, font.height + 8, /* dimensions */
+ 0, /* X11 border = 0, we draw our own */
+ XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
+ XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK,
+ (uint32_t[]){
+ 0, /* back pixel: black */
+ 1, /* override redirect: don’t manage this window */
+ XCB_EVENT_MASK_EXPOSURE
+ });
+
+ /* Map the window (make it visible) */
+ xcb_map_window(conn, win);
+
+ /* Create pixmap */
+ pixmap = xcb_generate_id(conn);
+ pixmap_gc = xcb_generate_id(conn);
+ xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font.height + 8);
+ xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
+
+ /* Set input focus (we have override_redirect=1, so the wm will not do
+ * this for us) */
+ xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME);
+
+ /* Create graphics context */
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ font.id });
+
+ /* Grab the keyboard to get all input */
+ xcb_flush(conn);
+
+ /* Try (repeatedly, if necessary) to grab the keyboard. We might not
+ * get the keyboard at the first attempt because of the keybinding
+ * still being active when started via a wm’s keybinding. */
+ xcb_grab_keyboard_cookie_t cookie;
+ xcb_grab_keyboard_reply_t *reply = NULL;
+
+ int count = 0;
+ while ((reply == NULL || reply->status != XCB_GRAB_STATUS_SUCCESS) && (count++ < 500)) {
+ cookie = xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
+ reply = xcb_grab_keyboard_reply(conn, cookie, NULL);
+ usleep(1000);
+ }
+
+ if (reply->status != XCB_GRAB_STATUS_SUCCESS) {
+ fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status);
+ exit(-1);
+ }
+
+ xcb_flush(conn);
+
+ xcb_generic_event_t *event;
+ while ((event = xcb_wait_for_event(conn)) != NULL) {
+ if (event->response_type == 0) {
+ fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence);
+ continue;
}
- xcb_flush(conn);
-
- xcb_generic_event_t *event;
- while ((event = xcb_wait_for_event(conn)) != NULL) {
- if (event->response_type == 0) {
- fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence);
- continue;
- }
+ /* Strip off the highest bit (set if the event is generated) */
+ int type = (event->response_type & 0x7F);
- /* Strip off the highest bit (set if the event is generated) */
- int type = (event->response_type & 0x7F);
+ switch (type) {
+ case XCB_KEY_PRESS:
+ handle_key_press(NULL, conn, (xcb_key_press_event_t*)event);
+ break;
- switch (type) {
- case XCB_KEY_PRESS:
- handle_key_press(NULL, conn, (xcb_key_press_event_t*)event);
- break;
+ case XCB_KEY_RELEASE:
+ handle_key_release(NULL, conn, (xcb_key_release_event_t*)event);
+ break;
- case XCB_KEY_RELEASE:
- handle_key_release(NULL, conn, (xcb_key_release_event_t*)event);
- break;
-
- case XCB_EXPOSE:
- handle_expose(NULL, conn, (xcb_expose_event_t*)event);
- break;
- }
-
- free(event);
+ case XCB_EXPOSE:
+ handle_expose(NULL, conn, (xcb_expose_event_t*)event);
+ break;
}
- return 0;
+ free(event);
+ }
+
+ return 0;
}
/*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * ucs2_to_utf8.c: Converts between UCS-2 and UTF-8, both of which are used in
+ * different contexts in X11.
*
*/
#include <stdlib.h>
#include <err.h>
#include <iconv.h>
+#include "libi3.h"
+
static iconv_t conversion_descriptor = 0;
static iconv_t conversion_descriptor2 = 0;
*
*/
char *convert_ucs_to_utf8(char *input) {
- size_t input_size = 2;
- /* UTF-8 may consume up to 4 byte */
- int buffer_size = 8;
-
- char *buffer = calloc(buffer_size, 1);
- if (buffer == NULL)
- err(EXIT_FAILURE, "malloc() failed\n");
- size_t output_size = buffer_size;
- /* We need to use an additional pointer, because iconv() modifies it */
- char *output = buffer;
-
- /* We convert the input into UCS-2 big endian */
- if (conversion_descriptor == 0) {
- conversion_descriptor = iconv_open("UTF-8", "UCS-2BE");
- if (conversion_descriptor == 0) {
- fprintf(stderr, "error opening the conversion context\n");
- exit(1);
- }
- }
-
- /* Get the conversion descriptor back to original state */
- iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
-
- /* Convert our text */
- int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
- if (rc == (size_t)-1) {
- perror("Converting to UCS-2 failed");
- return NULL;
- }
-
- return buffer;
+ size_t input_size = 2;
+ /* UTF-8 may consume up to 4 byte */
+ int buffer_size = 8;
+
+ char *buffer = scalloc(buffer_size);
+ size_t output_size = buffer_size;
+ /* We need to use an additional pointer, because iconv() modifies it */
+ char *output = buffer;
+
+ /* We convert the input into UCS-2 big endian */
+ if (conversion_descriptor == 0) {
+ conversion_descriptor = iconv_open("UTF-8", "UCS-2BE");
+ if (conversion_descriptor == 0)
+ errx(EXIT_FAILURE, "Error opening the conversion context");
+ }
+
+ /* Get the conversion descriptor back to original state */
+ iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
+
+ /* Convert our text */
+ int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
+ if (rc == (size_t)-1) {
+ free(buffer);
+ perror("Converting to UCS-2 failed");
+ return NULL;
+ }
+
+ return buffer;
}
/*
*
*/
char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
- size_t input_size = strlen(input) + 1;
- /* UCS-2 consumes exactly two bytes for each glyph */
- int buffer_size = input_size * 2;
-
- char *buffer = malloc(buffer_size);
- if (buffer == NULL)
- err(EXIT_FAILURE, "malloc() failed\n");
- size_t output_size = buffer_size;
- /* We need to use an additional pointer, because iconv() modifies it */
- char *output = buffer;
-
- /* We convert the input into UCS-2 big endian */
- if (conversion_descriptor2 == 0) {
- conversion_descriptor2 = iconv_open("UCS-2BE", "UTF-8");
- if (conversion_descriptor2 == 0) {
- fprintf(stderr, "error opening the conversion context\n");
- exit(1);
- }
- }
-
- /* Get the conversion descriptor back to original state */
- iconv(conversion_descriptor2, NULL, NULL, NULL, NULL);
-
- /* Convert our text */
- int rc = iconv(conversion_descriptor2, (void*)&input, &input_size, &output, &output_size);
- if (rc == (size_t)-1) {
- perror("Converting to UCS-2 failed");
- if (real_strlen != NULL)
- *real_strlen = 0;
- return NULL;
- }
+ size_t input_size = strlen(input) + 1;
+ /* UCS-2 consumes exactly two bytes for each glyph */
+ int buffer_size = input_size * 2;
+
+ char *buffer = smalloc(buffer_size);
+ size_t output_size = buffer_size;
+ /* We need to use an additional pointer, because iconv() modifies it */
+ char *output = buffer;
+
+ /* We convert the input into UCS-2 big endian */
+ if (conversion_descriptor2 == 0) {
+ conversion_descriptor2 = iconv_open("UCS-2BE", "UTF-8");
+ if (conversion_descriptor2 == 0)
+ errx(EXIT_FAILURE, "Error opening the conversion context");
+ }
+ /* Get the conversion descriptor back to original state */
+ iconv(conversion_descriptor2, NULL, NULL, NULL, NULL);
+
+ /* Convert our text */
+ int rc = iconv(conversion_descriptor2, (void*)&input, &input_size, &output, &output_size);
+ if (rc == (size_t)-1) {
+ perror("Converting to UCS-2 failed");
+ free(buffer);
if (real_strlen != NULL)
- *real_strlen = ((buffer_size - output_size) / 2) - 1;
+ *real_strlen = 0;
+ return NULL;
+ }
+
+ if (real_strlen != NULL)
+ *real_strlen = ((buffer_size - output_size) / 2) - 1;
- return buffer;
+ return buffer;
}
+++ /dev/null
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- */
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-
-#include <xcb/xcb.h>
-#include <xcb/xcb_keysyms.h>
-
-#include <X11/keysym.h>
-
-#include "i3-input.h"
-
-/*
- * Convenience-wrapper around xcb_change_gc which saves us declaring a variable
- *
- */
-void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value) {
- xcb_change_gc(conn, gc, mask, &value);
-}
-
-/*
- * Returns the colorpixel to use for the given hex color (think of HTML).
- *
- * The hex_color has to start with #, for example #FF00FF.
- *
- * NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
- * This has to be done by the caller.
- *
- */
-uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
- char strgroups[3][3] = {{hex[1], hex[2], '\0'},
- {hex[3], hex[4], '\0'},
- {hex[5], hex[6], '\0'}};
- uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
- (strtol(strgroups[1], NULL, 16)),
- (strtol(strgroups[2], NULL, 16))};
-
- return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
-}
-
-/*
- * Returns the mask for Mode_switch (to be used for looking up keysymbols by
- * keycode).
- *
- */
-uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode) {
- xcb_key_symbols_t *symbols = xcb_key_symbols_alloc(conn);
-
- xcb_get_modifier_mapping_reply_t *modmap_r;
- xcb_keycode_t *modmap, kc;
- xcb_keycode_t *modeswitchcodes = xcb_key_symbols_get_keycode(symbols, keycode);
- if (modeswitchcodes == NULL)
- return 0;
-
- modmap_r = xcb_get_modifier_mapping_reply(conn, xcb_get_modifier_mapping(conn), NULL);
- modmap = xcb_get_modifier_mapping_keycodes(modmap_r);
-
- for (int i = 0; i < 8; i++)
- for (int j = 0; j < modmap_r->keycodes_per_modifier; j++) {
- kc = modmap[i * modmap_r->keycodes_per_modifier + j];
- for (xcb_keycode_t *ktest = modeswitchcodes; *ktest; ktest++) {
- if (*ktest != kc)
- continue;
-
- free(modeswitchcodes);
- free(modmap_r);
- return (1 << i);
- }
- }
-
- return 0;
-}
-
-/*
- * Opens the window we use for input/output and maps it
- *
- */
-xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height) {
- xcb_window_t win = xcb_generate_id(conn);
- //xcb_cursor_t cursor_id = xcb_generate_id(conn);
-
-#if 0
- /* Use the default cursor (left pointer) */
- if (cursor > -1) {
- i3Font *cursor_font = load_font(conn, "cursor");
- xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id,
- XCB_CURSOR_LEFT_PTR, XCB_CURSOR_LEFT_PTR + 1,
- 0, 0, 0, 65535, 65535, 65535);
- }
-#endif
-
- uint32_t mask = 0;
- uint32_t values[3];
-
- mask |= XCB_CW_BACK_PIXEL;
- values[0] = 0;
-
- mask |= XCB_CW_OVERRIDE_REDIRECT;
- values[1] = 1;
-
- mask |= XCB_CW_EVENT_MASK;
- values[2] = XCB_EVENT_MASK_EXPOSURE;
-
- xcb_create_window(conn,
- XCB_COPY_FROM_PARENT,
- win, /* the window id */
- root, /* parent == root */
- 50, 50, width, height, /* dimensions */
- 0, /* border = 0, we draw our own */
- XCB_WINDOW_CLASS_INPUT_OUTPUT,
- XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
- mask,
- values);
-
-#if 0
- if (cursor > -1)
- xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
-#endif
-
- /* Map the window (= make it visible) */
- xcb_map_window(conn, win);
-
- return win;
-}
-
-/*
- * Returns the ID of the font matching the given pattern and stores the height
- * of the font (in pixels) in *font_height. die()s if no font matches.
- *
- */
-int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height) {
- xcb_void_cookie_t font_cookie;
- xcb_list_fonts_with_info_cookie_t info_cookie;
-
- /* Send all our requests first */
- int result;
- result = xcb_generate_id(conn);
- font_cookie = xcb_open_font_checked(conn, result, strlen(pattern), pattern);
- info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
-
- xcb_generic_error_t *error = xcb_request_check(conn, font_cookie);
- if (error != NULL) {
- fprintf(stderr, "ERROR: Could not open font: %d\n", error->error_code);
- exit(1);
- }
-
- /* Get information (height/name) for this font */
- xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL);
- if (reply == NULL)
- die("Could not load font \"%s\"\n", pattern);
-
- *font_height = reply->font_ascent + reply->font_descent;
-
- return result;
-}
# add an i3bar invocation automatically if no 'workspace_bar no' was found
if ($workspace_bar) {
print "\n";
- print "# XXX: Automatically added a call to i3bar to provide a workspace bar\n";
- print "exec i3status | i3bar -d\n";
+ print "# XXX: Automatically added a bar configuration\n";
+ print "bar {\n";
+ print " status_command i3status\n";
+ print "}\n";
}
# Depend on the specific file (.c for each .o) and on all headers
%.o: %.c ${HEADERS}
- echo "CC $<"
+ echo "[i3-msg] CC $<"
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
all: i3-msg
i3-msg: ${FILES}
- echo "LINK i3-msg"
+ echo "[i3-msg] LINK i3-msg"
$(CC) $(LDFLAGS) -o i3-msg ${FILES} $(LIBS)
install: all
- echo "INSTALL"
+ echo "[i3-msg] INSTALL"
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
$(INSTALL) -m 0755 i3-msg $(DESTDIR)$(PREFIX)/bin/
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
- *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
*
* i3-msg/main.c: Utility which sends messages to a running i3-instance using
* IPC via UNIX domain sockets.
*
- * This serves as an example for how to send your own messages to i3.
+ * This (in combination with libi3/ipc_send_message.c and
+ * libi3/ipc_recv_message.c) serves as an example for how to send your own
+ * messages to i3.
+ *
* Additionally, it’s even useful sometimes :-).
*
*/
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
+#include "libi3.h"
#include <i3/ipc.h>
static char *socket_path;
-/*
- * Try to get the socket path from X11 and return NULL if it doesn’t work.
- * As i3-msg is a short-running tool, we don’t bother with cleaning up the
- * connection and leave it up to the operating system on exit.
- *
- */
-static char *socket_path_from_x11() {
- xcb_connection_t *conn;
- int screen;
- if ((conn = xcb_connect(NULL, &screen)) == NULL ||
- xcb_connection_has_error(conn))
- return NULL;
- xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen);
- xcb_window_t root = root_screen->root;
-
- xcb_intern_atom_cookie_t atom_cookie;
- xcb_intern_atom_reply_t *atom_reply;
-
- atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH");
- atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL);
- if (atom_reply == NULL)
- return NULL;
-
- xcb_get_property_cookie_t prop_cookie;
- xcb_get_property_reply_t *prop_reply;
- prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom,
- XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX);
- prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL);
- if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0)
- return NULL;
- if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply),
- (char*)xcb_get_property_value(prop_reply)) == -1)
- return NULL;
- return socket_path;
-}
-
-/*
- * Formats a message (payload) of the given size and type and sends it to i3 via
- * the given socket file descriptor.
- *
- */
-static void ipc_send_message(int sockfd, uint32_t message_size,
- uint32_t message_type, uint8_t *payload) {
- int buffer_size = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t) + message_size;
- char msg[buffer_size];
- char *walk = msg;
-
- strcpy(walk, I3_IPC_MAGIC);
- walk += strlen(I3_IPC_MAGIC);
- memcpy(walk, &message_size, sizeof(uint32_t));
- walk += sizeof(uint32_t);
- memcpy(walk, &message_type, sizeof(uint32_t));
- walk += sizeof(uint32_t);
- memcpy(walk, payload, message_size);
-
- int sent_bytes = 0;
- int bytes_to_go = buffer_size;
- while (sent_bytes < bytes_to_go) {
- int n = write(sockfd, msg + sent_bytes, bytes_to_go);
- if (n == -1)
- err(EXIT_FAILURE, "write() failed");
-
- sent_bytes += n;
- bytes_to_go -= n;
- }
-}
-
-static void ipc_recv_message(int sockfd, uint32_t message_type,
- uint32_t *reply_length, uint8_t **reply) {
- /* Read the message header first */
- uint32_t to_read = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t);
- char msg[to_read];
- char *walk = msg;
-
- uint32_t read_bytes = 0;
- while (read_bytes < to_read) {
- int n = read(sockfd, msg + read_bytes, to_read);
- if (n == -1)
- err(EXIT_FAILURE, "read() failed");
- if (n == 0)
- errx(EXIT_FAILURE, "received EOF instead of reply");
-
- read_bytes += n;
- to_read -= n;
- }
-
- if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0)
- errx(EXIT_FAILURE, "invalid magic in reply");
-
- walk += strlen(I3_IPC_MAGIC);
- *reply_length = *((uint32_t*)walk);
- walk += sizeof(uint32_t);
- if (*((uint32_t*)walk) != message_type)
- errx(EXIT_FAILURE, "unexpected reply type (got %d, expected %d)", *((uint32_t*)walk), message_type);
- walk += sizeof(uint32_t);
-
- *reply = malloc(*reply_length);
- if ((*reply) == NULL)
- err(EXIT_FAILURE, "malloc() failed");
-
- to_read = *reply_length;
- read_bytes = 0;
- while (read_bytes < to_read) {
- int n = read(sockfd, *reply + read_bytes, to_read);
- if (n == -1)
- err(EXIT_FAILURE, "read() failed");
-
- read_bytes += n;
- to_read -= n;
- }
-}
-
int main(int argc, char *argv[]) {
socket_path = getenv("I3SOCK");
int o, option_index = 0;
if (o == 's') {
if (socket_path != NULL)
free(socket_path);
- socket_path = strdup(optarg);
+ socket_path = sstrdup(optarg);
} else if (o == 't') {
if (strcasecmp(optarg, "command") == 0)
message_type = I3_IPC_MESSAGE_TYPE_COMMAND;
message_type = I3_IPC_MESSAGE_TYPE_GET_OUTPUTS;
else if (strcasecmp(optarg, "get_tree") == 0)
message_type = I3_IPC_MESSAGE_TYPE_GET_TREE;
+ else if (strcasecmp(optarg, "get_marks") == 0)
+ message_type = I3_IPC_MESSAGE_TYPE_GET_MARKS;
+ else if (strcasecmp(optarg, "get_bar_config") == 0)
+ message_type = I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG;
else {
printf("Unknown message type\n");
- printf("Known types: command, get_workspaces, get_outputs, get_tree\n");
+ printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config\n");
exit(EXIT_FAILURE);
}
} else if (o == 'q') {
/* Fall back to the default socket path */
if (socket_path == NULL)
- socket_path = strdup("/tmp/i3-ipc.sock");
+ socket_path = sstrdup("/tmp/i3-ipc.sock");
/* Use all arguments, separated by whitespace, as payload.
* This way, you don’t have to do i3-msg 'mark foo', you can use
* i3-msg mark foo */
while (optind < argc) {
if (!payload) {
- if (!(payload = strdup(argv[optind])))
- err(EXIT_FAILURE, "strdup(argv[optind])");
+ payload = sstrdup(argv[optind]);
} else {
char *both;
if (asprintf(&both, "%s %s", payload, argv[optind]) == -1)
if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
err(EXIT_FAILURE, "Could not connect to i3");
- ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t*)payload);
+ if (ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t*)payload) == -1)
+ err(EXIT_FAILURE, "IPC: write()");
if (quiet)
return 0;
uint32_t reply_length;
uint8_t *reply;
- ipc_recv_message(sockfd, message_type, &reply_length, &reply);
- printf("%.*s", reply_length, reply);
+ int ret;
+ if ((ret = ipc_recv_message(sockfd, message_type, &reply_length, &reply)) != 0) {
+ if (ret == -1)
+ err(EXIT_FAILURE, "IPC: read()");
+ exit(1);
+ }
+ printf("%.*s\n", reply_length, reply);
free(reply);
close(sockfd);
include $(TOPDIR)/common.mk
+CPPFLAGS += -I$(TOPDIR)/include
+
# Depend on the object files of all source-files in src/*.c and on all header files
FILES=$(patsubst %.c,%.o,$(wildcard *.c))
HEADERS=$(wildcard *.h)
# Depend on the specific file (.c for each .o) and on all headers
%.o: %.c ${HEADERS}
- echo "CC $<"
+ echo "[i3-nagbar] CC $<"
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
all: i3-nagbar
-i3-nagbar: ${FILES}
- echo "LINK i3-nagbar"
- $(CC) $(LDFLAGS) -o $@ ${FILES} $(LIBS)
+i3-nagbar: $(TOPDIR)/libi3/libi3.a ${FILES}
+ echo "[i3-nagbar] LINK i3-nagbar"
+ $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS)
+
+$(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c
+ $(MAKE) -C $(TOPDIR)/libi3
install: all
- echo "INSTALL"
+ echo "[i3-nagbar] INSTALL"
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
$(INSTALL) -m 0755 i3-nagbar $(DESTDIR)$(PREFIX)/bin/
extern xcb_window_t root;
-uint32_t get_colorpixel(xcb_connection_t *conn, char *hex);
-xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height);
-int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height);
-void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value);
-
#endif
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009-2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * i3-nagbar is a utility which displays a nag message.
+ * i3-nagbar is a utility which displays a nag message, for example in the case
+ * when the user has an error in his configuration file.
*
*/
#include <ev.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_event.h>
+#include "libi3.h"
#include "i3-nagbar.h"
typedef struct {
static xcb_pixmap_t pixmap;
static xcb_gcontext_t pixmap_gc;
static xcb_rectangle_t rect = { 0, 0, 600, 20 };
-static int font_height;
-static char *prompt = "Please do not run this program.";
+static i3Font font;
+static char *prompt;
static button_t *buttons;
static int buttoncnt;
+
+/* Result of get_colorpixel() for the various colors. */
+static uint32_t color_background; /* background of the bar */
+static uint32_t color_button_background; /* background for buttons */
+static uint32_t color_border; /* color of the button border */
+static uint32_t color_border_bottom; /* color of the bottom border */
+static uint32_t color_text; /* color of the text */
+
xcb_window_t root;
+xcb_connection_t *conn;
/*
* Starts the given application by passing it through a shell. We use double fork
*
*/
static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
- printf("expose!\n");
-
/* re-draw the background */
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#900000"));
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ color_background });
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect);
/* restore font color */
uint32_t values[3];
- values[0] = get_colorpixel(conn, "#FFFFFF");
- values[1] = get_colorpixel(conn, "#900000");
+ values[0] = color_text;
+ values[1] = color_background;
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values);
xcb_image_text_8(conn, strlen(prompt), pixmap, pixmap_gc, 4 + 4/* X */,
- font_height + 2 + 4 /* Y = baseline of font */, prompt);
+ font.height + 2 + 4 /* Y = baseline of font */, prompt);
/* render close button */
int line_width = 4;
int w = 20;
int y = rect.width;
- values[0] = get_colorpixel(conn, "#680a0a");
+ values[0] = color_button_background;
values[1] = line_width;
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
xcb_rectangle_t close = { y - w - (2 * line_width), 0, w + (2 * line_width), rect.height };
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close);
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#d92424"));
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ color_border });
xcb_point_t points[] = {
{ y - w - (2 * line_width), line_width / 2 },
{ y - (line_width / 2), line_width / 2 },
};
xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points);
- values[0] = get_colorpixel(conn, "#ffffff");
- values[1] = get_colorpixel(conn, "#680a0a");
+ values[0] = color_text;
+ values[1] = color_button_background;
values[2] = 1;
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_LINE_WIDTH, values);
xcb_image_text_8(conn, strlen("x"), pixmap, pixmap_gc, y - w - line_width + (w / 2) - 4/* X */,
- font_height + 2 + 4 - 1/* Y = baseline of font */, "X");
+ font.height + 2 + 4 - 1/* Y = baseline of font */, "X");
y -= w;
y -= 20;
line_width = 1;
for (int c = 0; c < buttoncnt; c++) {
/* TODO: make w = text extents of the label */
- w = 90;
+ w = 100;
y -= 30;
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#680a0a"));
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ color_button_background });
close = (xcb_rectangle_t){ y - w - (2 * line_width), 2, w + (2 * line_width), rect.height - 6 };
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close);
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#d92424"));
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ color_border });
buttons[c].x = y - w - (2 * line_width);
buttons[c].width = w;
xcb_point_t points2[] = {
};
xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points2);
- values[0] = get_colorpixel(conn, "#ffffff");
- values[1] = get_colorpixel(conn, "#680a0a");
+ values[0] = color_text;
+ values[1] = color_button_background;
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values);
xcb_image_text_8(conn, strlen(buttons[c].label), pixmap, pixmap_gc, y - w - line_width + 6/* X */,
- font_height + 2 + 3/* Y = baseline of font */, buttons[c].label);
+ font.height + 2 + 3/* Y = baseline of font */, buttons[c].label);
y -= w;
}
/* border line at the bottom */
line_width = 2;
- values[0] = get_colorpixel(conn, "#470909");
+ values[0] = color_border_bottom;
values[1] = line_width;
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
xcb_point_t bottom[] = {
}
int main(int argc, char *argv[]) {
- char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
+ char *pattern = strdup("-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1");
int o, option_index = 0;
+ enum { TYPE_ERROR = 0, TYPE_WARNING = 1 } bar_type = TYPE_ERROR;
static struct option long_options[] = {
{"version", no_argument, 0, 'v'},
{"button", required_argument, 0, 'b'},
{"help", no_argument, 0, 'h'},
{"message", no_argument, 0, 'm'},
+ {"type", required_argument, 0, 't'},
{0, 0, 0, 0}
};
- char *options_string = "b:f:m:vh";
+ char *options_string = "b:f:m:t:vh";
+
+ prompt = strdup("Please do not run this program.");
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
switch (o) {
pattern = strdup(optarg);
break;
case 'm':
+ FREE(prompt);
prompt = strdup(optarg);
break;
+ case 't':
+ bar_type = (strcasecmp(optarg, "warning") == 0 ? TYPE_WARNING : TYPE_ERROR);
+ break;
case 'h':
printf("i3-nagbar " I3_VERSION "\n");
printf("i3-nagbar [-m <message>] [-b <button> <action>] [-f <font>] [-v]\n");
}
int screens;
- xcb_connection_t *conn;
if ((conn = xcb_connect(NULL, &screens)) == NULL ||
xcb_connection_has_error(conn))
die("Cannot open display\n");
xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
root = root_screen->root;
- uint32_t font_id = get_font_id(conn, pattern, &font_height);
+ if (bar_type == TYPE_ERROR) {
+ /* Red theme for error messages */
+ color_button_background = get_colorpixel("#680a0a");
+ color_background = get_colorpixel("#900000");
+ color_text = get_colorpixel("#ffffff");
+ color_border = get_colorpixel("#d92424");
+ color_border_bottom = get_colorpixel("#470909");
+ } else {
+ /* Yellowish theme for warnings */
+ color_button_background = get_colorpixel("#ffc100");
+ color_background = get_colorpixel("#ffa8000");
+ color_text = get_colorpixel("#000000");
+ color_border = get_colorpixel("#ab7100");
+ color_border_bottom = get_colorpixel("#ab7100");
+ }
+
+ font = load_font(pattern, true);
/* Open an input window */
- win = open_input_window(conn, 500, font_height + 8 + 8 /* 8px padding */);
+ win = xcb_generate_id(conn);
+
+ xcb_create_window(
+ conn,
+ XCB_COPY_FROM_PARENT,
+ win, /* the window id */
+ root, /* parent == root */
+ 50, 50, 500, font.height + 8 + 8 /* 8 px padding */, /* dimensions */
+ 0, /* x11 border = 0, we draw our own */
+ XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
+ XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
+ (uint32_t[]){
+ 0, /* back pixel: black */
+ XCB_EVENT_MASK_EXPOSURE |
+ XCB_EVENT_MASK_STRUCTURE_NOTIFY |
+ XCB_EVENT_MASK_BUTTON_PRESS |
+ XCB_EVENT_MASK_BUTTON_RELEASE
+ });
+
+ /* Map the window (make it visible) */
+ xcb_map_window(conn, win);
/* Setup NetWM atoms */
#define xmacro(name) \
uint32_t bottom_end_x;
} __attribute__((__packed__)) strut_partial = {0,};
- strut_partial.top = font_height + 6;
+ strut_partial.top = font.height + 6;
strut_partial.top_start_x = 0;
strut_partial.top_end_x = 800;
/* Create pixmap */
pixmap = xcb_generate_id(conn);
pixmap_gc = xcb_generate_id(conn);
- xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font_height + 8);
+ xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font.height + 8);
xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
/* Create graphics context */
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ font.id });
/* Grab the keyboard to get all input */
xcb_flush(conn);
xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
/* Create graphics context */
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ font.id });
break;
}
}
+++ /dev/null
-/*
- * vim:ts=8:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- *
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- */
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-
-#include <xcb/xcb.h>
-#include <xcb/xcb_keysyms.h>
-
-#include <X11/keysym.h>
-
-#include "i3-nagbar.h"
-
-/*
- * Convenience-wrapper around xcb_change_gc which saves us declaring a variable
- *
- */
-void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value) {
- xcb_change_gc(conn, gc, mask, &value);
-}
-
-/*
- * Returns the colorpixel to use for the given hex color (think of HTML).
- *
- * The hex_color has to start with #, for example #FF00FF.
- *
- * NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
- * This has to be done by the caller.
- *
- */
-uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
- char strgroups[3][3] = {{hex[1], hex[2], '\0'},
- {hex[3], hex[4], '\0'},
- {hex[5], hex[6], '\0'}};
- uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
- (strtol(strgroups[1], NULL, 16)),
- (strtol(strgroups[2], NULL, 16))};
-
- return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
-}
-
-/*
- * Opens the window we use for input/output and maps it
- *
- */
-xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height) {
- xcb_window_t win = xcb_generate_id(conn);
- //xcb_cursor_t cursor_id = xcb_generate_id(conn);
-
-#if 0
- /* Use the default cursor (left pointer) */
- if (cursor > -1) {
- i3Font *cursor_font = load_font(conn, "cursor");
- xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id,
- XCB_CURSOR_LEFT_PTR, XCB_CURSOR_LEFT_PTR + 1,
- 0, 0, 0, 65535, 65535, 65535);
- }
-#endif
-
- uint32_t mask = 0;
- uint32_t values[3];
-
- mask |= XCB_CW_BACK_PIXEL;
- values[0] = 0;
-
- mask |= XCB_CW_EVENT_MASK;
- values[1] = XCB_EVENT_MASK_EXPOSURE |
- XCB_EVENT_MASK_STRUCTURE_NOTIFY |
- XCB_EVENT_MASK_BUTTON_PRESS |
- XCB_EVENT_MASK_BUTTON_RELEASE;
-
- xcb_create_window(conn,
- XCB_COPY_FROM_PARENT,
- win, /* the window id */
- root, /* parent == root */
- 50, 50, width, height, /* dimensions */
- 0, /* border = 0, we draw our own */
- XCB_WINDOW_CLASS_INPUT_OUTPUT,
- XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
- mask,
- values);
-
-#if 0
- if (cursor > -1)
- xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
-#endif
-
- /* Map the window (= make it visible) */
- xcb_map_window(conn, win);
-
- return win;
-}
-
-/*
- * Returns the ID of the font matching the given pattern and stores the height
- * of the font (in pixels) in *font_height. die()s if no font matches.
- *
- */
-int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height) {
- xcb_void_cookie_t font_cookie;
- xcb_list_fonts_with_info_cookie_t info_cookie;
-
- /* Send all our requests first */
- int result;
- result = xcb_generate_id(conn);
- font_cookie = xcb_open_font_checked(conn, result, strlen(pattern), pattern);
- info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
-
- xcb_generic_error_t *error = xcb_request_check(conn, font_cookie);
- if (error != NULL) {
- fprintf(stderr, "ERROR: Could not open font: %d\n", error->error_code);
- exit(1);
- }
-
- /* Get information (height/name) for this font */
- xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL);
- if (reply == NULL)
- die("Could not load font \"%s\"\n", pattern);
-
- *font_height = reply->font_ascent + reply->font_descent;
-
- return result;
-}
--- /dev/null
+#!/bin/sh
+# This script tries to exec an editor by trying some known editors if $EDITOR is
+# not set.
+#
+# Distributions/packagers can enhance this script with a
+# distribution-specific mechanism to find the preferred pager.
+which $VISUAL >/dev/null && exec $VISUAL "$@"
+which $EDITOR >/dev/null && exec $EDITOR "$@"
+
+# Hopefully one of these is installed (no flamewars about preference please!):
+which nano >/dev/null && exec nano "$@"
+which vim >/dev/null && exec vim "$@"
+which vi >/dev/null && exec vi "$@"
+which emacs >/dev/null && exec emacs "$@"
--- /dev/null
+#!/bin/sh
+# This script tries to exec a pager by trying some known pagers if $PAGER is
+# not set.
+#
+# Distributions/packagers can enhance this script with a
+# distribution-specific mechanism to find the preferred pager.
+which $PAGER >/dev/null && exec $PAGER "$@"
+
+# Hopefully one of these is installed:
+which most >/dev/null && exec most "$@"
+which less >/dev/null && exec less "$@"
+# we don't use 'more' because it will exit if the file is 'too short'
+
+# If no pager is installed, try an editor
+exec i3-sensible-editor "$@"
--- /dev/null
+#!/bin/sh
+# This script tries to exec a terminal emulator by trying some known terminal
+# emulators.
+#
+# Distributions/packagers should enhance this script with a
+# distribution-specific mechanism to find the preferred terminal emulator. On
+# Debian, there is the x-terminal-emulator symlink for example.
+# Please don't touch the first line, though:
+which $TERMINAL >/dev/null && exec $TERMINAL "$@"
+
+# Hopefully one of these is installed:
+which xterm >/dev/null && exec xterm "$@"
+which urxvt >/dev/null && exec urxvt "$@"
+which rxvt >/dev/null && exec rxvt "$@"
+which roxterm >/dev/null && exec roxterm "$@"
floating_modifier Mod1
# start a terminal
-bindsym Mod1+Return exec urxvt
+bindsym Mod1+Return exec i3-sensible-terminal
# kill focused window
bindsym Mod1+Shift+q kill
# Start i3bar to display a workspace bar (plus the system information i3status
# finds out, if available)
-exec i3status | i3bar -d
+bar {
+ status_command i3status
+}
#######################################################################
# automatically start i3-config-wizard to offer the user to create a
floating_modifier $mod
# start a terminal
-bindcode $mod+36 exec urxvt
+bindcode $mod+36 exec i3-sensible-terminal
# kill focused window
bindcode $mod+Shift+24 kill
# Start i3bar to display a workspace bar (plus the system information i3status
# finds out, if available)
-exec i3status | i3bar -d
+bar {
+ status_command i3status
+}
all: i3bar doc
-i3bar: ${FILES}
- echo "LINK"
- $(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+i3bar: $(TOPDIR)/libi3/libi3.a ${FILES}
+ echo "[i3bar] LINK"
+ $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS)
+
+$(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c
+ $(MAKE) -C $(TOPDIR)/libi3
doc:
echo ""
- echo "SUBDIR doc"
+ echo "[i3bar] SUBDIR doc"
$(MAKE) -C doc
src/%.o: src/%.c ${HEADERS}
- echo "CC $<"
+ echo "[i3bar] CC $<"
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
install: all
- echo "INSTALL"
+ echo "[i3bar] INSTALL"
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
$(INSTALL) -m 0755 i3bar $(DESTDIR)$(PREFIX)/bin
clean:
rm -f src/*.o
- make -C doc clean
+ $(MAKE) -C doc clean
distclean: clean
rm -f i3bar
- make -C doc distclean
+ $(MAKE) -C doc distclean
.PHONY: install clean distclean doc
i3bar(1)
========
Axel Wagner <mail+i3bar@merovius.de>
-v0.7, July 2011
+v4.1, October 2011
== NAME
== SYNOPSIS
-*i3bar* [*-s* 'sock_path'] [*-c* 'command'] [*-m*|*-d*['pos']] [*-f* 'font'] [*-V*] [*-h*]
+*i3bar* [*-s* 'sock_path'] [*-b* 'bar_id'] [*-v*] [*-h*]
-== OPTIONS
+== WARNING
-*-s, --socket* 'sock_path'::
-Specifies the 'socketpath', via which *i3bar* connects to *i3*(1). If *i3bar* can not connect to *i3*, it will exit. Defaults to '/tmp/i3-ipc.sock'
+i3bar will automatically be invoked by i3 for every 'bar' configuration block.
-*-c, --command* 'command'::
-Execute '<command>' to get 'stdin'. You can also simply pipe into 'stdin', but starting the coomand for itself, *i3bar* is able to send 'SIGCONT' and 'SIGSTOP', when combined with *-m*
+Starting it manually is usually not what you want to do.
-*-m, --hide*::
-Hide the bar, when 'mod4' is not pressed. With this, dockmode will not be set, and the bar is out of the way most of the time so you have more room.
-If *-c* is specified, the childprocess is sent a 'SIGSTOP' on hiding and a 'SIGCONT' on unhiding of the bars.
-This is the default behavior of i3bar.
+You have been warned!
-*-d*['pos']*, --dock*[*=*'pos']::
-Put i3bar in dockmode. This will reserve some space for it, so it does not overlap other clients.
-You can specify either *bottom* (default) or *top* as 'pos'.
+== OPTIONS
+
+*-s, --socket* 'sock_path'::
+Overwrites the path to the i3 IPC socket.
-*-f, --font* 'font'::
-Specifies a 'X-core-font' to use. You can choose one with *xfontsel*(1). Defaults to '+++-misc-fixed-medium-r-semicondensed--12-110-75-75-c-60-iso10646-1+++'.
+*-b, --bar_id* 'bar_id'::
+Specifies the bar ID for which to get the configuration from i3.
-*-V, --verbose*::
-Be (very) verbose with the debug-output. If not set, only errors are reported to 'stderr'
+*-v, --version*::
+Display version number and exit.
*-h, --help*::
Display a short help-message and exit
== DESCRIPTION
-*i3bar* is an xcb- and libev-based status- and ws-bar. It is best thought of as an replacement for the *i3-wsbar*(1) + *dzen2*(1)-combination. It creates a workspace-bar for every active output ("screen") and displays a piped in statusline rightaligned on every bar.
-
-It does not sample any status-information itself, so you still need a program like *i3status*(1) or *conky*(1) for that.
-
-i3bar does not support any color or other markups, so stdin should be plain utf8, one line at a time. If you use *i3status*(1), you therefore should specify 'output_format = none' in the general section of its config file.
-
-Also, you should disable the internal workspace bar of *i3*(1), when using *i3bar* by specifying 'workspace_bar no' in your *i3*-configfile.
-
-== COLORS
+*i3bar* displays a bar at the bottom (or top) of your monitor(s) containing
+workspace switching buttons and a statusline generated by i3status(1) or
+similar. It is automatically invoked (and configured through) i3.
-*i3bar* does not yet support formatting in the displayed statusline. However it does support setting colors for the bar, the workspace-buttons and the statusline.
-
-For now this happens with the following command-line-options:
-
-*--color-bar-fg, --color-bar-bg, --color-active-ws-fg, --color-active-ws-bg, --color-inactive-ws-fg, --color-inactive-ws-bg, --color-urgent-ws-bg, --color-urgent-ws-fg, --color-focus-ws-fg, --color-focus-ws-bg*
-
-For each specified option you need to give a HEX-colorcode.
-
-Be advised that this command-line-options are only temporary and are very likely to be removed, when we finally have a config-file.
+i3bar does not support any color or other markups, so stdin should be plain
+utf8, one line at a time. If you use *i3status*(1), you therefore should
+specify 'output_format = none' in the general section of its config file.
== ENVIRONMENT
=== I3SOCK
-If no ipc-socket is specified on the commandline, this variable is used
-to determine the path, at wich the unix domain socket is expected, on which
-to connect to i3.
+Used as a fallback for the i3 IPC socket path if neither the commandline
+contains an argument nor the I3_SOCKET_PATH property is set on the X11 root
+window.
== EXAMPLES
-To get a docked bar with some statusinformation, you use
-
-*i3status | i3bar --dock*
+Nothing to see here, move along. As stated above, you should not run i3bar manually.
-If you rather have it displayed at the top of the screen, you use
-
-*i3status | i3bar --dock=top*
-
-If you want it to hide when not needed, you should instead simply use
-
-*i3bar -c i3status*
+Instead, see the i3 documentation, especially the User’s Guide.
== SEE ALSO
-+i3(1)+, +i3-wsbar(1)+, +dzen2(1)+, +i3status(1)+
++i3status(1)+ or +conky(1)+ for programs generating a statusline.
+
++dzen2(1)+ or +xmobar(1)+ for similar programs to i3bar.
== AUTHORS
/*
- * i3bar - an xcb-based status- and ws-bar for i3
+ * vim:ts=4:sw=4:expandtab
*
- * © 2010-2011 Axel Wagner and contributors
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
*
- * See file LICNSE for license information
+ * child.c: Getting Input for the statusline
*
*/
#ifndef CHILD_H_
/*
- * i3bar - an xcb-based status- and ws-bar for i3
- *
- * © 2010-2011 Axel Wagner and contributors
+ * vim:ts=4:sw=4:expandtab
*
- * See file LICNSE for license information
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
*
*/
#ifndef COMMON_H_
#define COMMON_H_
+#include <stdbool.h>
+
typedef struct rect_t rect;
-typedef int bool;
struct ev_loop* main_loop;
char *statusline;
char *statusline_buffer;
struct rect_t {
- int x;
- int y;
- int w;
- int h;
+ int x;
+ int y;
+ int w;
+ int h;
};
#include "queue.h"
#include "outputs.h"
#include "util.h"
#include "workspaces.h"
+#include "trayclients.h"
#include "xcb.h"
#include "ucs2_to_utf8.h"
#include "config.h"
+#include "libi3.h"
#endif
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
+ *
+ * config.c: Parses the configuration (received from i3).
+ *
+ */
#ifndef CONFIG_H_
#define CONFIG_H_
#include "common.h"
typedef enum {
- DOCKPOS_NONE = 0,
- DOCKPOS_TOP,
- DOCKPOS_BOT
-} dockpos_t;
+ POS_NONE = 0,
+ POS_TOP,
+ POS_BOT
+} position_t;
typedef struct config_t {
int hide_on_modifier;
- dockpos_t dockpos;
+ position_t position;
int verbose;
- xcb_colors_t *colors;
+ struct xcb_color_strings_t colors;
int disable_ws;
+ char *bar_id;
+ char *command;
+ char *fontname;
+ char *tray_output;
+ int num_outputs;
+ char **outputs;
} config_t;
config_t config;
+/**
+ * Start parsing the received bar configuration json-string
+ *
+ */
+void parse_config_json(char *json);
+
+/**
+ * free()s the color strings as soon as they are not needed anymore.
+ *
+ */
+void free_colors(struct xcb_color_strings_t *colors);
+
#endif
/*
- * i3bar - an xcb-based status- and ws-bar for i3
+ * vim:ts=4:sw=4:expandtab
*
- * © 2010-2011 Axel Wagner and contributors
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
*
- * See file LICNSE for license information
+ * ipc.c: Communicating with i3
*
*/
#ifndef IPC_H_
/*
- * i3bar - an xcb-based status- and ws-bar for i3
+ * vim:ts=4:sw=4:expandtab
*
- * © 2010-2011 Axel Wagner and contributors
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
*
- * See file LICNSE for license information
+ * outputs.c: Maintaining the output-list
*
*/
#ifndef OUTPUTS_H_
* Start parsing the received json-string
*
*/
-void parse_outputs_json(char* json);
+void parse_outputs_json(char* json);
/*
* Initiate the output-list
*
*/
-void init_outputs();
+void init_outputs();
/*
* Returns the output with the given name
*
*/
-i3_output* get_output_by_name(char* name);
+i3_output* get_output_by_name(char* name);
struct i3_output {
- char* name; /* Name of the output */
- bool active; /* If the output is active */
- int ws; /* The number of the currently visible ws */
- rect rect; /* The rect (relative to the root-win) */
+ char* name; /* Name of the output */
+ bool active; /* If the output is active */
+ int ws; /* The number of the currently visible ws */
+ rect rect; /* The rect (relative to the root-win) */
- xcb_window_t bar; /* The id of the bar of the output */
- xcb_pixmap_t buffer; /* An extra pixmap for double-buffering */
- xcb_gcontext_t bargc; /* The graphical context of the bar */
+ xcb_window_t bar; /* The id of the bar of the output */
+ xcb_pixmap_t buffer; /* An extra pixmap for double-buffering */
+ xcb_gcontext_t bargc; /* The graphical context of the bar */
- struct ws_head *workspaces; /* The workspaces on this output */
+ struct ws_head *workspaces; /* The workspaces on this output */
+ struct tc_head *trayclients; /* The tray clients on this output */
- SLIST_ENTRY(i3_output) slist; /* Pointer for the SLIST-Macro */
+ SLIST_ENTRY(i3_output) slist; /* Pointer for the SLIST-Macro */
};
#endif
+++ /dev/null
-/* $OpenBSD: queue.h,v 1.1 2007/10/26 03:14:08 niallo Exp $ */
-/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */
-
-/*
- * Copyright (c) 1991, 1993
- * The Regents of the University of California. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the University nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- *
- * @(#)queue.h 8.5 (Berkeley) 8/20/94
- */
-
-#ifndef _SYS_QUEUE_H_
-#define _SYS_QUEUE_H_
-
-/*
- * This file defines five types of data structures: singly-linked lists,
- * lists, simple queues, tail queues, and circular queues.
- *
- *
- * A singly-linked list is headed by a single forward pointer. The elements
- * are singly linked for minimum space and pointer manipulation overhead at
- * the expense of O(n) removal for arbitrary elements. New elements can be
- * added to the list after an existing element or at the head of the list.
- * Elements being removed from the head of the list should use the explicit
- * macro for this purpose for optimum efficiency. A singly-linked list may
- * only be traversed in the forward direction. Singly-linked lists are ideal
- * for applications with large datasets and few or no removals or for
- * implementing a LIFO queue.
- *
- * A list is headed by a single forward pointer (or an array of forward
- * pointers for a hash table header). The elements are doubly linked
- * so that an arbitrary element can be removed without a need to
- * traverse the list. New elements can be added to the list before
- * or after an existing element or at the head of the list. A list
- * may only be traversed in the forward direction.
- *
- * A simple queue is headed by a pair of pointers, one the head of the
- * list and the other to the tail of the list. The elements are singly
- * linked to save space, so elements can only be removed from the
- * head of the list. New elements can be added to the list before or after
- * an existing element, at the head of the list, or at the end of the
- * list. A simple queue may only be traversed in the forward direction.
- *
- * A tail queue is headed by a pair of pointers, one to the head of the
- * list and the other to the tail of the list. The elements are doubly
- * linked so that an arbitrary element can be removed without a need to
- * traverse the list. New elements can be added to the list before or
- * after an existing element, at the head of the list, or at the end of
- * the list. A tail queue may be traversed in either direction.
- *
- * A circle queue is headed by a pair of pointers, one to the head of the
- * list and the other to the tail of the list. The elements are doubly
- * linked so that an arbitrary element can be removed without a need to
- * traverse the list. New elements can be added to the list before or after
- * an existing element, at the head of the list, or at the end of the list.
- * A circle queue may be traversed in either direction, but has a more
- * complex end of list detection.
- *
- * For details on the use of these macros, see the queue(3) manual page.
- */
-
-#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC))
-#define _Q_INVALIDATE(a) (a) = ((void *)-1)
-#else
-#define _Q_INVALIDATE(a)
-#endif
-
-/*
- * Singly-linked List definitions.
- */
-#define SLIST_HEAD(name, type) \
-struct name { \
- struct type *slh_first; /* first element */ \
-}
-
-#define SLIST_HEAD_INITIALIZER(head) \
- { NULL }
-
-#define SLIST_ENTRY(type) \
-struct { \
- struct type *sle_next; /* next element */ \
-}
-
-/*
- * Singly-linked List access methods.
- */
-#define SLIST_FIRST(head) ((head)->slh_first)
-#define SLIST_END(head) NULL
-#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head))
-#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
-
-#define SLIST_FOREACH(var, head, field) \
- for((var) = SLIST_FIRST(head); \
- (var) != SLIST_END(head); \
- (var) = SLIST_NEXT(var, field))
-
-#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \
- for ((varp) = &SLIST_FIRST((head)); \
- ((var) = *(varp)) != SLIST_END(head); \
- (varp) = &SLIST_NEXT((var), field))
-
-/*
- * Singly-linked List functions.
- */
-#define SLIST_INIT(head) { \
- SLIST_FIRST(head) = SLIST_END(head); \
-}
-
-#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
- (elm)->field.sle_next = (slistelm)->field.sle_next; \
- (slistelm)->field.sle_next = (elm); \
-} while (0)
-
-#define SLIST_INSERT_HEAD(head, elm, field) do { \
- (elm)->field.sle_next = (head)->slh_first; \
- (head)->slh_first = (elm); \
-} while (0)
-
-#define SLIST_REMOVE_NEXT(head, elm, field) do { \
- (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \
-} while (0)
-
-#define SLIST_REMOVE_HEAD(head, field) do { \
- (head)->slh_first = (head)->slh_first->field.sle_next; \
-} while (0)
-
-#define SLIST_REMOVE(head, elm, type, field) do { \
- if ((head)->slh_first == (elm)) { \
- SLIST_REMOVE_HEAD((head), field); \
- } else { \
- struct type *curelm = (head)->slh_first; \
- \
- while (curelm->field.sle_next != (elm)) \
- curelm = curelm->field.sle_next; \
- curelm->field.sle_next = \
- curelm->field.sle_next->field.sle_next; \
- _Q_INVALIDATE((elm)->field.sle_next); \
- } \
-} while (0)
-
-/*
- * List definitions.
- */
-#define LIST_HEAD(name, type) \
-struct name { \
- struct type *lh_first; /* first element */ \
-}
-
-#define LIST_HEAD_INITIALIZER(head) \
- { NULL }
-
-#define LIST_ENTRY(type) \
-struct { \
- struct type *le_next; /* next element */ \
- struct type **le_prev; /* address of previous next element */ \
-}
-
-/*
- * List access methods
- */
-#define LIST_FIRST(head) ((head)->lh_first)
-#define LIST_END(head) NULL
-#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head))
-#define LIST_NEXT(elm, field) ((elm)->field.le_next)
-
-#define LIST_FOREACH(var, head, field) \
- for((var) = LIST_FIRST(head); \
- (var)!= LIST_END(head); \
- (var) = LIST_NEXT(var, field))
-
-/*
- * List functions.
- */
-#define LIST_INIT(head) do { \
- LIST_FIRST(head) = LIST_END(head); \
-} while (0)
-
-#define LIST_INSERT_AFTER(listelm, elm, field) do { \
- if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \
- (listelm)->field.le_next->field.le_prev = \
- &(elm)->field.le_next; \
- (listelm)->field.le_next = (elm); \
- (elm)->field.le_prev = &(listelm)->field.le_next; \
-} while (0)
-
-#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
- (elm)->field.le_prev = (listelm)->field.le_prev; \
- (elm)->field.le_next = (listelm); \
- *(listelm)->field.le_prev = (elm); \
- (listelm)->field.le_prev = &(elm)->field.le_next; \
-} while (0)
-
-#define LIST_INSERT_HEAD(head, elm, field) do { \
- if (((elm)->field.le_next = (head)->lh_first) != NULL) \
- (head)->lh_first->field.le_prev = &(elm)->field.le_next;\
- (head)->lh_first = (elm); \
- (elm)->field.le_prev = &(head)->lh_first; \
-} while (0)
-
-#define LIST_REMOVE(elm, field) do { \
- if ((elm)->field.le_next != NULL) \
- (elm)->field.le_next->field.le_prev = \
- (elm)->field.le_prev; \
- *(elm)->field.le_prev = (elm)->field.le_next; \
- _Q_INVALIDATE((elm)->field.le_prev); \
- _Q_INVALIDATE((elm)->field.le_next); \
-} while (0)
-
-#define LIST_REPLACE(elm, elm2, field) do { \
- if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \
- (elm2)->field.le_next->field.le_prev = \
- &(elm2)->field.le_next; \
- (elm2)->field.le_prev = (elm)->field.le_prev; \
- *(elm2)->field.le_prev = (elm2); \
- _Q_INVALIDATE((elm)->field.le_prev); \
- _Q_INVALIDATE((elm)->field.le_next); \
-} while (0)
-
-/*
- * Simple queue definitions.
- */
-#define SIMPLEQ_HEAD(name, type) \
-struct name { \
- struct type *sqh_first; /* first element */ \
- struct type **sqh_last; /* addr of last next element */ \
-}
-
-#define SIMPLEQ_HEAD_INITIALIZER(head) \
- { NULL, &(head).sqh_first }
-
-#define SIMPLEQ_ENTRY(type) \
-struct { \
- struct type *sqe_next; /* next element */ \
-}
-
-/*
- * Simple queue access methods.
- */
-#define SIMPLEQ_FIRST(head) ((head)->sqh_first)
-#define SIMPLEQ_END(head) NULL
-#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head))
-#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next)
-
-#define SIMPLEQ_FOREACH(var, head, field) \
- for((var) = SIMPLEQ_FIRST(head); \
- (var) != SIMPLEQ_END(head); \
- (var) = SIMPLEQ_NEXT(var, field))
-
-/*
- * Simple queue functions.
- */
-#define SIMPLEQ_INIT(head) do { \
- (head)->sqh_first = NULL; \
- (head)->sqh_last = &(head)->sqh_first; \
-} while (0)
-
-#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \
- if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \
- (head)->sqh_last = &(elm)->field.sqe_next; \
- (head)->sqh_first = (elm); \
-} while (0)
-
-#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \
- (elm)->field.sqe_next = NULL; \
- *(head)->sqh_last = (elm); \
- (head)->sqh_last = &(elm)->field.sqe_next; \
-} while (0)
-
-#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \
- if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\
- (head)->sqh_last = &(elm)->field.sqe_next; \
- (listelm)->field.sqe_next = (elm); \
-} while (0)
-
-#define SIMPLEQ_REMOVE_HEAD(head, field) do { \
- if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \
- (head)->sqh_last = &(head)->sqh_first; \
-} while (0)
-
-/*
- * Tail queue definitions.
- */
-#define TAILQ_HEAD(name, type) \
-struct name { \
- struct type *tqh_first; /* first element */ \
- struct type **tqh_last; /* addr of last next element */ \
-}
-
-#define TAILQ_HEAD_INITIALIZER(head) \
- { NULL, &(head).tqh_first }
-
-#define TAILQ_ENTRY(type) \
-struct { \
- struct type *tqe_next; /* next element */ \
- struct type **tqe_prev; /* address of previous next element */ \
-}
-
-/*
- * tail queue access methods
- */
-#define TAILQ_FIRST(head) ((head)->tqh_first)
-#define TAILQ_END(head) NULL
-#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
-#define TAILQ_LAST(head, headname) \
- (*(((struct headname *)((head)->tqh_last))->tqh_last))
-/* XXX */
-#define TAILQ_PREV(elm, headname, field) \
- (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
-#define TAILQ_EMPTY(head) \
- (TAILQ_FIRST(head) == TAILQ_END(head))
-
-#define TAILQ_FOREACH(var, head, field) \
- for((var) = TAILQ_FIRST(head); \
- (var) != TAILQ_END(head); \
- (var) = TAILQ_NEXT(var, field))
-
-#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
- for((var) = TAILQ_LAST(head, headname); \
- (var) != TAILQ_END(head); \
- (var) = TAILQ_PREV(var, headname, field))
-
-/*
- * Tail queue functions.
- */
-#define TAILQ_INIT(head) do { \
- (head)->tqh_first = NULL; \
- (head)->tqh_last = &(head)->tqh_first; \
-} while (0)
-
-#define TAILQ_INSERT_HEAD(head, elm, field) do { \
- if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \
- (head)->tqh_first->field.tqe_prev = \
- &(elm)->field.tqe_next; \
- else \
- (head)->tqh_last = &(elm)->field.tqe_next; \
- (head)->tqh_first = (elm); \
- (elm)->field.tqe_prev = &(head)->tqh_first; \
-} while (0)
-
-#define TAILQ_INSERT_TAIL(head, elm, field) do { \
- (elm)->field.tqe_next = NULL; \
- (elm)->field.tqe_prev = (head)->tqh_last; \
- *(head)->tqh_last = (elm); \
- (head)->tqh_last = &(elm)->field.tqe_next; \
-} while (0)
-
-#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
- if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\
- (elm)->field.tqe_next->field.tqe_prev = \
- &(elm)->field.tqe_next; \
- else \
- (head)->tqh_last = &(elm)->field.tqe_next; \
- (listelm)->field.tqe_next = (elm); \
- (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \
-} while (0)
-
-#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
- (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
- (elm)->field.tqe_next = (listelm); \
- *(listelm)->field.tqe_prev = (elm); \
- (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \
-} while (0)
-
-#define TAILQ_REMOVE(head, elm, field) do { \
- if (((elm)->field.tqe_next) != NULL) \
- (elm)->field.tqe_next->field.tqe_prev = \
- (elm)->field.tqe_prev; \
- else \
- (head)->tqh_last = (elm)->field.tqe_prev; \
- *(elm)->field.tqe_prev = (elm)->field.tqe_next; \
- _Q_INVALIDATE((elm)->field.tqe_prev); \
- _Q_INVALIDATE((elm)->field.tqe_next); \
-} while (0)
-
-#define TAILQ_REPLACE(head, elm, elm2, field) do { \
- if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \
- (elm2)->field.tqe_next->field.tqe_prev = \
- &(elm2)->field.tqe_next; \
- else \
- (head)->tqh_last = &(elm2)->field.tqe_next; \
- (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \
- *(elm2)->field.tqe_prev = (elm2); \
- _Q_INVALIDATE((elm)->field.tqe_prev); \
- _Q_INVALIDATE((elm)->field.tqe_next); \
-} while (0)
-
-/*
- * Circular queue definitions.
- */
-#define CIRCLEQ_HEAD(name, type) \
-struct name { \
- struct type *cqh_first; /* first element */ \
- struct type *cqh_last; /* last element */ \
-}
-
-#define CIRCLEQ_HEAD_INITIALIZER(head) \
- { CIRCLEQ_END(&head), CIRCLEQ_END(&head) }
-
-#define CIRCLEQ_ENTRY(type) \
-struct { \
- struct type *cqe_next; /* next element */ \
- struct type *cqe_prev; /* previous element */ \
-}
-
-/*
- * Circular queue access methods
- */
-#define CIRCLEQ_FIRST(head) ((head)->cqh_first)
-#define CIRCLEQ_LAST(head) ((head)->cqh_last)
-#define CIRCLEQ_END(head) ((void *)(head))
-#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next)
-#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev)
-#define CIRCLEQ_EMPTY(head) \
- (CIRCLEQ_FIRST(head) == CIRCLEQ_END(head))
-
-#define CIRCLEQ_FOREACH(var, head, field) \
- for((var) = CIRCLEQ_FIRST(head); \
- (var) != CIRCLEQ_END(head); \
- (var) = CIRCLEQ_NEXT(var, field))
-
-#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \
- for((var) = CIRCLEQ_LAST(head); \
- (var) != CIRCLEQ_END(head); \
- (var) = CIRCLEQ_PREV(var, field))
-
-/*
- * Circular queue functions.
- */
-#define CIRCLEQ_INIT(head) do { \
- (head)->cqh_first = CIRCLEQ_END(head); \
- (head)->cqh_last = CIRCLEQ_END(head); \
-} while (0)
-
-#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \
- (elm)->field.cqe_next = (listelm)->field.cqe_next; \
- (elm)->field.cqe_prev = (listelm); \
- if ((listelm)->field.cqe_next == CIRCLEQ_END(head)) \
- (head)->cqh_last = (elm); \
- else \
- (listelm)->field.cqe_next->field.cqe_prev = (elm); \
- (listelm)->field.cqe_next = (elm); \
-} while (0)
-
-#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \
- (elm)->field.cqe_next = (listelm); \
- (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \
- if ((listelm)->field.cqe_prev == CIRCLEQ_END(head)) \
- (head)->cqh_first = (elm); \
- else \
- (listelm)->field.cqe_prev->field.cqe_next = (elm); \
- (listelm)->field.cqe_prev = (elm); \
-} while (0)
-
-#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \
- (elm)->field.cqe_next = (head)->cqh_first; \
- (elm)->field.cqe_prev = CIRCLEQ_END(head); \
- if ((head)->cqh_last == CIRCLEQ_END(head)) \
- (head)->cqh_last = (elm); \
- else \
- (head)->cqh_first->field.cqe_prev = (elm); \
- (head)->cqh_first = (elm); \
-} while (0)
-
-#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \
- (elm)->field.cqe_next = CIRCLEQ_END(head); \
- (elm)->field.cqe_prev = (head)->cqh_last; \
- if ((head)->cqh_first == CIRCLEQ_END(head)) \
- (head)->cqh_first = (elm); \
- else \
- (head)->cqh_last->field.cqe_next = (elm); \
- (head)->cqh_last = (elm); \
-} while (0)
-
-#define CIRCLEQ_REMOVE(head, elm, field) do { \
- if ((elm)->field.cqe_next == CIRCLEQ_END(head)) \
- (head)->cqh_last = (elm)->field.cqe_prev; \
- else \
- (elm)->field.cqe_next->field.cqe_prev = \
- (elm)->field.cqe_prev; \
- if ((elm)->field.cqe_prev == CIRCLEQ_END(head)) \
- (head)->cqh_first = (elm)->field.cqe_next; \
- else \
- (elm)->field.cqe_prev->field.cqe_next = \
- (elm)->field.cqe_next; \
- _Q_INVALIDATE((elm)->field.cqe_prev); \
- _Q_INVALIDATE((elm)->field.cqe_next); \
-} while (0)
-
-#define CIRCLEQ_REPLACE(head, elm, elm2, field) do { \
- if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == \
- CIRCLEQ_END(head)) \
- (head)->cqh_last = (elm2); \
- else \
- (elm2)->field.cqe_next->field.cqe_prev = (elm2); \
- if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == \
- CIRCLEQ_END(head)) \
- (head)->cqh_first = (elm2); \
- else \
- (elm2)->field.cqe_prev->field.cqe_next = (elm2); \
- _Q_INVALIDATE((elm)->field.cqe_prev); \
- _Q_INVALIDATE((elm)->field.cqe_next); \
-} while (0)
-
-#endif /* !_SYS_QUEUE_H_ */
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
+ *
+ */
+#ifndef TRAYCLIENT_H_
+#define TRAYCLIENT_H_
+
+#include "common.h"
+
+typedef struct trayclient trayclient;
+
+TAILQ_HEAD(tc_head, trayclient);
+
+struct trayclient {
+ xcb_window_t win; /* The window ID of the tray client */
+ bool mapped; /* Whether this window is mapped */
+ int xe_version; /* The XEMBED version supported by the client */
+
+ TAILQ_ENTRY(trayclient) tailq; /* Pointer for the TAILQ-Macro */
+};
+
+#endif
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * ucs2_to_utf8.c: Converts between UCS-2 and UTF-8, both of which are used in
+ * different contexts in X11.
+ */
+#ifndef _UCS2_TO_UTF8
+#define _UCS2_TO_UTF8
+
char *convert_utf8_to_ucs2(char *input, int *real_strlen);
+
+#endif
/*
- * i3bar - an xcb-based status- and ws-bar for i3
+ * vim:ts=4:sw=4:expandtab
*
- * © 2010-2011 Axel Wagner and contributors
- *
- * See file LICNSE for license information
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
*/
#ifndef UTIL_H_
/*
- * i3bar - an xcb-based status- and ws-bar for i3
+ * vim:ts=4:sw=4:expandtab
*
- * © 2010-2011 Axel Wagner and contributors
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
*
- * See file LICNSE for license information
+ * workspaces.c: Maintaining the workspace-lists
*
*/
#ifndef WORKSPACES_H_
/*
- * i3bar - an xcb-based status- and ws-bar for i3
+ * vim:ts=4:sw=4:expandtab
*
- * © 2010-2011 Axel Wagner and contributors
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
*
- * See file LICNSE for license information
+ * xcb.c: Communicating with X
*
*/
#ifndef XCB_H_
#include <stdint.h>
//#include "outputs.h"
+#ifdef XCB_COMPAT
+#define XCB_ATOM_CARDINAL CARDINAL
+#endif
+
+#define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0
+#define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1
+#define SYSTEM_TRAY_REQUEST_DOCK 0
+#define SYSTEM_TRAY_BEGIN_MESSAGE 1
+#define SYSTEM_TRAY_CANCEL_MESSAGE 2
+#define XEMBED_MAPPED (1 << 0)
+#define XEMBED_EMBEDDED_NOTIFY 0
+
struct xcb_color_strings_t {
char *bar_fg;
char *bar_bg;
typedef struct xcb_colors_t xcb_colors_t;
/*
- * Initialize xcb and use the specified fontname for text-rendering
+ * Early initialization of the connection to X11: Everything which does not
+ * depend on 'config'.
+ *
+ */
+char *init_xcb_early();
+
+/**
+ * Initialization which depends on 'config' being usable. Called after the
+ * configuration has arrived.
*
*/
-char *init_xcb(char *fontname);
+void init_xcb_late(char *fontname);
/*
* Initialize the colors
ATOM_DO(_NET_WM_WINDOW_TYPE_DOCK)
ATOM_DO(_NET_WM_STRUT_PARTIAL)
ATOM_DO(I3_SOCKET_PATH)
+ATOM_DO(MANAGER)
+ATOM_DO(_NET_SYSTEM_TRAY_ORIENTATION)
+ATOM_DO(_NET_SYSTEM_TRAY_VISUAL)
+ATOM_DO(_NET_SYSTEM_TRAY_OPCODE)
+ATOM_DO(_XEMBED_INFO)
+ATOM_DO(_XEMBED)
#undef ATOM_DO
/*
- * i3bar - an xcb-based status- and ws-bar for i3
- *
- * © 2010-2011 Axel Wagner and contributors
+ * vim:ts=4:sw=4:expandtab
*
- * See file LICNSE for license information
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
*
- * src/child.c: Getting Input for the statusline
+ * child.c: Getting Input for the statusline
*
*/
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
+#include <err.h>
#include <ev.h>
#include "common.h"
ev_io_stop(main_loop, stdin_io);
FREE(stdin_io);
FREE(statusline_buffer);
- /* statusline pointed to memory within statusline_buffer */
- statusline = NULL;
+ /* statusline pointed to memory within statusline_buffer */
+ statusline = NULL;
}
if (child_sig != NULL) {
int n = 0;
int rec = 0;
int buffer_len = STDIN_CHUNK_SIZE;
- char *buffer = malloc(buffer_len);
+ char *buffer = smalloc(buffer_len);
buffer[0] = '\0';
while(1) {
n = read(fd, buffer + rec, buffer_len - rec);
if (rec == buffer_len) {
buffer_len += STDIN_CHUNK_SIZE;
- buffer = realloc(buffer, buffer_len);
- }
+ buffer = srealloc(buffer, buffer_len);
+ }
}
if (*buffer == '\0') {
FREE(buffer);
child_pid = 0;
if (command != NULL) {
int fd[2];
- pipe(fd);
+ if (pipe(fd) == -1)
+ err(EXIT_FAILURE, "pipe(fd)");
+
child_pid = fork();
switch (child_pid) {
case -1:
/* We set O_NONBLOCK because blocking is evil in event-driven software */
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
- stdin_io = malloc(sizeof(ev_io));
+ stdin_io = smalloc(sizeof(ev_io));
ev_io_init(stdin_io, &stdin_io_cb, STDIN_FILENO, EV_READ);
ev_io_start(main_loop, stdin_io);
/* We must cleanup, if the child unexpectedly terminates */
- child_sig = malloc(sizeof(ev_child));
+ child_sig = smalloc(sizeof(ev_child));
ev_child_init(child_sig, &child_sig_cb, child_pid, 0);
ev_child_start(main_loop, child_sig);
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
+ *
+ * config.c: Parses the configuration (received from i3).
+ *
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <i3/ipc.h>
+#include <yajl/yajl_parse.h>
+#include <yajl/yajl_version.h>
+
+#include "common.h"
+
+static char *cur_key;
+
+/*
+ * Parse a key.
+ *
+ * Essentially we just save it in cur_key.
+ *
+ */
+#if YAJL_MAJOR >= 2
+static int config_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) {
+#else
+static int config_map_key_cb(void *params_, const unsigned char *keyVal, unsigned keyLen) {
+#endif
+ FREE(cur_key);
+
+ cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1));
+ strncpy(cur_key, (const char*) keyVal, keyLen);
+ cur_key[keyLen] = '\0';
+
+ return 1;
+}
+
+/*
+ * Parse a null-value (current_workspace)
+ *
+ */
+static int config_null_cb(void *params_) {
+ if (!strcmp(cur_key, "id")) {
+ /* If 'id' is NULL, the bar config was not found. Error out. */
+ ELOG("No such bar config. Use 'i3-msg -t get_bar_config' to get the available configs.\n");
+ ELOG("Are you starting i3bar by hand? You should not:\n");
+ ELOG("Configure a 'bar' block in your i3 config and i3 will launch i3bar automatically.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ return 1;
+}
+
+/*
+ * Parse a string
+ *
+ */
+#if YAJL_MAJOR >= 2
+static int config_string_cb(void *params_, const unsigned char *val, size_t _len) {
+#else
+static int config_string_cb(void *params_, const unsigned char *val, unsigned int _len) {
+#endif
+ int len = (int)_len;
+ /* The id is ignored, we already have it in config.bar_id */
+ if (!strcmp(cur_key, "id"))
+ return 1;
+
+ if (!strcmp(cur_key, "mode")) {
+ DLOG("mode = %.*s, len = %d\n", len, val, len);
+ config.hide_on_modifier = (len == 4 && !strncmp((const char*)val, "hide", strlen("hide")));
+ return 1;
+ }
+
+ if (!strcmp(cur_key, "position")) {
+ DLOG("position = %.*s\n", len, val);
+ config.position = (len == 3 && !strncmp((const char*)val, "top", strlen("top")) ? POS_TOP : POS_BOT);
+ return 1;
+ }
+
+ if (!strcmp(cur_key, "status_command")) {
+ /* We cannot directly start the child here, because start_child() also
+ * needs to be run when no command was specified (to setup stdin).
+ * Therefore we save the command in 'config' and access it later in
+ * got_bar_config() */
+ DLOG("command = %.*s\n", len, val);
+ sasprintf(&config.command, "%.*s", len, val);
+ return 1;
+ }
+
+ if (!strcmp(cur_key, "font")) {
+ DLOG("font = %.*s\n", len, val);
+ sasprintf(&config.fontname, "%.*s", len, val);
+ return 1;
+ }
+
+ if (!strcmp(cur_key, "outputs")) {
+ DLOG("+output %.*s\n", len, val);
+ int new_num_outputs = config.num_outputs + 1;
+ config.outputs = srealloc(config.outputs, sizeof(char*) * new_num_outputs);
+ sasprintf(&config.outputs[config.num_outputs], "%.*s", len, val);
+ config.num_outputs = new_num_outputs;
+ return 1;
+ }
+
+ if (!strcmp(cur_key, "tray_output")) {
+ DLOG("tray_output %.*s\n", len, val);
+ FREE(config.tray_output);
+ sasprintf(&config.tray_output, "%.*s", len, val);
+ return 1;
+ }
+
+#define COLOR(json_name, struct_name) \
+ do { \
+ if (!strcmp(cur_key, #json_name)) { \
+ DLOG(#json_name " = " #struct_name " = %.*s\n", len, val); \
+ sasprintf(&(config.colors.struct_name), "%.*s", len, val); \
+ return 1; \
+ } \
+ } while (0)
+
+ COLOR(statusline, bar_fg);
+ COLOR(background, bar_bg);
+ COLOR(focused_workspace_text, focus_ws_fg);
+ COLOR(focused_workspace_bg, focus_ws_bg);
+ COLOR(active_workspace_text, active_ws_fg);
+ COLOR(active_workspace_bg, active_ws_bg);
+ COLOR(inactive_workspace_text, inactive_ws_fg);
+ COLOR(inactive_workspace_bg, inactive_ws_bg);
+ COLOR(urgent_workspace_text, urgent_ws_fg);
+ COLOR(urgent_workspace_bg, urgent_ws_bg);
+
+ printf("got unexpected string %.*s for cur_key = %s\n", len, val, cur_key);
+
+ return 0;
+}
+
+/*
+ * Parse a boolean value
+ *
+ */
+static int config_boolean_cb(void *params_, int val) {
+ if (!strcmp(cur_key, "workspace_buttons")) {
+ DLOG("workspace_buttons = %d\n", val);
+ config.disable_ws = !val;
+ return 1;
+ }
+
+ if (!strcmp(cur_key, "verbose")) {
+ DLOG("verbose = %d\n", val);
+ config.verbose = val;
+ return 1;
+ }
+
+ return 0;
+}
+
+/* A datastructure to pass all these callbacks to yajl */
+static yajl_callbacks outputs_callbacks = {
+ &config_null_cb,
+ &config_boolean_cb,
+ NULL,
+ NULL,
+ NULL,
+ &config_string_cb,
+ NULL,
+ &config_map_key_cb,
+ NULL,
+ NULL,
+ NULL
+};
+
+/*
+ * Start parsing the received bar configuration json-string
+ *
+ */
+void parse_config_json(char *json) {
+ yajl_handle handle;
+ yajl_status state;
+#if YAJL_MAJOR < 2
+ yajl_parser_config parse_conf = { 0, 0 };
+
+ handle = yajl_alloc(&outputs_callbacks, &parse_conf, NULL, NULL);
+#else
+ handle = yajl_alloc(&outputs_callbacks, NULL, NULL);
+#endif
+
+ state = yajl_parse(handle, (const unsigned char*) json, strlen(json));
+
+ /* FIXME: Proper errorhandling for JSON-parsing */
+ switch (state) {
+ case yajl_status_ok:
+ break;
+ case yajl_status_client_canceled:
+#if YAJL_MAJOR < 2
+ case yajl_status_insufficient_data:
+#endif
+ case yajl_status_error:
+ ELOG("Could not parse config-reply!\n");
+ exit(EXIT_FAILURE);
+ break;
+ }
+
+ yajl_free(handle);
+}
+
+/*
+ * free()s the color strings as soon as they are not needed anymore.
+ *
+ */
+void free_colors(struct xcb_color_strings_t *colors) {
+#define FREE_COLOR(x) \
+ do { \
+ if (colors->x) \
+ free(colors->x); \
+ } while (0)
+ FREE_COLOR(bar_fg);
+ FREE_COLOR(bar_bg);
+ FREE_COLOR(active_ws_fg);
+ FREE_COLOR(active_ws_bg);
+ FREE_COLOR(inactive_ws_fg);
+ FREE_COLOR(inactive_ws_bg);
+ FREE_COLOR(urgent_ws_fg);
+ FREE_COLOR(urgent_ws_bg);
+ FREE_COLOR(focus_ws_fg);
+ FREE_COLOR(focus_ws_bg);
+#undef FREE_COLOR
+}
+
/*
- * i3bar - an xcb-based status- and ws-bar for i3
- *
- * © 2010-2011 Axel Wagner and contributors
+ * vim:ts=4:sw=4:expandtab
*
- * See file LICNSE for license information
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
*
- * src/ipc.c: Communicating with i3
+ * ipc.c: Communicating with i3
*
*/
#include <stdlib.h>
#include "common.h"
ev_io *i3_connection;
-ev_timer *reconn = NULL;
const char *sock_path;
typedef void(*handler_t)(char*);
-/*
- * Retry to connect.
- *
- */
-void retry_connection(struct ev_loop *loop, ev_timer *w, int events) {
- static int retries = 8;
- if (init_connection(sock_path) == 0) {
- if (retries == 0) {
- ELOG("Retried 8 times - connection failed!\n");
- exit(EXIT_FAILURE);
- }
- retries--;
- return;
- }
- retries = 8;
- ev_timer_stop(loop, w);
- subscribe_events();
-
- /* We get the current outputs and workspaces, to
- * reconfigure all bars with the current configuration */
- i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
- if (!config.disable_ws) {
- i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
- }
-}
-
-/*
- * Schedule a reconnect
- *
- */
-void reconnect() {
- if (reconn == NULL) {
- if ((reconn = malloc(sizeof(ev_timer))) == NULL) {
- ELOG("malloc() failed: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
- } else {
- ev_timer_stop(main_loop, reconn);
- }
- ev_timer_init(reconn, retry_connection, 0.25, 0.25);
- ev_timer_start(main_loop, reconn);
-}
-
/*
* Called, when we get a reply to a command from i3.
* Since i3 does not give us much feedback on commands, we do not much
reconfig_windows();
}
+/*
+ * Called when we get the configuration for our bar instance
+ *
+ */
+void got_bar_config(char *reply) {
+ DLOG("Received bar config \"%s\"\n", reply);
+ /* We initiate the main-function by requesting infos about the outputs and
+ * workspaces. Everything else (creating the bars, showing the right workspace-
+ * buttons and more) is taken care of by the event-drivenness of the code */
+ i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
+ parse_config_json(reply);
+
+ /* Now we can actually use 'config', so let's subscribe to the appropriate
+ * events and request the workspaces if necessary. */
+ subscribe_events();
+ if (!config.disable_ws)
+ i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
+
+ /* Initialize the rest of XCB */
+ init_xcb_late(config.fontname);
+
+ /* Resolve color strings to colorpixels and save them, then free the strings. */
+ init_colors(&(config.colors));
+ free_colors(&(config.colors));
+
+ /* The name of this function is actually misleading. Even if no command is
+ * specified, this function initiates the watchers to listen on stdin and
+ * react accordingly */
+ start_child(config.command);
+ FREE(config.command);
+}
+
/* Data-structure to easily call the reply-handlers later */
handler_t reply_handlers[] = {
&got_command_reply,
&got_workspace_reply,
&got_subscribe_reply,
&got_output_reply,
+ NULL,
+ NULL,
+ &got_bar_config,
};
/*
/* First we only read the header, because we know its length */
uint32_t header_len = strlen(I3_IPC_MAGIC) + sizeof(uint32_t)*2;
- char *header = malloc(header_len);
- if (header == NULL) {
- ELOG("Could not allocate memory: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
+ char *header = smalloc(header_len);
/* We first parse the fixed-length IPC-header, to know, how much data
* we have to expect */
exit(EXIT_FAILURE);
}
if (n == 0) {
- /* EOF received. We try to recover a few times, because most likely
- * i3 just restarted */
- ELOG("EOF received, try to recover...\n");
- destroy_connection();
- reconnect();
- return;
+ /* EOF received. Since i3 will restart i3bar instances as appropriate,
+ * we exit here. */
+ DLOG("EOF received, exiting...\n");
+ clean_xcb();
+ exit(EXIT_SUCCESS);
}
rec += n;
}
/* Now that we know, what to expect, we can start read()ing the rest
* of the message */
- char *buffer = malloc(size + 1);
- if (buffer == NULL) {
- /* EOF received. We try to recover a few times, because most likely
- * i3 just restarted */
- ELOG("EOF received, try to recover...\n");
- destroy_connection();
- reconnect();
- return;
- }
+ char *buffer = smalloc(size + 1);
rec = 0;
while (rec < size) {
type ^= 1 << 31;
event_handlers[type](buffer);
} else {
- reply_handlers[type](buffer);
+ if (reply_handlers[type])
+ reply_handlers[type](buffer);
}
FREE(header);
/* TODO: I'm not entirely sure if this buffer really has to contain more
* than the pure header (why not just write() the payload from *payload?),
* but we leave it for now */
- char *buffer = malloc(to_write);
- if (buffer == NULL) {
- ELOG("Could not allocate memory: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
-
+ char *buffer = smalloc(to_write);
char *walk = buffer;
strncpy(buffer, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC));
*/
int init_connection(const char *socket_path) {
sock_path = socket_path;
- int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
- if (sockfd == -1) {
- ELOG("Could not create Socket: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- struct sockaddr_un addr;
- memset(&addr, 0, sizeof(struct sockaddr_un));
- addr.sun_family = AF_LOCAL;
- strcpy(addr.sun_path, sock_path);
- if (connect(sockfd, (const struct sockaddr*) &addr, sizeof(struct sockaddr_un)) < 0) {
- ELOG("Could not connect to i3! %s: %s\n", sock_path, strerror(errno));
- reconnect();
- return 0;
- }
-
- i3_connection = malloc(sizeof(ev_io));
- if (i3_connection == NULL) {
- ELOG("malloc() failed: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
+ int sockfd = ipc_connect(socket_path);
+ i3_connection = smalloc(sizeof(ev_io));
ev_io_init(i3_connection, &got_data, sockfd, EV_READ);
ev_io_start(main_loop, i3_connection);
return 1;
/*
- * i3bar - an xcb-based status- and ws-bar for i3
- *
- * © 2010-2011 Axel Wagner and contributors
+ * vim:ts=4:sw=4:expandtab
*
- * See file LICNSE for license information
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
*
*/
#include <stdio.h>
ELOG("glob() failed\n");
exit(EXIT_FAILURE);
}
- char *result = strdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
- if (result == NULL) {
- ELOG("malloc() failed: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
+ char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
globfree(&globbuf);
return result;
}
-static void read_color(char **color) {
- int len = strlen(optarg);
- if (len == 6 || (len == 7 && optarg[0] == '#')) {
- int offset = len - 6;
- int good = 1, i;
- for (i = offset; good && i < 6 + offset; ++i) {
- char c = optarg[i];
- if (!(c >= 'a' && c <= 'f')
- && !(c >= 'A' && c <= 'F')
- && !(c >= '0' && c <= '9')) {
- good = 0;
- break;
- }
- }
- if (good) {
- *color = strdup(optarg + offset);
- return;
- }
- }
-
- fprintf(stderr, "Bad color value \"%s\"\n", optarg);
- exit(EXIT_FAILURE);
-}
-
-static void free_colors(struct xcb_color_strings_t *colors) {
-#define FREE_COLOR(x) \
- do { \
- if (colors->x) \
- free(colors->x); \
- } while (0)
- FREE_COLOR(bar_fg);
- FREE_COLOR(bar_bg);
- FREE_COLOR(active_ws_fg);
- FREE_COLOR(active_ws_bg);
- FREE_COLOR(inactive_ws_fg);
- FREE_COLOR(inactive_ws_bg);
- FREE_COLOR(urgent_ws_fg);
- FREE_COLOR(urgent_ws_bg);
- FREE_COLOR(focus_ws_fg);
- FREE_COLOR(focus_ws_bg);
-#undef FREE_COLOR
-}
-
void print_usage(char *elf_name) {
- printf("Usage: %s [-s sock_path] [-c command] [-m|-d[pos]] [-f font] [-V] [-h]\n", elf_name);
+ printf("Usage: %s [-b bar_id] [-s sock_path] [-h] [-v]\n", elf_name);
+ printf("\n");
+ printf("--bar_id <bar_id>\tBar ID for which to get the configuration\n");
printf("-s <sock_path>\tConnect to i3 via <sock_path>\n");
- printf("-c <command>\tExecute <command> to get stdin\n");
- printf("-m\t\tHide the bars, when mod4 is not pressed.\n");
- printf("-d[<pos>]\tEnable dockmode. <pos> is \"top\" or \"bottom\". Default is bottom\n");
- printf("\t\tIf -c is specified, the childprocess is sent a SIGSTOP on hiding,\n");
- printf("\t\tand a SIGCONT on unhiding of the bars\n");
- printf("-f <font>\tUse X-Core-Font <font> for display\n");
- printf("-w\t\tDisable workspace-buttons\n");
- printf("-V\t\tBe (very) verbose with the debug-output\n");
printf("-h\t\tDisplay this help-message and exit\n");
+ printf("-v\t\tDisplay version number and exit\n");
+ printf("\n");
+ printf(" PLEASE NOTE that i3bar will be automatically started by i3\n"
+ " as soon as there is a 'bar' configuration block in your\n"
+ " config file. You should never need to start it manually.\n");
+ printf("\n");
}
/*
int opt;
int option_index = 0;
char *socket_path = getenv("I3SOCK");
- char *command = NULL;
- char *fontname = NULL;
char *i3_default_sock_path = "/tmp/i3-ipc.sock";
- struct xcb_color_strings_t colors = { NULL, };
- /* Definition of the standard-config */
- config.hide_on_modifier = 0;
- config.dockpos = DOCKPOS_NONE;
- config.disable_ws = 0;
+ /* Initialize the standard config to use 0 as default */
+ memset(&config, '\0', sizeof(config_t));
static struct option long_opt[] = {
{ "socket", required_argument, 0, 's' },
- { "command", required_argument, 0, 'c' },
- { "hide", no_argument, 0, 'm' },
- { "dock", optional_argument, 0, 'd' },
- { "font", required_argument, 0, 'f' },
- { "nows", no_argument, 0, 'w' },
+ { "bar_id", required_argument, 0, 0 },
{ "help", no_argument, 0, 'h' },
{ "version", no_argument, 0, 'v' },
- { "verbose", no_argument, 0, 'V' },
- { "color-bar-fg", required_argument, 0, 'A' },
- { "color-bar-bg", required_argument, 0, 'B' },
- { "color-active-ws-fg", required_argument, 0, 'C' },
- { "color-active-ws-bg", required_argument, 0, 'D' },
- { "color-inactive-ws-fg", required_argument, 0, 'E' },
- { "color-inactive-ws-bg", required_argument, 0, 'F' },
- { "color-urgent-ws-bg", required_argument, 0, 'G' },
- { "color-urgent-ws-fg", required_argument, 0, 'H' },
- { "color-focus-ws-bg", required_argument, 0, 'I' },
- { "color-focus-ws-fg", required_argument, 0, 'J' },
{ NULL, 0, 0, 0}
};
- while ((opt = getopt_long(argc, argv, "s:c:d::mf:whvVA:B:C:D:E:F:G:H:I:J:", long_opt, &option_index)) != -1) {
+ while ((opt = getopt_long(argc, argv, "s:hv", long_opt, &option_index)) != -1) {
switch (opt) {
case 's':
socket_path = expand_path(optarg);
break;
- case 'c':
- command = strdup(optarg);
- break;
- case 'm':
- config.hide_on_modifier = 1;
- break;
- case 'd':
- config.hide_on_modifier = 0;
- if (optarg == NULL) {
- config.dockpos = DOCKPOS_BOT;
- break;
- }
- if (!strcmp(optarg, "top")) {
- config.dockpos = DOCKPOS_TOP;
- } else if (!strcmp(optarg, "bottom")) {
- config.dockpos = DOCKPOS_BOT;
- } else {
- print_usage(argv[0]);
- exit(EXIT_FAILURE);
- }
- break;
- case 'f':
- fontname = strdup(optarg);
- break;
- case 'w':
- config.disable_ws = 1;
- break;
case 'v':
printf("i3bar version " I3_VERSION " © 2010-2011 Axel Wagner and contributors\n");
exit(EXIT_SUCCESS);
break;
- case 'V':
- config.verbose = 1;
- break;
- case 'A':
- read_color(&colors.bar_fg);
- break;
- case 'B':
- read_color(&colors.bar_bg);
- break;
- case 'C':
- read_color(&colors.active_ws_fg);
- break;
- case 'D':
- read_color(&colors.active_ws_bg);
- break;
- case 'E':
- read_color(&colors.inactive_ws_fg);
- break;
- case 'F':
- read_color(&colors.inactive_ws_bg);
- break;
- case 'G':
- read_color(&colors.urgent_ws_bg);
- break;
- case 'H':
- read_color(&colors.urgent_ws_fg);
- break;
- case 'I':
- read_color(&colors.focus_ws_bg);
- break;
- case 'J':
- read_color(&colors.focus_ws_fg);
+ case 0:
+ if (!strcmp(long_opt[option_index].name, "bar_id")) {
+ FREE(config.bar_id);
+ config.bar_id = sstrdup(optarg);
+ }
break;
default:
print_usage(argv[0]);
}
}
- if (fontname == NULL) {
- /* This is a very restrictive default. More sensefull would be something like
- * "-misc-*-*-*-*--*-*-*-*-*-*-*-*". But since that produces very ugly results
- * on my machine, let's stick with this until we have a configfile */
- fontname = "-misc-fixed-medium-r-semicondensed--12-110-75-75-c-60-iso10646-1";
- }
-
- if (config.dockpos != DOCKPOS_NONE) {
- if (config.hide_on_modifier) {
- ELOG("--dock and --hide are mutually exclusive!\n");
- exit(EXIT_FAILURE);
- }
- } else {
- config.hide_on_modifier = 1;
+ if (!config.bar_id) {
+ /* TODO: maybe we want -f which will automatically ask i3 for the first
+ * configured bar (and error out if there are too many)? */
+ ELOG("No bar_id passed. Please let i3 start i3bar or specify --bar_id\n");
+ exit(EXIT_FAILURE);
}
main_loop = ev_default_loop(0);
- init_colors(&colors);
- char *atom_sock_path = init_xcb(fontname);
+ char *atom_sock_path = init_xcb_early();
if (socket_path == NULL) {
socket_path = atom_sock_path;
socket_path = expand_path(i3_default_sock_path);
}
- free_colors(&colors);
-
init_outputs();
if (init_connection(socket_path)) {
- /* We subscribe to the i3-events we need */
- subscribe_events();
-
- /* We initiate the main-function by requesting infos about the outputs and
- * workspaces. Everything else (creating the bars, showing the right workspace-
- * buttons and more) is taken care of by the event-driveniness of the code */
- i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
- if (!config.disable_ws) {
- i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
- }
+ /* Request the bar configuration. When it arrives, we fill the config array. */
+ i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG, config.bar_id);
}
- /* The name of this function is actually misleading. Even if no -c is specified,
- * this function initiates the watchers to listen on stdin and react accordingly */
- start_child(command);
- FREE(command);
-
/* We listen to SIGTERM/QUIT/INT and try to exit cleanly, by stopping the main-loop.
* We only need those watchers on the stack, so putting them on the stack saves us
* some calls to free() */
- ev_signal *sig_term = malloc(sizeof(ev_signal));
- ev_signal *sig_int = malloc(sizeof(ev_signal));
- ev_signal *sig_hup = malloc(sizeof(ev_signal));
-
- if (sig_term == NULL || sig_int == NULL || sig_hup == NULL) {
- ELOG("malloc() failed: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
+ ev_signal *sig_term = smalloc(sizeof(ev_signal));
+ ev_signal *sig_int = smalloc(sizeof(ev_signal));
+ ev_signal *sig_hup = smalloc(sizeof(ev_signal));
ev_signal_init(sig_term, &sig_cb, SIGTERM);
ev_signal_init(sig_int, &sig_cb, SIGINT);
/*
- * i3bar - an xcb-based status- and ws-bar for i3
- *
- * © 2010-2011 Axel Wagner and contributors
+ * vim:ts=4:sw=4:expandtab
*
- * See file LICNSE for license information
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
*
- * src/outputs.c: Maintaining the output-list
+ * outputs.c: Maintaining the output-list
*
*/
#include <string.h>
i3_output *outputs_walk;
char *cur_key;
char *json;
- bool init;
+ bool in_rect;
};
/*
* Parse a boolean value (active)
*
*/
-static int outputs_boolean_cb(void *params_, bool val) {
+static int outputs_boolean_cb(void *params_, int val) {
struct outputs_json_params *params = (struct outputs_json_params*) params_;
if (strcmp(params->cur_key, "active")) {
struct outputs_json_params *params = (struct outputs_json_params*) params_;
if (!strcmp(params->cur_key, "current_workspace")) {
- char *copy = malloc(sizeof(const unsigned char) * (len + 1));
+ char *copy = smalloc(sizeof(const unsigned char) * (len + 1));
strncpy(copy, (const char*) val, len);
copy[len] = '\0';
return 0;
}
- char *name = malloc(sizeof(const unsigned char) * (len + 1));
+ char *name = smalloc(sizeof(const unsigned char) * (len + 1));
strncpy(name, (const char*) val, len);
name[len] = '\0';
i3_output *new_output = NULL;
if (params->cur_key == NULL) {
- new_output = malloc(sizeof(i3_output));
+ new_output = smalloc(sizeof(i3_output));
new_output->name = NULL;
new_output->ws = 0,
memset(&new_output->rect, 0, sizeof(rect));
new_output->bar = XCB_NONE;
- new_output->workspaces = malloc(sizeof(struct ws_head));
+ new_output->workspaces = smalloc(sizeof(struct ws_head));
TAILQ_INIT(new_output->workspaces);
+ new_output->trayclients = smalloc(sizeof(struct tc_head));
+ TAILQ_INIT(new_output->trayclients);
+
params->outputs_walk = new_output;
return 1;
}
+ if (!strcmp(params->cur_key, "rect")) {
+ params->in_rect = true;
+ }
+
return 1;
}
*/
static int outputs_end_map_cb(void *params_) {
struct outputs_json_params *params = (struct outputs_json_params*) params_;
- /* FIXME: What is at the end of a rect? */
+ if (params->in_rect) {
+ params->in_rect = false;
+ /* Ignore the end of a rect */
+ return 1;
+ }
+
+ /* See if we actually handle that output */
+ if (config.num_outputs > 0) {
+ bool handle_output = false;
+ for (int c = 0; c < config.num_outputs; c++) {
+ if (strcasecmp(params->outputs_walk->name, config.outputs[c]) != 0)
+ continue;
+
+ handle_output = true;
+ break;
+ }
+ if (!handle_output) {
+ DLOG("Ignoring output \"%s\", not configured to handle it.\n",
+ params->outputs_walk->name);
+ FREE(params->outputs_walk->name);
+ FREE(params->outputs_walk->workspaces);
+ FREE(params->outputs_walk->trayclients);
+ FREE(params->outputs_walk);
+ FREE(params->cur_key);
+ return 1;
+ }
+ }
i3_output *target = get_output_by_name(params->outputs_walk->name);
struct outputs_json_params *params = (struct outputs_json_params*) params_;
FREE(params->cur_key);
- params->cur_key = malloc(sizeof(unsigned char) * (keyLen + 1));
+ params->cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1));
strncpy(params->cur_key, (const char*) keyVal, keyLen);
params->cur_key[keyLen] = '\0';
*
*/
void init_outputs() {
- outputs = malloc(sizeof(struct outputs_head));
+ outputs = smalloc(sizeof(struct outputs_head));
SLIST_INIT(outputs);
}
params.outputs_walk = NULL;
params.cur_key = NULL;
params.json = json;
+ params.in_rect = false;
yajl_handle handle;
yajl_status state;
/*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
+ * ucs2_to_utf8.c: Converts between UCS-2 and UTF-8, both of which are used in
+ * different contexts in X11.
*/
#include <stdlib.h>
#include <stdio.h>
#include <err.h>
#include <iconv.h>
+#include "libi3.h"
+
static iconv_t conversion_descriptor = 0;
static iconv_t conversion_descriptor2 = 0;
*
*/
char *convert_ucs_to_utf8(char *input) {
- size_t input_size = 2;
- /* UTF-8 may consume up to 4 byte */
- int buffer_size = 8;
+ size_t input_size = 2;
+ /* UTF-8 may consume up to 4 byte */
+ int buffer_size = 8;
- char *buffer = calloc(buffer_size, 1);
- if (buffer == NULL)
- err(EXIT_FAILURE, "malloc() failed\n");
- size_t output_size = buffer_size;
- /* We need to use an additional pointer, because iconv() modifies it */
- char *output = buffer;
+ char *buffer = scalloc(buffer_size);
+ size_t output_size = buffer_size;
+ /* We need to use an additional pointer, because iconv() modifies it */
+ char *output = buffer;
- /* We convert the input into UCS-2 big endian */
+ /* We convert the input into UCS-2 big endian */
+ if (conversion_descriptor == 0) {
+ conversion_descriptor = iconv_open("UTF-8", "UCS-2BE");
if (conversion_descriptor == 0) {
- conversion_descriptor = iconv_open("UTF-8", "UCS-2BE");
- if (conversion_descriptor == 0) {
- fprintf(stderr, "error opening the conversion context\n");
- exit(1);
- }
+ fprintf(stderr, "error opening the conversion context\n");
+ exit(1);
}
+ }
- /* Get the conversion descriptor back to original state */
- iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
+ /* Get the conversion descriptor back to original state */
+ iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
- /* Convert our text */
- int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
- if (rc == (size_t)-1) {
- perror("Converting to UCS-2 failed");
- return NULL;
- }
+ /* Convert our text */
+ int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
+ if (rc == (size_t)-1) {
+ perror("Converting to UCS-2 failed");
+ free(buffer);
+ return NULL;
+ }
- return buffer;
+ return buffer;
}
/*
*
*/
char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
- size_t input_size = strlen(input) + 1;
- /* UCS-2 consumes exactly two bytes for each glyph */
- int buffer_size = input_size * 2;
+ size_t input_size = strlen(input) + 1;
+ /* UCS-2 consumes exactly two bytes for each glyph */
+ int buffer_size = input_size * 2;
- char *buffer = malloc(buffer_size);
- if (buffer == NULL)
- err(EXIT_FAILURE, "malloc() failed\n");
- size_t output_size = buffer_size;
- /* We need to use an additional pointer, because iconv() modifies it */
- char *output = buffer;
+ char *buffer = smalloc(buffer_size);
+ size_t output_size = buffer_size;
+ /* We need to use an additional pointer, because iconv() modifies it */
+ char *output = buffer;
- /* We convert the input into UCS-2 big endian */
+ /* We convert the input into UCS-2 big endian */
+ if (conversion_descriptor2 == 0) {
+ conversion_descriptor2 = iconv_open("UCS-2BE", "UTF-8");
if (conversion_descriptor2 == 0) {
- conversion_descriptor2 = iconv_open("UCS-2BE", "UTF-8");
- if (conversion_descriptor2 == 0) {
- fprintf(stderr, "error opening the conversion context\n");
- exit(1);
- }
+ fprintf(stderr, "error opening the conversion context\n");
+ exit(1);
}
+ }
- /* Get the conversion descriptor back to original state */
- iconv(conversion_descriptor2, NULL, NULL, NULL, NULL);
-
- /* Convert our text */
- int rc = iconv(conversion_descriptor2, (void*)&input, &input_size, &output, &output_size);
- if (rc == (size_t)-1) {
- perror("Converting to UCS-2 failed");
- if (real_strlen != NULL)
- *real_strlen = 0;
- return NULL;
- }
+ /* Get the conversion descriptor back to original state */
+ iconv(conversion_descriptor2, NULL, NULL, NULL, NULL);
+ /* Convert our text */
+ int rc = iconv(conversion_descriptor2, (void*)&input, &input_size, &output, &output_size);
+ if (rc == (size_t)-1) {
+ perror("Converting to UCS-2 failed");
+ free(buffer);
if (real_strlen != NULL)
- *real_strlen = ((buffer_size - output_size) / 2) - 1;
+ *real_strlen = 0;
+ return NULL;
+ }
+
+ if (real_strlen != NULL)
+ *real_strlen = ((buffer_size - output_size) / 2) - 1;
- return buffer;
+ return buffer;
}
/*
- * i3bar - an xcb-based status- and ws-bar for i3
- *
- * © 2010-2011 Axel Wagner and contributors
+ * vim:ts=4:sw=4:expandtab
*
- * See file LICNSE for license information
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
*
- * src/workspaces.c: Maintaining the workspace-lists
+ * workspaces.c: Maintaining the workspace-lists
*
*/
#include <string.h>
* Parse a boolean value (visible, focused, urgent)
*
*/
-static int workspaces_boolean_cb(void *params_, bool val) {
+static int workspaces_boolean_cb(void *params_, int val) {
struct workspaces_json_params *params = (struct workspaces_json_params*) params_;
if (!strcmp(params->cur_key, "visible")) {
if (!strcmp(params->cur_key, "name")) {
/* Save the name */
- params->workspaces_walk->name = malloc(sizeof(const unsigned char) * (len + 1));
+ params->workspaces_walk->name = smalloc(sizeof(const unsigned char) * (len + 1));
strncpy(params->workspaces_walk->name, (const char*) val, len);
params->workspaces_walk->name[len] = '\0';
if (!strcmp(params->cur_key, "output")) {
/* We add the ws to the TAILQ of the output, it belongs to */
- output_name = malloc(sizeof(const unsigned char) * (len + 1));
+ output_name = smalloc(sizeof(const unsigned char) * (len + 1));
strncpy(output_name, (const char*) val, len);
output_name[len] = '\0';
- params->workspaces_walk->output = get_output_by_name(output_name);
+ i3_output *target = get_output_by_name(output_name);
+ if (target) {
+ params->workspaces_walk->output = target;
- TAILQ_INSERT_TAIL(params->workspaces_walk->output->workspaces,
- params->workspaces_walk,
- tailq);
+ TAILQ_INSERT_TAIL(params->workspaces_walk->output->workspaces,
+ params->workspaces_walk,
+ tailq);
+ }
FREE(output_name);
return 1;
i3_ws *new_workspace = NULL;
if (params->cur_key == NULL) {
- new_workspace = malloc(sizeof(i3_ws));
+ new_workspace = smalloc(sizeof(i3_ws));
new_workspace->num = -1;
new_workspace->name = NULL;
new_workspace->visible = 0;
struct workspaces_json_params *params = (struct workspaces_json_params*) params_;
FREE(params->cur_key);
- params->cur_key = malloc(sizeof(unsigned char) * (keyLen + 1));
- if (params->cur_key == NULL) {
- ELOG("Could not allocate memory: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
+ params->cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1));
strncpy(params->cur_key, (const char*) keyVal, keyLen);
params->cur_key[keyLen] = '\0';
/*
- * i3bar - an xcb-based status- and ws-bar for i3
- *
- * © 2010-2011 Axel Wagner and contributors
+ * vim:ts=4:sw=4:expandtab
*
- * See file LICNSE for license information
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
*
- * src/xcb.c: Communicating with X
+ * xcb.c: Communicating with X
*
*/
#include <xcb/xcb.h>
#include <xcb/xproto.h>
#include <xcb/xcb_atom.h>
+
+#ifdef XCB_COMPAT
+#include "xcb_compat.h"
+#endif
+
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ev.h>
#include <errno.h>
#include <limits.h>
+#include <err.h>
#include <X11/Xlib.h>
#include <X11/XKBlib.h>
#include <X11/extensions/XKB.h>
#include "common.h"
-
-#if defined(__APPLE__)
-
-/*
- * Taken from FreeBSD
- * Returns a pointer to a new string which is a duplicate of the
- * string, but only copies at most n characters.
- *
- */
-char *strndup(const char *str, size_t n) {
- size_t len;
- char *copy;
-
- for (len = 0; len < n && str[len]; len++)
- continue;
-
- if ((copy = malloc(len + 1)) == NULL)
- return (NULL);
- memcpy(copy, str, len);
- copy[len] = '\0';
- return (copy);
-}
-
-#endif
+#include "libi3.h"
/* We save the Atoms in an easy to access array, indexed by an enum */
enum {
/* Variables, that are the same for all functions at all times */
xcb_connection_t *xcb_connection;
+int screen;
xcb_screen_t *xcb_screen;
xcb_window_t xcb_root;
xcb_font_t xcb_font;
}
}
-/*
- * Converts a colorstring to a colorpixel as expected from xcb_change_gc.
- * s is assumed to be in the format "rrggbb"
- *
- */
-uint32_t get_colorpixel(const char *s) {
- char strings[3][3] = { { s[0], s[1], '\0'} ,
- { s[2], s[3], '\0'} ,
- { s[4], s[5], '\0'} };
- uint8_t r = strtol(strings[0], NULL, 16);
- uint8_t g = strtol(strings[1], NULL, 16);
- uint8_t b = strtol(strings[2], NULL, 16);
- return (r << 16 | g << 8 | b);
-}
-
/*
* Redraws the statusline to the buffer
*
XCB_CONFIG_WINDOW_HEIGHT |
XCB_CONFIG_WINDOW_STACK_MODE;
values[0] = walk->rect.x;
- values[1] = walk->rect.y + walk->rect.h - font_height - 6;
+ if (config.position == POS_TOP)
+ values[1] = walk->rect.y;
+ else values[1] = walk->rect.y + walk->rect.h - font_height - 6;
values[2] = walk->rect.w;
values[3] = font_height + 6;
values[4] = XCB_STACK_MODE_ABOVE;
do { \
colors.name = get_colorpixel(new_colors->name ? new_colors->name : def); \
} while (0)
- PARSE_COLOR(bar_fg, "FFFFFF");
- PARSE_COLOR(bar_bg, "000000");
- PARSE_COLOR(active_ws_fg, "FFFFFF");
- PARSE_COLOR(active_ws_bg, "480000");
- PARSE_COLOR(inactive_ws_fg, "FFFFFF");
- PARSE_COLOR(inactive_ws_bg, "240000");
- PARSE_COLOR(urgent_ws_fg, "FFFFFF");
- PARSE_COLOR(urgent_ws_bg, "002400");
- PARSE_COLOR(focus_ws_fg, "FFFFFF");
- PARSE_COLOR(focus_ws_bg, "480000");
+ PARSE_COLOR(bar_fg, "#FFFFFF");
+ PARSE_COLOR(bar_bg, "#000000");
+ PARSE_COLOR(active_ws_fg, "#FFFFFF");
+ PARSE_COLOR(active_ws_bg, "#333333");
+ PARSE_COLOR(inactive_ws_fg, "#888888");
+ PARSE_COLOR(inactive_ws_bg, "#222222");
+ PARSE_COLOR(urgent_ws_fg, "#FFFFFF");
+ PARSE_COLOR(urgent_ws_bg, "#900000");
+ PARSE_COLOR(focus_ws_fg, "#FFFFFF");
+ PARSE_COLOR(focus_ws_bg, "#285577");
#undef PARSE_COLOR
}
i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, buffer);
}
+/*
+ * Configures the x coordinate of all trayclients. To be called after adding a
+ * new tray client or removing an old one.
+ *
+ */
+static void configure_trayclients() {
+ trayclient *trayclient;
+ i3_output *output;
+ SLIST_FOREACH(output, outputs, slist) {
+ if (!output->active)
+ continue;
+
+ int clients = 0;
+ TAILQ_FOREACH_REVERSE(trayclient, output->trayclients, tc_head, tailq) {
+ if (!trayclient->mapped)
+ continue;
+ clients++;
+
+ DLOG("Configuring tray window %08x to x=%d\n",
+ trayclient->win, output->rect.w - (clients * (font_height + 2)));
+ uint32_t x = output->rect.w - (clients * (font_height + 2));
+ xcb_configure_window(xcb_connection,
+ trayclient->win,
+ XCB_CONFIG_WINDOW_X,
+ &x);
+ }
+ }
+}
+
+/*
+ * Handles ClientMessages (messages sent from another client directly to us).
+ *
+ * At the moment, only the tray window will receive client messages. All
+ * supported client messages currently are _NET_SYSTEM_TRAY_OPCODE.
+ *
+ */
+static void handle_client_message(xcb_client_message_event_t* event) {
+ if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
+ event->format == 32) {
+ DLOG("_NET_SYSTEM_TRAY_OPCODE received\n");
+ /* event->data.data32[0] is the timestamp */
+ uint32_t op = event->data.data32[1];
+ uint32_t mask;
+ uint32_t values[2];
+ if (op == SYSTEM_TRAY_REQUEST_DOCK) {
+ xcb_window_t client = event->data.data32[2];
+
+ /* Listen for PropertyNotify events to get the most recent value of
+ * the XEMBED_MAPPED atom, also listen for UnmapNotify events */
+ mask = XCB_CW_EVENT_MASK;
+ values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE |
+ XCB_EVENT_MASK_STRUCTURE_NOTIFY;
+ xcb_change_window_attributes(xcb_connection,
+ client,
+ mask,
+ values);
+
+ /* Request the _XEMBED_INFO property. The XEMBED specification
+ * (which is referred by the tray specification) says this *has* to
+ * be set, but VLC does not set it… */
+ bool map_it = true;
+ int xe_version = 1;
+ xcb_get_property_cookie_t xembedc;
+ xembedc = xcb_get_property_unchecked(xcb_connection,
+ 0,
+ client,
+ atoms[_XEMBED_INFO],
+ XCB_GET_PROPERTY_TYPE_ANY,
+ 0,
+ 2 * 32);
+
+ xcb_get_property_reply_t *xembedr = xcb_get_property_reply(xcb_connection,
+ xembedc,
+ NULL);
+ if (xembedr != NULL && xembedr->length != 0) {
+ DLOG("xembed format = %d, len = %d\n", xembedr->format, xembedr->length);
+ uint32_t *xembed = xcb_get_property_value(xembedr);
+ DLOG("xembed version = %d\n", xembed[0]);
+ DLOG("xembed flags = %d\n", xembed[1]);
+ map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED);
+ xe_version = xembed[0];
+ if (xe_version > 1)
+ xe_version = 1;
+ free(xembedr);
+ } else {
+ ELOG("Window %08x violates the XEMBED protocol, _XEMBED_INFO not set\n", client);
+ }
+
+ DLOG("X window %08x requested docking\n", client);
+ i3_output *walk, *output = NULL;
+ SLIST_FOREACH(walk, outputs, slist) {
+ if (!walk->active)
+ continue;
+ if (config.tray_output &&
+ strcasecmp(walk->name, config.tray_output) != 0)
+ continue;
+ DLOG("using output %s\n", walk->name);
+ output = walk;
+ }
+ if (output == NULL) {
+ ELOG("No output found\n");
+ return;
+ }
+ xcb_reparent_window(xcb_connection,
+ client,
+ output->bar,
+ output->rect.w - font_height - 2,
+ 2);
+ /* We reconfigure the window to use a reasonable size. The systray
+ * specification explicitly says:
+ * Tray icons may be assigned any size by the system tray, and
+ * should do their best to cope with any size effectively
+ */
+ mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
+ values[0] = font_height;
+ values[1] = font_height;
+ xcb_configure_window(xcb_connection,
+ client,
+ mask,
+ values);
+
+ /* send the XEMBED_EMBEDDED_NOTIFY message */
+ void *event = scalloc(32);
+ xcb_client_message_event_t *ev = event;
+ ev->response_type = XCB_CLIENT_MESSAGE;
+ ev->window = client;
+ ev->type = atoms[_XEMBED];
+ ev->format = 32;
+ ev->data.data32[0] = XCB_CURRENT_TIME;
+ ev->data.data32[1] = atoms[XEMBED_EMBEDDED_NOTIFY];
+ ev->data.data32[2] = output->bar;
+ ev->data.data32[3] = xe_version;
+ xcb_send_event(xcb_connection,
+ 0,
+ client,
+ XCB_EVENT_MASK_NO_EVENT,
+ (char*)ev);
+ free(event);
+
+ /* Put the client inside the save set. Upon termination (whether
+ * killed or normal exit does not matter) of i3bar, these clients
+ * will be correctly reparented to their most closest living
+ * ancestor. Without this, tray icons might die when i3bar
+ * exits/crashes. */
+ xcb_change_save_set(xcb_connection, XCB_SET_MODE_INSERT, client);
+
+ if (map_it) {
+ DLOG("Mapping dock client\n");
+ xcb_map_window(xcb_connection, client);
+ } else {
+ DLOG("Not mapping dock client yet\n");
+ }
+ trayclient *tc = smalloc(sizeof(trayclient));
+ tc->win = client;
+ tc->mapped = map_it;
+ tc->xe_version = xe_version;
+ TAILQ_INSERT_TAIL(output->trayclients, tc, tailq);
+
+ /* Trigger an update to copy the statusline text to the appropriate
+ * position */
+ configure_trayclients();
+ draw_bars();
+ }
+ }
+}
+
+/*
+ * Handles UnmapNotify events. These events happen when a tray window unmaps
+ * itself. We then update our data structure
+ *
+ */
+static void handle_unmap_notify(xcb_unmap_notify_event_t* event) {
+ DLOG("UnmapNotify for window = %08x, event = %08x\n", event->window, event->event);
+
+ i3_output *walk;
+ SLIST_FOREACH(walk, outputs, slist) {
+ if (!walk->active)
+ continue;
+ DLOG("checking output %s\n", walk->name);
+ trayclient *trayclient;
+ TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
+ if (trayclient->win != event->window)
+ continue;
+
+ DLOG("Removing tray client with window ID %08x\n", event->window);
+ TAILQ_REMOVE(walk->trayclients, trayclient, tailq);
+
+ /* Trigger an update, we now have more space for the statusline */
+ configure_trayclients();
+ draw_bars();
+ return;
+ }
+ }
+}
+
+/*
+ * Handle PropertyNotify messages. Currently only the _XEMBED_INFO property is
+ * handled, which tells us whether a dock client should be mapped or unmapped.
+ *
+ */
+static void handle_property_notify(xcb_property_notify_event_t *event) {
+ DLOG("PropertyNotify\n");
+ if (event->atom == atoms[_XEMBED_INFO] &&
+ event->state == XCB_PROPERTY_NEW_VALUE) {
+ DLOG("xembed_info updated\n");
+ trayclient *trayclient = NULL, *walk;
+ i3_output *o_walk;
+ SLIST_FOREACH(o_walk, outputs, slist) {
+ if (!o_walk->active)
+ continue;
+
+ TAILQ_FOREACH(walk, o_walk->trayclients, tailq) {
+ if (walk->win != event->window)
+ continue;
+ trayclient = walk;
+ break;
+ }
+
+ if (trayclient)
+ break;
+ }
+ if (!trayclient) {
+ ELOG("PropertyNotify received for unknown window %08x\n",
+ event->window);
+ return;
+ }
+ xcb_get_property_cookie_t xembedc;
+ xembedc = xcb_get_property_unchecked(xcb_connection,
+ 0,
+ trayclient->win,
+ atoms[_XEMBED_INFO],
+ XCB_GET_PROPERTY_TYPE_ANY,
+ 0,
+ 2 * 32);
+
+ xcb_get_property_reply_t *xembedr = xcb_get_property_reply(xcb_connection,
+ xembedc,
+ NULL);
+ if (xembedr == NULL || xembedr->length == 0) {
+ DLOG("xembed_info unset\n");
+ return;
+ }
+
+ DLOG("xembed format = %d, len = %d\n", xembedr->format, xembedr->length);
+ uint32_t *xembed = xcb_get_property_value(xembedr);
+ DLOG("xembed version = %d\n", xembed[0]);
+ DLOG("xembed flags = %d\n", xembed[1]);
+ bool map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED);
+ DLOG("map-state now %d\n", map_it);
+ if (trayclient->mapped && !map_it) {
+ /* need to unmap the window */
+ xcb_unmap_window(xcb_connection, trayclient->win);
+ trayclient->mapped = map_it;
+ configure_trayclients();
+ draw_bars();
+ } else if (!trayclient->mapped && map_it) {
+ /* need to map the window */
+ xcb_map_window(xcb_connection, trayclient->win);
+ trayclient->mapped = map_it;
+ configure_trayclients();
+ draw_bars();
+ }
+ free(xembedr);
+ }
+}
+
+/*
+ * Handle ConfigureRequests by denying them and sending the client a
+ * ConfigureNotify with its actual size.
+ *
+ */
+static void handle_configure_request(xcb_configure_request_event_t *event) {
+ DLOG("ConfigureRequest for window = %08x\n", event->window);
+
+ trayclient *trayclient;
+ i3_output *output;
+ SLIST_FOREACH(output, outputs, slist) {
+ if (!output->active)
+ continue;
+
+ int clients = 0;
+ TAILQ_FOREACH_REVERSE(trayclient, output->trayclients, tc_head, tailq) {
+ if (!trayclient->mapped)
+ continue;
+ clients++;
+
+ if (trayclient->win != event->window)
+ continue;
+
+ xcb_rectangle_t rect;
+ rect.x = output->rect.w - (clients * (font_height + 2));
+ rect.y = 2;
+ rect.width = font_height;
+ rect.height = font_height;
+
+ DLOG("This is a tray window. x = %d\n", rect.x);
+ fake_configure_notify(xcb_connection, rect, event->window, 0);
+ return;
+ }
+ }
+
+ DLOG("WARNING: Could not find corresponding tray window.\n");
+}
+
/*
* This function is called immediately before the main loop locks. We flush xcb
* then (and only then)
exit(1);
}
- while ((event = xcb_poll_for_event(xcb_connection)) == NULL) {
- return;
- }
-
- switch (event->response_type & ~0x80) {
- case XCB_EXPOSE:
- /* Expose-events happen, when the window needs to be redrawn */
- redraw_bars();
- break;
- case XCB_BUTTON_PRESS:
- /* Button-press-events are mouse-buttons clicked on one of our bars */
- handle_button((xcb_button_press_event_t*) event);
- break;
+ while ((event = xcb_poll_for_event(xcb_connection)) != NULL) {
+ switch (event->response_type & ~0x80) {
+ case XCB_EXPOSE:
+ /* Expose-events happen, when the window needs to be redrawn */
+ redraw_bars();
+ break;
+ case XCB_BUTTON_PRESS:
+ /* Button-press-events are mouse-buttons clicked on one of our bars */
+ handle_button((xcb_button_press_event_t*) event);
+ break;
+ case XCB_CLIENT_MESSAGE:
+ /* Client messages are used for client-to-client communication, for
+ * example system tray widgets talk to us directly via client messages. */
+ handle_client_message((xcb_client_message_event_t*) event);
+ break;
+ case XCB_UNMAP_NOTIFY:
+ case XCB_DESTROY_NOTIFY:
+ /* UnmapNotifies are received when a tray window unmaps itself */
+ handle_unmap_notify((xcb_unmap_notify_event_t*) event);
+ break;
+ case XCB_PROPERTY_NOTIFY:
+ /* PropertyNotify */
+ handle_property_notify((xcb_property_notify_event_t*) event);
+ break;
+ case XCB_CONFIGURE_REQUEST:
+ /* ConfigureRequest, sent by a tray child */
+ handle_configure_request((xcb_configure_request_event_t*) event);
+ break;
+ }
+ free(event);
}
- FREE(event);
}
/*
}
/*
- * Initialize xcb and use the specified fontname for text-rendering
+ * Early initialization of the connection to X11: Everything which does not
+ * depend on 'config'.
*
*/
-char *init_xcb(char *fontname) {
+char *init_xcb_early() {
/* FIXME: xcb_connect leaks Memory */
- xcb_connection = xcb_connect(NULL, NULL);
+ xcb_connection = xcb_connect(NULL, &screen);
if (xcb_connection_has_error(xcb_connection)) {
ELOG("Cannot open display\n");
exit(EXIT_FAILURE);
xcb_screen = xcb_setup_roots_iterator(xcb_get_setup(xcb_connection)).data;
xcb_root = xcb_screen->root;
- /* We load and allocate the font */
- xcb_font = xcb_generate_id(xcb_connection);
- xcb_void_cookie_t open_font_cookie;
- open_font_cookie = xcb_open_font_checked(xcb_connection,
- xcb_font,
- strlen(fontname),
- fontname);
-
- /* We need to save info about the font, because we need the font's height and
- * information about the width of characters */
- xcb_query_font_cookie_t query_font_cookie;
- query_font_cookie = xcb_query_font(xcb_connection,
- xcb_font);
-
- /* To grab modifiers without blocking other applications from receiving key-events
- * involving that modifier, we sadly have to use xkb which is not yet fully supported
- * in xcb */
- if (config.hide_on_modifier) {
- int xkb_major, xkb_minor, xkb_errbase, xkb_err;
- xkb_major = XkbMajorVersion;
- xkb_minor = XkbMinorVersion;
-
- xkb_dpy = XkbOpenDisplay(NULL,
- &xkb_event_base,
- &xkb_errbase,
- &xkb_major,
- &xkb_minor,
- &xkb_err);
-
- if (xkb_dpy == NULL) {
- ELOG("No XKB!\n");
- exit(EXIT_FAILURE);
- }
-
- if (fcntl(ConnectionNumber(xkb_dpy), F_SETFD, FD_CLOEXEC) == -1) {
- ELOG("Could not set FD_CLOEXEC on xkbdpy: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- int i1;
- if (!XkbQueryExtension(xkb_dpy, &i1, &xkb_event_base, &xkb_errbase, &xkb_major, &xkb_minor)) {
- ELOG("XKB not supported by X-server!\n");
- exit(EXIT_FAILURE);
- }
-
- if (!XkbSelectEvents(xkb_dpy, XkbUseCoreKbd, XkbStateNotifyMask, XkbStateNotifyMask)) {
- ELOG("Could not grab Key!\n");
- exit(EXIT_FAILURE);
- }
-
- xkb_io = malloc(sizeof(ev_io));
- ev_io_init(xkb_io, &xkb_io_cb, ConnectionNumber(xkb_dpy), EV_READ);
- ev_io_start(main_loop, xkb_io);
- XFlush(xkb_dpy);
- }
-
/* We draw the statusline to a seperate pixmap, because it looks the same on all bars and
* this way, we can choose to crop it */
uint32_t mask = XCB_GC_FOREGROUND;
- uint32_t vals[3] = { colors.bar_bg, colors.bar_bg, xcb_font };
+ uint32_t vals[] = { colors.bar_bg, colors.bar_bg };
statusline_clear = xcb_generate_id(xcb_connection);
xcb_void_cookie_t clear_ctx_cookie = xcb_create_gc_checked(xcb_connection,
mask,
vals);
- mask |= XCB_GC_BACKGROUND | XCB_GC_FONT;
+ mask |= XCB_GC_BACKGROUND;
vals[0] = colors.bar_fg;
statusline_ctx = xcb_generate_id(xcb_connection);
xcb_void_cookie_t sl_ctx_cookie = xcb_create_gc_checked(xcb_connection,
/* The various Watchers to communicate with xcb */
- xcb_io = malloc(sizeof(ev_io));
- xcb_prep = malloc(sizeof(ev_prepare));
- xcb_chk = malloc(sizeof(ev_check));
+ xcb_io = smalloc(sizeof(ev_io));
+ xcb_prep = smalloc(sizeof(ev_prepare));
+ xcb_chk = smalloc(sizeof(ev_check));
ev_io_init(xcb_io, &xcb_io_cb, xcb_get_file_descriptor(xcb_connection), EV_READ);
ev_prepare_init(xcb_prep, &xcb_prep_cb);
}
}
+
+ if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline-buffer") ||
+ xcb_request_failed(clear_ctx_cookie, "Could not allocate statusline-buffer-clearcontext") ||
+ xcb_request_failed(sl_ctx_cookie, "Could not allocate statusline-buffer-context")) {
+ exit(EXIT_FAILURE);
+ }
+
+ return path;
+}
+
+/*
+ * Initialization which depends on 'config' being usable. Called after the
+ * configuration has arrived.
+ *
+ */
+void init_xcb_late(char *fontname) {
+ if (fontname == NULL) {
+ /* XXX: font fallback to 'misc' like i3 does it would be good. */
+ fontname = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
+ }
+
+ /* We load and allocate the font */
+ xcb_font = xcb_generate_id(xcb_connection);
+ xcb_void_cookie_t open_font_cookie;
+ open_font_cookie = xcb_open_font_checked(xcb_connection,
+ xcb_font,
+ strlen(fontname),
+ fontname);
+
+ /* We need to save info about the font, because we need the font's height and
+ * information about the width of characters */
+ xcb_query_font_cookie_t query_font_cookie;
+ query_font_cookie = xcb_query_font(xcb_connection,
+ xcb_font);
+
+ xcb_change_gc(xcb_connection,
+ statusline_ctx,
+ XCB_GC_FONT,
+ (uint32_t[]){ xcb_font });
+
+ xcb_flush(xcb_connection);
+
+ /* To grab modifiers without blocking other applications from receiving key-events
+ * involving that modifier, we sadly have to use xkb which is not yet fully supported
+ * in xcb */
+ if (config.hide_on_modifier) {
+ int xkb_major, xkb_minor, xkb_errbase, xkb_err;
+ xkb_major = XkbMajorVersion;
+ xkb_minor = XkbMinorVersion;
+
+ xkb_dpy = XkbOpenDisplay(NULL,
+ &xkb_event_base,
+ &xkb_errbase,
+ &xkb_major,
+ &xkb_minor,
+ &xkb_err);
+
+ if (xkb_dpy == NULL) {
+ ELOG("No XKB!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (fcntl(ConnectionNumber(xkb_dpy), F_SETFD, FD_CLOEXEC) == -1) {
+ ELOG("Could not set FD_CLOEXEC on xkbdpy: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ int i1;
+ if (!XkbQueryExtension(xkb_dpy, &i1, &xkb_event_base, &xkb_errbase, &xkb_major, &xkb_minor)) {
+ ELOG("XKB not supported by X-server!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (!XkbSelectEvents(xkb_dpy, XkbUseCoreKbd, XkbStateNotifyMask, XkbStateNotifyMask)) {
+ ELOG("Could not grab Key!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ xkb_io = smalloc(sizeof(ev_io));
+ ev_io_init(xkb_io, &xkb_io_cb, ConnectionNumber(xkb_dpy), EV_READ);
+ ev_io_start(main_loop, xkb_io);
+ XFlush(xkb_dpy);
+ }
+
/* Now we save the font-infos */
font_info = xcb_query_font_reply(xcb_connection,
query_font_cookie,
}
DLOG("Calculated Font-height: %d\n", font_height);
+}
- if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline-buffer") ||
- xcb_request_failed(clear_ctx_cookie, "Could not allocate statusline-buffer-clearcontext") ||
- xcb_request_failed(sl_ctx_cookie, "Could not allocate statusline-buffer-context")) {
+/*
+ * Initializes tray support by requesting the appropriate _NET_SYSTEM_TRAY atom
+ * for the X11 display we are running on, then acquiring the selection for this
+ * atom. Afterwards, tray clients will send ClientMessages to our window.
+ *
+ */
+void init_tray() {
+ DLOG("Initializing system tray functionality\n");
+ /* request the tray manager atom for the X11 display we are running on */
+ char atomname[strlen("_NET_SYSTEM_TRAY_S") + 11];
+ snprintf(atomname, strlen("_NET_SYSTEM_TRAY_S") + 11, "_NET_SYSTEM_TRAY_S%d", screen);
+ xcb_intern_atom_cookie_t tray_cookie;
+ xcb_intern_atom_reply_t *tray_reply;
+ tray_cookie = xcb_intern_atom(xcb_connection, 0, strlen(atomname), atomname);
+
+ /* tray support: we need a window to own the selection */
+ xcb_window_t selwin = xcb_generate_id(xcb_connection);
+ uint32_t selmask = XCB_CW_OVERRIDE_REDIRECT;
+ uint32_t selval[] = { 1 };
+ xcb_create_window(xcb_connection,
+ xcb_screen->root_depth,
+ selwin,
+ xcb_root,
+ -1, -1,
+ 1, 1,
+ 1,
+ XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ xcb_screen->root_visual,
+ selmask,
+ selval);
+
+ uint32_t orientation = _NET_SYSTEM_TRAY_ORIENTATION_HORZ;
+ /* set the atoms */
+ xcb_change_property(xcb_connection,
+ XCB_PROP_MODE_REPLACE,
+ selwin,
+ atoms[_NET_SYSTEM_TRAY_ORIENTATION],
+ XCB_ATOM_CARDINAL,
+ 32,
+ 1,
+ &orientation);
+
+ if (!(tray_reply = xcb_intern_atom_reply(xcb_connection, tray_cookie, NULL))) {
+ ELOG("Could not get atom %s\n", atomname);
exit(EXIT_FAILURE);
}
- return path;
+ xcb_set_selection_owner(xcb_connection,
+ selwin,
+ tray_reply->atom,
+ XCB_CURRENT_TIME);
+
+ /* Verify that we have the selection */
+ xcb_get_selection_owner_cookie_t selcookie;
+ xcb_get_selection_owner_reply_t *selreply;
+
+ selcookie = xcb_get_selection_owner(xcb_connection, tray_reply->atom);
+ if (!(selreply = xcb_get_selection_owner_reply(xcb_connection, selcookie, NULL))) {
+ ELOG("Could not get selection owner for %s\n", atomname);
+ exit(EXIT_FAILURE);
+ }
+
+ if (selreply->owner != selwin) {
+ ELOG("Could not set the %s selection. " \
+ "Maybe another tray is already running?\n", atomname);
+ /* NOTE that this error is not fatal. We just can’t provide tray
+ * functionality */
+ free(selreply);
+ return;
+ }
+
+ /* Inform clients waiting for a new _NET_SYSTEM_TRAY that we are here */
+ void *event = scalloc(32);
+ xcb_client_message_event_t *ev = event;
+ ev->response_type = XCB_CLIENT_MESSAGE;
+ ev->window = xcb_root;
+ ev->type = atoms[MANAGER];
+ ev->format = 32;
+ ev->data.data32[0] = XCB_CURRENT_TIME;
+ ev->data.data32[1] = tray_reply->atom;
+ ev->data.data32[2] = selwin;
+ xcb_send_event(xcb_connection,
+ 0,
+ xcb_root,
+ XCB_EVENT_MASK_STRUCTURE_NOTIFY,
+ (char*)ev);
+ free(event);
+ free(tray_reply);
}
/*
*/
void clean_xcb() {
i3_output *o_walk;
+ trayclient *trayclient;
free_workspaces();
SLIST_FOREACH(o_walk, outputs, slist) {
+ TAILQ_FOREACH(trayclient, o_walk->trayclients, tailq) {
+ /* Unmap, then reparent (to root) the tray client windows */
+ xcb_unmap_window(xcb_connection, trayclient->win);
+ xcb_reparent_window(xcb_connection,
+ trayclient->win,
+ xcb_root,
+ 0,
+ 0);
+ }
destroy_window(o_walk);
+ FREE(o_walk->trayclients);
FREE(o_walk->workspaces);
FREE(o_walk->name);
}
FREE_SLIST(outputs, i3_output);
FREE(outputs);
+ xcb_flush(xcb_connection);
xcb_disconnect(xcb_connection);
ev_check_stop(main_loop, xcb_chk);
void reconfig_windows() {
uint32_t mask;
uint32_t values[5];
+ static bool tray_configured = false;
i3_output *walk;
SLIST_FOREACH(walk, outputs, slist) {
values[0] = colors.bar_bg;
/* If hide_on_modifier is set, i3 is not supposed to manage our bar-windows */
values[1] = config.hide_on_modifier;
- /* The events we want to receive */
- values[2] = XCB_EVENT_MASK_EXPOSURE;
+ /* We enable the following EventMask fields:
+ * EXPOSURE, to get expose events (we have to re-draw then)
+ * SUBSTRUCTURE_REDIRECT, to get ConfigureRequests when the tray
+ * child windows use ConfigureWindow
+ * BUTTON_PRESS, to handle clicks on the workspace buttons
+ * */
+ values[2] = XCB_EVENT_MASK_EXPOSURE |
+ XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
if (!config.disable_ws) {
values[2] |= XCB_EVENT_MASK_BUTTON_PRESS;
}
walk->rect.w,
walk->rect.h);
+ /* Set the WM_CLASS and WM_NAME (we don't need UTF-8) atoms */
+ xcb_void_cookie_t class_cookie;
+ class_cookie = xcb_change_property(xcb_connection,
+ XCB_PROP_MODE_REPLACE,
+ walk->bar,
+ XCB_ATOM_WM_CLASS,
+ XCB_ATOM_STRING,
+ 8,
+ (strlen("i3bar") + 1) * 2,
+ "i3bar\0i3bar\0");
+
+ char *name;
+ if (asprintf(&name, "i3bar for output %s", walk->name) == -1)
+ err(EXIT_FAILURE, "asprintf()");
+ xcb_void_cookie_t name_cookie;
+ name_cookie = xcb_change_property(xcb_connection,
+ XCB_PROP_MODE_REPLACE,
+ walk->bar,
+ XCB_ATOM_WM_NAME,
+ XCB_ATOM_STRING,
+ 8,
+ strlen(name),
+ name);
+ free(name);
+
/* We want dock-windows (for now). When override_redirect is set, i3 is ignoring
* this one */
xcb_void_cookie_t dock_cookie = xcb_change_property(xcb_connection,
uint32_t bottom_start_x;
uint32_t bottom_end_x;
} __attribute__((__packed__)) strut_partial = {0,};
- switch (config.dockpos) {
- case DOCKPOS_NONE:
+ switch (config.position) {
+ case POS_NONE:
break;
- case DOCKPOS_TOP:
+ case POS_TOP:
strut_partial.top = font_height + 6;
strut_partial.top_start_x = walk->rect.x;
strut_partial.top_end_x = walk->rect.x + walk->rect.w;
break;
- case DOCKPOS_BOT:
+ case POS_BOT:
strut_partial.bottom = font_height + 6;
strut_partial.bottom_start_x = walk->rect.x;
strut_partial.bottom_end_x = walk->rect.x + walk->rect.w;
if (xcb_request_failed(win_cookie, "Could not create window") ||
xcb_request_failed(pm_cookie, "Could not create pixmap") ||
xcb_request_failed(dock_cookie, "Could not set dock mode") ||
+ xcb_request_failed(class_cookie, "Could not set WM_CLASS") ||
+ xcb_request_failed(name_cookie, "Could not set WM_NAME") ||
xcb_request_failed(strut_cookie, "Could not set strut") ||
xcb_request_failed(gc_cookie, "Could not create graphical context") ||
(!config.hide_on_modifier && xcb_request_failed(map_cookie, "Could not map window"))) {
exit(EXIT_FAILURE);
}
+
+ if (!tray_configured &&
+ (!config.tray_output ||
+ strcasecmp("none", config.tray_output) != 0)) {
+ init_tray();
+ tray_configured = true;
+ }
} else {
/* We already have a bar, so we just reconfigure it */
mask = XCB_CONFIG_WINDOW_X |
/* Luckily we already prepared a seperate pixmap containing the rendered
* statusline, we just have to copy the relevant parts to the relevant
* position */
+ trayclient *trayclient;
+ int traypx = 0;
+ TAILQ_FOREACH(trayclient, outputs_walk->trayclients, tailq) {
+ if (!trayclient->mapped)
+ continue;
+ /* We assume the tray icons are quadratic (we use the font
+ * *height* as *width* of the icons) because we configured them
+ * like this. */
+ traypx += font_height + 2;
+ }
+ /* Add 2px of padding if there are any tray icons */
+ if (traypx > 0)
+ traypx += 2;
xcb_copy_area(xcb_connection,
statusline_pm,
outputs_walk->buffer,
outputs_walk->bargc,
MAX(0, (int16_t)(statusline_width - outputs_walk->rect.w + 4)), 0,
- MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - 4)), 3,
- MIN(outputs_walk->rect.w - 4, statusline_width), font_height);
+ MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - traypx - 4)), 3,
+ MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font_height);
}
if (config.disable_ws) {
/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
* This header file includes all relevant files of i3 and the most often used
* system header files. This reduces boilerplate (the amount of code duplicated
* at the beginning of each source file) and is not significantly slower at
#include "output.h"
#include "ewmh.h"
#include "assignments.h"
+#include "regex.h"
+#include "libi3.h"
+#include "startup.h"
#endif
/*
* vim:ts=4:sw=4:expandtab
*
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * assignments.c: Assignments for specific windows (for_window).
+ *
*/
#ifndef _ASSIGNMENTS_H
#define _ASSIGNMENTS_H
xmacro(_NET_CURRENT_DESKTOP)
xmacro(_NET_ACTIVE_WINDOW)
xmacro(_NET_WORKAREA)
+xmacro(_NET_STARTUP_ID)
xmacro(WM_PROTOCOLS)
xmacro(WM_DELETE_WINDOW)
xmacro(UTF8_STRING)
xmacro(WM_STATE)
xmacro(WM_CLIENT_LEADER)
xmacro(WM_TAKE_FOCUS)
+xmacro(WM_WINDOW_ROLE)
xmacro(I3_SOCKET_PATH)
xmacro(I3_CONFIG_PATH)
+xmacro(I3_SYNC)
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009-2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * click.c: Button press (mouse click) events.
*
*/
#ifndef _CLICK_H
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * cmdparse.y: the parser for commands you send to i3 (or bind on keys)
+ *
+ */
#ifndef _CMDPARSE_H
#define _CMDPARSE_H
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * con.c: Functions which deal with containers directly (creating containers,
+ * searching containers, getting specific properties from containers,
+ * …).
+ *
+ */
#ifndef _CON_H
#define _CON_H
/*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
- *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
* include/config.h: Contains all structs/variables for the configurable
* part of i3 as well as functions handling the configuration file (calling
* mode).
*
*/
-
#ifndef _CONFIG_H
#define _CONFIG_H
#include <stdbool.h>
#include "queue.h"
#include "i3.h"
+#include "libi3.h"
typedef struct Config Config;
+typedef struct Barconfig Barconfig;
extern char *current_configpath;
extern Config config;
extern SLIST_HEAD(modes_head, Mode) modes;
+extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs;
/**
* Used during the config file lexing/parsing to keep the state of the lexer
*
*/
struct context {
- bool has_errors;
+ bool has_errors;
+ bool has_warnings;
- int line_number;
- char *line_copy;
- const char *filename;
+ int line_number;
+ char *line_copy;
+ const char *filename;
- char *compact_error;
+ char *compact_error;
- /* These are the same as in YYLTYPE */
- int first_column;
- int last_column;
+ /* These are the same as in YYLTYPE */
+ int first_column;
+ int last_column;
};
/**
*
*/
struct Colortriple {
- uint32_t border;
- uint32_t background;
- uint32_t text;
+ uint32_t border;
+ uint32_t background;
+ uint32_t text;
};
/**
*
*/
struct Variable {
- char *key;
- char *value;
- char *next_match;
+ char *key;
+ char *value;
+ char *next_match;
- SLIST_ENTRY(Variable) variables;
+ SLIST_ENTRY(Variable) variables;
};
/**
*
*/
struct Mode {
- char *name;
- struct bindings_head *bindings;
+ char *name;
+ struct bindings_head *bindings;
- SLIST_ENTRY(Mode) modes;
+ SLIST_ENTRY(Mode) modes;
};
/**
*
*/
struct Config {
- const char *terminal;
- i3Font font;
-
- char *ipc_socket_path;
- const char *restart_state_path;
-
- int default_layout;
- int container_stack_limit;
- int container_stack_limit_value;
-
- /** Default orientation for new containers */
- int default_orientation;
-
- /** By default, focus follows mouse. If the user explicitly wants to
- * turn this off (and instead rely only on the keyboard for changing
- * focus), we allow him to do this with this relatively special option.
- * It is not planned to add any different focus models. */
- bool disable_focus_follows_mouse;
-
- /** By default, a workspace bar is drawn at the bottom of the screen.
- * If you want to have a more fancy bar, it is recommended to replace
- * the whole bar by dzen2, for example using the i3-wsbar script which
- * comes with i3. Thus, you can turn it off entirely. */
- bool disable_workspace_bar;
-
- /** Think of the following layout: Horizontal workspace with a tabbed
- * con on the left of the screen and a terminal on the right of the
- * screen. You are in the second container in the tabbed container and
- * focus to the right. By default, i3 will set focus to the terminal on
- * the right. If you are in the first container in the tabbed container
- * however, focusing to the left will wrap. This option forces i3 to
- * always wrap, which will result in you having to use "focus parent"
- * more often. */
- bool force_focus_wrapping;
-
- /** The default border style for new windows. */
- border_style_t default_border;
-
- /** The modifier which needs to be pressed in combination with your mouse
- * buttons to do things with floating windows (move, resize) */
- uint32_t floating_modifier;
-
- /* Color codes are stored here */
- struct config_client {
- uint32_t background;
- struct Colortriple focused;
- struct Colortriple focused_inactive;
- struct Colortriple unfocused;
- struct Colortriple urgent;
- } client;
- struct config_bar {
- struct Colortriple focused;
- struct Colortriple unfocused;
- struct Colortriple urgent;
- } bar;
-
- /** What should happen when a new popup is opened during fullscreen mode */
- enum {
- PDF_LEAVE_FULLSCREEN = 0,
- PDF_IGNORE = 1
- } popup_during_fullscreen;
+ const char *terminal;
+ i3Font font;
+
+ char *ipc_socket_path;
+ const char *restart_state_path;
+
+ int default_layout;
+ int container_stack_limit;
+ int container_stack_limit_value;
+
+ /** Default orientation for new containers */
+ int default_orientation;
+
+ /** By default, focus follows mouse. If the user explicitly wants to
+ * turn this off (and instead rely only on the keyboard for changing
+ * focus), we allow him to do this with this relatively special option.
+ * It is not planned to add any different focus models. */
+ bool disable_focus_follows_mouse;
+
+ /** By default, a workspace bar is drawn at the bottom of the screen.
+ * If you want to have a more fancy bar, it is recommended to replace
+ * the whole bar by dzen2, for example using the i3-wsbar script which
+ * comes with i3. Thus, you can turn it off entirely. */
+ bool disable_workspace_bar;
+
+ /** Think of the following layout: Horizontal workspace with a tabbed
+ * con on the left of the screen and a terminal on the right of the
+ * screen. You are in the second container in the tabbed container and
+ * focus to the right. By default, i3 will set focus to the terminal on
+ * the right. If you are in the first container in the tabbed container
+ * however, focusing to the left will wrap. This option forces i3 to
+ * always wrap, which will result in you having to use "focus parent"
+ * more often. */
+ bool force_focus_wrapping;
+
+ /** By default, use the RandR API for multi-monitor setups.
+ * Unfortunately, the nVidia binary graphics driver doesn't support
+ * this API. Instead, it only support the less powerful Xinerama API,
+ * which can be enabled by this option.
+ *
+ * Note: this option takes only effect on the initial startup (eg.
+ * reconfiguration is not possible). On startup, the list of screens
+ * is fetched once and never updated. */
+ bool force_xinerama;
+
+ /** Automatic workspace back and forth switching. If this is set, a
+ * switch to the currently active workspace will switch to the
+ * previously focused one instead, making it possible to fast toggle
+ * between two workspaces. */
+ bool workspace_auto_back_and_forth;
+
+ /** The default border style for new windows. */
+ border_style_t default_border;
+
+ /** The default border style for new floating windows. */
+ border_style_t default_floating_border;
+
+ /** The modifier which needs to be pressed in combination with your mouse
+ * buttons to do things with floating windows (move, resize) */
+ uint32_t floating_modifier;
+
+ /* Color codes are stored here */
+ struct config_client {
+ uint32_t background;
+ struct Colortriple focused;
+ struct Colortriple focused_inactive;
+ struct Colortriple unfocused;
+ struct Colortriple urgent;
+ } client;
+ struct config_bar {
+ struct Colortriple focused;
+ struct Colortriple unfocused;
+ struct Colortriple urgent;
+ } bar;
+
+ /** What should happen when a new popup is opened during fullscreen mode */
+ enum {
+ PDF_LEAVE_FULLSCREEN = 0,
+ PDF_IGNORE = 1
+ } popup_during_fullscreen;
+};
+
+/**
+ * Holds the status bar configuration (i3bar). One of these structures is
+ * created for each 'bar' block in the config.
+ *
+ */
+struct Barconfig {
+ /** Automatically generated ID for this bar config. Used by the bar process
+ * to request a specific configuration. */
+ char *id;
+
+ /** Number of outputs in the outputs array */
+ int num_outputs;
+ /** Outputs on which this bar should show up on. We use an array for
+ * simplicity (since we store just strings). */
+ char **outputs;
+
+ /** Output on which the tray should be shown. The special value of 'no'
+ * disables the tray (it’s enabled by default). */
+ char *tray_output;
+
+ /** Path to the i3 IPC socket. This option is discouraged since programs
+ * can find out the path by looking for the I3_SOCKET_PATH property on the
+ * root window! */
+ char *socket_path;
+
+ /** Bar display mode (hide unless modifier is pressed or show in dock mode) */
+ enum { M_DOCK = 0, M_HIDE = 1 } mode;
+
+ /** Bar position (bottom by default). */
+ enum { P_BOTTOM = 0, P_TOP = 1 } position;
+
+ /** Command that should be run to get a statusline, for example 'i3status'.
+ * Will be passed to the shell. */
+ char *status_command;
+
+ /** Font specification for all text rendered on the bar. */
+ char *font;
+
+ /** Hide workspace buttons? Configuration option is 'workspace_buttons no'
+ * but we invert the bool to get the correct default when initializing with
+ * zero. */
+ bool hide_workspace_buttons;
+
+ /** Enable verbose mode? Useful for debugging purposes. */
+ bool verbose;
+
+ struct bar_colors {
+ char *background;
+ char *statusline;
+
+ char *focused_workspace_text;
+ char *focused_workspace_bg;
+
+ char *active_workspace_text;
+ char *active_workspace_bg;
+
+ char *inactive_workspace_text;
+ char *inactive_workspace_bg;
+
+ char *urgent_workspace_text;
+ char *urgent_workspace_bg;
+ } colors;
+
+ TAILQ_ENTRY(Barconfig) configs;
};
/**
* include/data.h: This file defines all data structures used by i3
*
*/
+#ifndef _DATA_H
+#define _DATA_H
+
+#define SN_API_NOT_YET_FROZEN 1
+#include <libsn/sn-launcher.h>
+
#include <xcb/randr.h>
#include <xcb/xcb_atom.h>
#include <stdbool.h>
+#include <pcre.h>
-#ifndef _DATA_H
-#define _DATA_H
#include "queue.h"
/*
*/
/* Forward definitions */
-typedef struct Font i3Font;
typedef struct Binding Binding;
typedef struct Rect Rect;
typedef struct xoutput Output;
SLIST_ENTRY(Ignore_Event) ignore_events;
};
+/**
+ * Stores internal information about a startup sequence, like the workspace it
+ * was initiated on.
+ *
+ */
+struct Startup_Sequence {
+ /** startup ID for this sequence, generated by libstartup-notification */
+ char *id;
+ /** workspace on which this startup was initiated */
+ char *workspace;
+ /** libstartup-notification context for this launch */
+ SnLauncherContext *context;
+
+ TAILQ_ENTRY(Startup_Sequence) sequences;
+};
+
+/**
+ * Regular expression wrapper. It contains the pattern itself as a string (like
+ * ^foo[0-9]$) as well as a pointer to the compiled PCRE expression and the
+ * pcre_extra data returned by pcre_study().
+ *
+ * This makes it easier to have a useful logfile, including the matching or
+ * non-matching pattern.
+ *
+ */
+struct regex {
+ char *pattern;
+ pcre *regex;
+ pcre_extra *extra;
+};
+
/******************************************************************************
* Major types
*****************************************************************************/
struct Autostart {
/** Command, like in command mode */
char *command;
+ /** no_startup_id flag for start_application(). Determines whether a
+ * startup notification context/ID should be created. */
+ bool no_startup_id;
TAILQ_ENTRY(Autostart) autostarts;
TAILQ_ENTRY(Autostart) autostarts_always;
};
-/**
- * Data structure for cached font information:
- * - font id in X11 (load it once)
- * - font height (multiple calls needed to get it)
- *
- */
-struct Font {
- /** The height of the font, built from font_ascent + font_descent */
- int height;
- /** The xcb-id for the font */
- xcb_font_t id;
-};
-
-
/**
* An Output is a physical output on your graphics driver. Outputs which
* are currently in use have (output->active == true). Each output has a
* application supports _NET_WM_NAME, in COMPOUND_TEXT otherwise). */
char *name_x;
+ /** The WM_WINDOW_ROLE of this window (for example, the pidgin buddy window
+ * sets "buddy list"). Useful to match specific windows in assignments or
+ * for_window. */
+ char *role;
+
/** Flag to force re-rendering the decoration upon changes */
bool name_x_changed;
};
struct Match {
- char *title;
- int title_len;
- char *application;
- char *class;
- char *instance;
- char *mark;
+ struct regex *title;
+ struct regex *application;
+ struct regex *class;
+ struct regex *instance;
+ struct regex *mark;
+ struct regex *role;
enum {
M_DONTCHECK = -1,
M_NODOCK = 0,
/*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * (c) 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * debug.c: Debugging functions, especially FormatEvent, which prints unhandled
+ * events. This code is from xcb-util.
*
*/
#ifndef _DEBUG_H
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * ewmh.c: Get/set certain EWMH properties easily.
*
*/
#ifndef _EWMH_C
/*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * floating.c: Floating windows.
*
*/
#ifndef _FLOATING_H
#include "tree.h"
/** Callback for dragging */
-typedef void(*callback_t)(Con*, Rect*, uint32_t, uint32_t, void*);
+typedef void(*callback_t)(Con*, Rect*, uint32_t, uint32_t, const void*);
/** Macro to create a callback function for dragging */
#define DRAGGING_CB(name) \
static void name(Con *con, Rect *old_rect, uint32_t new_x, \
- uint32_t new_y, void *extra)
+ uint32_t new_y, const void *extra)
/** On which border was the dragging initiated? */
typedef enum { BORDER_LEFT = (1 << 0),
* Calls the drag_pointer function with the drag_window callback
*
*/
-void floating_drag_window(Con *con, xcb_button_press_event_t *event);
+void floating_drag_window(Con *con, const xcb_button_press_event_t *event);
/**
* Called when the user clicked on a floating window while holding the
* Calls the drag_pointer function with the resize_window callback
*
*/
-void floating_resize_window(Con *con, bool proportional, xcb_button_press_event_t *event);
+void floating_resize_window(Con *con, const bool proportional, const xcb_button_press_event_t *event);
#if 0
/**
* the event and the new coordinates (x, y).
*
*/
-void drag_pointer(Con *con, xcb_button_press_event_t *event,
+void drag_pointer(Con *con, const xcb_button_press_event_t *event,
xcb_window_t confine_to, border_t border, callback_t callback,
- void *extra);
+ const void *extra);
#endif
/*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * handlers.c: Small handlers for various events (keypresses, focus changes,
+ * …).
*
*/
#ifndef _HANDLERS_H
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * i3.h: global variables that are used all over i3.
*
*/
+#ifndef _I3_H
+#define _I3_H
+
+#include <sys/time.h>
+#include <sys/resource.h>
+
#include <xcb/xcb_keysyms.h>
#include <X11/XKBlib.h>
+#define SN_API_NOT_YET_FROZEN 1
+#include <libsn/sn-launcher.h>
+
#include "queue.h"
#include "data.h"
#include "xcb.h"
-#ifndef _I3_H
-#define _I3_H
-
+/** The original value of RLIMIT_CORE when i3 was started. We need to restore
+ * this before starting any other process, since we set RLIMIT_CORE to
+ * RLIM_INFINITY for i3 debugging versions. */
+extern struct rlimit original_rlimit_core;
extern xcb_connection_t *conn;
+extern int conn_screen;
+/** The last timestamp we got from X11 (timestamps are included in some events
+ * and are used for some things, like determining a unique ID in startup
+ * notification). */
+extern xcb_timestamp_t last_timestamp;
+extern SnDisplay *sndisplay;
extern xcb_key_symbols_t *keysyms;
extern char **start_argv;
extern Display *xlibdpy, *xkbdpy;
extern bool xcursor_supported, xkb_supported;
extern xcb_window_t root;
extern struct ev_loop *main_loop;
+extern bool only_check_config;
#endif
/*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
- *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
*
* This public header defines the different constants and message types to use
* for the IPC interface to i3 (see docs/ipc for more information).
*
*/
-
#ifndef _I3_IPC_H
#define _I3_IPC_H
/** Requests the tree layout from i3 */
#define I3_IPC_MESSAGE_TYPE_GET_TREE 4
+/** Request the current defined marks from i3 */
+#define I3_IPC_MESSAGE_TYPE_GET_MARKS 5
+
+/** Request the configuration for a specific 'bar' */
+#define I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG 6
/*
* Messages from i3 to clients
/** Tree reply type */
#define I3_IPC_REPLY_TYPE_TREE 4
+/** Marks reply type */
+#define I3_IPC_REPLY_TYPE_MARKS 5
+
+/** Bar config reply type */
+#define I3_IPC_REPLY_TYPE_BAR_CONFIG 6
/*
* Events from i3 to clients. Events have the first bit set high.
/*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * ipc.c: UNIX domain socket IPC (initialization, client handling, protocol).
*
*/
-
#ifndef _IPC_H
#define _IPC_H
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * libi3: contains functions which are used by i3 *and* accompanying tools such
+ * as i3-msg, i3-config-wizard, …
+ *
+ */
+#ifndef _LIBI3_H
+#define _LIBI3_H
+
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <xcb/xcb.h>
+#include <xcb/xproto.h>
+#include <xcb/xcb_keysyms.h>
+
+typedef struct Font i3Font;
+
+/**
+ * Data structure for cached font information:
+ * - font id in X11 (load it once)
+ * - font height (multiple calls needed to get it)
+ *
+ */
+struct Font {
+ /** The height of the font, built from font_ascent + font_descent */
+ int height;
+ /** The xcb-id for the font */
+ xcb_font_t id;
+};
+
+/* Since this file also gets included by utilities which don’t use the i3 log
+ * infrastructure, we define a fallback. */
+#if !defined(ELOG)
+#define ELOG(fmt, ...) fprintf(stderr, "ERROR: " fmt, ##__VA_ARGS__)
+#endif
+
+/**
+ * Try to get the socket path from X11 and return NULL if it doesn’t work.
+ *
+ * The memory for the socket path is dynamically allocated and has to be
+ * free()d by the caller.
+ *
+ */
+char *socket_path_from_x11();
+
+/**
+ * Safe-wrapper around malloc which exits if malloc returns NULL (meaning that
+ * there is no more memory available)
+ *
+ */
+void *smalloc(size_t size);
+
+/**
+ * Safe-wrapper around calloc which exits if malloc returns NULL (meaning that
+ * there is no more memory available)
+ *
+ */
+void *scalloc(size_t size);
+
+/**
+ * Safe-wrapper around realloc which exits if realloc returns NULL (meaning
+ * that there is no more memory available).
+ *
+ */
+void *srealloc(void *ptr, size_t size);
+
+/**
+ * Safe-wrapper around strdup which exits if malloc returns NULL (meaning that
+ * there is no more memory available)
+ *
+ */
+char *sstrdup(const char *str);
+
+/**
+ * Safe-wrapper around asprintf which exits if it returns -1 (meaning that
+ * there is no more memory available)
+ *
+ */
+int sasprintf(char **strp, const char *fmt, ...);
+
+/**
+ * Connects to the i3 IPC socket and returns the file descriptor for the
+ * socket. die()s if anything goes wrong.
+ *
+ */
+int ipc_connect(const char *socket_path);
+
+/**
+ * Formats a message (payload) of the given size and type and sends it to i3 via
+ * the given socket file descriptor.
+ *
+ * Returns -1 when write() fails, errno will remain.
+ * Returns 0 on success.
+ *
+ */
+int ipc_send_message(int sockfd, uint32_t message_size,
+ uint32_t message_type, const uint8_t *payload);
+
+/**
+ * Reads a message from the given socket file descriptor and stores its length
+ * (reply_length) as well as a pointer to its contents (reply).
+ *
+ * Returns -1 when read() fails, errno will remain.
+ * Returns -2 when the IPC protocol is violated (invalid magic, unexpected
+ * message type, EOF instead of a message). Additionally, the error will be
+ * printed to stderr.
+ * Returns 0 on success.
+ *
+ */
+int ipc_recv_message(int sockfd, uint32_t message_type,
+ uint32_t *reply_length, uint8_t **reply);
+
+/**
+ * Generates a configure_notify event and sends it to the given window
+ * Applications need this to think they’ve configured themselves correctly.
+ * The truth is, however, that we will manage them.
+ *
+ */
+void fake_configure_notify(xcb_connection_t *conn, xcb_rectangle_t r, xcb_window_t window, int border_width);
+
+/**
+ * Returns the colorpixel to use for the given hex color (think of HTML). Only
+ * works for true-color (vast majority of cases) at the moment, avoiding a
+ * roundtrip to X11.
+ *
+ * The hex_color has to start with #, for example #FF00FF.
+ *
+ * NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
+ * This has to be done by the caller.
+ *
+ * NOTE that this function may in the future rely on a global xcb_connection_t
+ * variable called 'conn' to be present.
+ *
+ */
+uint32_t get_colorpixel(const char *hex) __attribute__((const));
+
+#if defined(__APPLE__)
+
+/*
+ * Taken from FreeBSD
+ * Returns a pointer to a new string which is a duplicate of the
+ * string, but only copies at most n characters.
+ *
+ */
+char *strndup(const char *str, size_t n);
+
+#endif
+
+/**
+ * All-in-one function which returns the modifier mask (XCB_MOD_MASK_*) for the
+ * given keysymbol, for example for XCB_NUM_LOCK (usually configured to mod2).
+ *
+ * This function initiates one round-trip. Use get_mod_mask_for() directly if
+ * you already have the modifier mapping and key symbols.
+ *
+ */
+uint32_t aio_get_mod_mask_for(uint32_t keysym, xcb_key_symbols_t *symbols);
+
+/**
+ * Returns the modifier mask (XCB_MOD_MASK_*) for the given keysymbol, for
+ * example for XCB_NUM_LOCK (usually configured to mod2).
+ *
+ * This function does not initiate any round-trips.
+ *
+ */
+uint32_t get_mod_mask_for(uint32_t keysym,
+ xcb_key_symbols_t *symbols,
+ xcb_get_modifier_mapping_reply_t *modmap_reply);
+
+/**
+ * Loads a font for usage, also getting its height. If fallback is true,
+ * the fonts 'fixed' or '-misc-*' will be loaded instead of exiting.
+ *
+ */
+i3Font load_font(const char *pattern, bool fallback);
+
+#endif
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * load_layout.c: Restore (parts of) the layout, for example after an inplace
+ * restart.
+ *
+ */
#ifndef _LOAD_LAYOUT_H
#define _LOAD_LAYOUT_H
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009-2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * log.c: Setting of loglevels, logging functions.
*
*/
#ifndef _LOG_H
/*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * manage.c: Initially managing new windows (or existing ones on restart).
*
*/
-
-#include "data.h"
-
#ifndef _MANAGE_H
#define _MANAGE_H
+#include "data.h"
+
/**
* Go through all existing windows (if the window manager is restarted) and
* manage them
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * A "match" is a data structure which acts like a mask or expression to match
+ * certain windows or not. For example, when using commands, you can specify a
+ * command like this: [title="*Firefox*"] kill. The title member of the match
+ * data structure will then be filled and i3 will check each window using
+ * match_matches_window() to find the windows affected by this command.
+ *
+ */
#ifndef _MATCH_H
#define _MATCH_H
*/
bool match_matches_window(Match *match, i3Window *window);
+/**
+ * Frees the given match. It must not be used afterwards!
+ *
+ */
+void match_free(Match *match);
+
#endif
/*
* vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * move.c: Moving containers into some direction.
+ *
*/
-
#ifndef _MOVE_H
#define _MOVE_H
/*
* vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * output.c: Output (monitor) related functions.
+ *
*/
-
#ifndef _OUTPUT_H
#define _OUTPUT_H
/*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * For more information on RandR, please see the X.org RandR specification at
+ * http://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt
+ * (take your time to read it completely, it answers all questions).
*
*/
-#include "data.h"
-#include <xcb/randr.h>
-
#ifndef _RANDR_H
#define _RANDR_H
+#include "data.h"
+#include <xcb/randr.h>
+
TAILQ_HEAD(outputs_head, xoutput);
extern struct outputs_head outputs;
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * regex.c: Interface to libPCRE (perl compatible regular expressions).
+ *
+ */
+#ifndef _REGEX_H
+#define _REGEX_H
+
+/**
+ * Creates a new 'regex' struct containing the given pattern and a PCRE
+ * compiled regular expression. Also, calls pcre_study because this regex will
+ * most likely be used often (like for every new window and on every relevant
+ * property change of existing windows).
+ *
+ * Returns NULL if the pattern could not be compiled into a regular expression
+ * (and ELOGs an appropriate error message).
+ *
+ */
+struct regex *regex_new(const char *pattern);
+
+/**
+ * Frees the given regular expression. It must not be used afterwards!
+ *
+ */
+void regex_free(struct regex *regex);
+
+/**
+ * Checks if the given regular expression matches the given input and returns
+ * true if it does. In either case, it logs the outcome using LOG(), so it will
+ * be visible without any debug loglevel.
+ *
+ */
+bool regex_matches(struct regex *regex, const char *input);
+
+#endif
/*
* vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * render.c: Renders (determines position/sizes) the layout tree, updating the
+ * various rects. Needs to be pushed to X11 (see x.c) to be visible.
+ *
*/
-
#ifndef _RENDER_H
#define _RENDER_H
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * resize.c: Interactive resizing.
+ *
+ */
#ifndef _RESIZE_H
#define _RESIZE_H
-int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, xcb_button_press_event_t *event);
+int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event);
#endif
--- /dev/null
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosddaemonhfoo
+#define foosddaemonhfoo
+
+/***
+ Copyright 2010 Lennart Poettering
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+***/
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ Reference implementation of a few systemd related interfaces for
+ writing daemons. These interfaces are trivial to implement. To
+ simplify porting we provide this reference implementation.
+ Applications are welcome to reimplement the algorithms described
+ here if they do not want to include these two source files.
+
+ The following functionality is provided:
+
+ - Support for logging with log levels on stderr
+ - File descriptor passing for socket-based activation
+ - Daemon startup and status notification
+ - Detection of systemd boots
+
+ You may compile this with -DDISABLE_SYSTEMD to disable systemd
+ support. This makes all those calls NOPs that are directly related to
+ systemd (i.e. only sd_is_xxx() will stay useful).
+
+ Since this is drop-in code we don't want any of our symbols to be
+ exported in any case. Hence we declare hidden visibility for all of
+ them.
+
+ You may find an up-to-date version of these source files online:
+
+ http://cgit.freedesktop.org/systemd/plain/src/sd-daemon.h
+ http://cgit.freedesktop.org/systemd/plain/src/sd-daemon.c
+
+ This should compile on non-Linux systems, too, but with the
+ exception of the sd_is_xxx() calls all functions will become NOPs.
+
+ See sd-daemon(7) for more information.
+*/
+
+#ifndef _sd_printf_attr_
+#if __GNUC__ >= 4
+#define _sd_printf_attr_(a,b) __attribute__ ((format (printf, a, b)))
+#else
+#define _sd_printf_attr_(a,b)
+#endif
+#endif
+
+#ifndef _sd_hidden_
+#if (__GNUC__ >= 4) && !defined(SD_EXPORT_SYMBOLS)
+#define _sd_hidden_ __attribute__ ((visibility("hidden")))
+#else
+#define _sd_hidden_
+#endif
+#endif
+
+/*
+ Log levels for usage on stderr:
+
+ fprintf(stderr, SD_NOTICE "Hello World!\n");
+
+ This is similar to printk() usage in the kernel.
+*/
+#define SD_EMERG "<0>" /* system is unusable */
+#define SD_ALERT "<1>" /* action must be taken immediately */
+#define SD_CRIT "<2>" /* critical conditions */
+#define SD_ERR "<3>" /* error conditions */
+#define SD_WARNING "<4>" /* warning conditions */
+#define SD_NOTICE "<5>" /* normal but significant condition */
+#define SD_INFO "<6>" /* informational */
+#define SD_DEBUG "<7>" /* debug-level messages */
+
+/* The first passed file descriptor is fd 3 */
+#define SD_LISTEN_FDS_START 3
+
+/*
+ Returns how many file descriptors have been passed, or a negative
+ errno code on failure. Optionally, removes the $LISTEN_FDS and
+ $LISTEN_PID file descriptors from the environment (recommended, but
+ problematic in threaded environments). If r is the return value of
+ this function you'll find the file descriptors passed as fds
+ SD_LISTEN_FDS_START to SD_LISTEN_FDS_START+r-1. Returns a negative
+ errno style error code on failure. This function call ensures that
+ the FD_CLOEXEC flag is set for the passed file descriptors, to make
+ sure they are not passed on to child processes. If FD_CLOEXEC shall
+ not be set, the caller needs to unset it after this call for all file
+ descriptors that are used.
+
+ See sd_listen_fds(3) for more information.
+*/
+int sd_listen_fds(int unset_environment) _sd_hidden_;
+
+/*
+ Helper call for identifying a passed file descriptor. Returns 1 if
+ the file descriptor is a FIFO in the file system stored under the
+ specified path, 0 otherwise. If path is NULL a path name check will
+ not be done and the call only verifies if the file descriptor
+ refers to a FIFO. Returns a negative errno style error code on
+ failure.
+
+ See sd_is_fifo(3) for more information.
+*/
+int sd_is_fifo(int fd, const char *path) _sd_hidden_;
+
+/*
+ Helper call for identifying a passed file descriptor. Returns 1 if
+ the file descriptor is a socket of the specified family (AF_INET,
+ ...) and type (SOCK_DGRAM, SOCK_STREAM, ...), 0 otherwise. If
+ family is 0 a socket family check will not be done. If type is 0 a
+ socket type check will not be done and the call only verifies if
+ the file descriptor refers to a socket. If listening is > 0 it is
+ verified that the socket is in listening mode. (i.e. listen() has
+ been called) If listening is == 0 it is verified that the socket is
+ not in listening mode. If listening is < 0 no listening mode check
+ is done. Returns a negative errno style error code on failure.
+
+ See sd_is_socket(3) for more information.
+*/
+int sd_is_socket(int fd, int family, int type, int listening) _sd_hidden_;
+
+/*
+ Helper call for identifying a passed file descriptor. Returns 1 if
+ the file descriptor is an Internet socket, of the specified family
+ (either AF_INET or AF_INET6) and the specified type (SOCK_DGRAM,
+ SOCK_STREAM, ...), 0 otherwise. If version is 0 a protocol version
+ check is not done. If type is 0 a socket type check will not be
+ done. If port is 0 a socket port check will not be done. The
+ listening flag is used the same way as in sd_is_socket(). Returns a
+ negative errno style error code on failure.
+
+ See sd_is_socket_inet(3) for more information.
+*/
+int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) _sd_hidden_;
+
+/*
+ Helper call for identifying a passed file descriptor. Returns 1 if
+ the file descriptor is an AF_UNIX socket of the specified type
+ (SOCK_DGRAM, SOCK_STREAM, ...) and path, 0 otherwise. If type is 0
+ a socket type check will not be done. If path is NULL a socket path
+ check will not be done. For normal AF_UNIX sockets set length to
+ 0. For abstract namespace sockets set length to the length of the
+ socket name (including the initial 0 byte), and pass the full
+ socket path in path (including the initial 0 byte). The listening
+ flag is used the same way as in sd_is_socket(). Returns a negative
+ errno style error code on failure.
+
+ See sd_is_socket_unix(3) for more information.
+*/
+int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) _sd_hidden_;
+
+/*
+ Informs systemd about changed daemon state. This takes a number of
+ newline separated environment-style variable assignments in a
+ string. The following variables are known:
+
+ READY=1 Tells systemd that daemon startup is finished (only
+ relevant for services of Type=notify). The passed
+ argument is a boolean "1" or "0". Since there is
+ little value in signaling non-readiness the only
+ value daemons should send is "READY=1".
+
+ STATUS=... Passes a single-line status string back to systemd
+ that describes the daemon state. This is free-from
+ and can be used for various purposes: general state
+ feedback, fsck-like programs could pass completion
+ percentages and failing programs could pass a human
+ readable error message. Example: "STATUS=Completed
+ 66% of file system check..."
+
+ ERRNO=... If a daemon fails, the errno-style error code,
+ formatted as string. Example: "ERRNO=2" for ENOENT.
+
+ BUSERROR=... If a daemon fails, the D-Bus error-style error
+ code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut"
+
+ MAINPID=... The main pid of a daemon, in case systemd did not
+ fork off the process itself. Example: "MAINPID=4711"
+
+ Daemons can choose to send additional variables. However, it is
+ recommended to prefix variable names not listed above with X_.
+
+ Returns a negative errno-style error code on failure. Returns > 0
+ if systemd could be notified, 0 if it couldn't possibly because
+ systemd is not running.
+
+ Example: When a daemon finished starting up, it could issue this
+ call to notify systemd about it:
+
+ sd_notify(0, "READY=1");
+
+ See sd_notifyf() for more complete examples.
+
+ See sd_notify(3) for more information.
+*/
+int sd_notify(int unset_environment, const char *state) _sd_hidden_;
+
+/*
+ Similar to sd_notify() but takes a format string.
+
+ Example 1: A daemon could send the following after initialization:
+
+ sd_notifyf(0, "READY=1\n"
+ "STATUS=Processing requests...\n"
+ "MAINPID=%lu",
+ (unsigned long) getpid());
+
+ Example 2: A daemon could send the following shortly before
+ exiting, on failure:
+
+ sd_notifyf(0, "STATUS=Failed to start up: %s\n"
+ "ERRNO=%i",
+ strerror(errno),
+ errno);
+
+ See sd_notifyf(3) for more information.
+*/
+int sd_notifyf(int unset_environment, const char *format, ...) _sd_printf_attr_(2,3) _sd_hidden_;
+
+/*
+ Returns > 0 if the system was booted with systemd. Returns < 0 on
+ error. Returns 0 if the system was not booted with systemd. Note
+ that all of the functions above handle non-systemd boots just
+ fine. You should NOT protect them with a call to this function. Also
+ note that this function checks whether the system, not the user
+ session is controlled by systemd. However the functions above work
+ for both user and system services.
+
+ See sd_booted(3) for more information.
+*/
+int sd_booted(void) _sd_hidden_;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
- *
- * © 2009-2010 Michael Stapelberg and contributors
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
* © 2009-2010 Jan-Erik Rediger
*
- * See file LICENSE for license information.
+ * sighandler.c: Interactive crash dialog upon SIGSEGV/SIGABRT/SIGFPE (offers
+ * to restart inplace).
*
*/
#ifndef _SIGHANDLER_H
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * startup.c: Startup notification code. Ensures a startup notification context
+ * is setup when launching applications. We store the current
+ * workspace to open windows in that startup notification context on
+ * the appropriate workspace.
+ *
+ */
+#ifndef _STARTUP_H
+#define _STARTUP_H
+
+#define SN_API_NOT_YET_FROZEN 1
+#include <libsn/sn-monitor.h>
+
+/**
+ * Starts the given application by passing it through a shell. We use double
+ * fork to avoid zombie processes. As the started application’s parent exits
+ * (immediately), the application is reparented to init (process-id 1), which
+ * correctly handles childs, so we don’t have to do it :-).
+ *
+ * The shell is determined by looking for the SHELL environment variable. If
+ * it does not exist, /bin/sh is used.
+ *
+ * The no_startup_id flag determines whether a startup notification context
+ * (and ID) should be created, which is the default and encouraged behavior.
+ *
+ */
+void start_application(const char *command, bool no_startup_id);
+
+/**
+ * Called by libstartup-notification when something happens
+ *
+ */
+void startup_monitor_event(SnMonitorEvent *event, void *userdata);
+
+/**
+ * Checks if the given window belongs to a startup notification by checking if
+ * the _NET_STARTUP_ID property is set on the window (or on its leader, if it’s
+ * unset).
+ *
+ * If so, returns the workspace on which the startup was initiated.
+ * Returns NULL otherwise.
+ *
+ */
+char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply);
+
+#endif
/*
* vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * tree.c: Everything that primarily modifies the layout tree data structure.
+ *
*/
-
#ifndef _TREE_H
#define _TREE_H
/*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * util.c: Utility functions, which can be useful everywhere within i3 (see
+ * also libi3).
*
*/
+#ifndef _UTIL_H
+#define _UTIL_H
+
#include <err.h>
#include "data.h"
-#ifndef _UTIL_H
-#define _UTIL_H
-
#define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
#define exit_if_null(pointer, ...) { if (pointer == NULL) die(__VA_ARGS__); }
#define STARTS_WITH(string, needle) (strncasecmp(string, needle, strlen(needle)) == 0)
*/
bool update_if_necessary(uint32_t *destination, const uint32_t new_value);
-/**
- * Safe-wrapper around malloc which exits if malloc returns NULL (meaning that
- * there is no more memory available)
- *
- */
-void *smalloc(size_t size);
-
-/**
- * Safe-wrapper around calloc which exits if malloc returns NULL (meaning that
- * there is no more memory available)
- *
- */
-void *scalloc(size_t size);
-
-/**
- * Safe-wrapper around realloc which exits if realloc returns NULL (meaning
- * that there is no more memory available).
- *
- */
-void *srealloc(void *ptr, size_t size);
-
-/**
- * Safe-wrapper around strdup which exits if malloc returns NULL (meaning that
- * there is no more memory available)
- *
- */
-char *sstrdup(const char *str);
-
-/**
- * Starts the given application by passing it through a shell. We use double
- * fork to avoid zombie processes. As the started application’s parent exits
- * (immediately), the application is reparented to init (process-id 1), which
- * correctly handles childs, so we don’t have to do it :-).
- *
- * The shell is determined by looking for the SHELL environment variable. If
- * it does not exist, /bin/sh is used.
- *
- */
-void start_application(const char *command);
-
/**
* exec()s an i3 utility, for example the config file migration script or
* i3-nagbar. This function first searches $PATH for the given utility named,
#endif
-#if defined(__APPLE__)
-
-/*
- * Taken from FreeBSD
- * Returns a pointer to a new string which is a duplicate of the
- * string, but only copies at most n characters.
- *
- */
-char *strndup(const char *str, size_t n);
-
-#endif
-
#endif
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * window.c: Updates window attributes (X11 hints/properties).
+ *
+ */
#ifndef _WINDOW_H
#define _WINDOW_H
*/
void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop);
+/**
+ * Updates the WM_WINDOW_ROLE
+ *
+ */
+void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
+
#endif
/*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * workspace.c: Modifying workspaces, accessing them, moving containers to
+ * workspaces.
*
*/
+#ifndef _WORKSPACE_H
+#define _WORKSPACE_H
#include "data.h"
#include "tree.h"
#include "randr.h"
-#ifndef _WORKSPACE_H
-#define _WORKSPACE_H
-
/**
* Returns a pointer to the workspace with the given number (starting at 0),
* creating the workspace if necessary (by allocating the necessary amount of
*/
bool workspace_is_visible(Con *ws);
-/** Switches to the given workspace */
-void workspace_show(const char *num);
+/**
+ * Switches to the given workspace
+ *
+ */
+void workspace_show(Con *ws);
+
+/**
+ * Looks up the workspace by name and switches to it.
+ *
+ */
+void workspace_show_by_name(const char *num);
/**
- * Focuses the next workspace.
+ * Returns the next workspace.
*
*/
-void workspace_next();
+Con* workspace_next();
/**
- * Focuses the previous workspace.
+ * Returns the previous workspace.
*
*/
-void workspace_prev();
+Con* workspace_prev();
+
+/**
+ * Focuses the previously focused workspace.
+ *
+ */
+void workspace_back_and_forth();
+
#if 0
/**
/*
* vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * x.c: Interface to X11, transfers our in-memory state to X11 (see also
+ * render.c). Basically a big state machine.
+ *
*/
-
#ifndef _X_H
#define _X_H
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009-2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * xcb.c: Helper functions for easier usage of XCB
*
*/
#ifndef _XCB_H
#define XCB_CURSOR_LEFT_PTR 68
#define XCB_CURSOR_SB_H_DOUBLE_ARROW 108
#define XCB_CURSOR_SB_V_DOUBLE_ARROW 116
+#define XCB_CURSOR_WATCH 150
/* from X11/keysymdef.h */
#define XCB_NUM_LOCK 0xff7f
extern unsigned int xcb_numlock_mask;
-/**
- * Loads a font for usage, also getting its height. If fallback is true,
- * i3 loads 'fixed' or '-misc-*' if the font cannot be found instead of
- * exiting.
- *
- */
-i3Font load_font(const char *pattern, bool fallback);
-
-/**
- * Returns the colorpixel to use for the given hex color (think of HTML).
- *
- * The hex_color has to start with #, for example #FF00FF.
- *
- * NOTE that get_colorpixel() does _NOT_ check the given color code for
- * validity. This has to be done by the caller.
- *
- */
-uint32_t get_colorpixel(char *hex);
-
/**
* Convenience wrapper around xcb_create_window which takes care of depth,
* generating an ID and checking for errors.
xcb_window_t create_window(xcb_connection_t *conn, Rect r, uint16_t window_class,
enum xcursor_cursor_t cursor, bool map, uint32_t mask, uint32_t *values);
-/**
- * Changes a single value in the graphic context (so one doesn’t have to
- * define an array of values)
- *
- */
-void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc,
- uint32_t mask, uint32_t value);
-
/**
* Draws a line from x,y to to_x,to_y using the given color
*
xcb_gcontext_t gc, uint32_t colorpixel, uint32_t x,
uint32_t y, uint32_t width, uint32_t height);
-/**
- * Generates a configure_notify event and sends it to the given window
- * Applications need this to think they’ve configured themselves correctly.
- * The truth is, however, that we will manage them.
- *
- */
-void fake_configure_notify(xcb_connection_t *conn, Rect r, xcb_window_t window, int border_width);
-
/**
* Generates a configure_notify_event with absolute coordinates (relative to
* the X root window, not to the client’s frame) for the given client.
*/
void send_take_focus(xcb_window_t window);
-/**
- * Finds out which modifier mask is the one for numlock, as the user may
- * change this.
- *
- */
-void xcb_get_numlock_mask(xcb_connection_t *conn);
-
/**
* Raises the given window (typically client->frame) above all other windows
*
*/
void xcb_warp_pointer_rect(xcb_connection_t *conn, Rect *rect);
+/**
+ * Set the cursor of the root window to the given cursor id.
+ * This function should only be used if xcursor_supported == false.
+ * Otherwise, use xcursor_set_root_cursor().
+ *
+ */
+void xcb_set_root_cursor(int cursor);
+
#endif
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * xcb_compat.h: uses #define to create aliases for xcb functions which got
+ * renamed. Makes the code work with >= 0.3.8 xcb-util and
+ * older versions.
+ *
+ */
#ifndef _XCB_COMPAT_H
#define _XCB_COMPAT_H
/*
* vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * xcursor.c: libXcursor support for themed cursors.
+ *
*/
#ifndef _XCURSOR_CURSOR_H
#define _XCURSOR_CURSOR_H
XCURSOR_CURSOR_POINTER = 0,
XCURSOR_CURSOR_RESIZE_HORIZONTAL,
XCURSOR_CURSOR_RESIZE_VERTICAL,
+ XCURSOR_CURSOR_WATCH,
XCURSOR_CURSOR_MAX
};
/*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * This is LEGACY code (we support RandR, which can do much more than
+ * Xinerama), but necessary for the poor users of the nVidia binary
+ * driver which does not support RandR in 2011 *sigh*.
*
*/
-#include "data.h"
-
#ifndef _XINERAMA_H
#define _XINERAMA_H
+#include "data.h"
+
/**
* We have just established a connection to the X server and need the initial
* Xinerama information to setup workspaces for each screen.
--- /dev/null
+# Default value so one can compile i3-msg standalone
+TOPDIR=..
+
+include $(TOPDIR)/common.mk
+
+CFLAGS += -I$(TOPDIR)/include
+
+# Depend on the object files of all source-files in src/*.c and on all header files
+FILES=$(patsubst %.c,%.o,$(wildcard *.c))
+HEADERS=$(wildcard *.h)
+
+# Depend on the specific file (.c for each .o) and on all headers
+%.o: %.c ${HEADERS}
+ echo "[libi3] CC $<"
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+
+all: libi3.a
+
+libi3.a: ${FILES}
+ echo "[libi3] AR libi3.a"
+ ar rcs libi3.a ${FILES}
+
+clean:
+ rm -f *.o libi3.a
+
+distclean: clean
--- /dev/null
+Introduction
+============
+
+libi3 is an *INTERNAL* library which contains functions that i3 and related
+tools (i3-msg, i3-input, i3-nagbar, i3-config-wizard, i3bar) use.
+
+It is NOT to be used by other programs.
+
+Structure
+=========
+
+Every function gets its own .c file, which in turn gets compiled into an .o
+object file. Afterwards, all .o files are archived into one static library
+(libi3.a). This library will be linked into all i3 binaries. The linker is able
+to eliminate unused .o files when linking, so only the functions which you
+actually use will be included in the corresponding binary.
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include <xcb/xcb.h>
+#include <xcb/xproto.h>
+
+#include "libi3.h"
+
+/*
+ * Generates a configure_notify event and sends it to the given window
+ * Applications need this to think they’ve configured themselves correctly.
+ * The truth is, however, that we will manage them.
+ *
+ */
+void fake_configure_notify(xcb_connection_t *conn, xcb_rectangle_t r, xcb_window_t window, int border_width) {
+ /* Every X11 event is 32 bytes long. Therefore, XCB will copy 32 bytes.
+ * In order to properly initialize these bytes, we allocate 32 bytes even
+ * though we only need less for an xcb_configure_notify_event_t */
+ void *event = scalloc(32);
+ xcb_configure_notify_event_t *generated_event = event;
+
+ generated_event->event = window;
+ generated_event->window = window;
+ generated_event->response_type = XCB_CONFIGURE_NOTIFY;
+
+ generated_event->x = r.x;
+ generated_event->y = r.y;
+ generated_event->width = r.width;
+ generated_event->height = r.height;
+
+ generated_event->border_width = border_width;
+ generated_event->above_sibling = XCB_NONE;
+ generated_event->override_redirect = false;
+
+ xcb_send_event(conn, false, window, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)generated_event);
+ xcb_flush(conn);
+
+ free(event);
+}
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "libi3.h"
+
+/*
+ * Returns the colorpixel to use for the given hex color (think of HTML). Only
+ * works for true-color (vast majority of cases) at the moment, avoiding a
+ * roundtrip to X11.
+ *
+ * The hex_color has to start with #, for example #FF00FF.
+ *
+ * NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
+ * This has to be done by the caller.
+ *
+ * NOTE that this function may in the future rely on a global xcb_connection_t
+ * variable called 'conn' to be present.
+ *
+ */
+uint32_t get_colorpixel(const char *hex) {
+ char strgroups[3][3] = {{hex[1], hex[2], '\0'},
+ {hex[3], hex[4], '\0'},
+ {hex[5], hex[6], '\0'}};
+ uint8_t r = strtol(strgroups[0], NULL, 16);
+ uint8_t g = strtol(strgroups[1], NULL, 16);
+ uint8_t b = strtol(strgroups[2], NULL, 16);
+
+ return (r << 16 | g << 8 | b);
+}
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <stdint.h>
+#include <stdlib.h>
+#include <xcb/xcb.h>
+#include <xcb/xcb_keysyms.h>
+
+#include "libi3.h"
+
+extern xcb_connection_t *conn;
+
+/*
+ * All-in-one function which returns the modifier mask (XCB_MOD_MASK_*) for the
+ * given keysymbol, for example for XCB_NUM_LOCK (usually configured to mod2).
+ *
+ * This function initiates one round-trip. Use get_mod_mask_for() directly if
+ * you already have the modifier mapping and key symbols.
+ *
+ */
+uint32_t aio_get_mod_mask_for(uint32_t keysym, xcb_key_symbols_t *symbols) {
+ xcb_get_modifier_mapping_cookie_t cookie;
+ xcb_get_modifier_mapping_reply_t *modmap_r;
+
+ xcb_flush(conn);
+
+ /* Get the current modifier mapping (this is blocking!) */
+ cookie = xcb_get_modifier_mapping(conn);
+ if (!(modmap_r = xcb_get_modifier_mapping_reply(conn, cookie, NULL)))
+ return 0;
+
+ uint32_t result = get_mod_mask_for(keysym, symbols, modmap_r);
+ free(modmap_r);
+ return result;
+}
+
+/*
+ * Returns the modifier mask (XCB_MOD_MASK_*) for the given keysymbol, for
+ * example for XCB_NUM_LOCK (usually configured to mod2).
+ *
+ * This function does not initiate any round-trips.
+ *
+ */
+uint32_t get_mod_mask_for(uint32_t keysym,
+ xcb_key_symbols_t *symbols,
+ xcb_get_modifier_mapping_reply_t *modmap_reply) {
+ xcb_keycode_t *codes, *modmap;
+ xcb_keycode_t mod_code;
+
+ modmap = xcb_get_modifier_mapping_keycodes(modmap_reply);
+
+ /* Get the list of keycodes for the given symbol */
+ if (!(codes = xcb_key_symbols_get_keycode(symbols, keysym)))
+ return 0;
+
+ /* Loop through all modifiers (Mod1-Mod5, Shift, Control, Lock) */
+ for (int mod = 0; mod < 8; mod++)
+ for (int j = 0; j < modmap_reply->keycodes_per_modifier; j++) {
+ /* Store the current keycode (for modifier 'mod') */
+ mod_code = modmap[(mod * modmap_reply->keycodes_per_modifier) + j];
+ /* Check if that keycode is in the list of previously resolved
+ * keycodes for our symbol. If so, return the modifier mask. */
+ for (xcb_keycode_t *code = codes; *code; code++) {
+ if (*code != mod_code)
+ continue;
+
+ free(codes);
+ /* This corresponds to the XCB_MOD_MASK_* constants */
+ return (1 << mod);
+ }
+ }
+
+ return 0;
+}
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <limits.h>
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_aux.h>
+
+#include "libi3.h"
+
+/*
+ * Try to get the socket path from X11 and return NULL if it doesn’t work.
+ *
+ * The memory for the socket path is dynamically allocated and has to be
+ * free()d by the caller.
+ *
+ */
+char *socket_path_from_x11() {
+ xcb_connection_t *conn;
+ xcb_intern_atom_cookie_t atom_cookie;
+ xcb_intern_atom_reply_t *atom_reply;
+ int screen;
+ char *socket_path;
+
+ if ((conn = xcb_connect(NULL, &screen)) == NULL ||
+ xcb_connection_has_error(conn))
+ return NULL;
+
+ atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH");
+
+ xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen);
+ xcb_window_t root = root_screen->root;
+
+ atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL);
+ if (atom_reply == NULL)
+ return NULL;
+
+ xcb_get_property_cookie_t prop_cookie;
+ xcb_get_property_reply_t *prop_reply;
+ prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom,
+ XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX);
+ prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL);
+ if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0)
+ return NULL;
+ if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply),
+ (char*)xcb_get_property_value(prop_reply)) == -1)
+ return NULL;
+ xcb_disconnect(conn);
+ return socket_path;
+}
+
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <string.h>
+#include <err.h>
+#include <stdlib.h>
+
+#include "libi3.h"
+
+/*
+ * Connects to the i3 IPC socket and returns the file descriptor for the
+ * socket. die()s if anything goes wrong.
+ *
+ */
+int ipc_connect(const char *socket_path) {
+ int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
+ if (sockfd == -1)
+ err(EXIT_FAILURE, "Could not create socket");
+
+ struct sockaddr_un addr;
+ memset(&addr, 0, sizeof(struct sockaddr_un));
+ addr.sun_family = AF_LOCAL;
+ strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
+ if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
+ err(EXIT_FAILURE, "Could not connect to i3");
+
+ return sockfd;
+}
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <i3/ipc.h>
+
+#include "libi3.h"
+
+/*
+ * Reads a message from the given socket file descriptor and stores its length
+ * (reply_length) as well as a pointer to its contents (reply).
+ *
+ * Returns -1 when read() fails, errno will remain.
+ * Returns -2 when the IPC protocol is violated (invalid magic, unexpected
+ * message type, EOF instead of a message). Additionally, the error will be
+ * printed to stderr.
+ * Returns 0 on success.
+ *
+ */
+int ipc_recv_message(int sockfd, uint32_t message_type,
+ uint32_t *reply_length, uint8_t **reply) {
+ /* Read the message header first */
+ uint32_t to_read = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t);
+ char msg[to_read];
+ char *walk = msg;
+
+ uint32_t read_bytes = 0;
+ while (read_bytes < to_read) {
+ int n = read(sockfd, msg + read_bytes, to_read);
+ if (n == -1)
+ return -1;
+ if (n == 0) {
+ fprintf(stderr, "IPC: received EOF instead of reply\n");
+ return -2;
+ }
+
+ read_bytes += n;
+ to_read -= n;
+ }
+
+ if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) {
+ fprintf(stderr, "IPC: invalid magic in reply\n");
+ return -2;
+ }
+
+ walk += strlen(I3_IPC_MAGIC);
+ *reply_length = *((uint32_t*)walk);
+ walk += sizeof(uint32_t);
+ if (*((uint32_t*)walk) != message_type) {
+ fprintf(stderr, "IPC: unexpected reply type (got %d, expected %d)\n", *((uint32_t*)walk), message_type);
+ return -2;
+ }
+ walk += sizeof(uint32_t);
+
+ *reply = smalloc(*reply_length);
+
+ to_read = *reply_length;
+ read_bytes = 0;
+ while (read_bytes < to_read) {
+ int n = read(sockfd, *reply + read_bytes, to_read);
+ if (n == -1)
+ return -1;
+
+ read_bytes += n;
+ to_read -= n;
+ }
+
+ return 0;
+}
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <err.h>
+
+#include <i3/ipc.h>
+
+#include "libi3.h"
+
+/*
+ * Formats a message (payload) of the given size and type and sends it to i3 via
+ * the given socket file descriptor.
+ *
+ * Returns -1 when write() fails, errno will remain.
+ * Returns 0 on success.
+ *
+ */
+int ipc_send_message(int sockfd, uint32_t message_size,
+ uint32_t message_type, const uint8_t *payload) {
+ int buffer_size = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t) + message_size;
+ char msg[buffer_size];
+ char *walk = msg;
+
+ strncpy(walk, I3_IPC_MAGIC, buffer_size - 1);
+ walk += strlen(I3_IPC_MAGIC);
+ memcpy(walk, &message_size, sizeof(uint32_t));
+ walk += sizeof(uint32_t);
+ memcpy(walk, &message_type, sizeof(uint32_t));
+ walk += sizeof(uint32_t);
+ memcpy(walk, payload, message_size);
+
+ int sent_bytes = 0;
+ int bytes_to_go = buffer_size;
+ while (sent_bytes < bytes_to_go) {
+ int n = write(sockfd, msg + sent_bytes, bytes_to_go);
+ if (n == -1)
+ return -1;
+
+ sent_bytes += n;
+ bytes_to_go -= n;
+ }
+
+ return 0;
+}
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <err.h>
+
+#include "libi3.h"
+
+extern xcb_connection_t *conn;
+
+/*
+ * Loads a font for usage, also getting its height. If fallback is true,
+ * the fonts 'fixed' or '-misc-*' will be loaded instead of exiting.
+ *
+ */
+i3Font load_font(const char *pattern, bool fallback) {
+ i3Font font;
+ xcb_void_cookie_t font_cookie;
+ xcb_list_fonts_with_info_cookie_t info_cookie;
+ xcb_list_fonts_with_info_reply_t *info_reply;
+ xcb_generic_error_t *error;
+
+ /* Send all our requests first */
+ font.id = xcb_generate_id(conn);
+ font_cookie = xcb_open_font_checked(conn, font.id, strlen(pattern), pattern);
+ info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
+
+ /* Check for errors. If errors, fall back to default font. */
+ error = xcb_request_check(conn, font_cookie);
+
+ /* If we fail to open font, fall back to 'fixed' */
+ if (fallback && error != NULL) {
+ ELOG("Could not open font %s (X error %d). Trying fallback to 'fixed'.\n",
+ pattern, error->error_code);
+ pattern = "fixed";
+ font_cookie = xcb_open_font_checked(conn, font.id, strlen(pattern), pattern);
+ info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
+
+ /* Check if we managed to open 'fixed' */
+ error = xcb_request_check(conn, font_cookie);
+
+ /* Fall back to '-misc-*' if opening 'fixed' fails. */
+ if (error != NULL) {
+ ELOG("Could not open fallback font 'fixed', trying with '-misc-*'.\n");
+ pattern = "-misc-*";
+ font_cookie = xcb_open_font_checked(conn, font.id, strlen(pattern), pattern);
+ info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
+
+ if ((error = xcb_request_check(conn, font_cookie)) != NULL)
+ errx(EXIT_FAILURE, "Could open neither requested font nor fallbacks "
+ "(fixed or -misc-*): X11 error %d", error->error_code);
+ }
+ }
+
+ /* Get information (height/name) for this font */
+ if (!(info_reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL)))
+ errx(EXIT_FAILURE, "Could not load font \"%s\"", pattern);
+
+ font.height = info_reply->font_ascent + info_reply->font_descent;
+
+ free(info_reply);
+
+ return font;
+}
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <err.h>
+
+#include "libi3.h"
+
+/*
+ * The s* functions (safe) are wrappers around malloc, strdup, …, which exits if one of
+ * the called functions returns NULL, meaning that there is no more memory available
+ *
+ */
+void *smalloc(size_t size) {
+ void *result = malloc(size);
+ if (result == NULL)
+ err(EXIT_FAILURE, "malloc(%zd)", size);
+ return result;
+}
+
+void *scalloc(size_t size) {
+ void *result = calloc(size, 1);
+ if (result == NULL)
+ err(EXIT_FAILURE, "calloc(%zd)", size);
+ return result;
+}
+
+void *srealloc(void *ptr, size_t size) {
+ void *result = realloc(ptr, size);
+ if (result == NULL && size > 0)
+ err(EXIT_FAILURE, "realloc(%zd)", size);
+ return result;
+}
+
+char *sstrdup(const char *str) {
+ char *result = strdup(str);
+ if (result == NULL)
+ err(EXIT_FAILURE, "strdup()");
+ return result;
+}
+
+int sasprintf(char **strp, const char *fmt, ...) {
+ va_list args;
+ int result;
+
+ va_start(args, fmt);
+ if ((result = vasprintf(strp, fmt, args)) == -1)
+ err(EXIT_FAILURE, "asprintf(%s)", fmt);
+ va_end(args);
+ return result;
+}
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <sys/types.h>
+#include <string.h>
+
+#include "libi3.h"
+
+#if defined(__APPLE__)
+
+/*
+ * Taken from FreeBSD
+ * Returns a pointer to a new string which is a duplicate of the
+ * string, but only copies at most n characters.
+ *
+ */
+char *strndup(const char *str, size_t n) {
+ size_t len;
+ char *copy;
+
+ for (len = 0; len < n && str[len]; len++)
+ continue;
+
+ copy = smalloc(len + 1);
+ memcpy(copy, str, len);
+ copy[len] = '\0';
+ return (copy);
+}
+
+#endif
A2M:=a2x -f manpage --asciidoc-opts="-f asciidoc.conf"
-all: i3.1 i3-msg.1 i3-input.1 i3-nagbar.1 i3-wsbar.1 i3-config-wizard.1 i3-migrate-config-to-v4.1
+all: i3.1 i3-msg.1 i3-input.1 i3-nagbar.1 i3-wsbar.1 i3-config-wizard.1 i3-migrate-config-to-v4.1 i3-sensible-editor.1 i3-sensible-pager.1 i3-sensible-terminal.1
%.1: %.man asciidoc.conf
${A2M} $<
pod2man $^ > $@
clean:
- for file in $$(echo i3 i3-msg i3-input i3-nagbar i3-wsbar i3-config-wizard i3-migrate-config-to-v4); \
+ for file in $$(echo i3 i3-msg i3-input i3-nagbar i3-wsbar i3-config-wizard i3-migrate-config-to-v4 i3-sensible-editor i3-sensible-pager i3-sensible-terminal); \
do \
rm -f $${file}.1 $${file}.html $${file}.xml; \
done
<refentrytitle>{mantitle}</refentrytitle>
<manvolnum>{manvolnum}</manvolnum>
<refmiscinfo class="source">i3</refmiscinfo>
-<refmiscinfo class="version">4.0.2</refmiscinfo>
+<refmiscinfo class="version">4.1</refmiscinfo>
<refmiscinfo class="manual">i3 Manual</refmiscinfo>
</refmeta>
<refnamediv>
i3-input(1)
=========
Michael Stapelberg <michael+i3@stapelberg.de>
-v3.delta, November 2009
+v4.1, September 2011
== NAME
== SYNOPSIS
-i3-input [-s <socket>] [-p <prefix>] [-l <limit>] [-P <prompt>] [-f <font>] [-v]
+i3-input [-s <socket>] [-F <format>] [-l <limit>] [-P <prompt>] [-f <font>] [-v]
== DESCRIPTION
the user, and send it/them to i3. This is useful, for example, for the
mark/goto command.
+The -F option takes a format string. In this string, every occurence of %s is
+replaced by the user input.
+
== EXAMPLE
------------------------------------------------
-i3-input -p 'mark ' -l 1 -P 'Mark: '
+i3-input -F 'mark %s' -l 1 -P 'Mark: '
------------------------------------------------
== ENVIRONMENT
=== I3SOCK
-If no ipc-socket is specified on the commandline, this variable is used
-to determine the path, at wich the unix domain socket is expected, on which
-to connect to i3.
+i3-input handles the different sources of socket paths in the following order:
+
+* I3SOCK environment variable
+* I3SOCK gets overwritten by the -s parameter, if specified
+* if neither are available, i3-input reads the socket path from the X11
+ property, which is the recommended way
+* if everything fails, i3-input tries +/tmp/i3-ipc.sock+
+
+The socket path is necessary to connect to i3 and actually issue the command.
== SEE ALSO
--- /dev/null
+i3-sensible-editor(1)
+===================
+Michael Stapelberg <michael+i3@stapelberg.de>
+v4.1, November 2011
+
+== NAME
+
+i3-sensible-editor - launches $EDITOR with fallbacks
+
+== SYNOPSIS
+
+i3-sensible-editor [arguments]
+
+== DESCRIPTION
+
+i3-sensible-editor is used by i3-nagbar(1) when you click on the edit button.
+
+It tries to start one of the following (in that order):
+
+* $VISUAL
+* $EDITOR
+* nano
+* vim
+* vi
+* emacs
+
+Please don’t complain about the order: If the user has any preference, he will
+have $VISUAL or $EDITOR set.
+
+== SEE ALSO
+
+i3(1)
+
+== AUTHOR
+
+Michael Stapelberg and contributors
--- /dev/null
+i3-sensible-pager(1)
+===================
+Michael Stapelberg <michael+i3@stapelberg.de>
+v4.1, November 2011
+
+== NAME
+
+i3-sensible-pager - launches $PAGER with fallbacks
+
+== SYNOPSIS
+
+i3-sensible-pager [arguments]
+
+== DESCRIPTION
+
+i3-sensible-pager is used by i3-nagbar(1) when you click on the view button.
+
+It tries to start one of the following (in that order):
+
+* $PAGER
+* most
+* less
+* i3-sensible-editor(1)
+
+Please don’t complain about the order: If the user has any preference, he will
+have $PAGER set.
+
+== SEE ALSO
+
+i3(1)
+
+== AUTHOR
+
+Michael Stapelberg and contributors
--- /dev/null
+i3-sensible-terminal(1)
+=======================
+Michael Stapelberg <michael+i3@stapelberg.de>
+v4.1, November 2011
+
+== NAME
+
+i3-sensible-terminal - launches $TERMINAL with fallbacks
+
+== SYNOPSIS
+
+i3-sensible-terminal [arguments]
+
+== DESCRIPTION
+
+i3-sensible-terminal is invoked in the i3 default config to start a terminal.
+This wrapper script is necessary since there is no distribution-independent
+terminal launcher (but for example Debian has x-terminal-emulator).
+Distribution packagers are responsible for shipping this script in a way which
+is appropriate for the distribution.
+
+It tries to start one of the following (in that order):
+
+* $TERMINAL (this is a non-standard variable)
+* xterm
+* urxvt
+* rxvt
+* roxterm
+
+Please don’t complain about the order: If the user has any preference, he will
+have $TERMINAL set or modified his i3 configuration file.
+
+== SEE ALSO
+
+i3(1)
+
+== AUTHOR
+
+Michael Stapelberg and contributors
# exit i3 (logs you out of your X session)
bindsym Mod1+Shift+e exit
-# Start i3bar to display a workspace bar (plus the system information i3status
-# finds out, if available)
-exec i3status | i3bar -d
+# display workspace buttons plus a statusline generated by i3status
+bar {
+ status_command i3status
+}
-------------------------------------------------------------
=== ~/.xsession
* i3 - an improved dynamic tiling window manager
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
+ * assignments.c: Assignments for specific windows (for_window).
+ *
*/
#include "all.h"
*
*/
void run_assignments(i3Window *window) {
- DLOG("Checking assignments...\n");
+ DLOG("Checking if any assignments match this window\n");
/* Check if any assignments match */
Assignment *current;
if (current->type == A_COMMAND) {
DLOG("execute command %s\n", current->dest.command);
char *full_command;
- asprintf(&full_command, "[id=\"%d\"] %s", window->id, current->dest.command);
+ sasprintf(&full_command, "[id=\"%d\"] %s", window->id, current->dest.command);
char *json_result = parse_cmd(full_command);
FREE(full_command);
FREE(json_result);
#include <stdint.h>
#include <xcb/xcb.h>
+#include "log.h"
#include "data.h"
#include "config.h"
-#include "log.h"
#include "util.h"
+#include "libi3.h"
#include "cfgparse.tab.h"
yy_push_state(EAT_WHITESPACE); \
} while (0)
+#define BAR_DOUBLE_COLOR do { \
+ yy_push_state(BAR_COLOR); \
+ yy_push_state(BAR_COLOR); \
+} while (0)
+
%}
EOL (\r?\n)
%s OUTPUT_COND
%s FOR_WINDOW_COND
%s EAT_WHITESPACE
+
%x BUFFER_LINE
+%x BAR
+%x BAR_MODE
+%x BAR_POSITION
+%x BAR_COLORS
+%x BAR_COLOR
+
+%x EXEC
%%
yycolumn = 1;
}
+ /* This part of the lexer handles the bar {} blocks */
+<BAR,BAR_MODE,BAR_POSITION,BAR_COLORS,BAR_COLOR>[ \t]+ { /* ignore whitespace */ ; }
+<BAR>"{" { return '{'; }
+<BAR>"}" { yy_pop_state(); return '}'; }
+<BAR>^[ \t]*#[^\n]* { return TOKCOMMENT; }
+<BAR>output { WS_STRING; return TOK_BAR_OUTPUT; }
+<BAR>tray_output { WS_STRING; return TOK_BAR_TRAY_OUTPUT; }
+<BAR>socket_path { WS_STRING; return TOK_BAR_SOCKET_PATH; }
+<BAR>mode { yy_push_state(BAR_MODE); return TOK_BAR_MODE; }
+<BAR_MODE>hide { yy_pop_state(); return TOK_BAR_HIDE; }
+<BAR_MODE>dock { yy_pop_state(); return TOK_BAR_DOCK; }
+<BAR>position { yy_push_state(BAR_POSITION); return TOK_BAR_POSITION; }
+<BAR_POSITION>bottom { yy_pop_state(); return TOK_BAR_BOTTOM; }
+<BAR_POSITION>top { yy_pop_state(); return TOK_BAR_TOP; }
+<BAR>status_command { WS_STRING; return TOK_BAR_STATUS_COMMAND; }
+<BAR>font { WS_STRING; return TOK_BAR_FONT; }
+<BAR>workspace_buttons { return TOK_BAR_WORKSPACE_BUTTONS; }
+<BAR>verbose { return TOK_BAR_VERBOSE; }
+<BAR>colors { yy_push_state(BAR_COLORS); return TOK_BAR_COLORS; }
+<BAR_COLORS>"{" { return '{'; }
+<BAR_COLORS>"}" { yy_pop_state(); return '}'; }
+<BAR_COLORS>^[ \t]*#[^\n]* { return TOKCOMMENT; }
+<BAR_COLORS>background { yy_push_state(BAR_COLOR); return TOK_BAR_COLOR_BACKGROUND; }
+<BAR_COLORS>statusline { yy_push_state(BAR_COLOR); return TOK_BAR_COLOR_STATUSLINE; }
+<BAR_COLORS>focused_workspace { BAR_DOUBLE_COLOR; return TOK_BAR_COLOR_FOCUSED_WORKSPACE; }
+<BAR_COLORS>active_workspace { BAR_DOUBLE_COLOR; return TOK_BAR_COLOR_ACTIVE_WORKSPACE; }
+<BAR_COLORS>inactive_workspace { BAR_DOUBLE_COLOR; return TOK_BAR_COLOR_INACTIVE_WORKSPACE; }
+<BAR_COLORS>urgent_workspace { BAR_DOUBLE_COLOR; return TOK_BAR_COLOR_URGENT_WORKSPACE; }
+<BAR_COLOR>#[0-9a-fA-F]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return HEXCOLOR; }
+<BAR,BAR_COLORS,BAR_MODE,BAR_POSITION>[a-zA-Z]+ { yylval.string = sstrdup(yytext); return WORD; }
+
+
<FOR_WINDOW_COND>"]" { yy_pop_state(); return ']'; }
+<ASSIGN_COND>"[" {
+ /* this is the case for the new assign syntax
+ * that uses criteria */
+ yy_pop_state();
+ yy_push_state(FOR_WINDOW_COND);
+ /* afterwards we will be in ASSIGN_TARGET_COND */
+ return '[';
+ }
<EAT_WHITESPACE>[ \t]* { yy_pop_state(); }
<WANT_QSTRING>\"[^\"]+\" {
yy_pop_state();
yylval.string = copy;
return STR;
}
-<WANT_STRING>[^\n]+ { BEGIN(INITIAL); yylval.string = sstrdup(yytext); return STR; }
-<OUTPUT_COND>[a-zA-Z0-9_-]+ { yylval.string = sstrdup(yytext); return OUTPUT; }
+<WANT_STRING>[^\n]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return STR; }
+<OUTPUT_COND>[a-zA-Z0-9_-]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return OUTPUT; }
^[ \t]*#[^\n]* { return TOKCOMMENT; }
-<COLOR_COND>[0-9a-fA-F]+ { yylval.string = sstrdup(yytext); return HEX; }
+<COLOR_COND>#[0-9a-fA-F]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return HEXCOLOR; }
<ASSIGN_TARGET_COND>[ \t]*→[ \t]* { BEGIN(WANT_STRING); }
<ASSIGN_TARGET_COND>[ \t]+ { BEGIN(WANT_STRING); }
+<EXEC>--no-startup-id { printf("no startup id\n"); yy_pop_state(); return TOK_NO_STARTUP_ID; }
+<EXEC>. { printf("anything else: *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); }
[0-9]+ { yylval.number = atoi(yytext); return NUMBER; }
+bar { yy_push_state(BAR); return TOK_BAR; }
mode { return TOKMODE; }
bind { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; }
bindcode { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; }
bindsym { yy_push_state(BINDSYM_COND); yy_push_state(EAT_WHITESPACE); return TOKBINDSYM; }
-floating_modifier { BEGIN(INITIAL); return TOKFLOATING_MODIFIER; }
-workspace { BEGIN(INITIAL); return TOKWORKSPACE; }
+floating_modifier { return TOKFLOATING_MODIFIER; }
+workspace { return TOKWORKSPACE; }
output { yy_push_state(OUTPUT_COND); yy_push_state(EAT_WHITESPACE); return TOKOUTPUT; }
-screen {
- /* for compatibility until v3.φ */
- ELOG("Assignments to screens are DEPRECATED and will not work. " \
- "Please replace them with assignments to outputs.\n");
- yy_push_state(OUTPUT_COND); yy_push_state(EAT_WHITESPACE);
- return TOKOUTPUT;
- }
terminal { WS_STRING; return TOKTERMINAL; }
font { WS_STRING; return TOKFONT; }
assign { yy_push_state(ASSIGN_TARGET_COND); yy_push_state(ASSIGN_COND); return TOKASSIGN; }
auto { return TOK_AUTO; }
workspace_layout { return TOK_WORKSPACE_LAYOUT; }
new_window { return TOKNEWWINDOW; }
+new_float { return TOKNEWFLOAT; }
normal { return TOK_NORMAL; }
none { return TOK_NONE; }
1pixel { return TOK_1PIXEL; }
focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; }
force_focus_wrapping { return TOK_FORCE_FOCUS_WRAPPING; }
+force_xinerama { return TOK_FORCE_XINERAMA; }
+workspace_auto_back_and_forth { return TOK_WORKSPACE_AUTO_BAF; }
workspace_bar { return TOKWORKSPACEBAR; }
popup_during_fullscreen { return TOK_POPUP_DURING_FULLSCREEN; }
ignore { return TOK_IGNORE; }
stack-limit { return TOKSTACKLIMIT; }
cols { /* yylval.number = STACK_LIMIT_COLS; */return TOKSTACKLIMIT; }
rows { /* yylval.number = STACK_LIMIT_ROWS; */return TOKSTACKLIMIT; }
-exec { WS_STRING; return TOKEXEC; }
-exec_always { WS_STRING; return TOKEXEC_ALWAYS; }
-client.background { BEGIN(COLOR_COND); yylval.single_color = &config.client.background; return TOKSINGLECOLOR; }
-client.focused { BEGIN(COLOR_COND); yylval.color = &config.client.focused; return TOKCOLOR; }
-client.focused_inactive { BEGIN(COLOR_COND); yylval.color = &config.client.focused_inactive; return TOKCOLOR; }
-client.unfocused { BEGIN(COLOR_COND); yylval.color = &config.client.unfocused; return TOKCOLOR; }
-client.urgent { BEGIN(COLOR_COND); yylval.color = &config.client.urgent; return TOKCOLOR; }
-bar.focused { BEGIN(COLOR_COND); yylval.color = &config.bar.focused; return TOKCOLOR; }
-bar.unfocused { BEGIN(COLOR_COND); yylval.color = &config.bar.unfocused; return TOKCOLOR; }
-bar.urgent { BEGIN(COLOR_COND); yylval.color = &config.bar.urgent; return TOKCOLOR; }
+exec { WS_STRING; yy_push_state(EXEC); yy_push_state(EAT_WHITESPACE); return TOKEXEC; }
+exec_always { WS_STRING; yy_push_state(EXEC); yy_push_state(EAT_WHITESPACE); return TOKEXEC_ALWAYS; }
+client.background { yy_push_state(COLOR_COND); yylval.single_color = &config.client.background; return TOKSINGLECOLOR; }
+client.focused { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.focused; return TOKCOLOR; }
+client.focused_inactive { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.focused_inactive; return TOKCOLOR; }
+client.unfocused { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.unfocused; return TOKCOLOR; }
+client.urgent { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.urgent; return TOKCOLOR; }
+bar.focused { yy_push_state(COLOR_COND); yylval.color = &config.bar.focused; return TOKCOLOR; }
+bar.unfocused { yy_push_state(COLOR_COND); yylval.color = &config.bar.unfocused; return TOKCOLOR; }
+bar.urgent { yy_push_state(COLOR_COND); yylval.color = &config.bar.urgent; return TOKCOLOR; }
Mod1 { yylval.number = BIND_MOD1; return MODIFIER; }
Mod2 { yylval.number = BIND_MOD2; return MODIFIER; }
Mod3 { yylval.number = BIND_MOD3; return MODIFIER; }
class { yy_push_state(WANT_QSTRING); return TOK_CLASS; }
instance { yy_push_state(WANT_QSTRING); return TOK_INSTANCE; }
+window_role { yy_push_state(WANT_QSTRING); return TOK_WINDOW_ROLE; }
id { yy_push_state(WANT_QSTRING); return TOK_ID; }
con_id { yy_push_state(WANT_QSTRING); return TOK_CON_ID; }
con_mark { yy_push_state(WANT_QSTRING); return TOK_MARK; }
title { yy_push_state(WANT_QSTRING); return TOK_TITLE; }
-{EOL} {
+<*>{EOL} {
FREE(context->line_copy);
context->line_number++;
- BEGIN(INITIAL);
yy_push_state(BUFFER_LINE);
}
-<BINDSYM_COND>[ \t]+ { BEGIN(WANT_STRING); }
-<OUTPUT_COND>[ \t]+ { BEGIN(WANT_STRING); }
+<BINDSYM_COND>[ \t]+ { yy_pop_state(); yy_push_state(WANT_STRING); }
+<OUTPUT_COND>[ \t]+ { yy_pop_state(); yy_push_state(WANT_STRING); }
[ \t]+ { /* ignore whitespace */ ; }
\"[^\"]+\" {
/* if ASSIGN_COND then */
if (yy_start_stack_ptr > 0)
yy_pop_state();
- else BEGIN(INITIAL);
/* yylval will be the string, but without quotes */
char *copy = sstrdup(yytext+1);
copy[strlen(copy)-1] = '\0';
yylval.string = copy;
return QUOTEDSTRING;
}
-<ASSIGN_COND>[^ \t\"]+ { BEGIN(ASSIGN_TARGET_COND); yylval.string = sstrdup(yytext); return STR_NG; }
+<ASSIGN_COND>[^ \t\"\[]+ { BEGIN(ASSIGN_TARGET_COND); yylval.string = sstrdup(yytext); return STR_NG; }
<BINDSYM_COND>[a-zA-Z0-9_]+ { yylval.string = sstrdup(yytext); return WORD; }
[a-zA-Z]+ { yylval.string = sstrdup(yytext); return WORD; }
. { return (int)yytext[0]; }
static pid_t configerror_pid = -1;
static Match current_match;
+static Barconfig current_bar;
+/* The pattern which was specified by the user, for example -misc-fixed-*. We
+ * store this in a separate variable because in the i3 config struct we just
+ * store the i3Font. */
+static char *font_pattern;
typedef struct yy_buffer_state *YY_BUFFER_STATE;
extern int yylex(struct context *context);
configerror_pid = -1;
}
+/* We need ev >= 4 for the following code. Since it is not *that* important (it
+ * only makes sure that there are no i3-nagbar instances left behind) we still
+ * support old systems with libev 3. */
+#if EV_VERSION_MAJOR >= 4
+/*
+ * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal
+ * SIGKILL (9) to make sure there are no left-over i3-nagbar processes.
+ *
+ */
+static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) {
+ if (configerror_pid != -1) {
+ LOG("Sending SIGKILL (9) to i3-nagbar with PID %d\n", configerror_pid);
+ kill(configerror_pid, SIGKILL);
+ }
+}
+#endif
+
/*
* Starts an i3-nagbar process which alerts the user that his configuration
* file contains one or more errors. Also offers two buttons: One to launch an
*
*/
static void start_configerror_nagbar(const char *config_path) {
- fprintf(stderr, "Would start i3-nagscreen now\n");
+ if (only_check_config)
+ return;
+
+ fprintf(stderr, "Starting i3-nagbar due to configuration errors\n");
configerror_pid = fork();
if (configerror_pid == -1) {
warn("Could not fork()");
if (configerror_pid == 0) {
char *editaction,
*pageraction;
- if (asprintf(&editaction, TERM_EMU " -e sh -c \"${EDITOR:-vi} \"%s\" && i3-msg reload\"", config_path) == -1)
- exit(1);
- if (asprintf(&pageraction, TERM_EMU " -e sh -c \"${PAGER:-less} \"%s\"\"", errorfilename) == -1)
- exit(1);
+ sasprintf(&editaction, "i3-sensible-terminal -e sh -c \"i3-sensible-editor \\\"%s\\\" && i3-msg reload\"", config_path);
+ sasprintf(&pageraction, "i3-sensible-terminal -e i3-sensible-pager \"%s\"", errorfilename);
char *argv[] = {
NULL, /* will be replaced by the executable path */
+ "-t",
+ (context->has_errors ? "error" : "warning"),
"-m",
- "You have an error in your i3 config file!",
+ (context->has_errors ?
+ "You have an error in your i3 config file!" :
+ "Your config is outdated. Please fix the warnings to make sure everything works."),
"-b",
"edit config",
editaction,
(errorfilename ? "-b" : NULL),
- "show errors",
+ (context->has_errors ? "show errors" : "show warnings"),
pageraction,
NULL
};
ev_child *child = smalloc(sizeof(ev_child));
ev_child_init(child, &nagbar_exited, configerror_pid, 0);
ev_child_start(main_loop, child);
+
+/* We need ev >= 4 for the following code. Since it is not *that* important (it
+ * only makes sure that there are no i3-nagbar instances left behind) we still
+ * support old systems with libev 3. */
+#if EV_VERSION_MAJOR >= 4
+ /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is
+ * still running) */
+ ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup));
+ ev_cleanup_init(cleanup, nagbar_cleanup);
+ ev_cleanup_start(main_loop, cleanup);
+#endif
}
/*
waitpid(configerror_pid, NULL, 0);
}
+/*
+ * Checks for duplicate key bindings (the same keycode or keysym is configured
+ * more than once). If a duplicate binding is found, a message is printed to
+ * stderr and the has_errors variable is set to true, which will start
+ * i3-nagbar.
+ *
+ */
+static void check_for_duplicate_bindings(struct context *context) {
+ Binding *bind, *current;
+ TAILQ_FOREACH(current, bindings, bindings) {
+ TAILQ_FOREACH(bind, bindings, bindings) {
+ /* Abort when we reach the current keybinding, only check the
+ * bindings before */
+ if (bind == current)
+ break;
+
+ /* Check if one is using keysym while the other is using bindsym.
+ * If so, skip. */
+ /* XXX: It should be checked at a later place (when translating the
+ * keysym to keycodes) if there are any duplicates */
+ if ((bind->symbol == NULL && current->symbol != NULL) ||
+ (bind->symbol != NULL && current->symbol == NULL))
+ continue;
+
+ /* If bind is NULL, current has to be NULL, too (see above).
+ * If the keycodes differ, it can't be a duplicate. */
+ if (bind->symbol != NULL &&
+ strcasecmp(bind->symbol, current->symbol) != 0)
+ continue;
+
+ /* Check if the keycodes or modifiers are different. If so, they
+ * can't be duplicate */
+ if (bind->keycode != current->keycode ||
+ bind->mods != current->mods)
+ continue;
+ context->has_errors = true;
+ if (current->keycode != 0) {
+ ELOG("Duplicate keybinding in config file:\n modmask %d with keycode %d, command \"%s\"\n",
+ current->mods, current->keycode, current->command);
+ } else {
+ ELOG("Duplicate keybinding in config file:\n modmask %d with keysym %s, command \"%s\"\n",
+ current->mods, current->symbol, current->command);
+ }
+ }
+ }
+}
+
+static void migrate_i3bar_exec(struct Autostart *exec) {
+ ELOG("**********************************************************************\n");
+ ELOG("IGNORING exec command: %s\n", exec->command);
+ ELOG("It contains \"i3bar\". Since i3 v4.1, i3bar will be automatically started\n");
+ ELOG("for each 'bar' configuration block in your i3 config. Please remove the exec\n");
+ ELOG("line and add the following to your i3 config:\n");
+ ELOG("\n");
+ ELOG(" bar {\n");
+ ELOG(" status_command i3status\n");
+ ELOG(" }\n");
+ ELOG("**********************************************************************\n");
+
+ /* Generate a dummy bar configuration */
+ Barconfig *bar_config = scalloc(sizeof(Barconfig));
+ /* The hard-coded ID is not a problem. It does not conflict with the
+ * auto-generated bar IDs and having multiple hard-coded IDs is irrelevant
+ * – they all just contain status_command = i3status */
+ bar_config->id = sstrdup("migrate-bar");
+ bar_config->status_command = sstrdup("i3status");
+ TAILQ_INSERT_TAIL(&barconfigs, bar_config, configs);
+
+ /* Trigger an i3-nagbar */
+ context->has_warnings = true;
+}
+
void parse_file(const char *f) {
SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
int fd, ret, read_bytes = 0;
exit(1);
}
- if (context->has_errors) {
+ check_for_duplicate_bindings(context);
+
+ /* XXX: The following code will be removed in i3 v4.3 (three releases from
+ * now, as of 2011-10-22) */
+ /* Check for any exec or exec_always lines starting i3bar. We remove these
+ * and add a bar block instead. Additionally, a i3-nagbar warning (not an
+ * error) will be displayed so that users update their config file. */
+ struct Autostart *exec, *next;
+ for (exec = TAILQ_FIRST(&autostarts); exec; ) {
+ next = TAILQ_NEXT(exec, autostarts);
+ if (strstr(exec->command, "i3bar") != NULL) {
+ migrate_i3bar_exec(exec);
+ TAILQ_REMOVE(&autostarts, exec, autostarts);
+ }
+ exec = next;
+ }
+
+ for (exec = TAILQ_FIRST(&autostarts_always); exec; ) {
+ next = TAILQ_NEXT(exec, autostarts_always);
+ if (strstr(exec->command, "i3bar") != NULL) {
+ migrate_i3bar_exec(exec);
+ TAILQ_REMOVE(&autostarts_always, exec, autostarts_always);
+ }
+ exec = next;
+ }
+
+ if (context->has_errors || context->has_warnings) {
start_configerror_nagbar(f);
}
yylex_destroy();
FREE(context->line_copy);
free(context);
+ FREE(font_pattern);
free(new);
free(buf);
%token <string> WORD "<word>"
%token <string> STR "<string>"
%token <string> STR_NG "<string (non-greedy)>"
-%token <string> HEX "<hex>"
+%token <string> HEXCOLOR "#<hex>"
%token <string> OUTPUT "<RandR output>"
%token TOKBINDCODE
%token TOKTERMINAL
%token <color> TOKCOLOR
%token TOKARROW "→"
%token TOKMODE "mode"
+%token TOK_BAR "bar"
%token TOK_ORIENTATION "default_orientation"
%token TOK_HORIZ "horizontal"
%token TOK_VERT "vertical"
%token TOK_AUTO "auto"
%token TOK_WORKSPACE_LAYOUT "workspace_layout"
%token TOKNEWWINDOW "new_window"
+%token TOKNEWFLOAT "new_float"
%token TOK_NORMAL "normal"
%token TOK_NONE "none"
%token TOK_1PIXEL "1pixel"
%token TOKFOCUSFOLLOWSMOUSE "focus_follows_mouse"
%token TOK_FORCE_FOCUS_WRAPPING "force_focus_wrapping"
+%token TOK_FORCE_XINERAMA "force_xinerama"
+%token TOK_WORKSPACE_AUTO_BAF "workspace_auto_back_and_forth"
%token TOKWORKSPACEBAR "workspace_bar"
%token TOK_DEFAULT "default"
%token TOK_STACKING "stacking"
%token TOK_LEAVE_FULLSCREEN "leave_fullscreen"
%token TOK_FOR_WINDOW "for_window"
+%token TOK_BAR_OUTPUT "output (bar)"
+%token TOK_BAR_TRAY_OUTPUT "tray_output"
+%token TOK_BAR_SOCKET_PATH "socket_path"
+%token TOK_BAR_MODE "mode (bar)"
+%token TOK_BAR_HIDE "hide"
+%token TOK_BAR_DOCK "dock"
+%token TOK_BAR_POSITION "position"
+%token TOK_BAR_BOTTOM "bottom"
+%token TOK_BAR_TOP "top"
+%token TOK_BAR_STATUS_COMMAND "status_command"
+%token TOK_BAR_FONT "font (bar)"
+%token TOK_BAR_WORKSPACE_BUTTONS "workspace_buttons"
+%token TOK_BAR_VERBOSE "verbose"
+%token TOK_BAR_COLORS "colors"
+%token TOK_BAR_COLOR_BACKGROUND "background"
+%token TOK_BAR_COLOR_STATUSLINE "statusline"
+%token TOK_BAR_COLOR_FOCUSED_WORKSPACE "focused_workspace"
+%token TOK_BAR_COLOR_ACTIVE_WORKSPACE "active_workspace"
+%token TOK_BAR_COLOR_INACTIVE_WORKSPACE "inactive_workspace"
+%token TOK_BAR_COLOR_URGENT_WORKSPACE "urgent_workspace"
+%token TOK_NO_STARTUP_ID "--no-startup-id"
+
%token TOK_MARK "mark"
%token TOK_CLASS "class"
%token TOK_INSTANCE "instance"
+%token TOK_WINDOW_ROLE "window_role"
%token TOK_ID "id"
%token TOK_CON_ID "con_id"
%token TOK_TITLE "title"
%type <number> layout_mode
%type <number> border_style
%type <number> new_window
+%type <number> new_float
%type <number> colorpixel
%type <number> bool
%type <number> popup_setting
+%type <number> bar_position_position
+%type <number> bar_mode_mode
+%type <number> optional_no_startup_id
%type <string> command
%type <string> word_or_number
+%type <string> qstring_or_number
%type <string> optional_workspace_name
%type <string> workspace_name
%type <string> window_class
bindline
| for_window
| mode
+ | bar
| floating_modifier
| orientation
| workspace_layout
| new_window
+ | new_float
| focus_follows_mouse
| force_focus_wrapping
+ | force_xinerama
+ | workspace_back_and_forth
| workspace_bar
| workspace
| assign
TOK_CLASS '=' STR
{
printf("criteria: class = %s\n", $3);
- current_match.class = $3;
+ current_match.class = regex_new($3);
+ free($3);
}
| TOK_INSTANCE '=' STR
{
printf("criteria: instance = %s\n", $3);
- current_match.instance = $3;
+ current_match.instance = regex_new($3);
+ free($3);
+ }
+ | TOK_WINDOW_ROLE '=' STR
+ {
+ printf("criteria: window_role = %s\n", $3);
+ current_match.role = regex_new($3);
+ free($3);
}
| TOK_CON_ID '=' STR
{
| TOK_MARK '=' STR
{
printf("criteria: mark = %s\n", $3);
- current_match.mark = $3;
+ current_match.mark = regex_new($3);
+ free($3);
}
| TOK_TITLE '=' STR
{
printf("criteria: title = %s\n", $3);
- current_match.title = $3;
+ current_match.title = regex_new($3);
+ free($3);
}
;
-
+qstring_or_number:
+ QUOTEDSTRING
+ | NUMBER { sasprintf(&$$, "%d", $1); }
+ ;
word_or_number:
WORD
| NUMBER
{
- asprintf(&$$, "%d", $1);
+ sasprintf(&$$, "%d", $1);
}
;
}
;
+bar:
+ TOK_BAR '{' barlines '}'
+ {
+ printf("\t new bar configuration finished, saving.\n");
+ /* Generate a unique ID for this bar */
+ current_bar.id = sstrdup("bar-XXXXXX");
+ /* This works similar to mktemp in that it replaces the last six X with
+ * random letters, but without the restriction that the given buffer
+ * has to contain a valid path name. */
+ char *x = current_bar.id + strlen("bar-");
+ while (*x != '\0') {
+ *(x++) = (rand() % 26) + 'a';
+ }
+
+ /* If no font was explicitly set, we use the i3 font as default */
+ if (!current_bar.font && font_pattern)
+ current_bar.font = sstrdup(font_pattern);
+
+ /* Copy the current (static) structure into a dynamically allocated
+ * one, then cleanup our static one. */
+ Barconfig *bar_config = scalloc(sizeof(Barconfig));
+ memcpy(bar_config, ¤t_bar, sizeof(Barconfig));
+ TAILQ_INSERT_TAIL(&barconfigs, bar_config, configs);
+
+ memset(¤t_bar, '\0', sizeof(Barconfig));
+ }
+ ;
+
+barlines:
+ /* empty */
+ | barlines barline
+ ;
+
+barline:
+ comment
+ | bar_status_command
+ | bar_output
+ | bar_tray_output
+ | bar_position
+ | bar_mode
+ | bar_font
+ | bar_workspace_buttons
+ | bar_verbose
+ | bar_socket_path
+ | bar_colors
+ | bar_color_background
+ | bar_color_statusline
+ | bar_color_focused_workspace
+ | bar_color_active_workspace
+ | bar_color_inactive_workspace
+ | bar_color_urgent_workspace
+ ;
+
+bar_status_command:
+ TOK_BAR_STATUS_COMMAND STR
+ {
+ DLOG("should add status command %s\n", $2);
+ FREE(current_bar.status_command);
+ current_bar.status_command = $2;
+ }
+ ;
+
+bar_output:
+ TOK_BAR_OUTPUT STR
+ {
+ DLOG("bar output %s\n", $2);
+ int new_outputs = current_bar.num_outputs + 1;
+ current_bar.outputs = srealloc(current_bar.outputs, sizeof(char*) * new_outputs);
+ current_bar.outputs[current_bar.num_outputs] = $2;
+ current_bar.num_outputs = new_outputs;
+ }
+ ;
+
+bar_tray_output:
+ TOK_BAR_TRAY_OUTPUT STR
+ {
+ DLOG("tray %s\n", $2);
+ FREE(current_bar.tray_output);
+ current_bar.tray_output = $2;
+ }
+ ;
+
+bar_position:
+ TOK_BAR_POSITION bar_position_position
+ {
+ DLOG("position %d\n", $2);
+ current_bar.position = $2;
+ }
+ ;
+
+bar_position_position:
+ TOK_BAR_TOP { $$ = P_TOP; }
+ | TOK_BAR_BOTTOM { $$ = P_BOTTOM; }
+ ;
+
+bar_mode:
+ TOK_BAR_MODE bar_mode_mode
+ {
+ DLOG("mode %d\n", $2);
+ current_bar.mode = $2;
+ }
+ ;
+
+bar_mode_mode:
+ TOK_BAR_HIDE { $$ = M_HIDE; }
+ | TOK_BAR_DOCK { $$ = M_DOCK; }
+ ;
+
+bar_font:
+ TOK_BAR_FONT STR
+ {
+ DLOG("font %s\n", $2);
+ FREE(current_bar.font);
+ current_bar.font = $2;
+ }
+ ;
+
+bar_workspace_buttons:
+ TOK_BAR_WORKSPACE_BUTTONS bool
+ {
+ DLOG("workspace_buttons = %d\n", $2);
+ /* We store this inverted to make the default setting right when
+ * initializing the struct with zero. */
+ current_bar.hide_workspace_buttons = !($2);
+ }
+ ;
+
+bar_verbose:
+ TOK_BAR_VERBOSE bool
+ {
+ DLOG("verbose = %d\n", $2);
+ current_bar.verbose = $2;
+ }
+ ;
+
+bar_socket_path:
+ TOK_BAR_SOCKET_PATH STR
+ {
+ DLOG("socket_path = %s\n", $2);
+ FREE(current_bar.socket_path);
+ current_bar.socket_path = $2;
+ }
+ ;
+
+bar_colors:
+ TOK_BAR_COLORS '{' barlines '}'
+ {
+ /* At the moment, the TOK_BAR_COLORS token is only to make the config
+ * friendlier for humans. We might change this in the future if it gets
+ * more complex. */
+ }
+ ;
+
+bar_color_background:
+ TOK_BAR_COLOR_BACKGROUND HEXCOLOR
+ {
+ DLOG("background = %s\n", $2);
+ current_bar.colors.background = $2;
+ }
+ ;
+
+bar_color_statusline:
+ TOK_BAR_COLOR_STATUSLINE HEXCOLOR
+ {
+ DLOG("statusline = %s\n", $2);
+ current_bar.colors.statusline = $2;
+ }
+ ;
+
+bar_color_focused_workspace:
+ TOK_BAR_COLOR_FOCUSED_WORKSPACE HEXCOLOR HEXCOLOR
+ {
+ DLOG("focused_ws = %s and %s\n", $2, $3);
+ current_bar.colors.focused_workspace_text = $2;
+ current_bar.colors.focused_workspace_bg = $3;
+ }
+ ;
+
+bar_color_active_workspace:
+ TOK_BAR_COLOR_ACTIVE_WORKSPACE HEXCOLOR HEXCOLOR
+ {
+ DLOG("active_ws = %s and %s\n", $2, $3);
+ current_bar.colors.active_workspace_text = $2;
+ current_bar.colors.active_workspace_bg = $3;
+ }
+ ;
+
+bar_color_inactive_workspace:
+ TOK_BAR_COLOR_INACTIVE_WORKSPACE HEXCOLOR HEXCOLOR
+ {
+ DLOG("inactive_ws = %s and %s\n", $2, $3);
+ current_bar.colors.inactive_workspace_text = $2;
+ current_bar.colors.inactive_workspace_bg = $3;
+ }
+ ;
+
+bar_color_urgent_workspace:
+ TOK_BAR_COLOR_URGENT_WORKSPACE HEXCOLOR HEXCOLOR
+ {
+ DLOG("urgent_ws = %s and %s\n", $2, $3);
+ current_bar.colors.urgent_workspace_text = $2;
+ current_bar.colors.urgent_workspace_bg = $3;
+ }
+ ;
+
floating_modifier:
TOKFLOATING_MODIFIER binding_modifiers
{
}
;
+new_float:
+ TOKNEWFLOAT border_style
+ {
+ DLOG("new floating windows should start with border style %d\n", $2);
+ config.default_floating_border = $2;
+ }
+ ;
+
border_style:
TOK_NORMAL { $$ = BS_NORMAL; }
| TOK_NONE { $$ = BS_NONE; }
}
;
+force_xinerama:
+ TOK_FORCE_XINERAMA bool
+ {
+ DLOG("force xinerama = %d\n", $2);
+ config.force_xinerama = $2;
+ }
+ ;
+
+workspace_back_and_forth:
+ TOK_WORKSPACE_AUTO_BAF bool
+ {
+ DLOG("automatic workspace back-and-forth = %d\n", $2);
+ config.workspace_auto_back_and_forth = $2;
+ }
+ ;
+
workspace_bar:
TOKWORKSPACEBAR bool
{
;
workspace:
- TOKWORKSPACE NUMBER TOKOUTPUT OUTPUT optional_workspace_name
+ TOKWORKSPACE qstring_or_number TOKOUTPUT OUTPUT optional_workspace_name
{
- int ws_num = $2;
- if (ws_num < 1) {
- DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
- } else {
- char *ws_name = NULL;
- if ($5 == NULL) {
- asprintf(&ws_name, "%d", ws_num);
- } else {
- ws_name = $5;
- }
+ char *ws_name = $2;
+
+ if ($5 != NULL) {
+ ELOG("The old (v3) syntax workspace <number> output <output> <name> is deprecated.\n");
+ ELOG("Please use the new syntax: workspace \"<workspace>\" output <output>\n");
+ ELOG("In your case, the following should work:\n");
+ ELOG(" workspace \"%s\" output %s\n", $5, $4);
+ ws_name = $5;
+ context->has_warnings = true;
+ }
- DLOG("Should assign workspace %s to output %s\n", ws_name, $4);
- /* Check for earlier assignments of the same workspace so that we
- * don’t have assignments of a single workspace to different
- * outputs */
- struct Workspace_Assignment *assignment;
- bool duplicate = false;
- TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
- if (strcasecmp(assignment->name, ws_name) == 0) {
- ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n",
- ws_name);
- assignment->output = $4;
- duplicate = true;
- }
- }
- if (!duplicate) {
- assignment = scalloc(sizeof(struct Workspace_Assignment));
- assignment->name = ws_name;
+ DLOG("Assigning workspace \"%s\" to output \"%s\"\n", ws_name, $4);
+ /* Check for earlier assignments of the same workspace so that we
+ * don’t have assignments of a single workspace to different
+ * outputs */
+ struct Workspace_Assignment *assignment;
+ bool duplicate = false;
+ TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
+ if (strcasecmp(assignment->name, ws_name) == 0) {
+ ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n",
+ ws_name);
assignment->output = $4;
- TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
+ duplicate = true;
}
}
+ if (!duplicate) {
+ assignment = scalloc(sizeof(struct Workspace_Assignment));
+ assignment->name = ws_name;
+ assignment->output = $4;
+ TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
+ }
}
| TOKWORKSPACE NUMBER workspace_name
{
assign:
TOKASSIGN window_class STR
{
+ /* This is the old, deprecated form of assignments. It’s provided for
+ * compatibility in version (4.1, 4.2, 4.3) and will be removed
+ * afterwards. It triggers an i3-nagbar warning starting from 4.1. */
+ ELOG("You are using the old assign syntax (without criteria). "
+ "Please see the User's Guide for the new syntax and fix "
+ "your config file.\n");
+ context->has_warnings = true;
printf("assignment of %s to *%s*\n", $2, $3);
char *workspace = $3;
char *criteria = $2;
char *separator = NULL;
if ((separator = strchr(criteria, '/')) != NULL) {
*(separator++) = '\0';
- match->title = sstrdup(separator);
+ char *pattern;
+ sasprintf(&pattern, "(?i)%s", separator);
+ match->title = regex_new(pattern);
+ free(pattern);
+ printf(" title = %s\n", separator);
+ }
+ if (*criteria != '\0') {
+ char *pattern;
+ sasprintf(&pattern, "(?i)%s", criteria);
+ match->class = regex_new(pattern);
+ free(pattern);
+ printf(" class = %s\n", criteria);
}
- if (*criteria != '\0')
- match->class = sstrdup(criteria);
free(criteria);
- printf(" class = %s\n", match->class);
- printf(" title = %s\n", match->title);
-
/* Compatibility with older versions: If the assignment target starts
* with ~, we create the equivalent of:
*
assignment->dest.workspace = workspace;
TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
}
+ | TOKASSIGN match STR
+ {
+ if (match_is_empty(¤t_match)) {
+ ELOG("Match is empty, ignoring this assignment\n");
+ break;
+ }
+ printf("new assignment, using above criteria, to workspace %s\n", $3);
+ Assignment *assignment = scalloc(sizeof(Assignment));
+ assignment->match = current_match;
+ assignment->type = A_TO_WORKSPACE;
+ assignment->dest.workspace = $3;
+ TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
+ }
;
window_class:
;
exec:
- TOKEXEC STR
+ TOKEXEC optional_no_startup_id STR
{
struct Autostart *new = smalloc(sizeof(struct Autostart));
- new->command = $2;
+ new->command = $3;
+ new->no_startup_id = $2;
TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
}
;
exec_always:
- TOKEXEC_ALWAYS STR
+ TOKEXEC_ALWAYS optional_no_startup_id STR
{
struct Autostart *new = smalloc(sizeof(struct Autostart));
- new->command = $2;
+ new->command = $3;
+ new->no_startup_id = $2;
TAILQ_INSERT_TAIL(&autostarts_always, new, autostarts_always);
}
;
+optional_no_startup_id:
+ /* empty */ { $$ = false; }
+ | TOK_NO_STARTUP_ID { $$ = true; }
+ ;
+
terminal:
TOKTERMINAL STR
{
{
config.font = load_font($2, true);
printf("font %s\n", $2);
- free($2);
+ FREE(font_pattern);
+ font_pattern = $2;
}
;
;
colorpixel:
- '#' HEX
+ HEXCOLOR
{
- char *hex;
- if (asprintf(&hex, "#%s", $2) == -1)
- die("asprintf()");
- free($2);
- $$ = get_colorpixel(hex);
- free(hex);
+ $$ = get_colorpixel($1);
+ free($1);
}
;
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009-2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * src/click.c: Contains the handlers for button press (mouse click) events
- * because they are quite large.
+ * click.c: Button press (mouse click) events.
*
*/
+#include "all.h"
+
#include <time.h>
#include <math.h>
#include <X11/XKBlib.h>
-#include "all.h"
-
-
typedef enum { CLICK_BORDER = 0, CLICK_DECORATION = 1, CLICK_INSIDE = 2 } click_destination_t;
/*
* then calls resize_graphical_handler().
*
*/
-static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press_event_t *event) {
+static bool tiling_resize_for_border(Con *con, border_t border, const xcb_button_press_event_t *event) {
DLOG("border = %d\n", border);
char way = (border == BORDER_TOP || border == BORDER_LEFT ? 'p' : 'n');
orientation_t orientation = (border == BORDER_TOP || border == BORDER_BOTTOM ? VERT : HORIZ);
* to the client).
*
*/
-static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *event) {
+static bool floating_mod_on_tiled_client(Con *con, const xcb_button_press_event_t *event) {
/* The client is in tiling layout. We can still initiate a resize with the
* right mouse button, by chosing the border which is the most near one to
* the position of the mouse pointer */
* Finds out which border was clicked on and calls tiling_resize_for_border().
*
*/
-static bool tiling_resize(Con *con, xcb_button_press_event_t *event, click_destination_t dest) {
+static bool tiling_resize(Con *con, const xcb_button_press_event_t *event, const click_destination_t dest) {
/* check if this was a click on the window border (and on which one) */
Rect bsr = con_border_style_rect(con);
DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x\n",
event->event_x, event->event_y, con, event->event);
DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width);
- if (dest == CLICK_DECORATION)
+ if (dest == CLICK_DECORATION) {
+ /* The user clicked on a window decoration. We ignore the following case:
+ * The container is a h-split, tabbed or stacked container with > 1
+ * window. Decorations will end up next to each other and the user
+ * expects to switch to a window by clicking on its decoration. */
+
+ /* Since the container might either be the child *or* already a split
+ * container (in the case of a nested split container), we need to make
+ * sure that we are dealing with the split container here. */
+ if (con_is_leaf(con) && con->parent->type == CT_CON)
+ con = con->parent;
+
+ if ((con->layout == L_STACKED ||
+ con->layout == L_TABBED ||
+ con->orientation == HORIZ) &&
+ con_num_children(con) > 1) {
+ DLOG("Not handling this resize, this container has > 1 child.\n");
+ return false;
+ }
return tiling_resize_for_border(con, BORDER_TOP, event);
+ }
if (event->event_x >= 0 && event->event_x <= bsr.x &&
event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height)
* functions for resizing/dragging.
*
*/
-static int route_click(Con *con, xcb_button_press_event_t *event, bool mod_pressed, click_destination_t dest) {
+static int route_click(Con *con, const xcb_button_press_event_t *event, const bool mod_pressed, const click_destination_t dest) {
DLOG("--> click properties: mod = %d, destination = %d\n", mod_pressed, dest);
DLOG("--> OUTCOME = %p\n", con);
DLOG("type = %d, name = %s\n", con->type, con->name);
Con *con;
DLOG("Button %d pressed on window 0x%08x\n", event->state, event->event);
+ last_timestamp = event->time;
+
const uint32_t mod = config.floating_modifier;
- bool mod_pressed = (mod != 0 && (event->state & mod) == mod);
+ const bool mod_pressed = (mod != 0 && (event->state & mod) == mod);
DLOG("floating_mod = %d, detail = %d\n", mod_pressed, event->detail);
if ((con = con_by_window_id(event->event)))
return route_click(con, event, mod_pressed, CLICK_INSIDE);
#include "config.h"
#include "util.h"
+#include "libi3.h"
int cmdyycolumn = 1;
/* handle a quoted string or everything up to the next whitespace */
%s WANT_QSTRING
+%x EXEC
+
%x BUFFER_LINE
%%
cmdyycolumn = 1;
}
- /* the next/prev tokens are here to recognize them *before* handling
- * strings ('workspace' command) */
-next { return TOK_NEXT; }
-prev { return TOK_PREV; }
+ /* the next/prev/back_and_forth tokens are here to recognize them *before*
+ * handling strings ('workspace' command) */
+next { BEGIN(INITIAL); return TOK_NEXT; }
+prev { BEGIN(INITIAL); return TOK_PREV; }
+back_and_forth { BEGIN(INITIAL); return TOK_BACK_AND_FORTH; }
<WANT_STRING>\"[^\"]+\" {
BEGIN(INITIAL);
<EAT_WHITESPACE>[ \t]* { yy_pop_state(); }
[ \t]* { /* ignore whitespace */ ; }
-exec { WS_STRING; return TOK_EXEC; }
+<EXEC>--no-startup-id { printf("no startup id\n"); yy_pop_state(); return TOK_NO_STARTUP_ID; }
+<EXEC>. { printf("anything else: *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); }
+exec { WS_STRING; yy_push_state(EXEC); yy_push_state(EAT_WHITESPACE); return TOK_EXEC; }
exit { return TOK_EXIT; }
reload { return TOK_RELOAD; }
restart { return TOK_RESTART; }
toggle { return TOK_TOGGLE; }
mode_toggle { return TOK_MODE_TOGGLE; }
workspace { WS_STRING; return TOK_WORKSPACE; }
+output { WS_STRING; return TOK_OUTPUT; }
focus { return TOK_FOCUS; }
move { return TOK_MOVE; }
open { return TOK_OPEN; }
class { BEGIN(WANT_QSTRING); return TOK_CLASS; }
instance { BEGIN(WANT_QSTRING); return TOK_INSTANCE; }
+window_role { BEGIN(WANT_QSTRING); return TOK_WINDOW_ROLE; }
id { BEGIN(WANT_QSTRING); return TOK_ID; }
con_id { BEGIN(WANT_QSTRING); return TOK_CON_ID; }
con_mark { BEGIN(WANT_QSTRING); return TOK_MARK; }
*
* cmdparse.y: the parser for commands you send to i3 (or bind on keys)
*
-
*/
#include <sys/types.h>
#include <sys/stat.h>
context->filename = "cmd";
if (cmdyyparse() != 0) {
fprintf(stderr, "Could not parse command\n");
- asprintf(&json_output, "{\"success\":false, \"error\":\"%s at position %d\"}",
- context->compact_error, context->first_column);
+ sasprintf(&json_output, "{\"success\":false, \"error\":\"%s at position %d\"}",
+ context->compact_error, context->first_column);
FREE(context->line_copy);
FREE(context->compact_error);
free(context);
%token TOK_ENABLE "enable"
%token TOK_DISABLE "disable"
%token TOK_WORKSPACE "workspace"
+%token TOK_OUTPUT "output"
%token TOK_TOGGLE "toggle"
%token TOK_FOCUS "focus"
%token TOK_MOVE "move"
%token TOK_OR "or"
%token TOK_PPT "ppt"
%token TOK_NOP "nop"
+%token TOK_BACK_AND_FORTH "back_and_forth"
+%token TOK_NO_STARTUP_ID "--no-startup-id"
%token TOK_CLASS "class"
%token TOK_INSTANCE "instance"
+%token TOK_WINDOW_ROLE "window_role"
%token TOK_ID "id"
%token TOK_CON_ID "con_id"
%token TOK_TITLE "title"
%type <number> resize_way
%type <number> resize_tiling
%type <number> optional_kill_mode
+%type <number> optional_no_startup_id
%%
}
} else if (current_match.mark != NULL && current->con->mark != NULL &&
- strcasecmp(current_match.mark, current->con->mark) == 0) {
+ regex_matches(current_match.mark, current->con->mark)) {
printf("match by mark\n");
- TAILQ_INSERT_TAIL(&owindows, current, owindows);
-
+ TAILQ_INSERT_TAIL(&owindows, current, owindows);
} else {
if (current->con->window == NULL)
continue;
TOK_CLASS '=' STR
{
printf("criteria: class = %s\n", $3);
- current_match.class = $3;
+ current_match.class = regex_new($3);
+ free($3);
}
| TOK_INSTANCE '=' STR
{
printf("criteria: instance = %s\n", $3);
- current_match.instance = $3;
+ current_match.instance = regex_new($3);
+ free($3);
+ }
+ | TOK_WINDOW_ROLE '=' STR
+ {
+ printf("criteria: window_role = %s\n", $3);
+ current_match.role = regex_new($3);
+ free($3);
}
| TOK_CON_ID '=' STR
{
| TOK_MARK '=' STR
{
printf("criteria: mark = %s\n", $3);
- current_match.mark = $3;
+ current_match.mark = regex_new($3);
+ free($3);
}
| TOK_TITLE '=' STR
{
printf("criteria: title = %s\n", $3);
- current_match.title = $3;
+ current_match.title = regex_new($3);
+ free($3);
}
;
;
exec:
- TOK_EXEC STR
+ TOK_EXEC optional_no_startup_id STR
{
- printf("should execute %s\n", $2);
- start_application($2);
- free($2);
+ char *command = $3;
+ bool no_startup_id = $2;
+
+ printf("should execute %s, no_startup_id = %d\n", command, no_startup_id);
+ start_application(command, no_startup_id);
+ free($3);
}
;
+optional_no_startup_id:
+ /* empty */ { $$ = false; }
+ | TOK_NO_STARTUP_ID { $$ = true; }
+ ;
+
exit:
TOK_EXIT
{
ELOG("You have to specify which window/container should be focused.\n");
ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n");
- asprintf(&json_output, "{\"success\":false, \"error\":\"You have to "
- "specify which window/container should be focused\"}");
+ sasprintf(&json_output, "{\"success\":false, \"error\":\"You have to "
+ "specify which window/container should be focused\"}");
break;
}
int count = 0;
TAILQ_FOREACH(current, &owindows, owindows) {
Con *ws = con_get_workspace(current->con);
- workspace_show(ws->name);
+
+ /* If the container is not on the current workspace,
+ * workspace_show() will switch to a different workspace and (if
+ * enabled) trigger a mouse pointer warp to the currently focused
+ * container (!) on the target workspace.
+ *
+ * Therefore, before calling workspace_show(), we make sure that
+ * 'current' will be focused on the workspace. However, we cannot
+ * just con_focus(current) because then the pointer will not be
+ * warped at all (the code thinks we are already there).
+ *
+ * So we focus 'current' to make it the currently focused window of
+ * the target workspace, then revert focus. */
+ Con *currently_focused = focused;
+ con_focus(current->con);
+ con_focus(currently_focused);
+
+ /* Now switch to the workspace, then focus */
+ workspace_show(ws);
LOG("focusing %p / %s\n", current->con, current->con->name);
con_focus(current->con);
count++;
workspace:
TOK_WORKSPACE TOK_NEXT
{
- workspace_next();
+ workspace_show(workspace_next());
tree_render();
}
| TOK_WORKSPACE TOK_PREV
{
- workspace_prev();
+ workspace_show(workspace_prev());
+ tree_render();
+ }
+ | TOK_WORKSPACE TOK_BACK_AND_FORTH
+ {
+ workspace_back_and_forth();
tree_render();
}
| TOK_WORKSPACE STR
{
printf("should switch to workspace %s\n", $2);
- workspace_show($2);
+
+ Con *ws = con_get_workspace(focused);
+
+ /* Check if the command wants to switch to the current workspace */
+ if (strcmp(ws->name, $2) == 0) {
+ printf("This workspace is already focused.\n");
+ if (config.workspace_auto_back_and_forth) {
+ workspace_back_and_forth();
+ free($2);
+ tree_render();
+ }
+ break;
+ }
+
+ workspace_show_by_name($2);
free($2);
tree_render();
printf("opening new container\n");
Con *con = tree_open_con(NULL, NULL);
con_focus(con);
- asprintf(&json_output, "{\"success\":true, \"id\":%ld}", (long int)con);
+ sasprintf(&json_output, "{\"success\":true, \"id\":%ld}", (long int)con);
tree_render();
}
;
move:
- TOK_MOVE direction
+ TOK_MOVE direction resize_px
{
- printf("moving in direction %d\n", $2);
- tree_move($2);
+ int direction = $2;
+ int px = $3;
+
+ /* TODO: make 'move' work with criteria. */
+ printf("moving in direction %d\n", direction);
+ if (con_is_floating(focused)) {
+ printf("floating move with %d pixels\n", px);
+ if (direction == TOK_LEFT) {
+ focused->parent->rect.x -= px;
+ } else if (direction == TOK_RIGHT) {
+ focused->parent->rect.x += px;
+ } else if (direction == TOK_UP) {
+ focused->parent->rect.y -= px;
+ } else if (direction == TOK_DOWN) {
+ focused->parent->rect.y += px;
+ }
+ } else {
+ tree_move(direction);
+ }
tree_render();
}
{
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(¤t_match) && focused->type == CT_WORKSPACE)
+ break;
+
printf("should move window to workspace %s\n", $3);
/* get the workspace */
Con *ws = workspace_get($3, NULL);
tree_render();
}
+ | TOK_MOVE TOK_WORKSPACE TOK_NEXT
+ {
+ owindow *current;
+
+ /* get the workspace */
+ Con *ws = workspace_next();
+
+ HANDLE_EMPTY_MATCH;
+
+ TAILQ_FOREACH(current, &owindows, owindows) {
+ printf("matching: %p / %s\n", current->con, current->con->name);
+ con_move_to_workspace(current->con, ws, true, false);
+ }
+
+ tree_render();
+ }
+ | TOK_MOVE TOK_WORKSPACE TOK_PREV
+ {
+ owindow *current;
+
+ /* get the workspace */
+ Con *ws = workspace_prev();
+
+ HANDLE_EMPTY_MATCH;
+
+ TAILQ_FOREACH(current, &owindows, owindows) {
+ printf("matching: %p / %s\n", current->con, current->con->name);
+ con_move_to_workspace(current->con, ws, true, false);
+ }
+
+ tree_render();
+ }
+ | TOK_MOVE TOK_OUTPUT STR
+ {
+ owindow *current;
+
+ printf("should move window to output %s", $3);
+
+ HANDLE_EMPTY_MATCH;
+
+ /* get the output */
+ Output *current_output = NULL;
+ Output *output;
+
+ TAILQ_FOREACH(current, &owindows, owindows)
+ current_output = get_output_containing(current->con->rect.x, current->con->rect.y);
+
+ assert(current_output != NULL);
+
+ if (strcasecmp($3, "up") == 0)
+ output = get_output_next(D_UP, current_output);
+ else if (strcasecmp($3, "down") == 0)
+ output = get_output_next(D_DOWN, current_output);
+ else if (strcasecmp($3, "left") == 0)
+ output = get_output_next(D_LEFT, current_output);
+ else if (strcasecmp($3, "right") == 0)
+ output = get_output_next(D_RIGHT, current_output);
+ else
+ output = get_output_by_name($3);
+ free($3);
+
+ if (!output)
+ break;
+
+ /* get visible workspace on output */
+ Con *ws = NULL;
+ GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
+ if (!ws)
+ break;
+
+ TAILQ_FOREACH(current, &owindows, owindows) {
+ printf("matching: %p / %s\n", current->con, current->con->name);
+ con_move_to_workspace(current->con, ws, true, false);
+ }
+
+ tree_render();
+ }
;
append_layout:
double percentage = 1.0 / children;
LOG("default percentage = %f\n", percentage);
+ orientation_t orientation = current->parent->orientation;
+
+ if ((orientation == HORIZ &&
+ (direction == TOK_UP || direction == TOK_DOWN)) ||
+ (orientation == VERT &&
+ (direction == TOK_LEFT || direction == TOK_RIGHT))) {
+ LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
+ (orientation == HORIZ ? "horizontal" : "vertical"));
+ break;
+ }
+
if (direction == TOK_UP || direction == TOK_LEFT) {
other = TAILQ_PREV(current, nodes_head, nodes);
} else {
}
if (other == TAILQ_END(workspaces)) {
LOG("No other container in this direction found, cannot resize.\n");
- return 0;
+ break;
}
LOG("other->percent = %f\n", other->percent);
LOG("current->percent before = %f\n", current->percent);
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
- * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * con.c contains all functions which deal with containers directly (creating
- * containers, searching containers, getting specific properties from
- * containers, …).
+ * con.c: Functions which deal with containers directly (creating containers,
+ * searching containers, getting specific properties from containers,
+ * …).
*
*/
#include "all.h"
* workspace or a new split container with the configured
* workspace_layout).
*/
- if (con->window != NULL && parent->type == CT_WORKSPACE) {
+ if (con->window != NULL &&
+ parent->type == CT_WORKSPACE &&
+ config.default_layout != L_DEFAULT) {
DLOG("Parent is a workspace. Applying default layout...\n");
Con *target = workspace_attach_to(parent);
con->urgent = false;
workspace_update_urgent_flag(con_get_workspace(con));
}
- DLOG("con_focus done = %p\n", con);
}
/*
Con *con_for_window(Con *con, i3Window *window, Match **store_match) {
Con *child;
Match *match;
- DLOG("searching con for window %p starting at con %p\n", window, con);
- DLOG("class == %s\n", window->class_class);
+ //DLOG("searching con for window %p starting at con %p\n", window, con);
+ //DLOG("class == %s\n", window->class_class);
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
TAILQ_FOREACH(match, &(child->swallow_head), matches) {
* focused. Must do before attaching because workspace_show checks to see
* if focused container is in its area. */
if (workspace_is_visible(workspace)) {
- workspace_show(workspace->name);
+ workspace_show(workspace);
/* Don’t warp if told so (when dragging floating windows with the
* mouse for example) */
/* Descend focus stack in case focus_next is a workspace which can
* occur if we move to the same workspace. Also show current workspace
* to ensure it is focused. */
- workspace_show(con_get_workspace(focus_next)->name);
+ workspace_show(con_get_workspace(focus_next));
con_focus(con_descend_focused(focus_next));
}
/* Every container 'above' (in the hierarchy) the workspace content should
* not be closed when the last child was removed */
- if (con->type == CT_WORKSPACE ||
- con->type == CT_OUTPUT ||
+ if (con->type == CT_OUTPUT ||
con->type == CT_ROOT ||
con->type == CT_DOCKAREA) {
DLOG("not handling, type = %d\n", con->type);
return;
}
+ /* For workspaces, close them only if they're not visible anymore */
+ if (con->type == CT_WORKSPACE) {
+ int children = con_num_children(con);
+ if (children == 0 && !workspace_is_visible(con)) {
+ LOG("Closing old workspace (%p / %s), it is empty\n", con, con->name);
+ tree_close(con, DONT_KILL_WINDOW, false, false);
+ ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
+ ewmh_update_workarea();
+ }
+ return;
+ }
+
/* TODO: check if this container would swallow any other client and
* don’t close it automatically. */
int children = con_num_children(con);
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * src/config.c: Contains all functions handling the configuration file (calling
- * the parser (src/cfgparse.y) with the correct path, switching key bindings
- * mode).
+ * config.c: Configuration file (calling the parser (src/cfgparse.y) with the
+ * correct path, switching key bindings mode).
*
*/
+#include "all.h"
/* We need Xlib for XStringToKeysym */
#include <X11/Xlib.h>
-#include "all.h"
-
char *current_configpath = NULL;
Config config;
struct modes_head modes;
+struct barconfig_head barconfigs = TAILQ_HEAD_INITIALIZER(barconfigs);
/**
* Ungrabs all keys, to be called before re-grabbing the keys because of a
xdg_config_home = "~/.config";
xdg_config_home = resolve_tilde(xdg_config_home);
- if (asprintf(&config_path, "%s/i3/config", xdg_config_home) == -1)
- die("asprintf() failed");
+ sasprintf(&config_path, "%s/i3/config", xdg_config_home);
free(xdg_config_home);
if (path_exists(config_path))
char *tok = strtok(buf, ":");
while (tok != NULL) {
tok = resolve_tilde(tok);
- if (asprintf(&config_path, "%s/i3/config", tok) == -1)
- die("asprintf() failed");
+ sasprintf(&config_path, "%s/i3/config", tok);
free(tok);
if (path_exists(config_path)) {
free(buf);
*
*/
void load_configuration(xcb_connection_t *conn, const char *override_configpath, bool reload) {
- if (reload) {
- /* First ungrab the keys */
- ungrab_all_keys(conn);
+ if (reload) {
+ /* First ungrab the keys */
+ ungrab_all_keys(conn);
- struct Mode *mode;
- Binding *bind;
- while (!SLIST_EMPTY(&modes)) {
- mode = SLIST_FIRST(&modes);
- FREE(mode->name);
-
- /* Clear the old binding list */
- bindings = mode->bindings;
- while (!TAILQ_EMPTY(bindings)) {
- bind = TAILQ_FIRST(bindings);
- TAILQ_REMOVE(bindings, bind, bindings);
- FREE(bind->translated_to);
- FREE(bind->command);
- FREE(bind);
- }
- FREE(bindings);
- SLIST_REMOVE(&modes, mode, Mode, modes);
- }
+ struct Mode *mode;
+ Binding *bind;
+ while (!SLIST_EMPTY(&modes)) {
+ mode = SLIST_FIRST(&modes);
+ FREE(mode->name);
+
+ /* Clear the old binding list */
+ bindings = mode->bindings;
+ while (!TAILQ_EMPTY(bindings)) {
+ bind = TAILQ_FIRST(bindings);
+ TAILQ_REMOVE(bindings, bind, bindings);
+ FREE(bind->translated_to);
+ FREE(bind->command);
+ FREE(bind);
+ }
+ FREE(bindings);
+ SLIST_REMOVE(&modes, mode, Mode, modes);
+ }
-#if 0
- struct Assignment *assign;
- while (!TAILQ_EMPTY(&assignments)) {
- assign = TAILQ_FIRST(&assignments);
- FREE(assign->windowclass_title);
- TAILQ_REMOVE(&assignments, assign, assignments);
- FREE(assign);
- }
-#endif
+ struct Assignment *assign;
+ while (!TAILQ_EMPTY(&assignments)) {
+ assign = TAILQ_FIRST(&assignments);
+ if (assign->type == A_TO_WORKSPACE)
+ FREE(assign->dest.workspace);
+ else if (assign->type == A_TO_OUTPUT)
+ FREE(assign->dest.output);
+ else if (assign->type == A_COMMAND)
+ FREE(assign->dest.command);
+ match_free(&(assign->match));
+ TAILQ_REMOVE(&assignments, assign, assignments);
+ FREE(assign);
+ }
+
+ /* Clear bar configs */
+ Barconfig *barconfig;
+ while (!TAILQ_EMPTY(&barconfigs)) {
+ barconfig = TAILQ_FIRST(&barconfigs);
+ FREE(barconfig->id);
+ for (int c = 0; c < barconfig->num_outputs; c++)
+ free(barconfig->outputs[c]);
+ FREE(barconfig->outputs);
+ FREE(barconfig->tray_output);
+ FREE(barconfig->socket_path);
+ FREE(barconfig->status_command);
+ FREE(barconfig->font);
+ FREE(barconfig->colors.background);
+ FREE(barconfig->colors.statusline);
+ FREE(barconfig->colors.focused_workspace_text);
+ FREE(barconfig->colors.focused_workspace_bg);
+ FREE(barconfig->colors.active_workspace_text);
+ FREE(barconfig->colors.active_workspace_bg);
+ FREE(barconfig->colors.inactive_workspace_text);
+ FREE(barconfig->colors.inactive_workspace_bg);
+ FREE(barconfig->colors.urgent_workspace_text);
+ FREE(barconfig->colors.urgent_workspace_bg);
+ TAILQ_REMOVE(&barconfigs, barconfig, configs);
+ FREE(barconfig);
+ }
- /* Clear workspace names */
+ /* Clear workspace names */
#if 0
- Workspace *ws;
- TAILQ_FOREACH(ws, workspaces, workspaces)
- workspace_set_name(ws, NULL);
+ Workspace *ws;
+ TAILQ_FOREACH(ws, workspaces, workspaces)
+ workspace_set_name(ws, NULL);
#endif
- }
+ }
- SLIST_INIT(&modes);
+ SLIST_INIT(&modes);
- struct Mode *default_mode = scalloc(sizeof(struct Mode));
- default_mode->name = sstrdup("default");
- default_mode->bindings = scalloc(sizeof(struct bindings_head));
- TAILQ_INIT(default_mode->bindings);
- SLIST_INSERT_HEAD(&modes, default_mode, modes);
+ struct Mode *default_mode = scalloc(sizeof(struct Mode));
+ default_mode->name = sstrdup("default");
+ default_mode->bindings = scalloc(sizeof(struct bindings_head));
+ TAILQ_INIT(default_mode->bindings);
+ SLIST_INSERT_HEAD(&modes, default_mode, modes);
- bindings = default_mode->bindings;
+ bindings = default_mode->bindings;
#define REQUIRED_OPTION(name) \
- if (config.name == NULL) \
- die("You did not specify required configuration option " #name "\n");
+ if (config.name == NULL) \
+ die("You did not specify required configuration option " #name "\n");
- /* Clear the old config or initialize the data structure */
- memset(&config, 0, sizeof(config));
+ /* Clear the old config or initialize the data structure */
+ memset(&config, 0, sizeof(config));
- /* Initialize default colors */
+ /* Initialize default colors */
#define INIT_COLOR(x, cborder, cbackground, ctext) \
- do { \
- x.border = get_colorpixel(cborder); \
- x.background = get_colorpixel(cbackground); \
- x.text = get_colorpixel(ctext); \
- } while (0)
-
- config.client.background = get_colorpixel("#000000");
- INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff");
- INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff");
- INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888");
- INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff");
- INIT_COLOR(config.bar.focused, "#4c7899", "#285577", "#ffffff");
- INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888");
- INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff");
-
- config.default_border = BS_NORMAL;
- /* Set default_orientation to NO_ORIENTATION for auto orientation. */
- config.default_orientation = NO_ORIENTATION;
-
- parse_configuration(override_configpath);
-
- if (reload) {
- translate_keysyms();
- grab_all_keys(conn, false);
- }
+ do { \
+ x.border = get_colorpixel(cborder); \
+ x.background = get_colorpixel(cbackground); \
+ x.text = get_colorpixel(ctext); \
+ } while (0)
+
+ config.client.background = get_colorpixel("#000000");
+ INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff");
+ INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff");
+ INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888");
+ INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff");
+ INIT_COLOR(config.bar.focused, "#4c7899", "#285577", "#ffffff");
+ INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888");
+ INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff");
+
+ config.default_border = BS_NORMAL;
+ config.default_floating_border = BS_NORMAL;
+ /* Set default_orientation to NO_ORIENTATION for auto orientation. */
+ config.default_orientation = NO_ORIENTATION;
+
+ parse_configuration(override_configpath);
+
+ if (reload) {
+ translate_keysyms();
+ grab_all_keys(conn, false);
+ }
- if (config.font.id == 0) {
- ELOG("You did not specify required configuration option \"font\"\n");
- config.font = load_font("fixed", true);
- }
+ if (config.font.id == 0) {
+ ELOG("You did not specify required configuration option \"font\"\n");
+ config.font = load_font("fixed", true);
+ }
#if 0
- /* Set an empty name for every workspace which got no name */
- Workspace *ws;
- TAILQ_FOREACH(ws, workspaces, workspaces) {
- if (ws->name != NULL) {
- /* If the font was not specified when the workspace name
- * was loaded, we need to predict the text width now */
- if (ws->text_width == 0)
- ws->text_width = predict_text_width(global_conn,
- config.font, ws->name, ws->name_len);
- continue;
- }
-
- workspace_set_name(ws, NULL);
- }
+ /* Set an empty name for every workspace which got no name */
+ Workspace *ws;
+ TAILQ_FOREACH(ws, workspaces, workspaces) {
+ if (ws->name != NULL) {
+ /* If the font was not specified when the workspace name
+ * was loaded, we need to predict the text width now */
+ if (ws->text_width == 0)
+ ws->text_width = predict_text_width(global_conn,
+ config.font, ws->name, ws->name_len);
+ continue;
+ }
+
+ workspace_set_name(ws, NULL);
+ }
#endif
}
/*
- * vim:ts=8:expandtab
+ * vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * debug.c: Contains debugging functions, especially FormatEvent, which prints unhandled events.
- * This code is from xcb-util.
+ * debug.c: Debugging functions, especially FormatEvent, which prints unhandled
+ * events. This code is from xcb-util.
*
*/
#include <stdio.h>
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009-2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * ewmh.c: Functions to get/set certain EWMH properties easily.
+ * ewmh.c: Get/set certain EWMH properties easily.
*
*/
-
#include "all.h"
/*
*
*/
void ewmh_update_client_list_stacking(xcb_window_t *stack, int num_windows) {
- DLOG("Updating _NET_CLIENT_LIST_STACKING\n");
xcb_change_property(
conn,
XCB_PROP_MODE_REPLACE,
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * src/floating.c: contains all functions for handling floating clients
+ * floating.c: Floating windows.
*
*/
-
-
#include "all.h"
extern xcb_connection_t *conn;
}
char *name;
- asprintf(&name, "[i3 con] floatingcon around %p", con);
+ sasprintf(&name, "[i3 con] floatingcon around %p", con);
x_set_name(nc, name);
free(name);
con->percent = 1.0;
con->floating = FLOATING_USER_ON;
+ /* 4: set the border style as specified with new_float */
+ if (automatic)
+ con->border_style = config.default_floating_border;
+
TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes);
TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused);
}
DRAGGING_CB(drag_window_callback) {
- struct xcb_button_press_event_t *event = extra;
+ const struct xcb_button_press_event_t *event = extra;
/* Reposition the client correctly while moving */
con->rect.x = old_rect->x + (new_x - event->root_x);
* Calls the drag_pointer function with the drag_window callback
*
*/
-void floating_drag_window(Con *con, xcb_button_press_event_t *event) {
+void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
DLOG("floating_drag_window\n");
/* Push changes before dragging, so that the window gets raised now and not
*
*/
struct resize_window_callback_params {
- border_t corner;
- bool proportional;
- xcb_button_press_event_t *event;
+ const border_t corner;
+ const bool proportional;
+ const xcb_button_press_event_t *event;
};
DRAGGING_CB(resize_window_callback) {
- struct resize_window_callback_params *params = extra;
- xcb_button_press_event_t *event = params->event;
+ const struct resize_window_callback_params *params = extra;
+ const xcb_button_press_event_t *event = params->event;
border_t corner = params->corner;
int32_t dest_x = con->rect.x;
* Calls the drag_pointer function with the resize_window callback
*
*/
-void floating_resize_window(Con *con, bool proportional,
- xcb_button_press_event_t *event) {
+void floating_resize_window(Con *con, const bool proportional,
+ const xcb_button_press_event_t *event) {
DLOG("floating_resize_window\n");
/* corner saves the nearest corner to the original click. It contains
* the event and the new coordinates (x, y).
*
*/
-void drag_pointer(Con *con, xcb_button_press_event_t *event, xcb_window_t
- confine_to, border_t border, callback_t callback, void *extra)
+void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
+ confine_to, border_t border, callback_t callback, const void *extra)
{
uint32_t new_x, new_y;
Rect old_rect;
}
#if 0
-/*
- * Changes focus in the given direction for floating clients.
- *
- * Changing to the left/right means going to the previous/next floating client,
- * changing to top/bottom means cycling through the Z-index.
- *
- */
-void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, direction_t direction) {
- DLOG("floating focus\n");
-
- if (direction == D_LEFT || direction == D_RIGHT) {
- /* Go to the next/previous floating client */
- Client *client;
-
- while ((client = (direction == D_LEFT ? TAILQ_PREV(currently_focused, floating_clients_head, floating_clients) :
- TAILQ_NEXT(currently_focused, floating_clients))) !=
- TAILQ_END(&(currently_focused->workspace->floating_clients))) {
- if (!client->floating)
- continue;
- set_focus(conn, client, true);
- return;
- }
- }
-}
-
/*
* Moves the client 10px to the specified direction.
*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
- * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * handlers.c: Small handlers for various events (keypresses, focus changes,
+ * …).
*
*/
-#include <time.h>
+#include "all.h"
+#include <time.h>
#include <xcb/randr.h>
-
#include <X11/XKBlib.h>
-
-#include "all.h"
+#define SN_API_NOT_YET_FROZEN 1
+#include <libsn/sn-monitor.h>
int randr_base = -1;
*
*/
static int handle_key_press(xcb_key_press_event_t *event) {
+
+ last_timestamp = event->time;
+
DLOG("Keypress %d, state raw = %d\n", event->detail, event->state);
/* Remove the numlock bit, all other bits are modifiers we can bind to */
/* Focus the output on which the user moved his cursor */
Con *old_focused = focused;
- con_focus(con_descend_focused(output_get_content(output->con)));
+ Con *next = con_descend_focused(output_get_content(output->con));
+ /* Since we are switching outputs, this *must* be a different workspace, so
+ * call workspace_show() */
+ workspace_show(con_get_workspace(next));
+ con_focus(next);
/* If the focus changed, we re-render to get updated decorations */
if (old_focused != focused)
static int handle_enter_notify(xcb_enter_notify_event_t *event) {
Con *con;
+ last_timestamp = event->time;
+
DLOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n",
event->event, event->mode, event->detail, event->sequence);
DLOG("coordinates %d, %d\n", event->event_x, event->event_y);
if (config.disable_focus_follows_mouse)
return 1;
+ /* Get the currently focused workspace to check if the focus change also
+ * involves changing workspaces. If so, we need to call workspace_show() to
+ * correctly update state and send the IPC event. */
+ Con *ws = con_get_workspace(con);
+ if (ws != con_get_workspace(focused))
+ workspace_show(ws);
+
con_focus(con_descend_focused(con));
tree_render();
*
*/
static int handle_motion_notify(xcb_motion_notify_event_t *event) {
+
+ last_timestamp = event->time;
+
/* Skip events where the pointer was over a child window, we are only
* interested in events on the root window. */
if (event->child != 0)
DLOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n");
xcb_refresh_keyboard_mapping(keysyms, event);
- xcb_get_numlock_mask(conn);
+ xcb_numlock_mask = aio_get_mod_mask_for(XCB_NUM_LOCK, keysyms);
ungrab_all_keys(conn);
translate_keysyms();
return true;
}
+/*
+ * Called when a window changes its WM_WINDOW_ROLE.
+ *
+ */
+static bool handle_windowrole_change(void *data, xcb_connection_t *conn, uint8_t state,
+ xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
+ Con *con;
+ if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
+ return false;
+
+ window_update_role(con->window, prop, false);
+
+ return true;
+}
+
#if 0
/*
* Updates the client’s WM_CLASS property
* Handle client messages (EWMH)
*
*/
-static int handle_client_message(xcb_client_message_event_t *event) {
+static void handle_client_message(xcb_client_message_event_t *event) {
+ /* If this is a startup notification ClientMessage, the library will handle
+ * it and call our monitor_event() callback. */
+ if (sn_xcb_display_process_event(sndisplay, (xcb_generic_event_t*)event))
+ return;
+
LOG("ClientMessage for window 0x%08x\n", event->window);
if (event->type == A__NET_WM_STATE) {
if (event->format != 32 || event->data.data32[1] != A__NET_WM_STATE_FULLSCREEN) {
DLOG("atom in clientmessage is %d, fullscreen is %d\n",
event->data.data32[1], A__NET_WM_STATE_FULLSCREEN);
DLOG("not about fullscreen atom\n");
- return 0;
+ return;
}
Con *con = con_by_window_id(event->window);
if (con == NULL) {
DLOG("Could not get window for client message\n");
- return 0;
+ return;
}
/* Check if the fullscreen state should be toggled */
tree_render();
x_push_changes(croot);
+ } else if (event->type == A_I3_SYNC) {
+ DLOG("i3 sync, yay\n");
+ xcb_window_t window = event->data.data32[0];
+ uint32_t rnd = event->data.data32[1];
+ DLOG("Sending random value %d back to X11 window 0x%08x\n", rnd, window);
+
+ void *reply = scalloc(32);
+ xcb_client_message_event_t *ev = reply;
+
+ ev->response_type = XCB_CLIENT_MESSAGE;
+ ev->window = window;
+ ev->type = A_I3_SYNC;
+ ev->format = 32;
+ ev->data.data32[0] = window;
+ ev->data.data32[1] = rnd;
+
+ xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char*)ev);
+ xcb_flush(conn);
+ free(reply);
} else {
- ELOG("unhandled clientmessage\n");
- return 0;
+ DLOG("unhandled clientmessage\n");
+ return;
}
-
- return 1;
}
#if 0
}
DLOG("focus is different, updating decorations\n");
+
+ /* Get the currently focused workspace to check if the focus change also
+ * involves changing workspaces. If so, we need to call workspace_show() to
+ * correctly update state and send the IPC event. */
+ Con *ws = con_get_workspace(con);
+ if (ws != con_get_workspace(focused))
+ workspace_show(ws);
+
con_focus(con);
/* We update focused_id because we don’t need to set focus again */
focused_id = event->event;
{ 0, 128, handle_windowname_change_legacy },
{ 0, UINT_MAX, handle_normal_hints },
{ 0, UINT_MAX, handle_clientleader_change },
- { 0, UINT_MAX, handle_transient_for }
+ { 0, UINT_MAX, handle_transient_for },
+ { 0, 128, handle_windowrole_change }
};
#define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t))
*
*/
void property_handlers_init() {
+
+ sn_monitor_context_new(sndisplay, conn_screen, startup_monitor_event, NULL, NULL);
+
property_handlers[0].atom = A__NET_WM_NAME;
property_handlers[1].atom = XCB_ATOM_WM_HINTS;
property_handlers[2].atom = XCB_ATOM_WM_NAME;
property_handlers[3].atom = XCB_ATOM_WM_NORMAL_HINTS;
property_handlers[4].atom = A_WM_CLIENT_LEADER;
property_handlers[5].atom = XCB_ATOM_WM_TRANSIENT_FOR;
+ property_handlers[6].atom = A_WM_WINDOW_ROLE;
}
static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) {
}
if (handler == NULL) {
- DLOG("Unhandled property notify for atom %d (0x%08x)\n", atom, atom);
+ //DLOG("Unhandled property notify for atom %d (0x%08x)\n", atom, atom);
return;
}
handle_focus_in((xcb_focus_in_event_t*)event);
break;
- case XCB_PROPERTY_NOTIFY:
- DLOG("Property notify\n");
+ case XCB_PROPERTY_NOTIFY: {
xcb_property_notify_event_t *e = (xcb_property_notify_event_t*)event;
+ last_timestamp = e->time;
property_notify(e->state, e->window, e->atom);
break;
+ }
default:
- DLOG("Unhandled event of type %d\n", type);
+ //DLOG("Unhandled event of type %d\n", type);
break;
}
}
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009-2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * ipc.c: Everything about the UNIX domain sockets for IPC
+ * ipc.c: UNIX domain socket IPC (initialization, client handling, protocol).
*
*/
+#include "all.h"
+
#include <sys/socket.h>
#include <sys/un.h>
#include <fcntl.h>
#include <yajl/yajl_parse.h>
#include <yajl/yajl_version.h>
-#include "all.h"
-
char *current_socketpath = NULL;
/* Shorter names for all those yajl_gen_* functions */
return result;
}
-static void ipc_send_message(int fd, const unsigned char *payload,
- int message_type, int message_size) {
- int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) +
- sizeof(uint32_t) + message_size;
- char msg[buffer_size];
- char *walk = msg;
-
- strncpy(walk, "i3-ipc", buffer_size - 1);
- walk += strlen("i3-ipc");
- memcpy(walk, &message_size, sizeof(uint32_t));
- walk += sizeof(uint32_t);
- memcpy(walk, &message_type, sizeof(uint32_t));
- walk += sizeof(uint32_t);
- memcpy(walk, payload, message_size);
-
- int sent_bytes = 0;
- int bytes_to_go = buffer_size;
- while (sent_bytes < bytes_to_go) {
- int n = write(fd, msg + sent_bytes, bytes_to_go);
- if (n == -1) {
- DLOG("write() failed: %s\n", strerror(errno));
- return;
- }
-
- sent_bytes += n;
- bytes_to_go -= n;
- }
-}
-
/*
* Sends the specified event to all IPC clients which are currently connected
* and subscribed to this kind of event.
if (!interested)
continue;
- ipc_send_message(current->fd, (const unsigned char*)payload,
- message_type, strlen(payload));
+ ipc_send_message(current->fd, strlen(payload), message_type, (const uint8_t*)payload);
}
}
*/
void ipc_shutdown() {
ipc_client *current;
- TAILQ_FOREACH(current, &all_clients, clients) {
+ while (!TAILQ_EMPTY(&all_clients)) {
+ current = TAILQ_FIRST(&all_clients);
shutdown(current->fd, SHUT_RDWR);
close(current->fd);
+ TAILQ_REMOVE(&all_clients, current, clients);
+ free(current);
}
}
/* If no reply was provided, we just use the default success message */
if (reply == NULL)
reply = "{\"success\":true}";
- ipc_send_message(fd, (const unsigned char*)reply,
- I3_IPC_REPLY_TYPE_COMMAND, strlen(reply));
+ ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_COMMAND, (const uint8_t*)reply);
FREE(save_reply);
}
ystr("urgent");
y(bool, con->urgent);
+ if (con->mark != NULL) {
+ ystr("mark");
+ ystr(con->mark);
+ }
+
ystr("focused");
y(bool, (con == focused));
#endif
y(get_buf, &payload, &length);
- ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_TREE, length);
+ ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_TREE, payload);
y(free);
}
+
/*
* Formats the reply message for a GET_WORKSPACES request and sends it to the
* client
#endif
y(get_buf, &payload, &length);
- ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_WORKSPACES, length);
+ ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_WORKSPACES, payload);
y(free);
}
#endif
y(get_buf, &payload, &length);
- ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_OUTPUTS, length);
+ ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_OUTPUTS, payload);
+ y(free);
+}
+
+/*
+ * Formats the reply message for a GET_MARKS request and sends it to the
+ * client
+ *
+ */
+IPC_HANDLER(get_marks) {
+#if YAJL_MAJOR >= 2
+ yajl_gen gen = yajl_gen_alloc(NULL);
+#else
+ yajl_gen gen = yajl_gen_alloc(NULL, NULL);
+#endif
+ y(array_open);
+
+ Con *con;
+ TAILQ_FOREACH(con, &all_cons, all_cons)
+ if (con->mark != NULL)
+ ystr(con->mark);
+
+ y(array_close);
+
+ const unsigned char *payload;
+#if YAJL_MAJOR >= 2
+ size_t length;
+#else
+ unsigned int length;
+#endif
+ y(get_buf, &payload, &length);
+
+ ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_MARKS, payload);
+ y(free);
+}
+
+/*
+ * Formats the reply message for a GET_BAR_CONFIG request and sends it to the
+ * client.
+ *
+ */
+IPC_HANDLER(get_bar_config) {
+#if YAJL_MAJOR >= 2
+ yajl_gen gen = yajl_gen_alloc(NULL);
+#else
+ yajl_gen gen = yajl_gen_alloc(NULL, NULL);
+#endif
+
+ /* If no ID was passed, we return a JSON array with all IDs */
+ if (message_size == 0) {
+ y(array_open);
+ Barconfig *current;
+ TAILQ_FOREACH(current, &barconfigs, configs) {
+ ystr(current->id);
+ }
+ y(array_close);
+
+ const unsigned char *payload;
+#if YAJL_MAJOR >= 2
+ size_t length;
+#else
+ unsigned int length;
+#endif
+ y(get_buf, &payload, &length);
+
+ ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_BAR_CONFIG, payload);
+ y(free);
+ return;
+ }
+
+ /* To get a properly terminated buffer, we copy
+ * message_size bytes out of the buffer */
+ char *bar_id = scalloc(message_size + 1);
+ strncpy(bar_id, (const char*)message, message_size);
+ LOG("IPC: looking for config for bar ID \"%s\"\n", bar_id);
+ Barconfig *current, *config = NULL;
+ TAILQ_FOREACH(current, &barconfigs, configs) {
+ if (strcmp(current->id, bar_id) != 0)
+ continue;
+
+ config = current;
+ break;
+ }
+
+ y(map_open);
+
+ if (!config) {
+ /* If we did not find a config for the given ID, the reply will contain
+ * a null 'id' field. */
+ ystr("id");
+ y(null);
+ } else {
+ ystr("id");
+ ystr(config->id);
+
+ if (config->num_outputs > 0) {
+ ystr("outputs");
+ y(array_open);
+ for (int c = 0; c < config->num_outputs; c++)
+ ystr(config->outputs[c]);
+ y(array_close);
+ }
+
+#define YSTR_IF_SET(name) \
+ do { \
+ if (config->name) { \
+ ystr( # name); \
+ ystr(config->name); \
+ } \
+ } while (0)
+
+ YSTR_IF_SET(tray_output);
+ YSTR_IF_SET(socket_path);
+
+ ystr("mode");
+ if (config->mode == M_HIDE)
+ ystr("hide");
+ else ystr("dock");
+
+ ystr("position");
+ if (config->position == P_BOTTOM)
+ ystr("bottom");
+ else ystr("top");
+
+ YSTR_IF_SET(status_command);
+ YSTR_IF_SET(font);
+
+ ystr("workspace_buttons");
+ y(bool, !config->hide_workspace_buttons);
+
+ ystr("verbose");
+ y(bool, config->verbose);
+
+#undef YSTR_IF_SET
+#define YSTR_IF_SET(name) \
+ do { \
+ if (config->colors.name) { \
+ ystr( # name); \
+ ystr(config->colors.name); \
+ } \
+ } while (0)
+
+ ystr("colors");
+ y(map_open);
+ YSTR_IF_SET(background);
+ YSTR_IF_SET(statusline);
+ YSTR_IF_SET(focused_workspace_text);
+ YSTR_IF_SET(focused_workspace_bg);
+ YSTR_IF_SET(active_workspace_text);
+ YSTR_IF_SET(active_workspace_bg);
+ YSTR_IF_SET(inactive_workspace_text);
+ YSTR_IF_SET(inactive_workspace_bg);
+ YSTR_IF_SET(urgent_workspace_text);
+ YSTR_IF_SET(urgent_workspace_bg);
+ y(map_close);
+
+#undef YSTR_IF_SET
+ }
+
+ y(map_close);
+
+ const unsigned char *payload;
+#if YAJL_MAJOR >= 2
+ size_t length;
+#else
+ unsigned int length;
+#endif
+ y(get_buf, &payload, &length);
+
+ ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_BAR_CONFIG, payload);
y(free);
}
yajl_free_error(p, err);
const char *reply = "{\"success\":false}";
- ipc_send_message(fd, (const unsigned char*)reply,
- I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply));
+ ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t*)reply);
yajl_free(p);
return;
}
yajl_free(p);
const char *reply = "{\"success\":true}";
- ipc_send_message(fd, (const unsigned char*)reply,
- I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply));
+ ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t*)reply);
}
/* The index of each callback function corresponds to the numeric
* value of the message type (see include/i3/ipc.h) */
-handler_t handlers[5] = {
+handler_t handlers[7] = {
handle_command,
handle_get_workspaces,
handle_subscribe,
handle_get_outputs,
- handle_tree
+ handle_tree,
+ handle_get_marks,
+ handle_get_bar_config
};
/*
/* We can call TAILQ_REMOVE because we break out of the
* TAILQ_FOREACH afterwards */
TAILQ_REMOVE(&all_clients, current, clients);
+ free(current);
break;
}
ev_io_stop(EV_A_ w);
+ free(w);
DLOG("IPC: client disconnected\n");
return;
ev_io_init(package, ipc_receive_message, client, EV_READ);
ev_io_start(EV_A_ package);
- DLOG("IPC: new client connected\n");
+ DLOG("IPC: new client connected on fd %d\n", w->fd);
ipc_client *new = scalloc(sizeof(ipc_client));
new->fd = client;
/*
* vim:ts=4:sw=4:expandtab
*
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * load_layout.c: Restore (parts of) the layout, for example after an inplace
+ * restart.
+ *
*/
+#include "all.h"
+
#include <yajl/yajl_common.h>
#include <yajl/yajl_gen.h>
#include <yajl/yajl_parse.h>
#include <yajl/yajl_version.h>
-#include "all.h"
-
/* TODO: refactor the whole parsing thing */
static char *last_key;
LOG("sticky_group of this container is %s\n", json_node->sticky_group);
} else if (strcasecmp(last_key, "orientation") == 0) {
char *buf = NULL;
- asprintf(&buf, "%.*s", (int)len, val);
+ sasprintf(&buf, "%.*s", (int)len, val);
if (strcasecmp(buf, "none") == 0)
json_node->orientation = NO_ORIENTATION;
else if (strcasecmp(buf, "horizontal") == 0)
free(buf);
} else if (strcasecmp(last_key, "border") == 0) {
char *buf = NULL;
- asprintf(&buf, "%.*s", (int)len, val);
+ sasprintf(&buf, "%.*s", (int)len, val);
if (strcasecmp(buf, "none") == 0)
json_node->border_style = BS_NONE;
else if (strcasecmp(buf, "1pixel") == 0)
free(buf);
} else if (strcasecmp(last_key, "layout") == 0) {
char *buf = NULL;
- asprintf(&buf, "%.*s", (int)len, val);
+ sasprintf(&buf, "%.*s", (int)len, val);
if (strcasecmp(buf, "default") == 0)
json_node->layout = L_DEFAULT;
else if (strcasecmp(buf, "stacked") == 0)
json_node->layout = L_OUTPUT;
else LOG("Unhandled \"layout\": %s\n", buf);
free(buf);
+ } else if (strcasecmp(last_key, "mark") == 0) {
+ char *buf = NULL;
+ sasprintf(&buf, "%.*s", (int)len, val);
+ json_node->mark = buf;
}
}
return 1;
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * src/log.c: handles the setting of loglevels, contains the logging functions.
+ * log.c: Setting of loglevels, logging functions.
*
*/
#include <stdarg.h>
*
*/
void vlog(char *fmt, va_list args) {
- char timebuf[64];
+ static char timebuf[64];
+ static struct tm result;
/* Get current time */
time_t t = time(NULL);
/* Convert time to local time (determined by the locale) */
- struct tm *tmp = localtime(&t);
+ struct tm *tmp = localtime_r(&t, &result);
/* Generate time prefix */
strftime(timebuf, sizeof(timebuf), "%x %X - ", tmp);
#ifdef DEBUG_TIMING
/*
* vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * main.c: Initialization, main loop
+ *
*/
#include <ev.h>
#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/time.h>
+#include <sys/resource.h>
#include "all.h"
+#include "sd-daemon.h"
+
+/* The original value of RLIMIT_CORE when i3 was started. We need to restore
+ * this before starting any other process, since we set RLIMIT_CORE to
+ * RLIM_INFINITY for i3 debugging versions. */
+struct rlimit original_rlimit_core;
+
static int xkb_event_base;
int xkb_current_group;
char **start_argv;
xcb_connection_t *conn;
+/* The screen (0 when you are using DISPLAY=:0) of the connection 'conn' */
+int conn_screen;
+
+/* Display handle for libstartup-notification */
+SnDisplay *sndisplay;
+
+/* The last timestamp we got from X11 (timestamps are included in some events
+ * and are used for some things, like determining a unique ID in startup
+ * notification). */
+xcb_timestamp_t last_timestamp = XCB_CURRENT_TIME;
xcb_screen_t *root_screen;
xcb_window_t root;
bool xcursor_supported = true;
bool xkb_supported = true;
+/* This will be set to true when -C is used so that functions can behave
+ * slightly differently. We don’t want i3-nagbar to be started when validating
+ * the config, for example. */
+bool only_check_config = false;
+
/*
* This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
* See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
xcb_key_symbols_free(keysyms);
keysyms = xcb_key_symbols_alloc(conn);
- xcb_get_numlock_mask(conn);
+ xcb_numlock_mask = aio_get_mod_mask_for(XCB_NUM_LOCK, keysyms);
ungrab_all_keys(conn);
DLOG("Re-grabbing...\n");
DLOG("Done\n");
}
+/*
+ * Exit handler which destroys the main_loop. Will trigger cleanup handlers.
+ *
+ */
+static void i3_exit() {
+/* We need ev >= 4 for the following code. Since it is not *that* important (it
+ * only makes sure that there are no i3-nagbar instances left behind) we still
+ * support old systems with libev 3. */
+#if EV_VERSION_MAJOR >= 4
+ ev_loop_destroy(main_loop);
+#endif
+}
+
int main(int argc, char *argv[]) {
- //parse_cmd("[ foo ] attach, attach ; focus");
- int screens;
char *override_configpath = NULL;
bool autostart = true;
char *layout_path = NULL;
bool delete_layout_path = false;
- bool only_check_config = false;
bool force_xinerama = false;
bool disable_signalhandler = false;
static struct option long_options[] = {
{"layout", required_argument, 0, 'L'},
{"restart", required_argument, 0, 0},
{"force-xinerama", no_argument, 0, 0},
+ {"force_xinerama", no_argument, 0, 0},
{"disable-signalhandler", no_argument, 0, 0},
+ {"get-socketpath", no_argument, 0, 0},
+ {"get_socketpath", no_argument, 0, 0},
{0, 0, 0, 0}
};
int option_index = 0, opt;
setlocale(LC_ALL, "");
+ /* Get the RLIMIT_CORE limit at startup time to restore this before
+ * starting processes. */
+ getrlimit(RLIMIT_CORE, &original_rlimit_core);
+
/* Disable output buffering to make redirects in .xsession actually useful for debugging */
if (!isatty(fileno(stdout)))
setbuf(stdout, NULL);
+ srand(time(NULL));
+
init_logging();
start_argv = argv;
/* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */
break;
case 0:
- if (strcmp(long_options[option_index].name, "force-xinerama") == 0) {
+ if (strcmp(long_options[option_index].name, "force-xinerama") == 0 ||
+ strcmp(long_options[option_index].name, "force_xinerama") == 0) {
force_xinerama = true;
ELOG("Using Xinerama instead of RandR. This option should be "
"avoided at all cost because it does not refresh the list "
} else if (strcmp(long_options[option_index].name, "disable-signalhandler") == 0) {
disable_signalhandler = true;
break;
+ } else if (strcmp(long_options[option_index].name, "get-socketpath") == 0 ||
+ strcmp(long_options[option_index].name, "get_socketpath") == 0) {
+ char *socket_path = socket_path_from_x11();
+ if (socket_path) {
+ printf("%s\n", socket_path);
+ return 0;
+ }
+
+ return 1;
} else if (strcmp(long_options[option_index].name, "restart") == 0) {
FREE(layout_path);
layout_path = sstrdup(optarg);
default:
fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V] [-C]\n", argv[0]);
fprintf(stderr, "\n");
- fprintf(stderr, "-a: disable autostart\n");
- fprintf(stderr, "-L <layoutfile>: load the layout from <layoutfile>\n");
- fprintf(stderr, "-v: display version and exit\n");
- fprintf(stderr, "-V: enable verbose mode\n");
- fprintf(stderr, "-d <loglevel>: enable debug loglevel <loglevel>\n");
- fprintf(stderr, "-c <configfile>: use the provided configfile instead\n");
- fprintf(stderr, "-C: check configuration file and exit\n");
- fprintf(stderr, "--force-xinerama: Use Xinerama instead of RandR. This "
- "option should only be used if you are stuck with the "
- "nvidia closed source driver which does not support RandR.\n");
+ fprintf(stderr, "\t-a disable autostart ('exec' lines in config)\n");
+ fprintf(stderr, "\t-c <file> use the provided configfile instead\n");
+ fprintf(stderr, "\t-C validate configuration file and exit\n");
+ fprintf(stderr, "\t-d <level> enable debug output with the specified loglevel\n");
+ fprintf(stderr, "\t-L <file> path to the serialized layout during restarts\n");
+ fprintf(stderr, "\t-v display version and exit\n");
+ fprintf(stderr, "\t-V enable verbose mode\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, "\t--force-xinerama\n"
+ "\tUse Xinerama instead of RandR.\n"
+ "\tThis option should only be used if you are stuck with the\n"
+ "\tnvidia closed source driver which does not support RandR.\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, "\t--get-socketpath\n"
+ "\tRetrieve the i3 IPC socket path from X11, print it, then exit.\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, "If you pass plain text arguments, i3 will interpret them as a command\n"
+ "to send to a currently running i3 (like i3-msg). This allows you to\n"
+ "use nice and logical commands, such as:\n"
+ "\n"
+ "\ti3 border none\n"
+ "\ti3 floating toggle\n"
+ "\ti3 kill window\n"
+ "\n");
exit(EXIT_FAILURE);
}
}
+ /* If the user passes more arguments, we act like i3-msg would: Just send
+ * the arguments as an IPC message to i3. This allows for nice semantic
+ * commands such as 'i3 border none'. */
+ if (optind < argc) {
+ /* We enable verbose mode so that the user knows what’s going on.
+ * This should make it easier to find mistakes when the user passes
+ * arguments by mistake. */
+ set_verbosity(true);
+
+ LOG("Additional arguments passed. Sending them as a command to i3.\n");
+ char *payload = NULL;
+ while (optind < argc) {
+ if (!payload) {
+ payload = sstrdup(argv[optind]);
+ } else {
+ char *both;
+ sasprintf(&both, "%s %s", payload, argv[optind]);
+ free(payload);
+ payload = both;
+ }
+ optind++;
+ }
+ LOG("Command is: %s (%d bytes)\n", payload, strlen(payload));
+ char *socket_path = socket_path_from_x11();
+ if (!socket_path) {
+ ELOG("Could not get i3 IPC socket path\n");
+ return 1;
+ }
+
+ int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
+ if (sockfd == -1)
+ err(EXIT_FAILURE, "Could not create socket");
+
+ struct sockaddr_un addr;
+ memset(&addr, 0, sizeof(struct sockaddr_un));
+ addr.sun_family = AF_LOCAL;
+ strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
+ if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
+ err(EXIT_FAILURE, "Could not connect to i3");
+
+ if (ipc_send_message(sockfd, strlen(payload), I3_IPC_MESSAGE_TYPE_COMMAND,
+ (uint8_t*)payload) == -1)
+ err(EXIT_FAILURE, "IPC: write()");
+
+ uint32_t reply_length;
+ uint8_t *reply;
+ int ret;
+ if ((ret = ipc_recv_message(sockfd, I3_IPC_MESSAGE_TYPE_COMMAND,
+ &reply_length, &reply)) != 0) {
+ if (ret == -1)
+ err(EXIT_FAILURE, "IPC: read()");
+ return 1;
+ }
+ printf("%.*s\n", reply_length, reply);
+ return 0;
+ }
+
+ /* I3_VERSION contains either something like this:
+ * "4.0.2 (2011-11-11, branch "release")".
+ * or: "4.0.2-123-gCOFFEEBABE (2011-11-11, branch "next")".
+ *
+ * So we check for the offset of the first opening round bracket to
+ * determine whether this is a git version or a release version. */
+ if ((strchr(I3_VERSION, '(') - I3_VERSION) > 10) {
+ struct rlimit limit = { RLIM_INFINITY, RLIM_INFINITY };
+ setrlimit(RLIMIT_CORE, &limit);
+
+ /* The following code is helpful, but not required. We thus don’t pay
+ * much attention to error handling, non-linux or other edge cases. */
+ char cwd[PATH_MAX];
+ LOG("CORE DUMPS: You are running a development version of i3, so coredumps were automatically enabled (ulimit -c unlimited).\n");
+ if (getcwd(cwd, sizeof(cwd)) != NULL)
+ LOG("CORE DUMPS: Your current working directory is \"%s\".\n", cwd);
+ int patternfd;
+ if ((patternfd = open("/proc/sys/kernel/core_pattern", O_RDONLY)) >= 0) {
+ memset(cwd, '\0', sizeof(cwd));
+ if (read(patternfd, cwd, sizeof(cwd)) > 0)
+ /* a trailing newline is included in cwd */
+ LOG("CORE DUMPS: Your core_pattern is: %s", cwd);
+ close(patternfd);
+ }
+ }
+
LOG("i3 (tree) version " I3_VERSION " starting\n");
- conn = xcb_connect(NULL, &screens);
+ conn = xcb_connect(NULL, &conn_screen);
if (xcb_connection_has_error(conn))
errx(EXIT_FAILURE, "Cannot open display\n");
+ sndisplay = sn_xcb_display_new(conn, NULL, NULL);
+
/* Initialize the libev event loop. This needs to be done before loading
* the config file because the parser will install an ev_child watcher
* for the nagbar when config errors are found. */
if (main_loop == NULL)
die("Could not initialize libev. Bad LIBEV_FLAGS?\n");
- root_screen = xcb_aux_get_screen(conn, screens);
+ root_screen = xcb_aux_get_screen(conn, conn_screen);
root = root_screen->root;
root_depth = root_screen->root_depth;
xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(conn, root);
/* Set a cursor for the root window (otherwise the root window will show no
cursor until the first client is launched). */
- if (xcursor_supported) {
- xcursor_set_root_cursor();
- } else {
- xcb_cursor_t cursor_id = xcb_generate_id(conn);
- i3Font cursor_font = load_font("cursor", false);
- int xcb_cursor = xcursor_get_xcb_cursor(XCURSOR_CURSOR_POINTER);
- xcb_create_glyph_cursor(conn, cursor_id, cursor_font.id, cursor_font.id,
- xcb_cursor, xcb_cursor + 1, 0, 0, 0, 65535, 65535, 65535);
- xcb_change_window_attributes(conn, root, XCB_CW_CURSOR, &cursor_id);
- xcb_free_cursor(conn, cursor_id);
- }
+ if (xcursor_supported)
+ xcursor_set_root_cursor(XCURSOR_CURSOR_POINTER);
+ else xcb_set_root_cursor(XCURSOR_CURSOR_POINTER);
if (xkb_supported) {
int errBase,
keysyms = xcb_key_symbols_alloc(conn);
- xcb_get_numlock_mask(conn);
+ xcb_numlock_mask = aio_get_mod_mask_for(XCB_NUM_LOCK, keysyms);
translate_keysyms();
grab_all_keys(conn, false);
free(greply);
- if (force_xinerama) {
+ /* Force Xinerama (for drivers which don't support RandR yet, esp. the
+ * nVidia binary graphics driver), when specified either in the config
+ * file or on command-line */
+ if (force_xinerama || config.force_xinerama) {
xinerama_init();
} else {
DLOG("Checking for XRandR...\n");
ev_io_start(main_loop, ipc_io);
}
+ /* Also handle the UNIX domain sockets passed via socket activation */
+ int fds = sd_listen_fds(1);
+ if (fds < 0)
+ ELOG("socket activation: Error in sd_listen_fds\n");
+ else if (fds == 0)
+ DLOG("socket activation: no sockets passed\n");
+ else {
+ for (int fd = SD_LISTEN_FDS_START; fd < (SD_LISTEN_FDS_START + fds); fd++) {
+ DLOG("socket activation: also listening on fd %d\n", fd);
+ struct ev_io *ipc_io = scalloc(sizeof(struct ev_io));
+ ev_io_init(ipc_io, ipc_new_client, fd, EV_READ);
+ ev_io_start(main_loop, ipc_io);
+ }
+ }
+
/* Set up i3 specific atoms like I3_SOCKET_PATH and I3_CONFIG_PATH */
x_set_i3_atoms();
struct Autostart *exec;
TAILQ_FOREACH(exec, &autostarts, autostarts) {
LOG("auto-starting %s\n", exec->command);
- start_application(exec->command);
+ start_application(exec->command, exec->no_startup_id);
}
}
struct Autostart *exec_always;
TAILQ_FOREACH(exec_always, &autostarts_always, autostarts_always) {
LOG("auto-starting (always!) %s\n", exec_always->command);
- start_application(exec_always->command);
+ start_application(exec_always->command, exec_always->no_startup_id);
+ }
+
+ /* Start i3bar processes for all configured bars */
+ Barconfig *barconfig;
+ TAILQ_FOREACH(barconfig, &barconfigs, configs) {
+ char *command = NULL;
+ sasprintf(&command, "i3bar --bar_id=%s --socket=\"%s\"",
+ barconfig->id, current_socketpath);
+ LOG("Starting bar process: %s\n", command);
+ start_application(command, true);
+ free(command);
}
+ /* Make sure to destroy the event loop to invoke the cleeanup callbacks
+ * when calling exit() */
+ atexit(i3_exit);
+
ev_loop(main_loop, 0);
}
* i3 - an improved dynamic tiling window manager
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * manage.c: Contains all functions for initially managing new windows
- * (or existing ones on restart).
+ * manage.c: Initially managing new windows (or existing ones on restart).
*
*/
-
#include "all.h"
/*
xcb_get_geometry_reply_t *geom;
xcb_get_window_attributes_reply_t *attr = NULL;
- DLOG("---> looking at window 0x%08x\n", window);
-
xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
utf8_title_cookie, title_cookie,
- class_cookie, leader_cookie, transient_cookie;
+ class_cookie, leader_cookie, transient_cookie,
+ role_cookie, startup_id_cookie;
geomc = xcb_get_geometry(conn, d);
}
if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE) {
- DLOG("map_state unviewable\n");
FREE_GEOMETRY();
goto out;
}
/* Don’t manage clients with the override_redirect flag */
- DLOG("override_redirect is %d\n", attr->override_redirect);
if (attr->override_redirect) {
FREE_GEOMETRY();
goto out;
transient_cookie = GET_PROPERTY(XCB_ATOM_WM_TRANSIENT_FOR, UINT32_MAX);
title_cookie = GET_PROPERTY(XCB_ATOM_WM_NAME, 128);
class_cookie = GET_PROPERTY(XCB_ATOM_WM_CLASS, 128);
+ role_cookie = GET_PROPERTY(A_WM_WINDOW_ROLE, 128);
+ startup_id_cookie = GET_PROPERTY(A__NET_STARTUP_ID, 512);
/* TODO: also get wm_normal_hints here. implement after we got rid of xcb-event */
- DLOG("reparenting!\n");
+ DLOG("Managing window 0x%08x\n", window);
i3Window *cwindow = scalloc(sizeof(i3Window));
cwindow->id = window;
window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL));
window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL));
window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL));
+ window_update_role(cwindow, xcb_get_property_reply(conn, role_cookie, NULL), true);
+
+ xcb_get_property_reply_t *startup_id_reply;
+ startup_id_reply = xcb_get_property_reply(conn, startup_id_cookie, NULL);
+ char *startup_ws = startup_workspace_for_window(cwindow, startup_id_reply);
+ DLOG("startup workspace = %s\n", startup_ws);
/* check if the window needs WM_TAKE_FOCUS */
cwindow->needs_take_focus = window_supports_protocol(cwindow->id, A_WM_TAKE_FOCUS);
else nc = tree_open_con(nc->parent, cwindow);
}
/* TODO: handle assignments with type == A_TO_OUTPUT */
+ } else if (startup_ws) {
+ /* If it’s not assigned, but was started on a specific workspace,
+ * we want to open it there */
+ DLOG("Using workspace on which this application was started (%s)\n", startup_ws);
+ nc = con_descend_tiling_focused(workspace_get(startup_ws, NULL));
+ DLOG("focused on ws %s: %p / %s\n", startup_ws, nc, nc->name);
+ if (nc->type == CT_WORKSPACE)
+ nc = tree_open_con(nc, cwindow);
+ else nc = tree_open_con(nc->parent, cwindow);
} else {
/* If not, insert it at the currently focused position */
if (focused->type == CT_CON && con_accepts_window(focused)) {
nc->border_width = geom->border_width;
char *name;
- asprintf(&name, "[i3 con] container around %p", cwindow);
+ sasprintf(&name, "[i3 con] container around %p", cwindow);
x_set_name(nc, name);
free(name);
if (fs == NULL) {
DLOG("Not in fullscreen mode, focusing\n");
if (!cwindow->dock) {
- /* Check that the workspace is visible. If the window was assigned
- * to an invisible workspace, we should not steal focus. */
- if (workspace_is_visible(ws)) {
+ /* Check that the workspace is visible and on the same output as
+ * the current focused container. If the window was assigned to an
+ * invisible workspace, we should not steal focus. */
+ Con *current_output = con_get_output(focused);
+ Con *target_output = con_get_output(ws);
+
+ if (workspace_is_visible(ws) && current_output == target_output) {
con_focus(nc);
} else DLOG("workspace not visible, not focusing\n");
} else DLOG("dock, not focusing\n");
if (want_floating) {
DLOG("geometry = %d x %d\n", nc->geometry.width, nc->geometry.height);
- floating_enable(nc, false);
+ floating_enable(nc, true);
}
/* to avoid getting an UnmapNotify event due to reparenting, we temporarily
* match_matches_window() to find the windows affected by this command.
*
*/
-
#include "all.h"
/*
match->application == NULL &&
match->class == NULL &&
match->instance == NULL &&
+ match->role == NULL &&
match->id == XCB_NONE &&
match->con_id == NULL &&
match->dock == -1 &&
void match_copy(Match *dest, Match *src) {
memcpy(dest, src, sizeof(Match));
-#define STRDUP(field) do { \
+/* The DUPLICATE_REGEX macro creates a new regular expression from the
+ * ->pattern of the old one. It therefore does use a little more memory then
+ * with a refcounting system, but it’s easier this way. */
+#define DUPLICATE_REGEX(field) do { \
if (src->field != NULL) \
- dest->field = sstrdup(src->field); \
+ dest->field = regex_new(src->field->pattern); \
} while (0)
- STRDUP(title);
- STRDUP(mark);
- STRDUP(application);
- STRDUP(class);
- STRDUP(instance);
+ DUPLICATE_REGEX(title);
+ DUPLICATE_REGEX(mark);
+ DUPLICATE_REGEX(application);
+ DUPLICATE_REGEX(class);
+ DUPLICATE_REGEX(instance);
+ DUPLICATE_REGEX(role);
}
/*
*
*/
bool match_matches_window(Match *match, i3Window *window) {
- LOG("checking window %d (%s)\n", window->id, window->class_class);
+ LOG("Checking window 0x%08x (class %s)\n", window->id, window->class_class);
- /* TODO: pcre, full matching, … */
if (match->class != NULL) {
- if (window->class_class != NULL && strcasecmp(match->class, window->class_class) == 0) {
+ if (window->class_class != NULL &&
+ regex_matches(match->class, window->class_class)) {
LOG("window class matches (%s)\n", window->class_class);
} else {
- LOG("window class does not match\n");
return false;
}
}
if (match->instance != NULL) {
- if (window->class_instance != NULL && strcasecmp(match->instance, window->class_instance) == 0) {
+ if (window->class_instance != NULL &&
+ regex_matches(match->instance, window->class_instance)) {
LOG("window instance matches (%s)\n", window->class_instance);
} else {
- LOG("window instance does not match\n");
return false;
}
}
}
}
- /* TODO: pcre match */
if (match->title != NULL) {
- if (window->name_json != NULL && strcasecmp(match->title, window->name_json) == 0) {
+ if (window->name_json != NULL &&
+ regex_matches(match->title, window->name_json)) {
LOG("title matches (%s)\n", window->name_json);
} else {
- LOG("title does not match\n");
+ return false;
+ }
+ }
+
+ if (match->role != NULL) {
+ if (window->role != NULL &&
+ regex_matches(match->role, window->role)) {
+ LOG("window_role matches (%s)\n", window->role);
+ } else {
return false;
}
}
if (match->dock != -1) {
- LOG("match->dock = %d, window->dock = %d\n", match->dock, window->dock);
if ((window->dock == W_DOCK_TOP && match->dock == M_DOCK_TOP) ||
(window->dock == W_DOCK_BOTTOM && match->dock == M_DOCK_BOTTOM) ||
((window->dock == W_DOCK_TOP || window->dock == W_DOCK_BOTTOM) &&
return true;
}
+
+/*
+ * Frees the given match. It must not be used afterwards!
+ *
+ */
+void match_free(Match *match) {
+ /* First step: free the regex fields / patterns */
+ regex_free(match->title);
+ regex_free(match->application);
+ regex_free(match->class);
+ regex_free(match->instance);
+ regex_free(match->mark);
+ regex_free(match->role);
+
+ /* Second step: free the regex helper struct itself */
+ FREE(match->title);
+ FREE(match->application);
+ FREE(match->class);
+ FREE(match->instance);
+ FREE(match->mark);
+ FREE(match->role);
+}
/*
* vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * move.c: Moving containers into some direction.
+ *
*/
-
#include "all.h"
+
#include "cmdparse.tab.h"
typedef enum { BEFORE, AFTER } position_t;
/*
* vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * output.c: Output (monitor) related functions.
+ *
*/
-
#include "all.h"
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
- *
- * © 2009-2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
* For more information on RandR, please see the X.org RandR specification at
* http://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt
* (take your time to read it completely, it answers all questions).
*
*/
-#include <time.h>
+#include "all.h"
+#include <time.h>
#include <xcb/randr.h>
-#include "all.h"
-
/* While a clean namespace is usually a pretty good thing, we really need
* to use shorter names than the whole xcb_randr_* default names. */
typedef xcb_randr_get_crtc_info_reply_t crtc_info;
-typedef xcb_randr_mode_info_t mode_info;
typedef xcb_randr_get_screen_resources_current_reply_t resources_reply;
/* Pointer to the result of the query for primary output */
output->con = con;
char *name;
- asprintf(&name, "[i3 con] output %s", con->name);
+ sasprintf(&name, "[i3 con] output %s", con->name);
x_set_name(con, name);
FREE(name);
FREE(topdock->name);
topdock->name = sstrdup("topdock");
- asprintf(&name, "[i3 con] top dockarea %s", con->name);
+ sasprintf(&name, "[i3 con] top dockarea %s", con->name);
x_set_name(topdock, name);
FREE(name);
DLOG("attaching\n");
FREE(content->name);
content->name = sstrdup("content");
- asprintf(&name, "[i3 con] content %s", con->name);
+ sasprintf(&name, "[i3 con] content %s", con->name);
x_set_name(content, name);
FREE(name);
con_attach(content, con, false);
FREE(bottomdock->name);
bottomdock->name = sstrdup("bottomdock");
- asprintf(&name, "[i3 con] bottom dockarea %s", con->name);
+ sasprintf(&name, "[i3 con] bottom dockarea %s", con->name);
x_set_name(bottomdock, name);
FREE(name);
DLOG("attaching\n");
if (visible && (previous = TAILQ_NEXT(workspace, focused))) {
LOG("Switching to previously used workspace \"%s\" on output \"%s\"\n",
previous->name, workspace_out->name);
- workspace_show(previous->name);
+ workspace_show(previous);
}
con_detach(workspace);
if (!visible) {
visible = TAILQ_FIRST(&(content->nodes_head));
focused = content;
- workspace_show(visible->name);
+ workspace_show(visible);
}
return;
}
LOG("Initializing first assigned workspace \"%s\" for output \"%s\"\n",
assignment->name, assignment->output);
focused = content;
- workspace_show(assignment->name);
+ workspace_show_by_name(assignment->name);
return;
}
if (!exists) {
/* Set ->num to the number of the workspace, if the name actually
* is a number or starts with a number */
- long parsed_num = strtol(ws->name, NULL, 10);
+ char *endptr = NULL;
+ long parsed_num = strtol(ws->name, &endptr, 10);
if (parsed_num == LONG_MIN ||
parsed_num == LONG_MAX ||
- parsed_num <= 0)
+ parsed_num < 0 ||
+ endptr == ws->name)
ws->num = -1;
else ws->num = parsed_num;
LOG("Used number %d for workspace with name %s\n", ws->num, ws->name);
c++;
FREE(ws->name);
- asprintf(&(ws->name), "%d", c);
+ sasprintf(&(ws->name), "%d", c);
current = NULL;
TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
}
con_attach(ws, content, false);
- asprintf(&name, "[i3 con] workspace %s", ws->name);
+ sasprintf(&name, "[i3 con] workspace %s", ws->name);
x_set_name(ws, name);
free(name);
new->id = id;
new->primary = (primary && primary->output == id);
FREE(new->name);
- asprintf(&new->name, "%.*s",
+ sasprintf(&new->name, "%.*s",
xcb_randr_get_output_info_name_length(output),
xcb_randr_get_output_info_name(output));
Con *next = NULL;
if (TAILQ_FIRST(&(croot->focus_head)) == output->con) {
DLOG("This output (%p) was focused! Getting next\n", output->con);
- next = con_next_focused(output->con);
+ next = focused;
DLOG("next = %p\n", next);
}
if (next) {
DLOG("now focusing next = %p\n", next);
con_focus(next);
+ workspace_show(con_get_workspace(next));
}
/* 3: move the dock clients to the first output */
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * regex.c: Interface to libPCRE (perl compatible regular expressions).
+ *
+ */
+#include "all.h"
+
+/*
+ * Creates a new 'regex' struct containing the given pattern and a PCRE
+ * compiled regular expression. Also, calls pcre_study because this regex will
+ * most likely be used often (like for every new window and on every relevant
+ * property change of existing windows).
+ *
+ * Returns NULL if the pattern could not be compiled into a regular expression
+ * (and ELOGs an appropriate error message).
+ *
+ */
+struct regex *regex_new(const char *pattern) {
+ const char *error;
+ int errorcode, offset;
+
+ struct regex *re = scalloc(sizeof(struct regex));
+ re->pattern = sstrdup(pattern);
+ int options = PCRE_UTF8;
+#ifdef PCRE_HAS_UCP
+ /* We use PCRE_UCP so that \B, \b, \D, \d, \S, \s, \W, \w and some POSIX
+ * character classes play nicely with Unicode */
+ options |= PCRE_UCP;
+#endif
+ while (!(re->regex = pcre_compile2(pattern, options, &errorcode, &error, &offset, NULL))) {
+ /* If the error is that PCRE was not compiled with UTF-8 support we
+ * disable it and try again */
+ if (errorcode == 32) {
+ options &= ~PCRE_UTF8;
+ continue;
+ }
+ ELOG("PCRE regular expression compilation failed at %d: %s\n",
+ offset, error);
+ return NULL;
+ }
+ re->extra = pcre_study(re->regex, 0, &error);
+ /* If an error happened, we print the error message, but continue.
+ * Studying the regular expression leads to faster matching, but it’s not
+ * absolutely necessary. */
+ if (error) {
+ ELOG("PCRE regular expression studying failed: %s\n", error);
+ }
+ return re;
+}
+
+/*
+ * Frees the given regular expression. It must not be used afterwards!
+ *
+ */
+void regex_free(struct regex *regex) {
+ if (!regex)
+ return;
+ FREE(regex->pattern);
+ FREE(regex->regex);
+ FREE(regex->extra);
+}
+
+/*
+ * Checks if the given regular expression matches the given input and returns
+ * true if it does. In either case, it logs the outcome using LOG(), so it will
+ * be visible without any debug loglevel.
+ *
+ */
+bool regex_matches(struct regex *regex, const char *input) {
+ int rc;
+
+ /* We use strlen() because pcre_exec() expects the length of the input
+ * string in bytes */
+ if ((rc = pcre_exec(regex->regex, regex->extra, input, strlen(input), 0, 0, NULL, 0)) == 0) {
+ LOG("Regular expression \"%s\" matches \"%s\"\n",
+ regex->pattern, input);
+ return true;
+ }
+
+ if (rc == PCRE_ERROR_NOMATCH) {
+ LOG("Regular expression \"%s\" does not match \"%s\"\n",
+ regex->pattern, input);
+ return false;
+ }
+
+ ELOG("PCRE error %d while trying to use regular expression \"%s\" on input \"%s\", see pcreapi(3)\n",
+ rc, regex->pattern, input);
+ return false;
+}
/*
* vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * render.c: Renders (determines position/sizes) the layout tree, updating the
+ * various rects. Needs to be pushed to X11 (see x.c) to be visible.
+ *
*/
-
#include "all.h"
/* change this to 'true' if you want to have additional borders around every
int x = con->rect.x;
int y = con->rect.y;
int height = con->rect.height;
- DLOG("Available height: %d\n", height);
/* Find the content container and ensure that there is exactly one. Also
* check for any non-CT_DOCKAREA clients. */
Con *ws = con_get_fullscreen_con(content, CF_OUTPUT);
Con *fullscreen = con_get_fullscreen_con(ws, CF_OUTPUT);
if (fullscreen) {
- DLOG("got fs node: %p\n", fullscreen);
fullscreen->rect = con->rect;
x_raise_con(fullscreen);
render_con(fullscreen, true);
child->rect.height = 0;
TAILQ_FOREACH(dockchild, &(child->nodes_head), nodes)
child->rect.height += dockchild->geometry.height;
- DLOG("This dockarea's height: %d\n", child->rect.height);
height -= child->rect.height;
}
- DLOG("Remaining: %d\n", height);
-
/* Second pass: Set the widths/heights */
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
if (child->type == CT_CON) {
DLOG("child at (%d, %d) with (%d x %d)\n",
child->rect.x, child->rect.y, child->rect.width, child->rect.height);
- DLOG("x now %d, y now %d\n", x, y);
x_raise_con(child);
render_con(child, false);
}
*
*/
void render_con(Con *con, bool render_fullscreen) {
- DLOG("currently rendering node %p / %s / layout %d\n",
- con, con->name, con->layout);
int children = con_num_children(con);
- DLOG("children: %d, orientation = %d\n", children, con->orientation);
+ DLOG("Rendering %snode %p / %s / layout %d / children %d / orient %d\n",
+ (render_fullscreen ? "fullscreen " : ""), con, con->name, con->layout,
+ children, con->orientation);
/* Copy container rect, subtract container border */
/* This is the actually usable space inside this container for clients */
if (!render_fullscreen)
*inset = rect_add(*inset, con_border_style_rect(con));
- DLOG("Starting with inset = (%d, %d) %d x %d\n", inset->x, inset->y, inset->width, inset->height);
/* Obey x11 border */
- DLOG("X11 border: %d\n", con->border_width);
inset->width -= (2 * con->border_width);
inset->height -= (2 * con->border_width);
/* Obey the aspect ratio, if any */
if (con->proportional_height != 0 &&
con->proportional_width != 0) {
- DLOG("proportional height = %d, width = %d\n", con->proportional_height, con->proportional_width);
double new_height = inset->height + 1;
int new_width = inset->width;
inset->height = new_height;
inset->width = new_width;
- DLOG("new_height = %f, new_width = %d\n", new_height, new_width);
}
if (con->height_increment > 1) {
fullscreen = con_get_fullscreen_con(con, (con->type == CT_ROOT ? CF_GLOBAL : CF_OUTPUT));
}
if (fullscreen) {
- DLOG("got fs node: %p\n", fullscreen);
fullscreen->rect = rect;
x_raise_con(fullscreen);
render_con(fullscreen, true);
if (con->layout == L_OUTPUT) {
render_l_output(con);
} else if (con->type == CT_ROOT) {
- DLOG("Root node, rendering outputs\n");
- Con *child;
- TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
- render_con(child, false);
+ Con *output;
+ TAILQ_FOREACH(output, &(con->nodes_head), nodes) {
+ render_con(output, false);
+ }
+
+ /* We need to render floating windows after rendering all outputs’
+ * tiling windows because they need to be on top of *every* output at
+ * all times. This is important when the user places floating
+ * windows/containers so that they overlap on another output. */
+ DLOG("Rendering floating windows:\n");
+ TAILQ_FOREACH(output, &(con->nodes_head), nodes) {
+ /* Get the active workspace of that output */
+ Con *content = output_get_content(output);
+ Con *workspace = TAILQ_FIRST(&(content->focus_head));
+
+ Con *child;
+ TAILQ_FOREACH(child, &(workspace->floating_head), floating_windows) {
+ DLOG("floating child at (%d,%d) with %d x %d\n", child->rect.x, child->rect.y, child->rect.width, child->rect.height);
+ x_raise_con(child);
+ render_con(child, false);
+ }
}
+
} else {
/* FIXME: refactor this into separate functions: */
/* first we have the decoration, if this is a leaf node */
if (con_is_leaf(child) && child->border_style == BS_NORMAL) {
- DLOG("that child is a leaf node, subtracting deco\n");
/* TODO: make a function for relative coords? */
child->deco_rect.x = child->rect.x - con->rect.x;
child->deco_rect.y = child->rect.y - con->rect.y;
/* stacked layout */
else if (con->layout == L_STACKED) {
- DLOG("stacked con\n");
child->rect.x = x;
child->rect.y = y;
child->rect.width = rect.width;
/* tabbed layout */
else if (con->layout == L_TABBED) {
- DLOG("tabbed con\n");
child->rect.x = x;
child->rect.y = y;
child->rect.width = rect.width;
/* dockarea layout */
else if (con->layout == L_DOCKAREA) {
- DLOG("dockarea con\n");
child->rect.x = x;
child->rect.y = y;
child->rect.width = rect.width;
DLOG("child at (%d, %d) with (%d x %d)\n",
child->rect.x, child->rect.y, child->rect.width, child->rect.height);
- DLOG("x now %d, y now %d\n", x, y);
x_raise_con(child);
render_con(child, false);
i++;
/* in a stacking or tabbed container, we ensure the focused client is raised */
if (con->layout == L_STACKED || con->layout == L_TABBED) {
- DLOG("stacked/tabbed, raising focused reverse\n");
TAILQ_FOREACH_REVERSE(child, &(con->focus_head), focus_head, focused)
x_raise_con(child);
- DLOG("done\n");
if ((child = TAILQ_FIRST(&(con->focus_head)))) {
- DLOG("con %p is stacking, raising %p\n", con, child);
/* By rendering the stacked container again, we handle the case
* that we have a non-leaf-container inside the stack. In that
* case, the children of the non-leaf-container need to be raised
x_raise_con(con);
}
}
-
- Con *child;
- TAILQ_FOREACH(child, &(con->floating_head), floating_windows) {
- DLOG("render floating:\n");
- DLOG("floating child at (%d,%d) with %d x %d\n", child->rect.x, child->rect.y, child->rect.width, child->rect.height);
- x_raise_con(child);
- render_con(child, false);
- }
-
- DLOG("-- level up\n");
}
/*
* vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * resize.c: Interactive resizing.
+ *
*/
#include "all.h"
};
DRAGGING_CB(resize_callback) {
- struct callback_params *params = extra;
+ const struct callback_params *params = extra;
Con *output = params->output;
DLOG("new x = %d, y = %d\n", new_x, new_y);
if (params->orientation == HORIZ) {
xcb_flush(conn);
}
-int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, xcb_button_press_event_t *event) {
+int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event) {
DLOG("resize handler\n");
uint32_t new_position;
xcb_flush(conn);
- struct callback_params params = { orientation, output, helpwin, &new_position };
+ const struct callback_params params = { orientation, output, helpwin, &new_position };
drag_pointer(NULL, event, grabwin, BORDER_TOP, resize_callback, ¶ms);
--- /dev/null
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ Copyright 2010 Lennart Poettering
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+***/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/fcntl.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stddef.h>
+
+#include "sd-daemon.h"
+
+int sd_listen_fds(int unset_environment) {
+
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+ return 0;
+#else
+ int r, fd;
+ const char *e;
+ char *p = NULL;
+ unsigned long l;
+
+ if (!(e = getenv("LISTEN_PID"))) {
+ r = 0;
+ goto finish;
+ }
+
+ errno = 0;
+ l = strtoul(e, &p, 10);
+
+ if (errno != 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (!p || *p || l <= 0) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ /* Is this for us? */
+ if (getpid() != (pid_t) l) {
+ r = 0;
+ goto finish;
+ }
+
+ if (!(e = getenv("LISTEN_FDS"))) {
+ r = 0;
+ goto finish;
+ }
+
+ errno = 0;
+ l = strtoul(e, &p, 10);
+
+ if (errno != 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (!p || *p) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + (int) l; fd ++) {
+ int flags;
+
+ if ((flags = fcntl(fd, F_GETFD)) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (flags & FD_CLOEXEC)
+ continue;
+
+ if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) {
+ r = -errno;
+ goto finish;
+ }
+ }
+
+ r = (int) l;
+
+finish:
+ if (unset_environment) {
+ unsetenv("LISTEN_PID");
+ unsetenv("LISTEN_FDS");
+ }
+
+ return r;
+#endif
+}
+
+int sd_is_fifo(int fd, const char *path) {
+ struct stat st_fd;
+
+ if (fd < 0)
+ return -EINVAL;
+
+ memset(&st_fd, 0, sizeof(st_fd));
+ if (fstat(fd, &st_fd) < 0)
+ return -errno;
+
+ if (!S_ISFIFO(st_fd.st_mode))
+ return 0;
+
+ if (path) {
+ struct stat st_path;
+
+ memset(&st_path, 0, sizeof(st_path));
+ if (stat(path, &st_path) < 0) {
+
+ if (errno == ENOENT || errno == ENOTDIR)
+ return 0;
+
+ return -errno;
+ }
+
+ return
+ st_path.st_dev == st_fd.st_dev &&
+ st_path.st_ino == st_fd.st_ino;
+ }
+
+ return 1;
+}
+
+static int sd_is_socket_internal(int fd, int type, int listening) {
+ struct stat st_fd;
+
+ if (fd < 0 || type < 0)
+ return -EINVAL;
+
+ if (fstat(fd, &st_fd) < 0)
+ return -errno;
+
+ if (!S_ISSOCK(st_fd.st_mode))
+ return 0;
+
+ if (type != 0) {
+ int other_type = 0;
+ socklen_t l = sizeof(other_type);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0)
+ return -errno;
+
+ if (l != sizeof(other_type))
+ return -EINVAL;
+
+ if (other_type != type)
+ return 0;
+ }
+
+ if (listening >= 0) {
+ int accepting = 0;
+ socklen_t l = sizeof(accepting);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0)
+ return -errno;
+
+ if (l != sizeof(accepting))
+ return -EINVAL;
+
+ if (!accepting != !listening)
+ return 0;
+ }
+
+ return 1;
+}
+
+union sockaddr_union {
+ struct sockaddr sa;
+ struct sockaddr_in in4;
+ struct sockaddr_in6 in6;
+ struct sockaddr_un un;
+ struct sockaddr_storage storage;
+};
+
+int sd_is_socket(int fd, int family, int type, int listening) {
+ int r;
+
+ if (family < 0)
+ return -EINVAL;
+
+ if ((r = sd_is_socket_internal(fd, type, listening)) <= 0)
+ return r;
+
+ if (family > 0) {
+ union sockaddr_union sockaddr;
+ socklen_t l;
+
+ memset(&sockaddr, 0, sizeof(sockaddr));
+ l = sizeof(sockaddr);
+
+ if (getsockname(fd, &sockaddr.sa, &l) < 0)
+ return -errno;
+
+ if (l < sizeof(sa_family_t))
+ return -EINVAL;
+
+ return sockaddr.sa.sa_family == family;
+ }
+
+ return 1;
+}
+
+int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) {
+ union sockaddr_union sockaddr;
+ socklen_t l;
+ int r;
+
+ if (family != 0 && family != AF_INET && family != AF_INET6)
+ return -EINVAL;
+
+ if ((r = sd_is_socket_internal(fd, type, listening)) <= 0)
+ return r;
+
+ memset(&sockaddr, 0, sizeof(sockaddr));
+ l = sizeof(sockaddr);
+
+ if (getsockname(fd, &sockaddr.sa, &l) < 0)
+ return -errno;
+
+ if (l < sizeof(sa_family_t))
+ return -EINVAL;
+
+ if (sockaddr.sa.sa_family != AF_INET &&
+ sockaddr.sa.sa_family != AF_INET6)
+ return 0;
+
+ if (family > 0)
+ if (sockaddr.sa.sa_family != family)
+ return 0;
+
+ if (port > 0) {
+ if (sockaddr.sa.sa_family == AF_INET) {
+ if (l < sizeof(struct sockaddr_in))
+ return -EINVAL;
+
+ return htons(port) == sockaddr.in4.sin_port;
+ } else {
+ if (l < sizeof(struct sockaddr_in6))
+ return -EINVAL;
+
+ return htons(port) == sockaddr.in6.sin6_port;
+ }
+ }
+
+ return 1;
+}
+
+int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) {
+ union sockaddr_union sockaddr;
+ socklen_t l;
+ int r;
+
+ if ((r = sd_is_socket_internal(fd, type, listening)) <= 0)
+ return r;
+
+ memset(&sockaddr, 0, sizeof(sockaddr));
+ l = sizeof(sockaddr);
+
+ if (getsockname(fd, &sockaddr.sa, &l) < 0)
+ return -errno;
+
+ if (l < sizeof(sa_family_t))
+ return -EINVAL;
+
+ if (sockaddr.sa.sa_family != AF_UNIX)
+ return 0;
+
+ if (path) {
+ if (length <= 0)
+ length = strlen(path);
+
+ if (length <= 0)
+ /* Unnamed socket */
+ return l == offsetof(struct sockaddr_un, sun_path);
+
+ if (path[0])
+ /* Normal path socket */
+ return
+ (l >= offsetof(struct sockaddr_un, sun_path) + length + 1) &&
+ memcmp(path, sockaddr.un.sun_path, length+1) == 0;
+ else
+ /* Abstract namespace socket */
+ return
+ (l == offsetof(struct sockaddr_un, sun_path) + length) &&
+ memcmp(path, sockaddr.un.sun_path, length) == 0;
+ }
+
+ return 1;
+}
+
+int sd_notify(int unset_environment, const char *state) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__) || !defined(SOCK_CLOEXEC)
+ return 0;
+#else
+ int fd = -1, r;
+ struct msghdr msghdr;
+ struct iovec iovec;
+ union sockaddr_union sockaddr;
+ const char *e;
+
+ if (!state) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ if (!(e = getenv("NOTIFY_SOCKET")))
+ return 0;
+
+ /* Must be an abstract socket, or an absolute path */
+ if ((e[0] != '@' && e[0] != '/') || e[1] == 0) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ memset(&sockaddr, 0, sizeof(sockaddr));
+ sockaddr.sa.sa_family = AF_UNIX;
+ strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path));
+
+ if (sockaddr.un.sun_path[0] == '@')
+ sockaddr.un.sun_path[0] = 0;
+
+ memset(&iovec, 0, sizeof(iovec));
+ iovec.iov_base = (char*) state;
+ iovec.iov_len = strlen(state);
+
+ memset(&msghdr, 0, sizeof(msghdr));
+ msghdr.msg_name = &sockaddr;
+ msghdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(e);
+
+ if (msghdr.msg_namelen > sizeof(struct sockaddr_un))
+ msghdr.msg_namelen = sizeof(struct sockaddr_un);
+
+ msghdr.msg_iov = &iovec;
+ msghdr.msg_iovlen = 1;
+
+ if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ r = 1;
+
+finish:
+ if (unset_environment)
+ unsetenv("NOTIFY_SOCKET");
+
+ if (fd >= 0)
+ close(fd);
+
+ return r;
+#endif
+}
+
+int sd_notifyf(int unset_environment, const char *format, ...) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+ return 0;
+#else
+ va_list ap;
+ char *p = NULL;
+ int r;
+
+ va_start(ap, format);
+ r = vasprintf(&p, format, ap);
+ va_end(ap);
+
+ if (r < 0 || !p)
+ return -ENOMEM;
+
+ r = sd_notify(unset_environment, p);
+ free(p);
+
+ return r;
+#endif
+}
+
+int sd_booted(void) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+ return 0;
+#else
+
+ struct stat a, b;
+
+ /* We simply test whether the systemd cgroup hierarchy is
+ * mounted */
+
+ if (lstat("/sys/fs/cgroup", &a) < 0)
+ return 0;
+
+ if (lstat("/sys/fs/cgroup/systemd", &b) < 0)
+ return 0;
+
+ return a.st_dev != b.st_dev;
+#endif
+}
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
- *
- * © 2009-2010 Michael Stapelberg and contributors
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
* © 2009-2010 Jan-Erik Rediger
*
- * See file LICENSE for license information.
- *
- * sighandler.c: contains all functions for signal handling
+ * sighandler.c: Interactive crash dialog upon SIGSEGV/SIGABRT/SIGFPE (offers
+ * to restart inplace).
*
*/
+#include "all.h"
+
#include <ev.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
#include <iconv.h>
#include <signal.h>
-#include <xcb/xcb.h>
-#include <xcb/xcb_aux.h>
#include <xcb/xcb_event.h>
-#include <xcb/xcb_keysyms.h>
#include <X11/keysym.h>
-#include "all.h"
-
static xcb_gcontext_t pixmap_gc;
static xcb_pixmap_t pixmap;
static int raised_signal;
/* re-draw the background */
xcb_rectangle_t border = { 0, 0, width, height},
inner = { 2, 2, width - 4, height - 4};
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel("#FF0000"));
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FF0000") });
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel("#000000"));
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#000000") });
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
/* restore font color */
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel("#FFFFFF"));
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FFFFFF") });
for (int i = 0; i < sizeof(crash_text) / sizeof(char*); i++) {
int text_len = strlen(crash_text[i]);
xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
/* Create graphics context */
- xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, config.font.id);
+ xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ config.font.id });
/* Grab the keyboard to get all input */
xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * startup.c: Startup notification code. Ensures a startup notification context
+ * is setup when launching applications. We store the current
+ * workspace to open windows in that startup notification context on
+ * the appropriate workspace.
+ *
+ */
+#include "all.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#define SN_API_NOT_YET_FROZEN 1
+#include <libsn/sn-launcher.h>
+
+static TAILQ_HEAD(startup_sequence_head, Startup_Sequence) startup_sequences =
+ TAILQ_HEAD_INITIALIZER(startup_sequences);
+
+/*
+ * After 60 seconds, a timeout will be triggered for each startup sequence.
+ *
+ * The timeout will just trigger completion of the sequence, so the normal
+ * completion process takes place (startup_monitor_event will free it).
+ *
+ */
+static void startup_timeout(EV_P_ ev_timer *w, int revents) {
+ const char *id = sn_launcher_context_get_startup_id(w->data);
+ DLOG("Timeout for startup sequence %s\n", id);
+
+ struct Startup_Sequence *current, *sequence = NULL;
+ TAILQ_FOREACH(current, &startup_sequences, sequences) {
+ if (strcmp(current->id, id) != 0)
+ continue;
+
+ sequence = current;
+ break;
+ }
+
+ /* Unref the context (for the timeout itself, see start_application) */
+ sn_launcher_context_unref(w->data);
+
+ if (!sequence) {
+ DLOG("Sequence already deleted, nevermind.\n");
+ return;
+ }
+
+ /* Complete the startup sequence, will trigger its deletion. */
+ sn_launcher_context_complete(w->data);
+ free(w);
+}
+
+/*
+ * Starts the given application by passing it through a shell. We use double fork
+ * to avoid zombie processes. As the started application’s parent exits (immediately),
+ * the application is reparented to init (process-id 1), which correctly handles
+ * childs, so we don’t have to do it :-).
+ *
+ * The shell is determined by looking for the SHELL environment variable. If it
+ * does not exist, /bin/sh is used.
+ *
+ * The no_startup_id flag determines whether a startup notification context
+ * (and ID) should be created, which is the default and encouraged behavior.
+ *
+ */
+void start_application(const char *command, bool no_startup_id) {
+ SnLauncherContext *context;
+
+ if (!no_startup_id) {
+ /* Create a startup notification context to monitor the progress of this
+ * startup. */
+ context = sn_launcher_context_new(sndisplay, conn_screen);
+ sn_launcher_context_set_name(context, "i3");
+ sn_launcher_context_set_description(context, "exec command in i3");
+ /* Chop off everything starting from the first space (if there are any
+ * spaces in the command), since we don’t want the parameters. */
+ char *first_word = sstrdup(command);
+ char *space = strchr(first_word, ' ');
+ if (space)
+ *space = '\0';
+ sn_launcher_context_initiate(context, "i3", first_word, last_timestamp);
+ free(first_word);
+
+ /* Trigger a timeout after 60 seconds */
+ struct ev_timer *timeout = scalloc(sizeof(struct ev_timer));
+ ev_timer_init(timeout, startup_timeout, 60.0, 0.);
+ timeout->data = context;
+ ev_timer_start(main_loop, timeout);
+
+ LOG("startup id = %s\n", sn_launcher_context_get_startup_id(context));
+
+ /* Save the ID and current workspace in our internal list of startup
+ * sequences */
+ Con *ws = con_get_workspace(focused);
+ struct Startup_Sequence *sequence = scalloc(sizeof(struct Startup_Sequence));
+ sequence->id = sstrdup(sn_launcher_context_get_startup_id(context));
+ sequence->workspace = sstrdup(ws->name);
+ sequence->context = context;
+ TAILQ_INSERT_TAIL(&startup_sequences, sequence, sequences);
+
+ /* Increase the refcount once (it starts with 1, so it will be 2 now) for
+ * the timeout. Even if the sequence gets completed, the timeout still
+ * needs the context (but will unref it then) */
+ sn_launcher_context_ref(context);
+ }
+
+ LOG("executing: %s\n", command);
+ if (fork() == 0) {
+ /* Child process */
+ setsid();
+ setrlimit(RLIMIT_CORE, &original_rlimit_core);
+ if (fork() == 0) {
+ /* Setup the environment variable(s) */
+ if (!no_startup_id)
+ sn_launcher_context_setup_child_process(context);
+
+ /* Stores the path of the shell */
+ static const char *shell = NULL;
+
+ if (shell == NULL)
+ if ((shell = getenv("SHELL")) == NULL)
+ shell = "/bin/sh";
+
+ /* This is the child */
+ execl(shell, shell, "-c", command, (void*)NULL);
+ /* not reached */
+ }
+ _exit(0);
+ }
+ wait(0);
+
+ if (!no_startup_id) {
+ /* Change the pointer of the root window to indicate progress */
+ if (xcursor_supported)
+ xcursor_set_root_cursor(XCURSOR_CURSOR_WATCH);
+ else xcb_set_root_cursor(XCURSOR_CURSOR_WATCH);
+ }
+}
+
+/*
+ * Called by libstartup-notification when something happens
+ *
+ */
+void startup_monitor_event(SnMonitorEvent *event, void *userdata) {
+ SnStartupSequence *snsequence;
+
+ snsequence = sn_monitor_event_get_startup_sequence(event);
+
+ /* Get the corresponding internal startup sequence */
+ const char *id = sn_startup_sequence_get_id(snsequence);
+ struct Startup_Sequence *current, *sequence = NULL;
+ TAILQ_FOREACH(current, &startup_sequences, sequences) {
+ if (strcmp(current->id, id) != 0)
+ continue;
+
+ sequence = current;
+ break;
+ }
+
+ if (!sequence) {
+ DLOG("Got event for startup sequence that we did not initiate (ID = %s). Ignoring.\n", id);
+ return;
+ }
+
+ switch (sn_monitor_event_get_type(event)) {
+ case SN_MONITOR_EVENT_COMPLETED:
+ DLOG("startup sequence %s completed\n", sn_startup_sequence_get_id(snsequence));
+
+ /* Unref the context, will be free()d */
+ sn_launcher_context_unref(sequence->context);
+
+ /* Delete our internal sequence */
+ TAILQ_REMOVE(&startup_sequences, sequence, sequences);
+
+ if (TAILQ_EMPTY(&startup_sequences)) {
+ DLOG("No more startup sequences running, changing root window cursor to default pointer.\n");
+ /* Change the pointer of the root window to indicate progress */
+ if (xcursor_supported)
+ xcursor_set_root_cursor(XCURSOR_CURSOR_POINTER);
+ else xcb_set_root_cursor(XCURSOR_CURSOR_POINTER);
+ }
+ break;
+ default:
+ /* ignore */
+ break;
+ }
+}
+
+/*
+ * Checks if the given window belongs to a startup notification by checking if
+ * the _NET_STARTUP_ID property is set on the window (or on its leader, if it’s
+ * unset).
+ *
+ * If so, returns the workspace on which the startup was initiated.
+ * Returns NULL otherwise.
+ *
+ */
+char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply) {
+ /* The _NET_STARTUP_ID is only needed during this function, so we get it
+ * here and don’t save it in the 'cwindow'. */
+ if (startup_id_reply == NULL || xcb_get_property_value_length(startup_id_reply) == 0) {
+ FREE(startup_id_reply);
+ DLOG("No _NET_STARTUP_ID set on this window\n");
+ if (cwindow->leader == XCB_NONE)
+ return NULL;
+
+ xcb_get_property_cookie_t cookie;
+ cookie = xcb_get_property(conn, false, cwindow->leader, A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512);
+ DLOG("Checking leader window 0x%08x\n", cwindow->leader);
+ startup_id_reply = xcb_get_property_reply(conn, cookie, NULL);
+
+ if (startup_id_reply == NULL || xcb_get_property_value_length(startup_id_reply) == 0) {
+ DLOG("No _NET_STARTUP_ID set on the leader either\n");
+ FREE(startup_id_reply);
+ return NULL;
+ }
+ }
+
+ char *startup_id;
+ if (asprintf(&startup_id, "%.*s", xcb_get_property_value_length(startup_id_reply),
+ (char*)xcb_get_property_value(startup_id_reply)) == -1) {
+ perror("asprintf()");
+ DLOG("Could not get _NET_STARTUP_ID\n");
+ free(startup_id_reply);
+ return NULL;
+ }
+
+ struct Startup_Sequence *current, *sequence = NULL;
+ TAILQ_FOREACH(current, &startup_sequences, sequences) {
+ if (strcmp(current->id, startup_id) != 0)
+ continue;
+
+ sequence = current;
+ break;
+ }
+
+ free(startup_id);
+ free(startup_id_reply);
+
+ if (!sequence) {
+ DLOG("WARNING: This sequence (ID %s) was not found\n", startup_id);
+ return NULL;
+ }
+
+ return sequence->workspace;
+}
/*
* vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * tree.c: Everything that primarily modifies the layout tree data structure.
+ *
*/
-
#include "all.h"
struct Con *croot;
if (!workspace)
return false;
- workspace_show(workspace->name);
+ workspace_show(workspace);
Con *focus = con_descend_direction(workspace, direction);
if (focus) {
con_focus(focus);
return true;
}
+ Con *parent = con->parent;
+
if (con->type == CT_FLOATING_CON) {
- /* TODO: implement focus for floating windows */
- return false;
- }
+ /* left/right focuses the previous/next floating container */
+ if (orientation == HORIZ) {
+ Con *next;
+ if (way == 'n')
+ next = TAILQ_NEXT(con, floating_windows);
+ else next = TAILQ_PREV(con, floating_head, floating_windows);
+
+ /* If there is no next/previous container, wrap */
+ if (!next) {
+ if (way == 'n')
+ next = TAILQ_FIRST(&(parent->floating_head));
+ else next = TAILQ_LAST(&(parent->floating_head), floating_head);
+ }
- Con *parent = con->parent;
+ /* Still no next/previous container? bail out */
+ if (!next)
+ return false;
+
+ con_focus(con_descend_focused(next));
+ return true;
+ } else {
+ /* up/down cycles through the Z-index */
+ /* TODO: implement cycling through the z-index */
+ return false;
+ }
+ }
/* If the orientation does not match or there is no other con to focus, we
* need to go higher in the hierarchy */
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * © 2009-2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
- *
- * util.c: Utility functions, which can be useful everywhere.
+ * util.c: Utility functions, which can be useful everywhere within i3 (see
+ * also libi3).
*
*/
+#include "all.h"
+
#include <sys/wait.h>
#include <stdarg.h>
#include <iconv.h>
#include <yajl/yajl_version.h>
#include <libgen.h>
-#include "all.h"
+#define SN_API_NOT_YET_FROZEN 1
+#include <libsn/sn-launcher.h>
static iconv_t conversion_descriptor = 0;
return ((*destination = new_value) != old_value);
}
-/*
- * The s* functions (safe) are wrappers around malloc, strdup, …, which exits if one of
- * the called functions returns NULL, meaning that there is no more memory available
- *
- */
-void *smalloc(size_t size) {
- void *result = malloc(size);
- exit_if_null(result, "Error: out of memory (malloc(%zd))\n", size);
- return result;
-}
-
-void *scalloc(size_t size) {
- void *result = calloc(size, 1);
- exit_if_null(result, "Error: out of memory (calloc(%zd))\n", size);
- return result;
-}
-
-void *srealloc(void *ptr, size_t size) {
- void *result = realloc(ptr, size);
- if (result == NULL && size > 0)
- die("Error: out memory (realloc(%zd))\n", size);
- return result;
-}
-
-char *sstrdup(const char *str) {
- char *result = strdup(str);
- exit_if_null(result, "Error: out of memory (strdup())\n");
- return result;
-}
-
-/*
- * Starts the given application by passing it through a shell. We use double fork
- * to avoid zombie processes. As the started application’s parent exits (immediately),
- * the application is reparented to init (process-id 1), which correctly handles
- * childs, so we don’t have to do it :-).
- *
- * The shell is determined by looking for the SHELL environment variable. If it
- * does not exist, /bin/sh is used.
- *
- */
-void start_application(const char *command) {
- LOG("executing: %s\n", command);
- if (fork() == 0) {
- /* Child process */
- setsid();
- if (fork() == 0) {
- /* Stores the path of the shell */
- static const char *shell = NULL;
-
- if (shell == NULL)
- if ((shell = getenv("SHELL")) == NULL)
- shell = "/bin/sh";
-
- /* This is the child */
- execl(shell, shell, "-c", command, (void*)NULL);
- /* not reached */
- }
- exit(0);
- }
- wait(0);
-}
-
/*
* exec()s an i3 utility, for example the config file migration script or
* i3-nagbar. This function first searches $PATH for the given utility named,
* argv[0]’s dirname */
char *pathbuf = strdup(start_argv[0]);
char *dir = dirname(pathbuf);
- asprintf(&migratepath, "%s/%s", dir, name);
+ sasprintf(&migratepath, "%s/%s", dir, name);
argv[0] = migratepath;
execvp(migratepath, argv);
exit(1);
}
dir = dirname(buffer);
- asprintf(&migratepath, "%s/%s", dir, name);
+ sasprintf(&migratepath, "%s/%s", dir, name);
argv[0] = migratepath;
execvp(migratepath, argv);
#endif
int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
if (rc == (size_t)-1) {
perror("Converting to UCS-2 failed");
+ FREE(buffer);
if (real_strlen != NULL)
*real_strlen = 0;
return NULL;
if (dir == NULL) {
struct passwd *pw = getpwuid(getuid());
const char *username = pw ? pw->pw_name : "unknown";
- if (asprintf(&dir, "/tmp/i3-%s", username) == -1) {
- perror("asprintf()");
- return NULL;
- }
+ sasprintf(&dir, "/tmp/i3-%s", username);
} else {
char *tmp;
- if (asprintf(&tmp, "%s/i3", dir) == -1) {
- perror("asprintf()");
- return NULL;
- }
+ sasprintf(&tmp, "%s/i3", dir);
dir = tmp;
}
if (!path_exists(dir)) {
}
}
char *filename;
- if (asprintf(&filename, "%s/%s.%d", dir, prefix, getpid()) == -1) {
- perror("asprintf()");
- filename = NULL;
- }
-
+ sasprintf(&filename, "%s/%s.%d", dir, prefix, getpid());
free(dir);
return filename;
}
}
#endif
-
-#if defined(__APPLE__)
-
-/*
- * Taken from FreeBSD
- * Returns a pointer to a new string which is a duplicate of the
- * string, but only copies at most n characters.
- *
- */
-char *strndup(const char *str, size_t n) {
- size_t len;
- char *copy;
-
- for (len = 0; len < n && str[len]; len++)
- continue;
-
- if ((copy = malloc(len + 1)) == NULL)
- return (NULL);
- memcpy(copy, str, len);
- copy[len] = '\0';
- return (copy);
-}
-
-#endif
* i3 - an improved dynamic tiling window manager
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
+ * window.c: Updates window attributes (X11 hints/properties).
+ *
*/
#include "all.h"
*/
void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
- DLOG("empty property, not updating\n");
+ DLOG("WM_CLASS not set.\n");
FREE(prop);
return;
}
*/
void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
- DLOG("prop == NULL\n");
+ DLOG("WM_NAME not set (_NET_WM_NAME is what you want anyways).\n");
FREE(prop);
return;
}
return;
}
+ LOG("WM_NAME changed to \"%s\"\n", new_name);
LOG("Using legacy window title. Note that in order to get Unicode window "
"titles in i3, the application has to set _NET_WM_NAME (UTF-8)\n");
*/
void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
- DLOG("prop == NULL\n");
+ DLOG("CLIENT_LEADER not set.\n");
FREE(prop);
return;
}
*/
void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
- DLOG("prop == NULL\n");
+ DLOG("TRANSIENT_FOR not set.\n");
FREE(prop);
return;
}
*/
void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
- DLOG("prop == NULL\n");
+ DLOG("_NET_WM_STRUT_PARTIAL not set.\n");
FREE(prop);
return;
}
free(prop);
}
+
+/*
+ * Updates the WM_WINDOW_ROLE
+ *
+ */
+void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
+ if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
+ DLOG("WM_WINDOW_ROLE not set.\n");
+ FREE(prop);
+ return;
+ }
+
+ char *new_role;
+ if (asprintf(&new_role, "%.*s", xcb_get_property_value_length(prop),
+ (char*)xcb_get_property_value(prop)) == -1) {
+ perror("asprintf()");
+ DLOG("Could not get WM_WINDOW_ROLE\n");
+ free(prop);
+ return;
+ }
+ FREE(win->role);
+ win->role = new_role;
+ LOG("WM_WINDOW_ROLE changed to \"%s\"\n", win->role);
+
+ if (before_mgmt) {
+ free(prop);
+ return;
+ }
+
+ run_assignments(win);
+
+ free(prop);
+}
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
- * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * workspace.c: Functions for modifying workspaces
+ * workspace.c: Modifying workspaces, accessing them, moving containers to
+ * workspaces.
*
*/
#include "all.h"
+/* Stores a copy of the name of the last used workspace for the workspace
+ * back-and-forth switching. */
+static char *previous_workspace_name = NULL;
+
/*
* Returns a pointer to the workspace with the given number (starting at 0),
* creating the workspace if necessary (by allocating the necessary amount of
* will handle CT_WORKSPACEs differently */
workspace = con_new(NULL, NULL);
char *name;
- asprintf(&name, "[i3 con] workspace %s", num);
+ sasprintf(&name, "[i3 con] workspace %s", num);
x_set_name(workspace, name);
free(name);
workspace->type = CT_WORKSPACE;
workspace->name = sstrdup(num);
/* We set ->num to the number if this workspace’s name consists only of
* a positive number. Otherwise it’s a named ws and num will be -1. */
- char *end;
- long parsed_num = strtol(num, &end, 10);
+ char *endptr = NULL;
+ long parsed_num = strtol(num, &endptr, 10);
if (parsed_num == LONG_MIN ||
parsed_num == LONG_MAX ||
parsed_num < 0 ||
- (end && *end != '\0'))
+ endptr == num)
workspace->num = -1;
else workspace->num = parsed_num;
LOG("num = %d\n", workspace->num);
workspace_reassign_sticky(current);
}
-/*
- * Switches to the given workspace
- *
- */
-void workspace_show(const char *num) {
- Con *workspace, *current, *old = NULL;
- bool changed_num_workspaces;
- workspace = workspace_get(num, &changed_num_workspaces);
+static void _workspace_show(Con *workspace, bool changed_num_workspaces) {
+ Con *current, *old = NULL;
/* disable fullscreen for the other workspaces and get the workspace we are
* currently on. */
/* enable fullscreen for the target workspace. If it happens to be the
* same one we are currently on anyways, we can stop here. */
workspace->fullscreen_mode = CF_OUTPUT;
- if (workspace == con_get_workspace(focused)) {
+ current = con_get_workspace(focused);
+ if (workspace == current) {
DLOG("Not switching, already there.\n");
return;
}
+ /* Remember currently focused workspace for switching back to it later with
+ * the 'workspace back_and_forth' command.
+ * NOTE: We have to duplicate the name as the original will be freed when
+ * the corresponding workspace is cleaned up. */
+
+ FREE(previous_workspace_name);
+ if (current)
+ previous_workspace_name = sstrdup(current->name);
+
workspace_reassign_sticky(workspace);
LOG("switching to %p\n", workspace);
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
}
+/*
+ * Switches to the given workspace
+ *
+ */
+void workspace_show(Con *workspace) {
+ _workspace_show(workspace, false);
+}
+
+/*
+ * Looks up the workspace by name and switches to it.
+ *
+ */
+void workspace_show_by_name(const char *num) {
+ Con *workspace;
+ bool changed_num_workspaces;
+ workspace = workspace_get(num, &changed_num_workspaces);
+ _workspace_show(workspace, changed_num_workspaces);
+}
+
/*
* Focuses the next workspace.
*
*/
-void workspace_next() {
+Con* workspace_next() {
Con *current = con_get_workspace(focused);
Con *next = NULL;
Con *output;
found_current = 1;
} else if (child->num == -1 && (current->num != -1 || found_current)) {
next = child;
- goto workspace_next_show;
+ goto workspace_next_end;
}
}
}
next = child;
}
}
-
-workspace_next_show:
- workspace_show(next->name);
+workspace_next_end:
+ return next;
}
/*
* Focuses the previous workspace.
*
*/
-void workspace_prev() {
+Con* workspace_prev() {
Con *current = con_get_workspace(focused);
Con *prev = NULL;
Con *output;
if (child->type != CT_WORKSPACE)
continue;
if (child == current) {
- found_current = 1;
+ found_current = true;
} else if (child->num == -1 && (current->num != -1 || found_current)) {
prev = child;
- goto workspace_prev_show;
+ goto workspace_prev_end;
}
}
}
}
}
-workspace_prev_show:
- workspace_show(prev->name);
+workspace_prev_end:
+ return prev;
+}
+
+/*
+ * Focuses the previously focused workspace.
+ *
+ */
+void workspace_back_and_forth() {
+ if (!previous_workspace_name) {
+ DLOG("No previous workspace name set. Not switching.");
+ return;
+ }
+
+ workspace_show_by_name(previous_workspace_name);
}
static bool get_urgency_flag(Con *con) {
/*
* vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * x.c: Interface to X11, transfers our in-memory state to X11 (see also
+ * render.c). Basically a big state machine.
+ *
*/
-
#include "all.h"
/* Stores the X11 window ID of the currently focused window */
parent->layout != L_TABBED) ||
con->type == CT_FLOATING_CON)
return;
- DLOG("decoration should be rendered for con %p\n", con);
/* Skip containers whose height is 0 (for example empty dockareas) */
- if (con->rect.height == 0) {
- DLOG("height == 0, not rendering\n");
+ if (con->rect.height == 0)
return;
- }
/* Skip containers whose pixmap has not yet been created (can happen when
* decoration rendering happens recursively for a window for which
* x_push_node() was not yet called) */
- if (leaf && con->pixmap == XCB_NONE) {
- DLOG("pixmap not yet created, not rendering\n");
+ if (leaf && con->pixmap == XCB_NONE)
return;
- }
/* 1: build deco_params and compare with cache */
struct deco_render_params *p = scalloc(sizeof(struct deco_render_params));
!parent->pixmap_recreated &&
!con->pixmap_recreated &&
memcmp(p, con->deco_render_params, sizeof(struct deco_render_params)) == 0) {
- DLOG("CACHE HIT, copying existing pixmaps\n");
free(p);
goto copy_pixmaps;
}
- DLOG("CACHE MISS\n");
Con *next = con;
while ((next = TAILQ_NEXT(next, nodes))) {
- DLOG("Also invalidating cache of %p\n", next);
FREE(next->deco_render_params);
}
);
#endif
- xcb_change_gc_single(conn, con->pm_gc, XCB_GC_FOREGROUND, config.client.background);
+ xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]) { config.client.background });
xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, sizeof(background) / sizeof(xcb_rectangle_t), background);
}
* (left, bottom and right part). We don’t just fill the whole
* rectangle because some childs are not freely resizable and we want
* their background color to "shine through". */
- xcb_change_gc_single(conn, con->pm_gc, XCB_GC_FOREGROUND, p->color->background);
+ xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->background });
xcb_rectangle_t borders[] = {
{ 0, 0, br.x, r->height },
{ 0, r->height + br.height + br.y, r->width, r->height },
/* if this is a borderless/1pixel window, we don’t * need to render the
* decoration. */
- if (p->border_style != BS_NORMAL) {
- DLOG("border style not BS_NORMAL, aborting rendering of decoration\n");
+ if (p->border_style != BS_NORMAL)
goto copy_pixmaps;
- }
/* 4: paint the bar */
- xcb_change_gc_single(conn, parent->pm_gc, XCB_GC_FOREGROUND, p->color->background);
+ xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->background });
xcb_rectangle_t drect = { con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height };
xcb_poly_fill_rectangle(conn, parent->pixmap, parent->pm_gc, 1, &drect);
/* 5: draw two unconnected lines in border color */
- xcb_change_gc_single(conn, parent->pm_gc, XCB_GC_FOREGROUND, p->color->border);
+ xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->border });
Rect *dr = &(con->deco_rect);
xcb_segment_t segments[] = {
{ dr->x, dr->y,
Con *il_parent = parent;
if (il_parent->layout != L_STACKED) {
while (1) {
- DLOG("il_parent = %p, layout = %d\n", il_parent, il_parent->layout);
+ //DLOG("il_parent = %p, layout = %d\n", il_parent, il_parent->layout);
if (il_parent->layout == L_STACKED)
indent_level++;
if (il_parent->type == CT_WORKSPACE || il_parent->type == CT_DOCKAREA || il_parent->type == CT_OUTPUT)
indent_mult++;
}
}
- DLOG("indent_level = %d, indent_mult = %d\n", indent_level, indent_mult);
+ //DLOG("indent_level = %d, indent_mult = %d\n", indent_level, indent_mult);
int indent_px = (indent_level * 5) * indent_mult;
if (win->uses_net_wm_name)
}
}
rect.height = max_y + max_height;
- if (rect.height == 0) {
- DLOG("Unmapping container %p because it does not contain anything.\n", con);
+ if (rect.height == 0)
con->mapped = false;
- }
}
/* reparent the child window (when the window was moved due to a sticky
* (height == 0). */
if ((state->rect.width != rect.width ||
state->rect.height != rect.height)) {
- DLOG("CACHE: creating new pixmap for con %p (old: %d x %d, new: %d x %d)\n",
- con, state->rect.width, state->rect.height,
- rect.width, rect.height);
if (con->pixmap == 0) {
con->pixmap = xcb_generate_id(conn);
con->pm_gc = xcb_generate_id(conn);
order_changed = true;
if ((state->initial || order_changed) && prev != CIRCLEQ_END(&state_head)) {
stacking_changed = true;
- DLOG("Stacking 0x%08x above 0x%08x\n", prev->id, state->id);
+ //DLOG("Stacking 0x%08x above 0x%08x\n", prev->id, state->id);
uint32_t mask = 0;
mask |= XCB_CONFIG_WINDOW_SIBLING;
mask |= XCB_CONFIG_WINDOW_STACK_MODE;
if (stacking_changed)
ewmh_update_client_list_stacking(btt_stack, btt_stack_num);
- DLOG("\n\n PUSHING CHANGES\n\n");
+ DLOG("PUSHING CHANGES\n");
x_push_node(con);
+ if (warp_to) {
+ xcb_query_pointer_reply_t *pointerreply = xcb_query_pointer_reply(conn, pointercookie, NULL);
+ if (!pointerreply) {
+ ELOG("Could not query pointer position, not warping pointer\n");
+ } else {
+ int mid_x = warp_to->x + (warp_to->width / 2);
+ int mid_y = warp_to->y + (warp_to->height / 2);
+
+ Output *current = get_output_containing(pointerreply->root_x, pointerreply->root_y);
+ Output *target = get_output_containing(mid_x, mid_y);
+ if (current != target)
+ xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, mid_x, mid_y);
+ }
+ warp_to = NULL;
+ }
+
//DLOG("Re-enabling EnterNotify\n");
values[0] = FRAME_EVENT_MASK;
CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) {
if (focused->window != NULL)
to_focus = focused->window->id;
- DLOG("focused_id = 0x%08x, to_focus = 0x%08x\n", focused_id, to_focus);
if (focused_id != to_focus) {
if (!focused->mapped) {
DLOG("Not updating focus (to %p / %s), focused window is not mapped.\n", focused, focused->name);
focused_id = root;
}
- if (warp_to) {
- xcb_query_pointer_reply_t *pointerreply = xcb_query_pointer_reply(conn, pointercookie, NULL);
- if (!pointerreply) {
- ELOG("Could not query pointer position, not warping pointer\n");
- } else {
- int mid_x = warp_to->x + (warp_to->width / 2);
- int mid_y = warp_to->y + (warp_to->height / 2);
-
- Output *current = get_output_containing(pointerreply->root_x, pointerreply->root_y);
- Output *target = get_output_containing(mid_x, mid_y);
- if (current != target)
- xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, mid_x, mid_y);
- }
- warp_to = NULL;
- }
-
xcb_flush(conn);
- DLOG("\n\n ENDING CHANGES\n\n");
+ DLOG("ENDING CHANGES\n");
/* Disable EnterWindow events for windows which will be unmapped in
* x_push_node_unmaps() now. Unmapping windows happens when switching
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
- *
- * © 2009-2010 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
* xcb.c: Helper functions for easier usage of XCB
*
*/
-
#include "all.h"
-TAILQ_HEAD(cached_fonts_head, Font) cached_fonts = TAILQ_HEAD_INITIALIZER(cached_fonts);
unsigned int xcb_numlock_mask;
-/*
- * Loads a font for usage, also getting its height. If fallback is true,
- * i3 loads 'fixed' or '-misc-*' if the font cannot be found instead of
- * exiting.
- *
- */
-i3Font load_font(const char *pattern, bool fallback) {
- i3Font new;
- xcb_void_cookie_t font_cookie;
- xcb_list_fonts_with_info_cookie_t info_cookie;
-
- /* Send all our requests first */
- new.id = xcb_generate_id(conn);
- font_cookie = xcb_open_font_checked(conn, new.id, strlen(pattern), pattern);
- info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
-
- /* Check for errors. If errors, fall back to default font. */
- xcb_generic_error_t *error = xcb_request_check(conn, font_cookie);
-
- /* If we fail to open font, fall back to 'fixed'. If opening 'fixed' fails fall back to '-misc-*' */
- if (error != NULL) {
- ELOG("Could not open font %s (X error %d). Reverting to backup font.\n", pattern, error->error_code);
- pattern = "fixed";
- font_cookie = xcb_open_font_checked(conn, new.id, strlen(pattern), pattern);
- info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
-
- /* Check if we managed to open 'fixed' */
- xcb_generic_error_t *error = xcb_request_check(conn, font_cookie);
-
- /* Fall back to '-misc-*' if opening 'fixed' fails. */
- if (error != NULL) {
- ELOG("Could not open fallback font '%s', trying with '-misc-*'\n",pattern);
- pattern = "-misc-*";
- font_cookie = xcb_open_font_checked(conn, new.id, strlen(pattern), pattern);
- info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
-
- check_error(conn, font_cookie, "Could open neither requested font nor fallback (fixed or -misc-*");
- }
- }
-
- /* Get information (height/name) for this font */
- xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL);
- exit_if_null(reply, "Could not load font \"%s\"\n", pattern);
-
- new.height = reply->font_ascent + reply->font_descent;
-
- free(reply);
-
- return new;
-}
-
-/*
- * Returns the colorpixel to use for the given hex color (think of HTML).
- *
- * The hex_color has to start with #, for example #FF00FF.
- *
- * NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
- * This has to be done by the caller.
- *
- */
-uint32_t get_colorpixel(char *hex) {
- char strgroups[3][3] = {{hex[1], hex[2], '\0'},
- {hex[3], hex[4], '\0'},
- {hex[5], hex[6], '\0'}};
- uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
- (strtol(strgroups[1], NULL, 16)),
- (strtol(strgroups[2], NULL, 16))};
-
- return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
-}
-
/*
* Convenience wrapper around xcb_create_window which takes care of depth, generating an ID and checking
* for errors.
return result;
}
-/*
- * Changes a single value in the graphic context (so one doesn’t have to define an array of values)
- *
- */
-void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value) {
- xcb_change_gc(conn, gc, mask, &value);
-}
-
/*
* Draws a line from x,y to to_x,to_y using the given color
*
*/
void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc,
uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t to_x, uint32_t to_y) {
- xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, colorpixel);
- xcb_point_t points[] = {{x, y}, {to_x, to_y}};
- xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, drawable, gc, 2, points);
+ xcb_change_gc(conn, gc, XCB_GC_FOREGROUND, (uint32_t[]){ colorpixel });
+ xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, drawable, gc, 2,
+ (xcb_point_t[]) { {x, y}, {to_x, to_y} });
}
/*
*/
void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc,
uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
- xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, colorpixel);
+ xcb_change_gc(conn, gc, XCB_GC_FOREGROUND, (uint32_t[]){ colorpixel });
xcb_rectangle_t rect = {x, y, width, height};
xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
}
-/*
- * Generates a configure_notify event and sends it to the given window
- * Applications need this to think they’ve configured themselves correctly.
- * The truth is, however, that we will manage them.
- *
- */
-void fake_configure_notify(xcb_connection_t *conn, Rect r, xcb_window_t window, int border_width) {
- /* Every X11 event is 32 bytes long. Therefore, XCB will copy 32 bytes.
- * In order to properly initialize these bytes, we allocate 32 bytes even
- * though we only need less for an xcb_configure_notify_event_t */
- void *event = scalloc(32);
- xcb_configure_notify_event_t *generated_event = event;
-
- generated_event->event = window;
- generated_event->window = window;
- generated_event->response_type = XCB_CONFIGURE_NOTIFY;
-
- generated_event->x = r.x;
- generated_event->y = r.y;
- generated_event->width = r.width;
- generated_event->height = r.height;
-
- generated_event->border_width = border_width;
- generated_event->above_sibling = XCB_NONE;
- generated_event->override_redirect = false;
-
- xcb_send_event(conn, false, window, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)generated_event);
- xcb_flush(conn);
-
- free(event);
-}
-
/*
* Generates a configure_notify_event with absolute coordinates (relative to the X root
* window, not to the client’s frame) for the given client.
*
*/
void fake_absolute_configure_notify(Con *con) {
- Rect absolute;
+ xcb_rectangle_t absolute;
if (con->window == NULL)
return;
free(event);
}
-/*
- * Finds out which modifier mask is the one for numlock, as the user may change this.
- *
- */
-void xcb_get_numlock_mask(xcb_connection_t *conn) {
- xcb_key_symbols_t *keysyms;
- xcb_get_modifier_mapping_cookie_t cookie;
- xcb_get_modifier_mapping_reply_t *reply;
- xcb_keycode_t *modmap;
- int mask, i;
- const int masks[8] = { XCB_MOD_MASK_SHIFT,
- XCB_MOD_MASK_LOCK,
- XCB_MOD_MASK_CONTROL,
- XCB_MOD_MASK_1,
- XCB_MOD_MASK_2,
- XCB_MOD_MASK_3,
- XCB_MOD_MASK_4,
- XCB_MOD_MASK_5 };
-
- /* Request the modifier map */
- cookie = xcb_get_modifier_mapping(conn);
-
- /* Get the keysymbols */
- keysyms = xcb_key_symbols_alloc(conn);
-
- if ((reply = xcb_get_modifier_mapping_reply(conn, cookie, NULL)) == NULL) {
- xcb_key_symbols_free(keysyms);
- return;
- }
-
- modmap = xcb_get_modifier_mapping_keycodes(reply);
-
- /* Get the keycode for numlock */
-#ifdef OLD_XCB_KEYSYMS_API
- xcb_keycode_t numlock = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
-#else
- /* For now, we only use the first keysymbol. */
- xcb_keycode_t *numlock_syms = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK);
- if (numlock_syms == NULL)
- return;
- xcb_keycode_t numlock = *numlock_syms;
- free(numlock_syms);
-#endif
-
- /* Check all modifiers (Mod1-Mod5, Shift, Control, Lock) */
- for (mask = 0; mask < 8; mask++)
- for (i = 0; i < reply->keycodes_per_modifier; i++)
- if (modmap[(mask * reply->keycodes_per_modifier) + i] == numlock)
- xcb_numlock_mask = masks[mask];
-
- xcb_key_symbols_free(keysyms);
- free(reply);
-}
-
/*
* Raises the given window (typically client->frame) above all other windows
*
LOG("warp pointer to: %d %d\n", mid_x, mid_y);
xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, mid_x, mid_y);
}
+
+/*
+ * Set the cursor of the root window to the given cursor id.
+ * This function should only be used if xcursor_supported == false.
+ * Otherwise, use xcursor_set_root_cursor().
+ *
+ */
+void xcb_set_root_cursor(int cursor) {
+ xcb_cursor_t cursor_id = xcb_generate_id(conn);
+ i3Font cursor_font = load_font("cursor", false);
+ int xcb_cursor = xcursor_get_xcb_cursor(cursor);
+ xcb_create_glyph_cursor(conn, cursor_id, cursor_font.id, cursor_font.id,
+ xcb_cursor, xcb_cursor + 1, 0, 0, 0, 65535, 65535, 65535);
+ xcb_change_window_attributes(conn, root, XCB_CW_CURSOR, &cursor_id);
+ xcb_free_cursor(conn, cursor_id);
+ xcb_flush(conn);
+}
/*
* vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * xcursor.c: libXcursor support for themed cursors.
+ *
*/
#include <assert.h>
#include <X11/Xcursor/Xcursor.h>
static const int xcb_cursors[XCURSOR_CURSOR_MAX] = {
XCB_CURSOR_LEFT_PTR,
XCB_CURSOR_SB_H_DOUBLE_ARROW,
- XCB_CURSOR_SB_V_DOUBLE_ARROW
+ XCB_CURSOR_SB_V_DOUBLE_ARROW,
+ XCB_CURSOR_WATCH
};
static Cursor load_cursor(const char *name) {
cursors[XCURSOR_CURSOR_POINTER] = load_cursor("left_ptr");
cursors[XCURSOR_CURSOR_RESIZE_HORIZONTAL] = load_cursor("sb_h_double_arrow");
cursors[XCURSOR_CURSOR_RESIZE_VERTICAL] = load_cursor("sb_v_double_arrow");
+ cursors[XCURSOR_CURSOR_WATCH] = load_cursor("watch");
}
/*
* races might occur (even though we flush the Xlib connection).
*
*/
-void xcursor_set_root_cursor() {
+void xcursor_set_root_cursor(int cursor_id) {
XSetWindowAttributes attributes;
- attributes.cursor = xcursor_get_cursor(XCURSOR_CURSOR_POINTER);
+ attributes.cursor = xcursor_get_cursor(cursor_id);
XChangeWindowAttributes(xlibdpy, DefaultRootWindow(xlibdpy), CWCursor, &attributes);
XFlush(xlibdpy);
}
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
- *
- * © 2009-2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
* This is LEGACY code (we support RandR, which can do much more than
* Xinerama), but necessary for the poor users of the nVidia binary
- * driver which does not support RandR in 2010 *sigh*.
+ * driver which does not support RandR in 2011 *sigh*.
*
*/
+#include "all.h"
#include <xcb/xinerama.h>
-#include "all.h"
static int num_screens;
s->rect.height = min(s->rect.height, screen_info[screen].height);
} else {
s = scalloc(sizeof(Output));
- asprintf(&(s->name), "xinerama-%d", num_screens);
+ sasprintf(&(s->name), "xinerama-%d", num_screens);
DLOG("Created new Xinerama screen %s (%p)\n", s->name, s);
s->active = true;
s->rect.x = screen_info[screen].x_org;
+++ /dev/null
-test:
- PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" -It/lib t/12*.t
-
-clean:
- rm -rf testsuite-* latest
-
-all: test
-
-testfull:
- PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" -It/lib t/01* t/02* t/03* t/05* t/17* t/18* t/19* t/20*
--- /dev/null
+#!/usr/bin/env perl
+# vim:ts=4:sw=4:expandtab
+use strict; use warnings;
+use ExtUtils::MakeMaker;
+
+WriteMakefile(
+ NAME => 'i3 testsuite',
+ MIN_PERL_VERSION => '5.010000', # 5.10.0
+ PREREQ_PM => {
+ 'AnyEvent' => 0,
+ 'AnyEvent::I3' => '0.09',
+ 'X11::XCB' => '0.03',
+ 'Test::Most' => 0,
+ 'Test::Deep' => 0,
+ 'EV' => 0,
+ 'Inline' => 0,
+ },
+ # don't install any files from this directory
+ PM => {},
+ clean => {
+ FILES => 'testsuite-* latest'
+ }
+);
+# and don't run the tests while installing
+sub MY::test { }
#!/usr/bin/env perl
# vim:ts=4:sw=4:expandtab
-#
# © 2010-2011 Michael Stapelberg and contributors
-#
-# syntax: ./complete-run.pl --display :1 --display :2
-# to run the test suite on the X11 displays :1 and :2
-# use 'Xdummy :1' and 'Xdummy :2' before to start two
-# headless X11 servers
-#
use strict;
use warnings;
-use EV;
-use AnyEvent;
-use IO::Scalar; # not in core :\
-use File::Temp qw(tempfile tempdir);
use v5.10;
-use DateTime;
-use Data::Dumper;
+# the following are modules which ship with Perl (>= 5.10):
+use Pod::Usage;
use Cwd qw(abs_path);
-use Proc::Background;
+use File::Basename qw(basename);
+use File::Temp qw(tempfile tempdir);
+use Getopt::Long;
+use IO::Socket::UNIX;
+use POSIX;
+use Time::HiRes qw(sleep gettimeofday tv_interval);
use TAP::Harness;
use TAP::Parser;
use TAP::Parser::Aggregator;
-use File::Basename qw(basename);
+# these are shipped with the testsuite
+use lib qw(lib);
+use SocketActivation;
+use StartXDummy;
+use StatusLine;
+# the following modules are not shipped with Perl
+use AnyEvent;
+use AnyEvent::Handle;
use AnyEvent::I3 qw(:all);
-use Try::Tiny;
-use Getopt::Long;
-use Time::HiRes qw(sleep);
-use X11::XCB::Connection;
+use X11::XCB;
+
+# We actually use AnyEvent to make sure it loads an event loop implementation.
+# Afterwards, we overwrite SIGCHLD:
+my $cv = AnyEvent->condvar;
-# install a dummy CHLD handler to overwrite the CHLD handler of AnyEvent / EV
-# XXX: we could maybe also use a different loop than the default loop in EV?
+# Install a dummy CHLD handler to overwrite the CHLD handler of AnyEvent.
+# AnyEvent’s handler wait()s for every child which conflicts with TAP (TAP
+# needs to get the exit status to determine if a test is successful).
$SIG{CHLD} = sub {
};
# reads in a whole file
sub slurp {
- open my $fh, '<', shift;
+ open(my $fh, '<', shift);
local $/;
<$fh>;
}
+# convinience wrapper to write to the log file
+my $log;
+sub Log { say $log "@_" }
+
my $coverage_testing = 0;
+my $valgrind = 0;
+my $help = 0;
+# Number of tests to run in parallel. Important to know how many Xdummy
+# instances we need to start (unless @displays are given). Defaults to
+# num_cores * 2.
+my $parallel = undef;
my @displays = ();
+my @childpids = ();
my $result = GetOptions(
"coverage-testing" => \$coverage_testing,
+ "valgrind" => \$valgrind,
"display=s" => \@displays,
+ "parallel=i" => \$parallel,
+ "help|?" => \$help,
);
+pod2usage(-verbose => 2, -exitcode => 0) if $help;
+
@displays = split(/,/, join(',', @displays));
@displays = map { s/ //g; $_ } @displays;
-@displays = qw(:1) if @displays == 0;
+# No displays specified, let’s start some Xdummy instances.
+if (@displays == 0) {
+ my ($displays, $pids) = start_xdummy($parallel);
+ @displays = @$displays;
+ @childpids = @$pids;
+}
# connect to all displays for two reasons:
# 1: check if the display actually works
my @conns;
my @wdisplays;
for my $display (@displays) {
- try {
- my $x = X11::XCB::Connection->new(display => $display);
+ my $screen;
+ my $x = X11::XCB->new($display, $screen);
+ if ($x->has_error) {
+ Log "WARNING: Not using X11 display $display, could not connect";
+ } else {
push @conns, $x;
push @wdisplays, $display;
- } catch {
- say STDERR "WARNING: Not using X11 display $display, could not connect";
- };
+ }
}
-my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler";
+die "No usable displays found" if @wdisplays == 0;
+
my $config = slurp('i3-test.config');
# 1: get a list of all testcases
# 2: create an output directory for this test-run
my $outdir = "testsuite-";
-$outdir .= DateTime->now->strftime("%Y-%m-%d-%H-%M-%S-");
+$outdir .= POSIX::strftime("%Y-%m-%d-%H-%M-%S-", localtime());
$outdir .= `git describe --tags`;
chomp($outdir);
mkdir($outdir) or die "Could not create $outdir";
unlink("latest") if -e "latest";
symlink("$outdir", "latest") or die "Could not symlink latest to $outdir";
+my $logfile = "$outdir/complete-run.log";
+open $log, '>', $logfile or die "Could not create '$logfile': $!";
+say "Writing logfile to '$logfile'...";
+
# 3: run all tests
my @done;
my $num = @testfiles;
my $aggregator = TAP::Parser::Aggregator->new();
$aggregator->start();
-my $cv = AnyEvent->condvar;
+status_init(displays => \@wdisplays, tests => $num);
# We start tests concurrently: For each display, one test gets started. Every
# test starts another test after completing.
#
sub take_job {
my ($display) = @_;
- my ($fh, $tmpfile) = tempfile();
- say $fh $config;
- say $fh "ipc-socket /tmp/nested-$display";
- close($fh);
my $test = shift @testfiles;
return unless $test;
- my $logpath = "$outdir/i3-log-for-" . basename($test);
- my $cmd = "export DISPLAY=$display; exec $i3cmd -c $tmpfile >$logpath 2>&1";
+
my $dont_start = (slurp($test) =~ /# !NO_I3_INSTANCE!/);
+ my $basename = basename($test);
+ my $logpath = "$outdir/i3-log-for-$basename";
+
+ my ($fh, $tmpfile) = tempfile("i3-cfg-for-$basename.XXXXXX", UNLINK => 1);
+ say $fh $config;
+ say $fh "ipc-socket /tmp/nested-$display";
+ close($fh);
- my $process = Proc::Background->new($cmd) unless $dont_start;
- say "[$display] Running $test with logfile $logpath";
+ my $activate_cv = AnyEvent->condvar;
+ my $time_before_start = [gettimeofday];
+
+ my $pid;
+ if ($dont_start) {
+ $activate_cv->send(1);
+ } else {
+ $pid = activate_i3(
+ unix_socket_path => "/tmp/nested-$display-activation",
+ display => $display,
+ configfile => $tmpfile,
+ outdir => $outdir,
+ logpath => $logpath,
+ valgrind => $valgrind,
+ cv => $activate_cv
+ );
+
+ my $child_watcher;
+ $child_watcher = AnyEvent->child(pid => $pid, cb => sub {
+ Log status($display, "child died. pid = $pid");
+ undef $child_watcher;
+ });
+ }
- sleep 0.5;
my $kill_i3 = sub {
+ my $kill_cv = AnyEvent->condvar;
+
# Don’t bother killing i3 when we haven’t started it
- return if $dont_start;
+ if ($dont_start) {
+ $kill_cv->send();
+ return $kill_cv;
+ }
# When measuring code coverage, try to exit i3 cleanly (otherwise, .gcda
# files are not written) and fallback to killing it
- if ($coverage_testing) {
+ if ($coverage_testing || $valgrind) {
my $exited = 0;
- try {
- say "Exiting i3 cleanly...";
- i3("/tmp/nested-$display")->command('exit')->recv;
- $exited = 1;
- };
- return if $exited;
+ Log status($display, 'Exiting i3 cleanly...');
+ my $i3 = i3("/tmp/nested-$display");
+ $i3->connect->cb(sub {
+ if (!$_[0]->recv) {
+ # Could not connect to i3, just kill -9 it
+ kill(9, $pid) or die "Could not kill i3 using kill($pid)";
+ $kill_cv->send();
+ } else {
+ # Connected. Now send exit and continue once that’s acked.
+ $i3->command('exit')->cb(sub {
+ $kill_cv->send();
+ });
+ }
+ });
+ } else {
+ Log status($display, 'killing i3');
+
+ # No coverage testing or valgrind? Just kill -9 i3.
+ kill(9, $pid) or die "Could not kill i3 using kill($pid)";
+ $kill_cv->send();
}
- say "[$display] killing i3";
- kill(9, $process->pid) or die "could not kill i3";
+ return $kill_cv;
};
- my $output;
- my $parser = TAP::Parser->new({
- exec => [ 'sh', '-c', "DISPLAY=$display /usr/bin/perl -It/lib $test" ],
- spool => IO::Scalar->new(\$output),
- merge => 1,
- });
-
- my @watchers;
- my ($stdout, $stderr) = $parser->get_select_handles;
- for my $handle ($parser->get_select_handles) {
- my $w;
- $w = AnyEvent->io(
- fh => $handle,
- poll => 'r',
- cb => sub {
- # Ignore activity on stderr (unnecessary with merge => 1,
- # but let’s keep it in here if we want to use merge => 0
- # for some reason in the future).
- return if defined($stderr) and $handle == $stderr;
-
- my $result = $parser->next;
- if (defined($result)) {
- # TODO: check if we should bail out
- return;
- }
-
- # $result is not defined, we are done parsing
- say "[$display] $test finished";
- close($parser->delete_spool);
- $aggregator->add($test, $parser);
- push @done, [ $test, $output ];
-
- $kill_i3->();
+ # This will be called as soon as i3 is running and answered to our
+ # IPC request
+ $activate_cv->cb(sub {
+ my $time_activating = [gettimeofday];
+ my $start_duration = tv_interval($time_before_start, $time_activating);
+ my ($status) = $activate_cv->recv;
+ if ($dont_start) {
+ Log status($display, 'Not starting i3, testcase does that');
+ } else {
+ my $duration = sprintf("%.2f", $start_duration);
+ Log status($display, "i3 startup: took $duration sec, status = $status");
+ }
- undef $_ for @watchers;
- if (@done == $num) {
- $cv->send;
- } else {
- take_job($display);
+ Log status($display, "Starting $test");
+
+ my $output;
+ open(my $spool, '>', \$output);
+ my $parser = TAP::Parser->new({
+ exec => [ 'sh', '-c', qq|DISPLAY=$display LOGPATH="$logpath" OUTDIR="$outdir" VALGRIND=$valgrind /usr/bin/perl -Ilib $test| ],
+ spool => $spool,
+ merge => 1,
+ });
+
+ my $tests_completed;
+
+ my @watchers;
+ my ($stdout, $stderr) = $parser->get_select_handles;
+ for my $handle ($parser->get_select_handles) {
+ my $w;
+ $w = AnyEvent->io(
+ fh => $handle,
+ poll => 'r',
+ cb => sub {
+ # Ignore activity on stderr (unnecessary with merge => 1,
+ # but let’s keep it in here if we want to use merge => 0
+ # for some reason in the future).
+ return if defined($stderr) and $handle == $stderr;
+
+ my $result = $parser->next;
+ if (defined($result)) {
+ $tests_completed++;
+ status($display, "Running $test: [$tests_completed/??]");
+ # TODO: check if we should bail out
+ return;
+ }
+
+ # $result is not defined, we are done parsing
+ Log status($display, "$test finished");
+ close($parser->delete_spool);
+ $aggregator->add($test, $parser);
+ push @done, [ $test, $output ];
+
+ status_completed(scalar @done);
+
+ my $exitcv = $kill_i3->();
+ $exitcv->cb(sub {
+
+ undef $_ for @watchers;
+ if (@done == $num) {
+ $cv->send;
+ } else {
+ take_job($display);
+ }
+ });
}
- }
- );
- push @watchers, $w;
- }
+ );
+ push @watchers, $w;
+ }
+ });
}
$cv->recv;
$aggregator->stop();
+# print empty lines to seperate failed tests from statuslines
+print "\n\n";
+
for (@done) {
my ($test, $output) = @$_;
- say "output for $test:";
- say $output;
+ Log "output for $test:";
+ Log $output;
+ # print error messages of failed tests
+ say for $output =~ /^not ok.+\n+((?:^#.+\n)+)/mg
}
# 4: print summary
$harness->summary($aggregator);
+
+close $log;
+
+kill(15, $_) for @childpids;
+
+__END__
+
+=head1 NAME
+
+complete-run.pl - Run the i3 testsuite
+
+=head1 SYNOPSIS
+
+complete-run.pl [files...]
+
+=head1 EXAMPLE
+
+To run the whole testsuite on a reasonable number of Xdummy instances (your
+running X11 will not be touched), run:
+ ./complete-run.pl
+
+To run only a specific test (useful when developing a new feature), run:
+ ./complete-run t/100-fullscreen.t
+
+=head1 OPTIONS
+
+=over 8
+
+=item B<--display>
+
+Specifies which X11 display should be used. Can be specified multiple times and
+will parallelize the tests:
+
+ # Run tests on the second X server
+ ./complete-run.pl -d :1
+
+ # Run four tests in parallel on some Xdummy servers
+ ./complete-run.pl -d :1,:2,:3,:4
+
+Note that it is not necessary to specify this anymore. If omitted,
+complete-run.pl will start (num_cores * 2) Xdummy instances.
+
+=item B<--valgrind>
+
+Runs i3 under valgrind to find memory problems. The output will be available in
+C<latest/valgrind.log>.
+
+=item B<--coverage-testing>
+
+Exits i3 cleanly (instead of kill -9) to make coverage testing work properly.
+
+=item B<--parallel>
+
+Number of Xdummy instances to start (if you don’t want to start num_cores * 2
+instances for some reason).
+
+ # Run all tests on a single Xdummy instance
+ ./complete-run.pl -p 1
--- /dev/null
+package SocketActivation;
+# vim:ts=4:sw=4:expandtab
+
+use strict;
+use warnings;
+use IO::Socket::UNIX; # core
+use Cwd qw(abs_path); # core
+use POSIX (); # core
+use AnyEvent::Handle; # not core
+use Exporter 'import';
+use v5.10;
+
+our @EXPORT = qw(activate_i3);
+
+#
+# Starts i3 using socket activation. Creates a listening socket (with bind +
+# listen) which is then passed to i3, who in turn calls accept and handles the
+# requests.
+#
+# Since the kernel buffers the connect, the parent process can connect to the
+# socket immediately after forking. It then sends a request and waits until it
+# gets an answer. Obviously, i3 has to be initialized to actually answer the
+# request.
+#
+# This way, we can wait *precisely* the amount of time which i3 waits to get
+# ready, which is a *HUGE* speed gain (and a lot more robust) in comparison to
+# using sleep() with a fixed amount of time.
+#
+# unix_socket_path: Location of the socket to use for the activation
+# display: X11 $ENV{DISPLAY}
+# configfile: path to the configuration file to use
+# logpath: path to the logfile to which i3 will append
+# cv: an AnyEvent->condvar which will be triggered once i3 is ready
+#
+sub activate_i3 {
+ my %args = @_;
+
+ # remove the old unix socket
+ unlink($args{unix_socket_path});
+
+ # pass all file descriptors up to three to the children.
+ # we need to set this flag before opening the socket.
+ open(my $fdtest, '<', '/dev/null');
+ $^F = fileno($fdtest);
+ close($fdtest);
+ my $socket = IO::Socket::UNIX->new(
+ Listen => 1,
+ Local => $args{unix_socket_path},
+ );
+
+ my $pid = fork;
+ if (!defined($pid)) {
+ die "could not fork()";
+ }
+ if ($pid == 0) {
+ $ENV{LISTEN_PID} = $$;
+ $ENV{LISTEN_FDS} = 1;
+ delete $ENV{DESKTOP_STARTUP_ID};
+ $ENV{DISPLAY} = $args{display};
+ $ENV{PATH} = join(':',
+ '../i3-nagbar',
+ '../i3-msg',
+ '../i3-config-wizard',
+ '../i3bar',
+ '..',
+ $ENV{PATH}
+ );
+ # Only pass file descriptors 0 (stdin), 1 (stdout), 2 (stderr) and
+ # 3 (socket) to the child.
+ $^F = 3;
+
+ # If the socket does not use file descriptor 3 by chance already, we
+ # close fd 3 and dup2() the socket to 3.
+ if (fileno($socket) != 3) {
+ POSIX::close(3);
+ POSIX::dup2(fileno($socket), 3);
+ }
+
+ # Construct the command to launch i3. Use maximum debug level, disable
+ # the interactive signalhandler to make it crash immediately instead.
+ my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler";
+
+ if ($args{valgrind}) {
+ $i3cmd =
+ qq|valgrind -v --log-file="$args{outdir}/valgrind.log" | .
+ qq|--leak-check=full --track-origins=yes --num-callers=20 | .
+ qq|--tool=memcheck -- $i3cmd|;
+ }
+
+ # Append to $args{logpath} instead of overwriting because i3 might be
+ # run multiple times in one testcase.
+ my $cmd = "exec $i3cmd -c $args{configfile} >>$args{logpath} 2>&1";
+
+ # We need to use the shell due to using output redirections.
+ exec '/bin/sh', '-c', $cmd;
+
+ # if we are still here, i3 could not be found or exec failed. bail out.
+ exit 1;
+ }
+
+ # close the socket, the child process should be the only one which keeps a file
+ # descriptor on the listening socket.
+ $socket->close;
+
+ # We now connect (will succeed immediately) and send a request afterwards.
+ # As soon as the reply is there, i3 is considered ready.
+ my $cl = IO::Socket::UNIX->new(Peer => $args{unix_socket_path});
+ my $hdl;
+ $hdl = AnyEvent::Handle->new(
+ fh => $cl,
+ on_error => sub {
+ $hdl->destroy;
+ $args{cv}->send(0);
+ });
+
+ # send a get_tree message without payload
+ $hdl->push_write('i3-ipc' . pack("LL", 0, 4));
+
+ # wait for the reply
+ $hdl->push_read(chunk => 1, => sub {
+ my ($h, $line) = @_;
+ $args{cv}->send(1);
+ undef $hdl;
+ });
+
+ return $pid;
+}
+
+1
--- /dev/null
+package StartXDummy;
+# vim:ts=4:sw=4:expandtab
+
+use strict;
+use warnings;
+use Exporter 'import';
+use Time::HiRes qw(sleep);
+use v5.10;
+
+our @EXPORT = qw(start_xdummy);
+
+# reads in a whole file
+sub slurp {
+ open(my $fh, '<', shift) or return '';
+ local $/;
+ <$fh>;
+}
+
+=head2 start_xdummy($parallel)
+
+Starts C<$parallel> (or number of cores * 2 if undef) Xdummy processes (see
+the file ./Xdummy) and returns two arrayrefs: a list of X11 display numbers to
+the Xdummy processes and a list of PIDs of the processes.
+
+=cut
+sub start_xdummy {
+ my ($parallel) = @_;
+
+ my @displays = ();
+ my @childpids = ();
+
+ # Yeah, I know it’s non-standard, but Perl’s POSIX module doesn’t have
+ # _SC_NPROCESSORS_CONF.
+ my $cpuinfo = slurp('/proc/cpuinfo');
+ my $num_cores = scalar grep { /model name/ } split("\n", $cpuinfo);
+ # If /proc/cpuinfo does not exist, we fall back to 2 cores.
+ $num_cores ||= 2;
+
+ $parallel ||= $num_cores * 2;
+
+ # First get the last used display number, then increment it by one.
+ # Effectively falls back to 1 if no X server is running.
+ my ($displaynum) = reverse ('0', sort </tmp/.X11-unix/X*>);
+ $displaynum =~ s/.*(\d)$/$1/;
+ $displaynum++;
+
+ say "Starting $parallel Xdummy instances, starting at :$displaynum...";
+
+ for my $idx (0 .. ($parallel-1)) {
+ my $pid = fork();
+ die "Could not fork: $!" unless defined($pid);
+ if ($pid == 0) {
+ # Child, close stdout/stderr, then start Xdummy.
+ close STDOUT;
+ close STDERR;
+ # We use -config /dev/null to prevent Xdummy from using the system
+ # Xorg configuration. The tests should be independant from the
+ # actual system X configuration.
+ exec './Xdummy', ":$displaynum", '-config', '/dev/null';
+ exit 1;
+ }
+ push(@childpids, $pid);
+ push(@displays, ":$displaynum");
+ $displaynum++;
+ }
+
+ # Wait until the X11 sockets actually appear. Pretty ugly solution, but as
+ # long as we can’t socket-activate X11…
+ my $sockets_ready;
+ do {
+ $sockets_ready = 1;
+ for (@displays) {
+ my $path = "/tmp/.X11-unix/X" . substr($_, 1);
+ $sockets_ready = 0 unless -S $path;
+ }
+ sleep 0.1;
+ } until $sockets_ready;
+
+ return \@displays, \@childpids;
+}
+
+1
--- /dev/null
+package StatusLine;
+use strict; use warnings;
+
+# enable autoflush on STDOUT.
+# this is essential, because we print our statuslines without a newline
+$| = 1;
+
+use Exporter 'import';
+our @EXPORT = qw/status_init status status_completed/;
+
+my $ansi_clear_line = "\033[2K";
+my $ansi_save_cursor = "\0337";
+my $ansi_restore_cursor = "\0338";
+my %ansi_line_upwards;
+
+my $tests_total;
+
+# setup %ansi_line_upwards to map all working displays to the
+# specific movement commands and initialize all status lines
+sub status_init {
+ my %args = @_;
+ my $displays = $args{displays};
+ $tests_total = $args{tests};
+
+ for my $n (1 .. @$displays) {
+ # since we are moving upwards, get $display in reverse order
+ my $display = $displays->[-$n];
+
+ $ansi_line_upwards{$display} = "\033[$n\101";
+
+ # print an empty line for this status line
+ print "\n";
+ }
+
+ status_completed(0);
+}
+
+# generates the status text, prints it in the appropiate line
+# and returns it, so it can be used in conjuction with C<Log()>
+sub status {
+ my ($display, $msg) = @_;
+ my $status = "[$display] $msg";
+
+ print
+ $ansi_save_cursor,
+ $ansi_line_upwards{$display},
+ $ansi_clear_line,
+ $status,
+ $ansi_restore_cursor;
+
+ return $status;
+}
+
+sub status_completed {
+ my $num = shift;
+ print
+ $ansi_save_cursor,
+ $ansi_clear_line,
+ "completed $num of $tests_total tests",
+ $ansi_restore_cursor;
+}
+
+
+__PACKAGE__ __END__
--- /dev/null
+package i3test;
+# vim:ts=4:sw=4:expandtab
+use strict; use warnings;
+
+use File::Temp qw(tmpnam tempfile tempdir);
+use Test::Builder;
+use X11::XCB::Rect;
+use X11::XCB::Window;
+use X11::XCB qw(:all);
+use AnyEvent::I3;
+use EV;
+use List::Util qw(first);
+use Time::HiRes qw(sleep);
+use Cwd qw(abs_path);
+use SocketActivation;
+
+use v5.10;
+
+use Exporter ();
+our @EXPORT = qw(
+ get_workspace_names
+ get_unused_workspace
+ fresh_workspace
+ get_ws_content
+ get_ws
+ get_focused
+ open_empty_con
+ open_window
+ open_floating_window
+ get_dock_clients
+ cmd
+ sync_with_i3
+ does_i3_live
+ exit_gracefully
+ workspace_exists
+ focused_ws
+ get_socket_path
+ launch_with_config
+ wait_for_event
+ wait_for_map
+ wait_for_unmap
+);
+
+my $tester = Test::Builder->new();
+my $_cached_socket_path = undef;
+my $_sync_window = undef;
+my $tmp_socket_path = undef;
+
+BEGIN {
+ my $window_count = 0;
+ sub counter_window {
+ return $window_count++;
+ }
+}
+
+sub import {
+ my $class = shift;
+ my $pkg = caller;
+ eval "package $pkg;
+use Test::Most" . (@_ > 0 ? " qw(@_)" : "") . ";
+use Data::Dumper;
+use AnyEvent::I3;
+use Time::HiRes qw(sleep);
+use Test::Deep qw(eq_deeply cmp_deeply cmp_set cmp_bag cmp_methods useclass noclass set bag subbagof superbagof subsetof supersetof superhashof subhashof bool str arraylength Isa ignore methods regexprefonly regexpmatches num regexponly scalref reftype hashkeysonly blessed array re hash regexpref hash_each shallow array_each code arrayelementsonly arraylengthonly scalarrefonly listmethods any hashkeys isa);
+use v5.10;
+use strict;
+use warnings;
+";
+ @_ = ($class);
+ goto \&Exporter::import;
+}
+
+#
+# Waits for the next event and calls the given callback for every event to
+# determine if this is the event we are waiting for.
+#
+# Can be used to wait until a window is mapped, until a ClientMessage is
+# received, etc.
+#
+# wait_for_event $x, 0.25, sub { $_[0]->{response_type} == MAP_NOTIFY };
+#
+sub wait_for_event {
+ my ($x, $timeout, $cb) = @_;
+
+ my $cv = AE::cv;
+
+ my $prep = EV::prepare sub {
+ $x->flush;
+ };
+
+ my $check = EV::check sub {
+ while (defined(my $event = $x->poll_for_event)) {
+ if ($cb->($event)) {
+ $cv->send(1);
+ last;
+ }
+ }
+ };
+
+ my $watcher = EV::io $x->get_file_descriptor, EV::READ, sub {
+ # do nothing, we only need this watcher so that EV picks up the events
+ };
+
+ # Trigger timeout after $timeout seconds (can be fractional)
+ my $t = AE::timer $timeout, 0, sub { warn "timeout ($timeout secs)"; $cv->send(0) };
+
+ my $result = $cv->recv;
+ undef $t;
+ return $result;
+}
+
+# thin wrapper around wait_for_event which waits for MAP_NOTIFY
+# make sure to include 'structure_notify' in the window’s event_mask attribute
+sub wait_for_map {
+ my ($x) = @_;
+ wait_for_event $x, 2, sub { $_[0]->{response_type} == MAP_NOTIFY };
+}
+
+# Wrapper around wait_for_event which waits for UNMAP_NOTIFY. Also calls
+# sync_with_i3 to make sure i3 also picked up and processed the UnmapNotify
+# event.
+sub wait_for_unmap {
+ my ($x) = @_;
+ wait_for_event $x, 2, sub { $_[0]->{response_type} == UNMAP_NOTIFY };
+ sync_with_i3($x);
+}
+
+#
+# Opens a new window (see X11::XCB::Window), maps it, waits until it got mapped
+# and synchronizes with i3.
+#
+# set dont_map to a true value to avoid mapping
+#
+# default values:
+# class => WINDOW_CLASS_INPUT_OUTPUT
+# rect => [ 0, 0, 30, 30 ]
+# background_color => '#c0c0c0'
+# event_mask => [ 'structure_notify' ]
+# name => 'Window <n>'
+#
+sub open_window {
+ my ($x, $args) = @_;
+ my %args = ($args ? %$args : ());
+
+ my $dont_map = delete $args{dont_map};
+
+ $args{class} //= WINDOW_CLASS_INPUT_OUTPUT;
+ $args{rect} //= [ 0, 0, 30, 30 ];
+ $args{background_color} //= '#c0c0c0';
+ $args{event_mask} //= [ 'structure_notify' ];
+ $args{name} //= 'Window ' . counter_window();
+
+ my $window = $x->root->create_child(%args);
+
+ return $window if $dont_map;
+
+ $window->map;
+ wait_for_map($x);
+ # We sync with i3 here to make sure $x->input_focus is updated.
+ sync_with_i3($x);
+ return $window;
+}
+
+# Thin wrapper around open_window which sets window_type to
+# _NET_WM_WINDOW_TYPE_UTILITY to make the window floating.
+sub open_floating_window {
+ my ($x, $args) = @_;
+ my %args = ($args ? %$args : ());
+
+ $args{window_type} = $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY');
+
+ return open_window($x, \%args);
+}
+
+sub open_empty_con {
+ my ($i3) = @_;
+
+ my $reply = $i3->command('open')->recv;
+ return $reply->{id};
+}
+
+sub get_workspace_names {
+ my $i3 = i3(get_socket_path());
+ my $tree = $i3->get_tree->recv;
+ my @outputs = @{$tree->{nodes}};
+ my @cons;
+ for my $output (@outputs) {
+ # get the first CT_CON of each output
+ my $content = first { $_->{type} == 2 } @{$output->{nodes}};
+ @cons = (@cons, @{$content->{nodes}});
+ }
+ [ map { $_->{name} } @cons ]
+}
+
+sub get_unused_workspace {
+ my @names = get_workspace_names();
+ my $tmp;
+ do { $tmp = tmpnam() } while ($tmp ~~ @names);
+ $tmp
+}
+
+sub fresh_workspace {
+ my $unused = get_unused_workspace;
+ cmd("workspace $unused");
+ $unused
+}
+
+sub get_ws {
+ my ($name) = @_;
+ my $i3 = i3(get_socket_path());
+ my $tree = $i3->get_tree->recv;
+
+ my @outputs = @{$tree->{nodes}};
+ my @workspaces;
+ for my $output (@outputs) {
+ # get the first CT_CON of each output
+ my $content = first { $_->{type} == 2 } @{$output->{nodes}};
+ @workspaces = (@workspaces, @{$content->{nodes}});
+ }
+
+ # as there can only be one workspace with this name, we can safely
+ # return the first entry
+ return first { $_->{name} eq $name } @workspaces;
+}
+
+#
+# returns the content (== tree, starting from the node of a workspace)
+# of a workspace. If called in array context, also includes the focus
+# stack of the workspace
+#
+sub get_ws_content {
+ my ($name) = @_;
+ my $con = get_ws($name);
+ return wantarray ? ($con->{nodes}, $con->{focus}) : $con->{nodes};
+}
+
+sub get_focused {
+ my ($ws) = @_;
+ my $con = get_ws($ws);
+
+ my @focused = @{$con->{focus}};
+ my $lf;
+ while (@focused > 0) {
+ $lf = $focused[0];
+ last unless defined($con->{focus});
+ @focused = @{$con->{focus}};
+ my @cons = grep { $_->{id} == $lf } (@{$con->{nodes}}, @{$con->{'floating_nodes'}});
+ $con = $cons[0];
+ }
+
+ return $lf;
+}
+
+sub get_dock_clients {
+ my $which = shift;
+
+ my $tree = i3(get_socket_path())->get_tree->recv;
+ my @outputs = @{$tree->{nodes}};
+ # Children of all dockareas
+ my @docked;
+ for my $output (@outputs) {
+ if (!defined($which)) {
+ @docked = (@docked, map { @{$_->{nodes}} }
+ grep { $_->{type} == 5 }
+ @{$output->{nodes}});
+ } elsif ($which eq 'top') {
+ my $first = first { $_->{type} == 5 } @{$output->{nodes}};
+ @docked = (@docked, @{$first->{nodes}});
+ } elsif ($which eq 'bottom') {
+ my @matching = grep { $_->{type} == 5 } @{$output->{nodes}};
+ my $last = $matching[-1];
+ @docked = (@docked, @{$last->{nodes}});
+ }
+ }
+ return @docked;
+}
+
+sub cmd {
+ i3(get_socket_path())->command(@_)->recv
+}
+
+sub workspace_exists {
+ my ($name) = @_;
+ ($name ~~ @{get_workspace_names()})
+}
+
+sub focused_ws {
+ my $i3 = i3(get_socket_path());
+ my $tree = $i3->get_tree->recv;
+ my @outputs = @{$tree->{nodes}};
+ my @cons;
+ for my $output (@outputs) {
+ # get the first CT_CON of each output
+ my $content = first { $_->{type} == 2 } @{$output->{nodes}};
+ my $first = first { $_->{fullscreen_mode} == 1 } @{$content->{nodes}};
+ return $first->{name}
+ }
+}
+
+#
+# Sends an I3_SYNC ClientMessage with a random value to the root window.
+# i3 will reply with the same value, but, due to the order of events it
+# processes, only after all other events are done.
+#
+# This can be used to ensure the results of a cmd 'focus left' are pushed to
+# X11 and that $x->input_focus returns the correct value afterwards.
+#
+# See also docs/testsuite for a long explanation
+#
+sub sync_with_i3 {
+ my ($x) = @_;
+
+ # Since we need a (mapped) window for receiving a ClientMessage, we create
+ # one on the first call of sync_with_i3. It will be re-used in all
+ # subsequent calls.
+ if (!defined($_sync_window)) {
+ $_sync_window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => X11::XCB::Rect->new(x => -15, y => -15, width => 10, height => 10 ),
+ override_redirect => 1,
+ background_color => '#ff0000',
+ event_mask => [ 'structure_notify' ],
+ );
+
+ $_sync_window->map;
+
+ wait_for_event $x, 2, sub { $_[0]->{response_type} == MAP_NOTIFY };
+ }
+
+ my $root = $x->get_root_window();
+ # Generate a random number to identify this particular ClientMessage.
+ my $myrnd = int(rand(255)) + 1;
+
+ # Generate a ClientMessage, see xcb_client_message_t
+ my $msg = pack "CCSLLLLLLL",
+ CLIENT_MESSAGE, # response_type
+ 32, # format
+ 0, # sequence
+ $root, # destination window
+ $x->atom(name => 'I3_SYNC')->id,
+
+ $_sync_window->id, # data[0]: our own window id
+ $myrnd, # data[1]: a random value to identify the request
+ 0,
+ 0,
+ 0;
+
+ # Send it to the root window -- since i3 uses the SubstructureRedirect
+ # event mask, it will get the ClientMessage.
+ $x->send_event(0, $root, EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg);
+
+ # now wait until the reply is here
+ return wait_for_event $x, 2, sub {
+ my ($event) = @_;
+ # TODO: const
+ return 0 unless $event->{response_type} == 161;
+
+ my ($win, $rnd) = unpack "LL", $event->{data};
+ return ($rnd == $myrnd);
+ };
+}
+
+sub does_i3_live {
+ my $tree = i3(get_socket_path())->get_tree->recv;
+ my @nodes = @{$tree->{nodes}};
+ my $ok = (@nodes > 0);
+ $tester->ok($ok, 'i3 still lives');
+ return $ok;
+}
+
+# Tries to exit i3 gracefully (with the 'exit' cmd) or kills the PID if that fails
+sub exit_gracefully {
+ my ($pid, $socketpath) = @_;
+ $socketpath ||= get_socket_path();
+
+ my $exited = 0;
+ eval {
+ say "Exiting i3 cleanly...";
+ i3($socketpath)->command('exit')->recv;
+ $exited = 1;
+ };
+
+ if (!$exited) {
+ kill(9, $pid) or die "could not kill i3";
+ }
+
+ if ($socketpath =~ m,^/tmp/i3-test-socket-,) {
+ unlink($socketpath);
+ }
+}
+
+# Gets the socket path from the I3_SOCKET_PATH atom stored on the X11 root window
+sub get_socket_path {
+ my ($cache) = @_;
+ $cache ||= 1;
+
+ if ($cache && defined($_cached_socket_path)) {
+ return $_cached_socket_path;
+ }
+
+ my $x = X11::XCB::Connection->new;
+ my $atom = $x->atom(name => 'I3_SOCKET_PATH');
+ my $cookie = $x->get_property(0, $x->get_root_window(), $atom->id, GET_PROPERTY_TYPE_ANY, 0, 256);
+ my $reply = $x->get_property_reply($cookie->{sequence});
+ my $socketpath = $reply->{value};
+ $_cached_socket_path = $socketpath;
+ return $socketpath;
+}
+
+#
+# launches a new i3 process with the given string as configuration file.
+# useful for tests which test specific config file directives.
+#
+# be sure to use !NO_I3_INSTANCE! somewhere in the file to signal
+# complete-run.pl that it should not create an instance of i3
+#
+sub launch_with_config {
+ my ($config, $dont_add_socket_path) = @_;
+
+ $dont_add_socket_path //= 0;
+
+ if (!defined($tmp_socket_path)) {
+ $tmp_socket_path = File::Temp::tempnam('/tmp', 'i3-test-socket-');
+ }
+
+ my ($fh, $tmpfile) = tempfile('i3-test-config-XXXXX', UNLINK => 1);
+ say $fh $config;
+ say $fh "ipc-socket $tmp_socket_path" unless $dont_add_socket_path;
+ close($fh);
+
+ my $cv = AnyEvent->condvar;
+ my $pid = activate_i3(
+ unix_socket_path => "$tmp_socket_path-activation",
+ display => $ENV{DISPLAY},
+ configfile => $tmpfile,
+ outdir => $ENV{OUTDIR},
+ logpath => $ENV{LOGPATH},
+ valgrind => $ENV{VALGRIND},
+ cv => $cv,
+ );
+
+ # blockingly wait until i3 is ready
+ $cv->recv;
+
+ # force update of the cached socket path in lib/i3test
+ get_socket_path(0);
+
+ return $pid;
+}
+
+1
+++ /dev/null
-#!perl
-
-use Test::More tests => 2;
-
-BEGIN {
- use_ok( 'X11::XCB::Connection' );
- use_ok( 'X11::XCB::Window' );
-}
-
-diag( "Testing i3, Perl $], $^X" );
--- /dev/null
+#!perl
+
+use Test::More tests => 2;
+
+BEGIN {
+ use_ok( 'X11::XCB::Connection' );
+ use_ok( 'X11::XCB::Window' );
+}
+
+diag( "Testing i3, Perl $], $^X" );
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+ use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
+
+my $window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => $original_rect,
+ background_color => '#C0C0C0',
+);
+
+isa_ok($window, 'X11::XCB::Window');
+
+is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
+
+$window->map;
+
+sleep(0.25);
+
+my $new_rect = $window->rect;
+ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# checks if i3 supports I3_SYNC
+#
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+my $result = sync_with_i3($x);
+ok($result, 'syncing was successful');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+fresh_workspace;
+
+#####################################################################
+# Ensure IPC works by switching workspaces
+#####################################################################
+
+# Create a window so we can get a focus different from NULL
+my $window = open_window($x);
+
+my $focus = $x->input_focus;
+
+# Switch to another workspace
+fresh_workspace;
+
+sync_with_i3($x);
+my $new_focus = $x->input_focus;
+isnt($focus, $new_focus, "Focus changed");
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+ use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
+
+my $window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => $original_rect,
+ override_redirect => 1,
+ background_color => '#C0C0C0',
+);
+
+isa_ok($window, 'X11::XCB::Window');
+
+is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
+
+$window->map;
+
+my $new_rect = $window->rect;
+isa_ok($new_rect, 'X11::XCB::Rect');
+
+is_deeply($new_rect, $original_rect, "window untouched");
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+ use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+# Create a floating window which is smaller than the minimum enforced size of i3
+my $window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30],
+ background_color => '#C0C0C0',
+ # replace the type with 'utility' as soon as the coercion works again in X11::XCB
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
+ event_mask => [ 'structure_notify' ],
+);
+
+isa_ok($window, 'X11::XCB::Window');
+
+$window->map;
+
+wait_for_map $x;
+
+my ($absolute, $top) = $window->rect;
+
+ok($window->mapped, 'Window is mapped');
+cmp_ok($absolute->{width}, '>=', 75, 'i3 raised the width to 75');
+cmp_ok($absolute->{height}, '>=', 50, 'i3 raised the height to 50');
+
+ok($absolute->{x} != 0 && $absolute->{y} != 0, 'i3 did not map it to (0x0)');
+
+$window->unmap;
+
+$window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 1, 1, 80, 90],
+ background_color => '#C0C0C0',
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
+ event_mask => [ 'structure_notify' ],
+);
+
+isa_ok($window, 'X11::XCB::Window');
+
+$window->map;
+
+wait_for_map $x;
+
+($absolute, $top) = $window->rect;
+
+cmp_ok($absolute->{width}, '==', 80, "i3 let the width at 80");
+cmp_ok($absolute->{height}, '==', 90, "i3 let the height at 90");
+
+# We need to compare the position with decorations due to the way
+# we do decoration rendering (on the parent frame) in the tree branch
+cmp_ok($top->{x}, '==', 1, 'i3 mapped it to x=1');
+cmp_ok($top->{y}, '==', 19, 'i3 mapped it to y=18');
+
+$window->unmap;
+
+#####################################################################
+# check that a tiling window which is then made floating still has
+# at least the size of its initial geometry
+#####################################################################
+
+$window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 1, 1, 80, 90],
+ background_color => '#C0C0C0',
+ #window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
+ event_mask => [ 'structure_notify' ],
+);
+
+isa_ok($window, 'X11::XCB::Window');
+
+$window->map;
+
+wait_for_map $x;
+
+cmd 'floating enable';
+
+($absolute, $top) = $window->rect;
+
+cmp_ok($absolute->{width}, '==', 80, "i3 let the width at 80");
+cmp_ok($absolute->{height}, '==', 90, "i3 let the height at 90");
+
+$window->unmap;
+
+done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
-
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => $original_rect,
- background_color => '#C0C0C0',
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
-
-$window->map;
-
-sleep(0.25);
-
-my $new_rect = $window->rect;
-ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-use List::Util qw(first);
-
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-sub fullscreen_windows {
- scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($tmp)}
-}
-
-# get the output of this workspace
-my $tree = $i3->get_tree->recv;
-my @outputs = @{$tree->{nodes}};
-my $output;
-for my $o (@outputs) {
- # get the first CT_CON of each output
- my $content = first { $_->{type} == 2 } @{$o->{nodes}};
- if (defined(first { $_->{name} eq $tmp } @{$content->{nodes}})) {
- $output = $o;
- last;
- }
-}
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
-##################################
-# map a window, then fullscreen it
-##################################
-
-my $original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
-
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => $original_rect,
- background_color => '#C0C0C0',
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
-
-$window->map;
-
-sleep 0.25;
-
-# open another container to make the window get only half of the screen
-cmd 'open';
-
-my $new_rect = $window->rect;
-ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
-$original_rect = $new_rect;
-
-sleep 0.25;
-
-$window->fullscreen(1);
-
-sleep 0.25;
-
-$new_rect = $window->rect;
-ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen");
-
-my $orect = $output->{rect};
-my $wrect = $new_rect;
-
-# see if the window really is fullscreen. 20 px for borders are allowed
-my $threshold = 20;
-ok(($wrect->{x} - $orect->{x}) < $threshold, 'x coordinate fullscreen');
-ok(($wrect->{y} - $orect->{y}) < $threshold, 'y coordinate fullscreen');
-ok(abs($wrect->{width} - $orect->{width}) < $threshold, 'width coordinate fullscreen');
-ok(abs($wrect->{height} - $orect->{height}) < $threshold, 'height coordinate fullscreen');
-
-
-$window->unmap;
-
-#########################################################
-# test with a window which is fullscreened before mapping
-#########################################################
-
-# open another container because the empty one will swallow the window we
-# map in a second
-cmd 'open';
-
-$original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => $original_rect,
- background_color => 61440,
-);
-
-is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
-
-$window->fullscreen(1);
-$window->map;
-
-sleep(0.25);
-
-$new_rect = $window->rect;
-ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen");
-ok($window->mapped, "Window is mapped after opening it in fullscreen mode");
-
-$wrect = $new_rect;
-
-# see if the window really is fullscreen. 20 px for borders are allowed
-ok(($wrect->{x} - $orect->{x}) < $threshold, 'x coordinate fullscreen');
-ok(($wrect->{y} - $orect->{y}) < $threshold, 'y coordinate fullscreen');
-ok(abs($wrect->{width} - $orect->{width}) < $threshold, 'width coordinate fullscreen');
-ok(abs($wrect->{height} - $orect->{height}) < $threshold, 'height coordinate fullscreen');
-
-###############################################################################
-# test if setting two windows in fullscreen mode at the same time does not work
-###############################################################################
-
-$original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
-my $swindow = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => $original_rect,
- background_color => '#C0C0C0',
-);
-
-$swindow->map;
-sleep 0.25;
-
-ok(!$swindow->mapped, 'window not mapped while fullscreen window active');
-
-$new_rect = $swindow->rect;
-ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
-
-$swindow->fullscreen(1);
-sleep 0.25;
-
-is(fullscreen_windows(), 1, 'amount of fullscreen windows');
-
-$window->fullscreen(0);
-sleep 0.25;
-is(fullscreen_windows(), 0, 'amount of fullscreen windows');
-
-ok($swindow->mapped, 'window mapped after other fullscreen ended');
-
-###########################################################################
-# as $swindow is out of state at the moment (it requested to be fullscreen,
-# but the WM denied), we check what happens if we go out of fullscreen now
-# (nothing should happen)
-###########################################################################
-
-$swindow->fullscreen(0);
-sleep 0.25;
-
-is(fullscreen_windows(), 0, 'amount of fullscreen windows after disabling');
-
-cmd 'fullscreen';
-
-is(fullscreen_windows(), 1, 'amount of fullscreen windows after fullscreen command');
-
-cmd 'fullscreen';
-
-is(fullscreen_windows(), 0, 'amount of fullscreen windows after fullscreen command');
-
-# clean up the workspace so that it will be cleaned when switching away
-cmd 'kill' for (@{get_ws_content($tmp)});
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
-
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => $original_rect,
- override_redirect => 1,
- background_color => '#C0C0C0',
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
-
-$window->map;
-
-my $new_rect = $window->rect;
-isa_ok($new_rect, 'X11::XCB::Rect');
-
-is_deeply($new_rect, $original_rect, "window untouched");
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
-# Create a floating window which is smaller than the minimum enforced size of i3
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#C0C0C0',
- # replace the type with 'utility' as soon as the coercion works again in X11::XCB
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
-my ($absolute, $top) = $window->rect;
-
-ok($window->mapped, 'Window is mapped');
-cmp_ok($absolute->{width}, '>=', 75, 'i3 raised the width to 75');
-cmp_ok($absolute->{height}, '>=', 50, 'i3 raised the height to 50');
-
-ok($absolute->{x} != 0 && $absolute->{y} != 0, 'i3 did not map it to (0x0)');
-
-$window->unmap;
-
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 1, 1, 80, 90],
- background_color => '#C0C0C0',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
-($absolute, $top) = $window->rect;
-
-cmp_ok($absolute->{width}, '==', 80, "i3 let the width at 80");
-cmp_ok($absolute->{height}, '==', 90, "i3 let the height at 90");
-
-# We need to compare the position with decorations due to the way
-# we do decoration rendering (on the parent frame) in the tree branch
-cmp_ok($top->{x}, '==', 1, 'i3 mapped it to x=1');
-cmp_ok($top->{y}, '==', 19, 'i3 mapped it to y=18');
-
-$window->unmap;
-
-#####################################################################
-# check that a tiling window which is then made floating still has
-# at least the size of its initial geometry
-#####################################################################
-
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 1, 1, 80, 90],
- background_color => '#C0C0C0',
- #window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
-cmd 'floating enable';
-
-($absolute, $top) = $window->rect;
-
-cmp_ok($absolute->{width}, '==', 80, "i3 let the width at 80");
-cmp_ok($absolute->{height}, '==', 90, "i3 let the height at 90");
-
-$window->unmap;
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-my $x = X11::XCB::Connection->new;
-
-fresh_workspace;
-
-#####################################################################
-# Ensure IPC works by switching workspaces
-#####################################################################
-
-# Create a window so we can get a focus different from NULL
-my $window = open_standard_window($x);
-diag("window->id = " . $window->id);
-
-sleep 0.25;
-
-my $focus = $x->input_focus;
-diag("old focus = $focus");
-
-# Switch to another workspace
-fresh_workspace;
-
-my $new_focus = $x->input_focus;
-isnt($focus, $new_focus, "Focus changed");
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $i3 = i3(get_socket_path());
-my $tmp = fresh_workspace;
-
-#####################################################################
-# Create two windows and make sure focus switching works
-#####################################################################
-
-# Change mode of the container to "default" for following tests
-cmd 'layout default';
-cmd 'split v';
-
-my $top = open_standard_window($x);
-my $mid = open_standard_window($x);
-my $bottom = open_standard_window($x);
-sleep 0.25;
-
-diag("top id = " . $top->id);
-diag("mid id = " . $mid->id);
-diag("bottom id = " . $bottom->id);
-
-#
-# Returns the input focus after sending the given command to i3 via IPC
-# end sleeping for half a second to make sure i3 reacted
-#
-sub focus_after {
- my $msg = shift;
-
- $i3->command($msg)->recv;
- return $x->input_focus;
-}
-
-$focus = $x->input_focus;
-is($focus, $bottom->id, "Latest window focused");
-
-$focus = focus_after('focus up');
-is($focus, $mid->id, "Middle window focused");
-
-$focus = focus_after('focus up');
-is($focus, $top->id, "Top window focused");
-
-#####################################################################
-# Test focus wrapping
-#####################################################################
-
-$focus = focus_after('focus up');
-is($focus, $bottom->id, "Bottom window focused (wrapping to the top works)");
-
-$focus = focus_after('focus down');
-is($focus, $top->id, "Top window focused (wrapping to the bottom works)");
-
-###############################################
-# Test focus with empty containers and colspan
-###############################################
-
-#my $otmp = get_unused_workspace();
-#$i3->command("workspace $otmp")->recv;
-#
-#$top = i3test::open_standard_window($x);
-#$bottom = i3test::open_standard_window($x);
-#sleep 0.25;
-#
-#$focus = focus_after("mj");
-#$focus = focus_after("mh");
-#$focus = focus_after("k");
-#is($focus, $bottom->id, "Selecting top window without snapping doesn't work");
-#
-#$focus = focus_after("sl");
-#is($focus, $bottom->id, "Bottom window focused");
-#
-#$focus = focus_after("k");
-#is($focus, $top->id, "Top window focused");
-#
-## Same thing, but left/right instead of top/bottom
-#
-#my $o2tmp = get_unused_workspace();
-#$i3->command("workspace $o2tmp")->recv;
-#
-#my $left = i3test::open_standard_window($x);
-#my $right = i3test::open_standard_window($x);
-#sleep 0.25;
-#
-#$focus = focus_after("ml");
-#$focus = focus_after("h");
-#$focus = focus_after("mk");
-#$focus = focus_after("l");
-#is($focus, $left->id, "Selecting right window without snapping doesn't work");
-#
-#$focus = focus_after("sj");
-#is($focus, $left->id, "left window focused");
-#
-#$focus = focus_after("l");
-#is($focus, $right->id, "right window focused");
-
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-# Beware that this test uses workspace 9 to perform some tests (it expects
-# the workspace to be empty).
-# TODO: skip it by default?
-
-use i3test tests => 8;
-use X11::XCB qw(:all);
-use Time::HiRes qw(sleep);
-
-BEGIN {
- use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-SKIP: {
- skip "Testcase not yet modified for new move concept", 7;
-
-my $x = X11::XCB::Connection->new;
-
-my $i3 = i3;
-
-# Switch to the nineth workspace
-$i3->command('9')->recv;
-
-#####################################################################
-# Create two windows and make sure focus switching works
-#####################################################################
-
-my $top = i3test::open_standard_window($x);
-sleep(0.25);
-my $mid = i3test::open_standard_window($x);
-sleep(0.25);
-my $bottom = i3test::open_standard_window($x);
-sleep(0.25);
-
-diag("top id = " . $top->id);
-diag("mid id = " . $mid->id);
-diag("bottom id = " . $bottom->id);
-
-#
-# Returns the input focus after sending the given command to i3 via IPC
-# end sleeping for half a second to make sure i3 reacted
-#
-sub focus_after {
- my $msg = shift;
-
- $i3->command($msg)->recv;
- return $x->input_focus;
-}
-
-$focus = $x->input_focus;
-is($focus, $bottom->id, "Latest window focused");
-
-$focus = focus_after("ml");
-is($focus, $bottom->id, "Right window still focused");
-
-$focus = focus_after("h");
-is($focus, $mid->id, "Middle window focused");
-
-#####################################################################
-# Now move to the top window, move right, then move left again
-# (e.g., does i3 remember the focus in the last container?)
-#####################################################################
-
-$focus = focus_after("k");
-is($focus, $top->id, "Top window focused");
-
-$focus = focus_after("l");
-is($focus, $bottom->id, "Right window focused");
-
-$focus = focus_after("h");
-is($focus, $top->id, "Top window focused");
-
-#####################################################################
-# Move window cross-workspace
-#####################################################################
-
-for my $cmd (qw(m12 t m13 12 13)) {
- $i3->command($cmd)->recv;
-}
-ok(1, "Still living");
-}
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-# Checks if the focus is correctly restored, when creating a floating client
-# over an unfocused tiling client and destroying the floating one again.
-
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Window') or BAIL_OUT('Could not load X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $i3 = i3(get_socket_path());
-fresh_workspace;
-
-cmd 'split h';
-my $tiled_left = open_standard_window($x);
-my $tiled_right = open_standard_window($x);
-
-sleep 0.25;
-
-# Get input focus before creating the floating window
-my $focus = $x->input_focus;
-
-# Create a floating window which is smaller than the minimum enforced size of i3
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 1, 1, 30, 30],
- background_color => '#C0C0C0',
- type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 1;
-sleep 0.25;
-is($x->input_focus, $window->id, 'floating window focused');
-
-$window->unmap;
-
-sleep 0.25;
-
-is($x->input_focus, $focus, 'Focus correctly restored');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-# Beware that this test uses workspace 9 to perform some tests (it expects
-# the workspace to be empty).
-# TODO: skip it by default?
-
-use i3test tests => 22;
-use X11::XCB qw(:all);
-use Time::HiRes qw(sleep);
-
-BEGIN {
- use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-SKIP: {
- skip "stacking test not yet updated", 21;
-
-my $x = X11::XCB::Connection->new;
-
-my $i3 = i3;
-
-# Switch to the nineth workspace
-$i3->command('9')->recv;
-
-#####################################################################
-# Create two windows and make sure focus switching works
-#####################################################################
-
-my $top = i3test::open_standard_window($x);
-sleep(0.25);
-my $mid = i3test::open_standard_window($x);
-sleep(0.25);
-my $bottom = i3test::open_standard_window($x);
-sleep(0.25);
-
-diag("top id = " . $top->id);
-diag("mid id = " . $mid->id);
-diag("bottom id = " . $bottom->id);
-
-#
-# Returns the input focus after sending the given command to i3 via IPC
-# end sleeping for half a second to make sure i3 reacted
-#
-sub focus_after {
- my $msg = shift;
-
- $i3->command($msg)->recv;
- return $x->input_focus;
-}
-
-$focus = $x->input_focus;
-is($focus, $bottom->id, "Latest window focused");
-
-$focus = focus_after("s");
-is($focus, $bottom->id, "Last window still focused");
-
-$focus = focus_after("k");
-is($focus, $mid->id, "Middle window focused");
-
-$focus = focus_after("k");
-is($focus, $top->id, "Top window focused");
-
-#####################################################################
-# Test focus wrapping
-#####################################################################
-
-$focus = focus_after("k");
-is($focus, $bottom->id, "Bottom window focused (wrapping to the top works)");
-
-$focus = focus_after("j");
-is($focus, $top->id, "Top window focused (wrapping to the bottom works)");
-
-#####################################################################
-# Restore of focus after moving windows out/into the stack
-#####################################################################
-
-$focus = focus_after("ml");
-is($focus, $top->id, "Top window still focused (focus after moving)");
-
-$focus = focus_after("h");
-is($focus, $bottom->id, "Bottom window focused (focus after moving)");
-
-my $new = i3test::open_standard_window($x);
-sleep(0.25);
-
-# By now, we have this layout:
-# ----------------
-# | mid |
-# | bottom | top
-# | new |
-# ----------------
-
-$focus = focus_after("l");
-is($focus, $top->id, "Got top window");
-
-$focus = focus_after("mh");
-is($focus, $top->id, "Moved it into the stack");
-
-$focus = focus_after("k");
-is($focus, $new->id, "Window above is new");
-
-$focus = focus_after("k");
-is($focus, $bottom->id, "Window above is bottom");
-
-$focus = focus_after("k");
-is($focus, $mid->id, "Window above is mid");
-
-$focus = focus_after("k");
-is($focus, $top->id, "At top again");
-
-$focus = focus_after("ml");
-is($focus, $top->id, "Still at top, moved out");
-
-$focus = focus_after("h");
-is($focus, $mid->id, "At mid again");
-
-$focus = focus_after("j");
-is($focus, $bottom->id, "At bottom again");
-
-$focus = focus_after("l");
-is($focus, $top->id, "At top again");
-
-$focus = focus_after("mh");
-is($focus, $top->id, "Still at top, moved into");
-
-$focus = focus_after("k");
-is($focus, $bottom->id, "Window above is bottom");
-
-$focus = focus_after("k");
-is($focus, $mid->id, "Window above is mid");
-
-}
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-use List::Util qw(first);
-
-BEGIN {
- use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
-
-#####################################################################
-# verify that there is no dock window yet
-#####################################################################
-
-# Children of all dockareas
-my @docked = get_dock_clients;
-is(@docked, 0, 'no dock clients yet');
-
-#####################################################################
-# Create a dock window and see if it gets managed
-#####################################################################
-
-my $screens = $x->screens;
-
-# Get the primary screen
-my $primary = first { $_->primary } @{$screens};
-
-# TODO: focus the primary screen before
-
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#FF0000',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$window->map;
-
-sleep 0.25;
-
-my $rect = $window->rect;
-is($rect->width, $primary->rect->width, 'dock client is as wide as the screen');
-is($rect->height, 30, 'height is unchanged');
-
-#####################################################################
-# check that we can find it in the layout tree at the expected position
-#####################################################################
-
-@docked = get_dock_clients('top');
-is(@docked, 1, 'one dock client found');
-
-# verify the position/size
-my $docknode = $docked[0];
-
-is($docknode->{rect}->{x}, 0, 'dock node placed at x=0');
-is($docknode->{rect}->{y}, 0, 'dock node placed at y=0');
-is($docknode->{rect}->{width}, $primary->rect->width, 'dock node as wide as the screen');
-is($docknode->{rect}->{height}, 30, 'dock node has unchanged height');
-
-#####################################################################
-# check that re-configuring the height works
-#####################################################################
-
-$window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 50, height => 40));
-
-sleep 0.25;
-
-@docked = get_dock_clients('top');
-is(@docked, 1, 'one dock client found');
-
-# verify the position/size
-$docknode = $docked[0];
-
-is($docknode->{rect}->{x}, 0, 'dock node placed at x=0');
-is($docknode->{rect}->{y}, 0, 'dock node placed at y=0');
-is($docknode->{rect}->{width}, $primary->rect->width, 'dock node as wide as the screen');
-is($docknode->{rect}->{height}, 40, 'dock height changed');
-
-$window->destroy;
-
-sleep 0.25;
-
-@docked = get_dock_clients();
-is(@docked, 0, 'no more dock clients');
-
-#####################################################################
-# check if it gets placed on bottom (by coordinates)
-#####################################################################
-
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 1000, 30, 30],
- background_color => '#FF0000',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$window->map;
-
-sleep 0.25;
-
-my $rect = $window->rect;
-is($rect->width, $primary->rect->width, 'dock client is as wide as the screen');
-is($rect->height, 30, 'height is unchanged');
-
-@docked = get_dock_clients('bottom');
-is(@docked, 1, 'dock client on bottom');
-
-$window->destroy;
-
-sleep 0.25;
-
-@docked = get_dock_clients();
-is(@docked, 0, 'no more dock clients');
-
-#####################################################################
-# check if it gets placed on bottom (by hint)
-#####################################################################
-
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 1000, 30, 30],
- background_color => '#FF0000',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$window->_create();
-
-# Add a _NET_WM_STRUT_PARTIAL hint
-my $atomname = $x->atom(name => '_NET_WM_STRUT_PARTIAL');
-my $atomtype = $x->atom(name => 'CARDINAL');
-
-$x->change_property(
- PROP_MODE_REPLACE,
- $window->id,
- $atomname->id,
- $atomtype->id,
- 32, # 32 bit integer
- 12,
- pack('L12', 0, 0, 16, 0, 0, 0, 0, 0, 0, 1280, 0, 0)
-);
-
-$window->map;
-
-sleep 0.25;
-
-@docked = get_dock_clients('top');
-is(@docked, 1, 'dock client on top');
-
-$window->destroy;
-
-sleep 0.25;
-
-@docked = get_dock_clients();
-is(@docked, 0, 'no more dock clients');
-
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 1000, 30, 30],
- background_color => '#FF0000',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$window->_create();
-
-# Add a _NET_WM_STRUT_PARTIAL hint
-my $atomname = $x->atom(name => '_NET_WM_STRUT_PARTIAL');
-my $atomtype = $x->atom(name => 'CARDINAL');
-
-$x->change_property(
- PROP_MODE_REPLACE,
- $window->id,
- $atomname->id,
- $atomtype->id,
- 32, # 32 bit integer
- 12,
- pack('L12', 0, 0, 0, 16, 0, 0, 0, 0, 0, 1280, 0, 0)
-);
-
-$window->map;
-
-sleep 0.25;
-
-@docked = get_dock_clients('bottom');
-is(@docked, 1, 'dock client on bottom');
-
-$window->destroy;
-
-
-#####################################################################
-# regression test: transient dock client
-#####################################################################
-
-my $fwindow = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#FF0000',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$fwindow->transient_for($window);
-$fwindow->map;
-
-sleep 0.25;
-
-does_i3_live;
-
-done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use X11::XCB qw(:all);
+use List::Util qw(first);
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+sub fullscreen_windows {
+ scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($tmp)}
+}
+
+# get the output of this workspace
+my $tree = $i3->get_tree->recv;
+my @outputs = @{$tree->{nodes}};
+my $output;
+for my $o (@outputs) {
+ # get the first CT_CON of each output
+ my $content = first { $_->{type} == 2 } @{$o->{nodes}};
+ if (defined(first { $_->{name} eq $tmp } @{$content->{nodes}})) {
+ $output = $o;
+ last;
+ }
+}
+
+BEGIN {
+ use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+##################################
+# map a window, then fullscreen it
+##################################
+
+my $original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
+
+my $window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => $original_rect,
+ background_color => '#C0C0C0',
+ event_mask => [ 'structure_notify' ],
+);
+
+isa_ok($window, 'X11::XCB::Window');
+
+is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
+
+$window->map;
+
+wait_for_map $x;
+
+# open another container to make the window get only half of the screen
+cmd 'open';
+
+my $new_rect = $window->rect;
+ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
+$original_rect = $new_rect;
+
+$window->fullscreen(1);
+
+sync_with_i3($x);
+
+$new_rect = $window->rect;
+ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen");
+
+my $orect = $output->{rect};
+my $wrect = $new_rect;
+
+# see if the window really is fullscreen. 20 px for borders are allowed
+my $threshold = 20;
+ok(($wrect->{x} - $orect->{x}) < $threshold, 'x coordinate fullscreen');
+ok(($wrect->{y} - $orect->{y}) < $threshold, 'y coordinate fullscreen');
+ok(abs($wrect->{width} - $orect->{width}) < $threshold, 'width coordinate fullscreen');
+ok(abs($wrect->{height} - $orect->{height}) < $threshold, 'height coordinate fullscreen');
+
+
+$window->unmap;
+
+#########################################################
+# test with a window which is fullscreened before mapping
+#########################################################
+
+# open another container because the empty one will swallow the window we
+# map in a second
+cmd 'open';
+
+$original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
+$window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => $original_rect,
+ background_color => 61440,
+ event_mask => [ 'structure_notify' ],
+);
+
+is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
+
+$window->fullscreen(1);
+$window->map;
+
+wait_for_map $x;
+
+$new_rect = $window->rect;
+ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen");
+ok($window->mapped, "Window is mapped after opening it in fullscreen mode");
+
+$wrect = $new_rect;
+
+# see if the window really is fullscreen. 20 px for borders are allowed
+ok(($wrect->{x} - $orect->{x}) < $threshold, 'x coordinate fullscreen');
+ok(($wrect->{y} - $orect->{y}) < $threshold, 'y coordinate fullscreen');
+ok(abs($wrect->{width} - $orect->{width}) < $threshold, 'width coordinate fullscreen');
+ok(abs($wrect->{height} - $orect->{height}) < $threshold, 'height coordinate fullscreen');
+
+###############################################################################
+# test if setting two windows in fullscreen mode at the same time does not work
+###############################################################################
+
+$original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
+my $swindow = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => $original_rect,
+ background_color => '#C0C0C0',
+ event_mask => [ 'structure_notify' ],
+);
+
+$swindow->map;
+
+sync_with_i3($x);
+
+ok(!$swindow->mapped, 'window not mapped while fullscreen window active');
+
+$new_rect = $swindow->rect;
+ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
+
+$swindow->fullscreen(1);
+sync_with_i3($x);
+
+is(fullscreen_windows(), 1, 'amount of fullscreen windows');
+
+$window->fullscreen(0);
+sync_with_i3($x);
+is(fullscreen_windows(), 0, 'amount of fullscreen windows');
+
+ok($swindow->mapped, 'window mapped after other fullscreen ended');
+
+###########################################################################
+# as $swindow is out of state at the moment (it requested to be fullscreen,
+# but the WM denied), we check what happens if we go out of fullscreen now
+# (nothing should happen)
+###########################################################################
+
+$swindow->fullscreen(0);
+sync_with_i3($x);
+
+is(fullscreen_windows(), 0, 'amount of fullscreen windows after disabling');
+
+cmd 'fullscreen';
+
+is(fullscreen_windows(), 1, 'amount of fullscreen windows after fullscreen command');
+
+cmd 'fullscreen';
+
+is(fullscreen_windows(), 0, 'amount of fullscreen windows after fullscreen command');
+
+# clean up the workspace so that it will be cleaned when switching away
+cmd 'kill' for (@{get_ws_content($tmp)});
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# Create two windows and make sure focus switching works
+#####################################################################
+
+# Change mode of the container to "default" for following tests
+cmd 'layout default';
+cmd 'split v';
+
+my $top = open_window($x);
+my $mid = open_window($x);
+my $bottom = open_window($x);
+
+#
+# Returns the input focus after sending the given command to i3 via IPC
+# end sleeping for half a second to make sure i3 reacted
+#
+sub focus_after {
+ my $msg = shift;
+
+ cmd $msg;
+ sync_with_i3 $x;
+ return $x->input_focus;
+}
+
+$focus = $x->input_focus;
+is($focus, $bottom->id, "Latest window focused");
+
+$focus = focus_after('focus up');
+is($focus, $mid->id, "Middle window focused");
+
+$focus = focus_after('focus up');
+is($focus, $top->id, "Top window focused");
+
+#####################################################################
+# Test focus wrapping
+#####################################################################
+
+$focus = focus_after('focus up');
+is($focus, $bottom->id, "Bottom window focused (wrapping to the top works)");
+
+$focus = focus_after('focus down');
+is($focus, $top->id, "Top window focused (wrapping to the bottom works)");
+
+###############################################
+# Test focus with empty containers and colspan
+###############################################
+
+#my $otmp = get_unused_workspace();
+#$i3->command("workspace $otmp")->recv;
+#
+#$top = i3test::open_standard_window($x);
+#$bottom = i3test::open_standard_window($x);
+#sleep 0.25;
+#
+#$focus = focus_after("mj");
+#$focus = focus_after("mh");
+#$focus = focus_after("k");
+#is($focus, $bottom->id, "Selecting top window without snapping doesn't work");
+#
+#$focus = focus_after("sl");
+#is($focus, $bottom->id, "Bottom window focused");
+#
+#$focus = focus_after("k");
+#is($focus, $top->id, "Top window focused");
+#
+## Same thing, but left/right instead of top/bottom
+#
+#my $o2tmp = get_unused_workspace();
+#$i3->command("workspace $o2tmp")->recv;
+#
+#my $left = i3test::open_standard_window($x);
+#my $right = i3test::open_standard_window($x);
+#sleep 0.25;
+#
+#$focus = focus_after("ml");
+#$focus = focus_after("h");
+#$focus = focus_after("mk");
+#$focus = focus_after("l");
+#is($focus, $left->id, "Selecting right window without snapping doesn't work");
+#
+#$focus = focus_after("sj");
+#is($focus, $left->id, "left window focused");
+#
+#$focus = focus_after("l");
+#is($focus, $right->id, "right window focused");
+
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use X11::XCB qw(:all);
+use List::Util qw(first);
+
+BEGIN {
+ use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
+}
+
+my $x = X11::XCB::Connection->new;
+
+#####################################################################
+# verify that there is no dock window yet
+#####################################################################
+
+# Children of all dockareas
+my @docked = get_dock_clients;
+is(@docked, 0, 'no dock clients yet');
+
+#####################################################################
+# Create a dock window and see if it gets managed
+#####################################################################
+
+my $screens = $x->screens;
+
+# Get the primary screen
+my $primary = first { $_->primary } @{$screens};
+
+# TODO: focus the primary screen before
+my $window = open_window($x, {
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+ });
+
+my $rect = $window->rect;
+is($rect->width, $primary->rect->width, 'dock client is as wide as the screen');
+is($rect->height, 30, 'height is unchanged');
+
+#####################################################################
+# check that we can find it in the layout tree at the expected position
+#####################################################################
+
+@docked = get_dock_clients('top');
+is(@docked, 1, 'one dock client found');
+
+# verify the position/size
+my $docknode = $docked[0];
+
+is($docknode->{rect}->{x}, 0, 'dock node placed at x=0');
+is($docknode->{rect}->{y}, 0, 'dock node placed at y=0');
+is($docknode->{rect}->{width}, $primary->rect->width, 'dock node as wide as the screen');
+is($docknode->{rect}->{height}, 30, 'dock node has unchanged height');
+
+#####################################################################
+# check that re-configuring the height works
+#####################################################################
+
+$window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 50, height => 40));
+
+sync_with_i3 $x;
+
+@docked = get_dock_clients('top');
+is(@docked, 1, 'one dock client found');
+
+# verify the position/size
+$docknode = $docked[0];
+
+is($docknode->{rect}->{x}, 0, 'dock node placed at x=0');
+is($docknode->{rect}->{y}, 0, 'dock node placed at y=0');
+is($docknode->{rect}->{width}, $primary->rect->width, 'dock node as wide as the screen');
+is($docknode->{rect}->{height}, 40, 'dock height changed');
+
+$window->destroy;
+
+wait_for_unmap $x;
+
+@docked = get_dock_clients();
+is(@docked, 0, 'no more dock clients');
+
+#####################################################################
+# check if it gets placed on bottom (by coordinates)
+#####################################################################
+
+$window = open_window($x, {
+ rect => [ 0, 1000, 30, 30 ],
+ background_color => '#FF0000',
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+ });
+
+my $rect = $window->rect;
+is($rect->width, $primary->rect->width, 'dock client is as wide as the screen');
+is($rect->height, 30, 'height is unchanged');
+
+@docked = get_dock_clients('bottom');
+is(@docked, 1, 'dock client on bottom');
+
+$window->destroy;
+
+wait_for_unmap $x;
+
+@docked = get_dock_clients();
+is(@docked, 0, 'no more dock clients');
+
+#####################################################################
+# check if it gets placed on bottom (by hint)
+#####################################################################
+
+$window = open_window($x, {
+ dont_map => 1,
+ rect => [ 0, 1000, 30, 30 ],
+ background_color => '#FF0000',
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+ });
+
+$window->_create();
+
+# Add a _NET_WM_STRUT_PARTIAL hint
+my $atomname = $x->atom(name => '_NET_WM_STRUT_PARTIAL');
+my $atomtype = $x->atom(name => 'CARDINAL');
+
+$x->change_property(
+ PROP_MODE_REPLACE,
+ $window->id,
+ $atomname->id,
+ $atomtype->id,
+ 32, # 32 bit integer
+ 12,
+ pack('L12', 0, 0, 16, 0, 0, 0, 0, 0, 0, 1280, 0, 0)
+);
+
+$window->map;
+
+wait_for_map $x;
+
+@docked = get_dock_clients('top');
+is(@docked, 1, 'dock client on top');
+
+$window->destroy;
+
+wait_for_unmap $x;
+
+@docked = get_dock_clients();
+is(@docked, 0, 'no more dock clients');
+
+$window = open_window($x, {
+ dont_map => 1,
+ rect => [ 0, 1000, 30, 30 ],
+ background_color => '#FF0000',
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+ });
+
+$window->_create();
+
+# Add a _NET_WM_STRUT_PARTIAL hint
+my $atomname = $x->atom(name => '_NET_WM_STRUT_PARTIAL');
+my $atomtype = $x->atom(name => 'CARDINAL');
+
+$x->change_property(
+ PROP_MODE_REPLACE,
+ $window->id,
+ $atomname->id,
+ $atomtype->id,
+ 32, # 32 bit integer
+ 12,
+ pack('L12', 0, 0, 0, 16, 0, 0, 0, 0, 0, 1280, 0, 0)
+);
+
+$window->map;
+
+wait_for_map $x;
+
+@docked = get_dock_clients('bottom');
+is(@docked, 1, 'dock client on bottom');
+
+$window->destroy;
+
+
+#####################################################################
+# regression test: transient dock client
+#####################################################################
+
+$fwindow = open_window($x, {
+ dont_map => 1,
+ background_color => '#FF0000',
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+ });
+
+$fwindow->transient_for($window);
+$fwindow->map;
+
+wait_for_map $x;
+
+does_i3_live;
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Beware that this test uses workspace 9 to perform some tests (it expects
+# the workspace to be empty).
+# TODO: skip it by default?
+
+use i3test tests => 8;
+use X11::XCB qw(:all);
+use Time::HiRes qw(sleep);
+
+BEGIN {
+ use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
+}
+
+SKIP: {
+ skip "Testcase not yet modified for new move concept", 7;
+
+my $x = X11::XCB::Connection->new;
+
+my $i3 = i3;
+
+# Switch to the nineth workspace
+$i3->command('9')->recv;
+
+#####################################################################
+# Create two windows and make sure focus switching works
+#####################################################################
+
+my $top = i3test::open_standard_window($x);
+sleep(0.25);
+my $mid = i3test::open_standard_window($x);
+sleep(0.25);
+my $bottom = i3test::open_standard_window($x);
+sleep(0.25);
+
+diag("top id = " . $top->id);
+diag("mid id = " . $mid->id);
+diag("bottom id = " . $bottom->id);
+
+#
+# Returns the input focus after sending the given command to i3 via IPC
+# end sleeping for half a second to make sure i3 reacted
+#
+sub focus_after {
+ my $msg = shift;
+
+ $i3->command($msg)->recv;
+ return $x->input_focus;
+}
+
+$focus = $x->input_focus;
+is($focus, $bottom->id, "Latest window focused");
+
+$focus = focus_after("ml");
+is($focus, $bottom->id, "Right window still focused");
+
+$focus = focus_after("h");
+is($focus, $mid->id, "Middle window focused");
+
+#####################################################################
+# Now move to the top window, move right, then move left again
+# (e.g., does i3 remember the focus in the last container?)
+#####################################################################
+
+$focus = focus_after("k");
+is($focus, $top->id, "Top window focused");
+
+$focus = focus_after("l");
+is($focus, $bottom->id, "Right window focused");
+
+$focus = focus_after("h");
+is($focus, $top->id, "Top window focused");
+
+#####################################################################
+# Move window cross-workspace
+#####################################################################
+
+for my $cmd (qw(m12 t m13 12 13)) {
+ $i3->command($cmd)->recv;
+}
+ok(1, "Still living");
+}
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Checks if the focus is correctly restored, when creating a floating client
+# over an unfocused tiling client and destroying the floating one again.
+
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+fresh_workspace;
+
+cmd 'split h';
+my $tiled_left = open_window($x);
+my $tiled_right = open_window($x);
+
+# Get input focus before creating the floating window
+my $focus = $x->input_focus;
+
+# Create a floating window which is smaller than the minimum enforced size of i3
+my $window = open_floating_window($x);
+
+is($x->input_focus, $window->id, 'floating window focused');
+
+$window->unmap;
+
+wait_for_unmap($x);
+
+is($x->input_focus, $focus, 'Focus correctly restored');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Beware that this test uses workspace 9 to perform some tests (it expects
+# the workspace to be empty).
+# TODO: skip it by default?
+
+use i3test tests => 22;
+use X11::XCB qw(:all);
+use Time::HiRes qw(sleep);
+
+BEGIN {
+ use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
+}
+
+SKIP: {
+ skip "stacking test not yet updated", 21;
+
+my $x = X11::XCB::Connection->new;
+
+my $i3 = i3;
+
+# Switch to the nineth workspace
+$i3->command('9')->recv;
+
+#####################################################################
+# Create two windows and make sure focus switching works
+#####################################################################
+
+my $top = i3test::open_standard_window($x);
+my $mid = i3test::open_standard_window($x);
+my $bottom = i3test::open_standard_window($x);
+sleep(0.25);
+
+diag("top id = " . $top->id);
+diag("mid id = " . $mid->id);
+diag("bottom id = " . $bottom->id);
+
+#
+# Returns the input focus after sending the given command to i3 via IPC
+# end sleeping for half a second to make sure i3 reacted
+#
+sub focus_after {
+ my $msg = shift;
+
+ $i3->command($msg)->recv;
+ return $x->input_focus;
+}
+
+$focus = $x->input_focus;
+is($focus, $bottom->id, "Latest window focused");
+
+$focus = focus_after("s");
+is($focus, $bottom->id, "Last window still focused");
+
+$focus = focus_after("k");
+is($focus, $mid->id, "Middle window focused");
+
+$focus = focus_after("k");
+is($focus, $top->id, "Top window focused");
+
+#####################################################################
+# Test focus wrapping
+#####################################################################
+
+$focus = focus_after("k");
+is($focus, $bottom->id, "Bottom window focused (wrapping to the top works)");
+
+$focus = focus_after("j");
+is($focus, $top->id, "Top window focused (wrapping to the bottom works)");
+
+#####################################################################
+# Restore of focus after moving windows out/into the stack
+#####################################################################
+
+$focus = focus_after("ml");
+is($focus, $top->id, "Top window still focused (focus after moving)");
+
+$focus = focus_after("h");
+is($focus, $bottom->id, "Bottom window focused (focus after moving)");
+
+my $new = i3test::open_standard_window($x);
+sleep(0.25);
+
+# By now, we have this layout:
+# ----------------
+# | mid |
+# | bottom | top
+# | new |
+# ----------------
+
+$focus = focus_after("l");
+is($focus, $top->id, "Got top window");
+
+$focus = focus_after("mh");
+is($focus, $top->id, "Moved it into the stack");
+
+$focus = focus_after("k");
+is($focus, $new->id, "Window above is new");
+
+$focus = focus_after("k");
+is($focus, $bottom->id, "Window above is bottom");
+
+$focus = focus_after("k");
+is($focus, $mid->id, "Window above is mid");
+
+$focus = focus_after("k");
+is($focus, $top->id, "At top again");
+
+$focus = focus_after("ml");
+is($focus, $top->id, "Still at top, moved out");
+
+$focus = focus_after("h");
+is($focus, $mid->id, "At mid again");
+
+$focus = focus_after("j");
+is($focus, $bottom->id, "At bottom again");
+
+$focus = focus_after("l");
+is($focus, $top->id, "At top again");
+
+$focus = focus_after("mh");
+is($focus, $top->id, "Still at top, moved into");
+
+$focus = focus_after("k");
+is($focus, $bottom->id, "Window above is bottom");
+
+$focus = focus_after("k");
+is($focus, $mid->id, "Window above is mid");
+
+}
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-use Digest::SHA1 qw(sha1_base64);
-
-BEGIN {
- use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $i3 = i3(get_socket_path());
-my $tmp = fresh_workspace;
-
-cmd 'split h';
-
-#####################################################################
-# Create two windows and make sure focus switching works
-#####################################################################
-
-my $top = open_standard_window($x);
-sleep 0.25;
-my $mid = open_standard_window($x);
-sleep 0.25;
-my $bottom = open_standard_window($x);
-sleep 0.25;
-
-diag("top id = " . $top->id);
-diag("mid id = " . $mid->id);
-diag("bottom id = " . $bottom->id);
-
-#
-# Returns the input focus after sending the given command to i3 via IPC
-# end sleeping for half a second to make sure i3 reacted
-#
-sub focus_after {
- my $msg = shift;
-
- cmd $msg;
- return $x->input_focus;
-}
-
-$focus = $x->input_focus;
-is($focus, $bottom->id, "Latest window focused");
-
-$focus = focus_after('focus left');
-is($focus, $mid->id, "Middle window focused");
-
-#####################################################################
-# Now goto a mark which does not exist
-#####################################################################
-
-my $random_mark = sha1_base64(rand());
-
-$focus = focus_after(qq|[con_mark="$random_mark"] focus|);
-is($focus, $mid->id, "focus unchanged");
-
-$i3->command("mark $random_mark")->recv;
-
-$focus = focus_after('focus left');
-is($focus, $top->id, "Top window focused");
-
-$focus = focus_after(qq|[con_mark="$random_mark"] focus|);
-is($focus, $mid->id, "goto worked");
-
-# check that we can specify multiple criteria
-
-$focus = focus_after('focus left');
-is($focus, $top->id, "Top window focused");
-
-$focus = focus_after(qq|[con_mark="$random_mark" con_mark="$random_mark"] focus|);
-is($focus, $mid->id, "goto worked");
-
-#####################################################################
-# Check whether the focus command will switch to a different
-# workspace if necessary
-#####################################################################
-
-my $tmp2 = fresh_workspace;
-
-is(focused_ws(), $tmp2, 'tmp2 now focused');
-
-cmd qq|[con_mark="$random_mark"] focus|;
-
-is(focused_ws(), $tmp, 'tmp now focused');
-
-done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use File::Temp;
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+cmd 'split h';
+
+#####################################################################
+# Create two windows and make sure focus switching works
+#####################################################################
+
+my $top = open_window($x);
+my $mid = open_window($x);
+my $bottom = open_window($x);
+
+#
+# Returns the input focus after sending the given command to i3 via IPC
+# and syncing with i3
+#
+sub focus_after {
+ my $msg = shift;
+
+ cmd $msg;
+ sync_with_i3($x);
+ return $x->input_focus;
+}
+
+$focus = $x->input_focus;
+is($focus, $bottom->id, "Latest window focused");
+
+$focus = focus_after('focus left');
+is($focus, $mid->id, "Middle window focused");
+
+#####################################################################
+# Now goto a mark which does not exist
+#####################################################################
+
+my $random_mark = mktemp('mark.XXXXXX');
+
+$focus = focus_after(qq|[con_mark="$random_mark"] focus|);
+is($focus, $mid->id, "focus unchanged");
+
+cmd "mark $random_mark";
+
+$focus = focus_after('focus left');
+is($focus, $top->id, "Top window focused");
+
+$focus = focus_after(qq|[con_mark="$random_mark"] focus|);
+is($focus, $mid->id, "goto worked");
+
+# check that we can specify multiple criteria
+
+$focus = focus_after('focus left');
+is($focus, $top->id, "Top window focused");
+
+$focus = focus_after(qq|[con_mark="$random_mark" con_mark="$random_mark"] focus|);
+is($focus, $mid->id, "goto worked");
+
+#####################################################################
+# Check whether the focus command will switch to a different
+# workspace if necessary
+#####################################################################
+
+my $tmp2 = fresh_workspace;
+
+is(focused_ws(), $tmp2, 'tmp2 now focused');
+
+cmd qq|[con_mark="$random_mark"] focus|;
+
+is(focused_ws(), $tmp, 'tmp now focused');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+ use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
+}
+
+my $x = X11::XCB::Connection->new;
+
+fresh_workspace;
+
+#####################################################################
+# Create a floating window and see if resizing works
+#####################################################################
+
+my $window = open_floating_window($x);
+
+# See if configurerequests cause window movements (they should not)
+my ($a, $t) = $window->rect;
+$window->rect(X11::XCB::Rect->new(x => $a->x, y => $a->y, width => $a->width, height => $a->height));
+
+sync_with_i3($x);
+
+my ($na, $nt) = $window->rect;
+is_deeply($na, $a, 'Rects are equal after configurerequest');
+
+sub test_resize {
+ $window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 100, height => 100));
+
+ sync_with_i3($x);
+
+ my ($absolute, $top) = $window->rect;
+
+ # Make sure the width/height are different from what we’re gonna test, so
+ # that the test will work.
+ isnt($absolute->width, 300, 'width != 300');
+ isnt($absolute->height, 500, 'height != 500');
+
+ $window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 300, height => 500));
+
+ sync_with_i3($x);
+
+ ($absolute, $top) = $window->rect;
+
+ is($absolute->width, 300, 'width = 300');
+ is($absolute->height, 500, 'height = 500');
+}
+
+# Test with default border
+test_resize;
+
+# Test borderless
+cmd 'border none';
+
+test_resize;
+
+# Test with 1-px-border
+cmd 'border 1pixel';
+
+test_resize;
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use X11::XCB qw(:all);
+use List::Util qw(first);
+
+BEGIN {
+ use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# Create two windows and put them in stacking mode
+#####################################################################
+
+cmd 'split v';
+
+my $top = open_window($x);
+my $bottom = open_window($x);
+
+my @urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
+is(@urgent, 0, 'no window got the urgent flag');
+
+# cmd 'layout stacking';
+
+#####################################################################
+# Add the urgency hint, switch to a different workspace and back again
+#####################################################################
+$top->add_hint('urgency');
+sync_with_i3($x);
+
+@content = @{get_ws_content($tmp)};
+@urgent = grep { $_->{urgent} } @content;
+$top_info = first { $_->{window} == $top->id } @content;
+$bottom_info = first { $_->{window} == $bottom->id } @content;
+
+ok($top_info->{urgent}, 'top window is marked urgent');
+ok(!$bottom_info->{urgent}, 'bottom window is not marked urgent');
+is(@urgent, 1, 'exactly one window got the urgent flag');
+
+cmd '[id="' . $top->id . '"] focus';
+
+@urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
+is(@urgent, 0, 'no window got the urgent flag after focusing');
+
+$top->add_hint('urgency');
+sync_with_i3($x);
+
+@urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
+is(@urgent, 0, 'no window got the urgent flag after re-setting urgency hint');
+
+#####################################################################
+# Check if the workspace urgency hint gets set/cleared correctly
+#####################################################################
+my $ws = get_ws($tmp);
+ok(!$ws->{urgent}, 'urgent flag not set on workspace');
+
+my $otmp = fresh_workspace;
+
+$top->add_hint('urgency');
+sync_with_i3($x);
+
+$ws = get_ws($tmp);
+ok($ws->{urgent}, 'urgent flag set on workspace');
+
+cmd "workspace $tmp";
+
+$ws = get_ws($tmp);
+ok(!$ws->{urgent}, 'urgent flag not set on workspace after switching');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+ use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+####################################################################################
+# first part: test if a floating window will be correctly positioned above its leader
+#
+# This is verified by opening two windows, then opening a floating window above the
+# right one, then above the left one. If the floating windows are all positioned alike,
+# one of both (depending on your screen resolution) will be positioned wrong.
+####################################################################################
+
+my $left = open_window($x, { name => 'Left' });
+my $right = open_window($x, { name => 'Right' });
+
+my ($abs, $rgeom) = $right->rect;
+
+my $child = open_floating_window($x, {
+ dont_map => 1,
+ name => 'Child window',
+ });
+$child->client_leader($right);
+$child->map;
+
+ok(wait_for_map($x), 'child window mapped');
+
+my $cgeom;
+($abs, $cgeom) = $child->rect;
+cmp_ok($cgeom->x, '>=', $rgeom->x, 'Child X >= right container X');
+
+my $child2 = open_floating_window($x, {
+ dont_map => 1,
+ name => 'Child window 2',
+ });
+$child2->client_leader($left);
+$child2->map;
+
+ok(wait_for_map($x), 'second child window mapped');
+
+($abs, $cgeom) = $child2->rect;
+cmp_ok(($cgeom->x + $cgeom->width), '<', $rgeom->x, 'child above left window');
+
+# check wm_transient_for
+my $fwindow = open_window($x, { dont_map => 1 });
+$fwindow->transient_for($right);
+$fwindow->map;
+
+ok(wait_for_map($x), 'transient window mapped');
+
+my ($absolute, $top) = $fwindow->rect;
+ok($absolute->{x} != 0 && $absolute->{y} != 0, 'i3 did not map it to (0x0)');
+
+SKIP: {
+ skip "(workspace placement by client_leader not yet implemented)", 3;
+
+#####################################################################
+# Create a parent window
+#####################################################################
+
+my $window = open_window($x, { dont_map => 1, name => 'Parent window' });
+$window->map;
+
+ok(wait_for_map($x), 'parent window mapped');
+
+#########################################################################
+# Switch to a different workspace and open a child window. It should be opened
+# on the old workspace.
+#########################################################################
+fresh_workspace;
+
+my $child = open_window($x, { dont_map => 1, name => 'Child window' });
+$child->client_leader($window);
+$child->map;
+
+ok(wait_for_map($x), 'child window mapped');
+
+isnt($x->input_focus, $child->id, "Child window focused");
+
+# Switch back
+cmd "workspace $tmp";
+
+is($x->input_focus, $child->id, "Child window focused");
+
+}
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+####################
+# Request workspaces
+####################
+
+SKIP: {
+ skip "IPC API not yet stabilized", 2;
+
+my $workspaces = $i3->get_workspaces->recv;
+
+ok(@{$workspaces} > 0, "More than zero workspaces found");
+
+#my $name_exists = all { defined($_->{name}) } @{$workspaces};
+#ok($name_exists, "All workspaces have a name");
+
+}
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use List::Util qw(first);
+
+# to not depend on List::MoreUtils
+sub all (&@) {
+ my $cb = shift;
+ for (@_) {
+ return 0 unless $cb->();
+ }
+ return 1;
+}
+
+sub none (&@) {
+ my $cb = shift;
+ for (@_) {
+ return 0 if $cb->();
+ }
+ return 1;
+}
+
+my $i3 = i3(get_socket_path());
+
+####################
+# Request tree
+####################
+
+my $tree = $i3->get_tree->recv;
+
+my $expected = {
+ fullscreen_mode => 0,
+ nodes => ignore(),
+ window => undef,
+ name => 'root',
+ orientation => ignore(),
+ type => 0,
+ id => ignore(),
+ rect => ignore(),
+ window_rect => ignore(),
+ geometry => ignore(),
+ swallows => ignore(),
+ percent => undef,
+ layout => 'default',
+ focus => ignore(),
+ focused => JSON::XS::false,
+ urgent => JSON::XS::false,
+ border => 'normal',
+ 'floating_nodes' => ignore(),
+};
+
+cmp_deeply($tree, $expected, 'root node OK');
+
+my @nodes = @{$tree->{nodes}};
+
+ok(@nodes > 0, 'root node has at least one leaf');
+
+ok((all { $_->{type} == 1 } @nodes), 'all nodes are of type CT_OUTPUT');
+ok((none { defined($_->{window}) } @nodes), 'no CT_OUTPUT contains a window');
+ok((all { @{$_->{nodes}} > 0 } @nodes), 'all nodes have at least one leaf (workspace)');
+my @workspaces;
+for my $ws (@nodes) {
+ my $content = first { $_->{type} == 2 } @{$ws->{nodes}};
+ @workspaces = (@workspaces, @{$content->{nodes}});
+}
+
+ok((all { $_->{type} == 4 } @workspaces), 'all workspaces are of type CT_WORKSPACE');
+#ok((all { @{$_->{nodes}} == 0 } @workspaces), 'all workspaces are empty yet');
+ok((none { defined($_->{window}) } @workspaces), 'no CT_OUTPUT contains a window');
+
+# TODO: get the focused container
+
+$i3->command('open')->recv;
+
+# TODO: get the focused container, check if it changed.
+# TODO: get the old focused container, check if there is a new child
+
+#diag(Dumper(\@workspaces));
+
+#diag(Dumper($tree));
+
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests whether we can switch to a non-existant workspace
+# (necessary for further tests)
+#
+use List::Util qw(first);
+use i3test;
+
+# to ensure that workspace 1 stays open
+cmd 'open';
+
+my $tmp = fresh_workspace;
+ok(workspace_exists($tmp), 'workspace created');
+# if the workspace could not be created, we cannot run any other test
+# (every test starts by creating its workspace)
+if (!workspace_exists($tmp)) {
+ BAIL_OUT('Cannot create workspace, further tests make no sense');
+}
+
+my $otmp = fresh_workspace;
+diag("Other temporary workspace name: $otmp\n");
+
+# As the old workspace was empty, it should get
+# cleaned up as we switch away from it
+cmd "workspace $otmp";
+ok(!workspace_exists($tmp), 'old workspace cleaned up');
+
+# Switch to the same workspace again to make sure it doesn’t get cleaned up
+cmd "workspace $otmp";
+cmd "workspace $otmp";
+ok(workspace_exists($otmp), 'other workspace still exists');
+
+
+#####################################################################
+# check if the workspace next / prev commands work
+#####################################################################
+
+cmd 'workspace next';
+
+ok(!workspace_exists('next'), 'workspace "next" does not exist');
+
+cmd "workspace $tmp";
+cmd 'open';
+
+ok(workspace_exists($tmp), 'workspace created');
+
+cmd "workspace $otmp";
+cmd 'open';
+
+ok(workspace_exists($tmp), 'workspace tmp still exists');
+ok(workspace_exists($otmp), 'workspace otmp created');
+
+is(focused_ws(), $otmp, 'focused workspace is otmp');
+
+cmd 'workspace prev';
+is(focused_ws(), $tmp, 'focused workspace is tmp after workspace prev');
+
+cmd 'workspace next';
+is(focused_ws(), $otmp, 'focused workspace is otmp after workspace next');
+
+
+#####################################################################
+# check that wrapping works
+#####################################################################
+
+cmd 'workspace next';
+is(focused_ws(), '1', 'focused workspace is 1 after workspace next');
+
+cmd 'workspace next';
+is(focused_ws(), $tmp, 'focused workspace is tmp after workspace next');
+
+cmd 'workspace next';
+is(focused_ws(), $otmp, 'focused workspace is otmp after workspace next');
+
+
+cmd 'workspace prev';
+is(focused_ws(), $tmp, 'focused workspace is tmp after workspace prev');
+
+cmd 'workspace prev';
+is(focused_ws(), '1', 'focused workspace is tmp after workspace prev');
+
+cmd 'workspace prev';
+is(focused_ws(), $otmp, 'focused workspace is otmp after workspace prev');
+
+
+#####################################################################
+# check if we can change to "next" / "prev"
+#####################################################################
+
+cmd 'workspace "next"';
+
+ok(workspace_exists('next'), 'workspace "next" exists');
+is(focused_ws(), 'next', 'now on workspace next');
+
+cmd 'workspace "prev"';
+
+ok(workspace_exists('prev'), 'workspace "prev" exists');
+is(focused_ws(), 'prev', 'now on workspace prev');
+
+#####################################################################
+# check that the numbers are assigned/recognized correctly
+#####################################################################
+
+cmd "workspace 3: $tmp";
+my $ws = get_ws("3: $tmp");
+ok(defined($ws), "workspace 3: $tmp was created");
+is($ws->{num}, 3, 'workspace number is 3');
+
+cmd "workspace 0: $tmp";
+my $ws = get_ws("0: $tmp");
+ok(defined($ws), "workspace 0: $tmp was created");
+is($ws->{num}, 0, 'workspace number is 0');
+
+cmd "workspace aa: $tmp";
+my $ws = get_ws("aa: $tmp");
+ok(defined($ws), "workspace aa: $tmp was created");
+is($ws->{num}, -1, 'workspace number is -1');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests whether opening an empty container and killing it again works
+#
+use List::Util qw(first);
+use i3test;
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+# Open a new container
+cmd 'open';
+
+ok(@{get_ws_content($tmp)} == 1, 'container opened');
+
+cmd 'kill';
+ok(@{get_ws_content($tmp)} == 0, 'container killed');
+
+##############################################################
+# open two containers and kill the one which is not focused
+# by its ID to test if the parser correctly matches the window
+##############################################################
+
+cmd 'open';
+cmd 'open';
+ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
+
+my $content = get_ws_content($tmp);
+my $not_focused = first { !$_->{focused} } @{$content};
+my $id = $not_focused->{id};
+
+cmd "[con_id=\"$id\"] kill";
+
+$content = get_ws_content($tmp);
+ok(@{$content} == 1, 'one container killed');
+ok($content->[0]->{id} != $id, 'correct window killed');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests all kinds of matching methods
+#
+use i3test;
+use X11::XCB qw(:all);
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+# Open a new window
+my $x = X11::XCB::Connection->new;
+my $window = open_window($x);
+my $content = get_ws_content($tmp);
+ok(@{$content} == 1, 'window mapped');
+my $win = $content->[0];
+
+######################################################################
+# first test that matches which should not match this window really do
+# not match it
+######################################################################
+# TODO: specify more match types
+# we can match on any (non-empty) class here since that window does not have
+# WM_CLASS set
+cmd q|[class=".*"] kill|;
+cmd q|[con_id="99999"] kill|;
+
+$content = get_ws_content($tmp);
+ok(@{$content} == 1, 'window still there');
+
+# now kill the window
+cmd 'nop now killing the window';
+my $id = $win->{id};
+cmd qq|[con_id="$id"] kill|;
+
+wait_for_unmap $x;
+
+cmd 'nop checking if its gone';
+$content = get_ws_content($tmp);
+ok(@{$content} == 0, 'window killed');
+
+# TODO: same test, but with pcre expressions
+
+######################################################################
+# check that multiple criteria work are checked with a logical AND,
+# not a logical OR (that is, matching is not cancelled after the first
+# criterion matches).
+######################################################################
+
+$tmp = fresh_workspace;
+
+# TODO: move to X11::XCB
+sub set_wm_class {
+ my ($id, $class, $instance) = @_;
+
+ # Add a _NET_WM_STRUT_PARTIAL hint
+ my $atomname = $x->atom(name => 'WM_CLASS');
+ my $atomtype = $x->atom(name => 'STRING');
+
+ $x->change_property(
+ PROP_MODE_REPLACE,
+ $id,
+ $atomname->id,
+ $atomtype->id,
+ 8,
+ length($class) + length($instance) + 2,
+ "$instance\x00$class\x00"
+ );
+}
+
+my $left = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#0000ff',
+ event_mask => [ 'structure_notify' ],
+);
+
+$left->_create;
+set_wm_class($left->id, 'special', 'special');
+$left->name('left');
+$left->map;
+ok(wait_for_map($x), 'left window mapped');
+
+my $right = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#0000ff',
+ event_mask => [ 'structure_notify' ],
+);
+
+$right->_create;
+set_wm_class($right->id, 'special', 'special');
+$right->name('right');
+$right->map;
+ok(wait_for_map($x), 'right window mapped');
+
+# two windows should be here
+$content = get_ws_content($tmp);
+ok(@{$content} == 2, 'two windows opened');
+
+cmd '[class="special" title="left"] kill';
+
+sync_with_i3($x);
+
+$content = get_ws_content($tmp);
+is(@{$content}, 1, 'one window still there');
+
+######################################################################
+# check that regular expressions work
+######################################################################
+
+$tmp = fresh_workspace;
+
+$left = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#0000ff',
+ event_mask => [ 'structure_notify' ],
+);
+
+$left->_create;
+set_wm_class($left->id, 'special7', 'special7');
+$left->name('left');
+$left->map;
+ok(wait_for_map($x), 'left window mapped');
+
+# two windows should be here
+$content = get_ws_content($tmp);
+ok(@{$content} == 1, 'window opened');
+
+cmd '[class="^special[0-9]$"] kill';
+
+wait_for_unmap $x;
+
+$content = get_ws_content($tmp);
+is(@{$content}, 0, 'window killed');
+
+######################################################################
+# check that UTF-8 works when matching
+######################################################################
+
+$tmp = fresh_workspace;
+
+$left = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#0000ff',
+ event_mask => [ 'structure_notify' ],
+);
+
+$left->_create;
+set_wm_class($left->id, 'special7', 'special7');
+$left->name('ä 3');
+$left->map;
+ok(wait_for_map($x), 'left window mapped');
+
+# two windows should be here
+$content = get_ws_content($tmp);
+ok(@{$content} == 1, 'window opened');
+
+cmd '[title="^\w [3]$"] kill';
+
+wait_for_unmap $x;
+
+$content = get_ws_content($tmp);
+is(@{$content}, 0, 'window killed');
+
+done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-# Beware that this test uses workspace 9 to perform some tests (it expects
-# the workspace to be empty).
-# TODO: skip it by default?
-
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-my $x = X11::XCB::Connection->new;
-
-fresh_workspace;
-
-#####################################################################
-# Create a floating window and see if resizing works
-#####################################################################
-
-# Create a floating window
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#C0C0C0',
- # replace the type with 'utility' as soon as the coercion works again in X11::XCB
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-sleep 0.25;
-
-# See if configurerequests cause window movements (they should not)
-my ($a, $t) = $window->rect;
-$window->rect(X11::XCB::Rect->new(x => $a->x, y => $a->y, width => $a->width, height => $a->height));
-
-sleep 0.25;
-my ($na, $nt) = $window->rect;
-is_deeply($na, $a, 'Rects are equal after configurerequest');
-
-sub test_resize {
- $window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 100, height => 100));
-
- my ($absolute, $top) = $window->rect;
-
- # Make sure the width/height are different from what we’re gonna test, so
- # that the test will work.
- isnt($absolute->width, 300, 'width != 300');
- isnt($absolute->height, 500, 'height != 500');
-
- $window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 300, height => 500));
- sleep 0.25;
-
- ($absolute, $top) = $window->rect;
-
- is($absolute->width, 300, 'width = 300');
- is($absolute->height, 500, 'height = 500');
-}
-
-# Test with default border
-test_resize;
-
-# Test borderless
-cmd 'border none';
-
-test_resize;
-
-# Test with 1-px-border
-cmd 'border 1pixel';
-
-test_resize;
-
-done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests multiple commands (using ';') and multiple operations (using ',')
+#
+use i3test;
+
+my $tmp = fresh_workspace;
+
+sub multiple_cmds {
+ my ($cmd) = @_;
+
+ cmd 'open';
+ cmd 'open';
+ ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
+
+ cmd $cmd;
+ ok(@{get_ws_content($tmp)} == 0, "both containers killed (cmd = $cmd)");
+}
+multiple_cmds('kill;kill');
+multiple_cmds('kill; kill');
+multiple_cmds('kill ; kill');
+multiple_cmds('kill ;kill');
+multiple_cmds('kill ;kill');
+multiple_cmds('kill ; kill');
+multiple_cmds("kill;\tkill");
+multiple_cmds("kill\t;kill");
+multiple_cmds("kill\t;\tkill");
+multiple_cmds("kill\t ;\tkill");
+multiple_cmds("kill\t ;\t kill");
+multiple_cmds("kill \t ; \t kill");
+
+#####################################################################
+# test if un-quoted strings are handled correctly
+#####################################################################
+
+$tmp = fresh_workspace;
+cmd 'open';
+my $unused = get_unused_workspace;
+ok(!($unused ~~ @{get_workspace_names()}), 'workspace does not exist yet');
+cmd "move workspace $unused; nop parser test";
+ok(($unused ~~ @{get_workspace_names()}), 'workspace exists after moving');
+
+#####################################################################
+# quote the workspace name and use a ; (command separator) in its name
+#####################################################################
+
+cmd 'open';
+$unused = get_unused_workspace;
+$unused .= ';a';
+ok(!($unused ~~ @{get_workspace_names()}), 'workspace does not exist yet');
+cmd qq|move workspace "$unused"; nop parser test|;
+ok(($unused ~~ @{get_workspace_names()}), 'workspace exists after moving');
+
+# TODO: need a non-invasive command before implementing a test which uses ','
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests focus switching (next/prev)
+#
+use i3test;
+
+my $tmp = fresh_workspace;
+
+######################################################################
+# Open one container, verify that 'focus down' and 'focus right' do nothing
+######################################################################
+cmd 'open';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+my $old_focused = $focus->[0];
+
+cmd 'focus down';
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $old_focused, 'focus did not change with only one con');
+
+cmd 'focus right';
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $old_focused, 'focus did not change with only one con');
+
+######################################################################
+# Open another container, verify that 'focus right' switches
+######################################################################
+my $left = $old_focused;
+
+cmd 'open';
+($nodes, $focus) = get_ws_content($tmp);
+isnt($old_focused, $focus->[0], 'new container is focused');
+my $mid = $focus->[0];
+
+cmd 'open';
+($nodes, $focus) = get_ws_content($tmp);
+isnt($old_focused, $focus->[0], 'new container is focused');
+my $right = $focus->[0];
+
+cmd 'focus right';
+($nodes, $focus) = get_ws_content($tmp);
+isnt($focus->[0], $right, 'focus did change');
+is($focus->[0], $left, 'left container focused (wrapping)');
+
+cmd 'focus right';
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $mid, 'middle container focused');
+
+cmd 'focus right';
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $right, 'right container focused');
+
+cmd 'focus left';
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $mid, 'middle container focused');
+
+cmd 'focus left';
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $left, 'left container focused');
+
+cmd 'focus left';
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $right, 'right container focused');
+
+
+######################################################################
+# Test focus command
+######################################################################
+
+cmd qq|[con_id="$mid"] focus|;
+($nodes, $focus) = get_ws_content($tmp);
+is($focus->[0], $mid, 'middle container focused');
+
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests splitting
+#
+use i3test;
+use X11::XCB qw(:all);
+
+my $tmp = fresh_workspace;
+
+my $ws = get_ws($tmp);
+is($ws->{orientation}, 'horizontal', 'orientation horizontal by default');
+cmd 'split v';
+$ws = get_ws($tmp);
+is($ws->{orientation}, 'vertical', 'split v changes workspace orientation');
+
+######################################################################
+# Open two containers, split, open another container. Then verify
+# the layout is like we expect it to be
+######################################################################
+cmd 'open';
+cmd 'open';
+my $content = get_ws_content($tmp);
+
+is(@{$content}, 2, 'two containers on workspace level');
+my $first = $content->[0];
+my $second = $content->[1];
+
+is(@{$first->{nodes}}, 0, 'first container has no children');
+is(@{$second->{nodes}}, 0, 'second container has no children (yet)');
+my $old_name = $second->{name};
+
+
+cmd 'split h';
+cmd 'open';
+
+$content = get_ws_content($tmp);
+
+is(@{$content}, 2, 'two containers on workspace level');
+$first = $content->[0];
+$second = $content->[1];
+
+is(@{$first->{nodes}}, 0, 'first container has no children');
+isnt($second->{name}, $old_name, 'second container was replaced');
+is($second->{orientation}, 'horizontal', 'orientation is horizontal');
+is(@{$second->{nodes}}, 2, 'second container has 2 children');
+is($second->{nodes}->[0]->{name}, $old_name, 'found old second container');
+
+# TODO: extend this test-case (test next/prev)
+# - wrapping (no horizontal switch possible, goes level-up)
+# - going level-up "manually"
+
+######################################################################
+# Test splitting multiple times without actually creating windows
+######################################################################
+
+$tmp = fresh_workspace;
+
+$ws = get_ws($tmp);
+is($ws->{orientation}, 'horizontal', 'orientation horizontal by default');
+cmd 'split v';
+$ws = get_ws($tmp);
+is($ws->{orientation}, 'vertical', 'split v changes workspace orientation');
+
+cmd 'open';
+my @content = @{get_ws_content($tmp)};
+
+# recursively sums up all nodes and their children
+sub sum_nodes {
+ my ($nodes) = @_;
+
+ return 0 if !@{$nodes};
+
+ my @children = (map { @{$_->{nodes}} } @{$nodes},
+ map { @{$_->{'floating_nodes'}} } @{$nodes});
+
+ return @{$nodes} + sum_nodes(\@children);
+}
+
+my $old_count = sum_nodes(\@content);
+cmd 'split v';
+
+@content = @{get_ws_content($tmp)};
+$old_count = sum_nodes(\@content);
+
+cmd 'split v';
+
+@content = @{get_ws_content($tmp)};
+my $count = sum_nodes(\@content);
+is($count, $old_count, 'not more windows after splitting again');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests moving. Basically, there are four different code-paths:
+# 1) move a container which cannot be moved (single container on a workspace)
+# 2) move a container before another single container
+# 3) move a container inside another container
+# 4) move a container in a different direction so that we need to go up in tree
+#
+use i3test;
+use X11::XCB::Connection;
+
+my $x = X11::XCB::Connection->new;
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+######################################################################
+# 1) move a container which cannot be moved
+######################################################################
+
+cmd 'open';
+
+my $old_content = get_ws_content($tmp);
+is(@{$old_content}, 1, 'one container on this workspace');
+
+my $first = $old_content->[0]->{id};
+
+cmd 'move left';
+cmd 'move right';
+cmd 'move up';
+cmd 'move down';
+
+my $content = get_ws_content($tmp);
+is_deeply($old_content, $content, 'workspace unmodified after useless moves');
+
+######################################################################
+# 2) move a container before another single container
+######################################################################
+
+cmd 'open';
+$content = get_ws_content($tmp);
+is(@{$content}, 2, 'two containers on this workspace');
+my $second = $content->[1]->{id};
+
+is($content->[0]->{id}, $first, 'first container unmodified');
+
+# Move the second container before the first one (→ swap them)
+cmd 'move left';
+$content = get_ws_content($tmp);
+is($content->[0]->{id}, $second, 'first container modified');
+
+# We should not be able to move any further
+cmd 'move left';
+$content = get_ws_content($tmp);
+is($content->[0]->{id}, $second, 'first container unmodified');
+
+# Now move in the other direction
+cmd 'move right';
+$content = get_ws_content($tmp);
+is($content->[0]->{id}, $first, 'first container modified');
+
+# We should not be able to move any further
+cmd 'move right';
+$content = get_ws_content($tmp);
+is($content->[0]->{id}, $first, 'first container unmodified');
+
+######################################################################
+# 3) move a container inside another container
+######################################################################
+
+# Split the current (second) container and create a new container on workspace
+# level. Our layout looks like this now:
+# --------------------------
+# | | second | |
+# | first | ------ | third |
+# | | | |
+# --------------------------
+cmd 'split v';
+cmd 'focus parent';
+cmd 'open';
+
+$content = get_ws_content($tmp);
+is(@{$content}, 3, 'three containers on this workspace');
+my $third = $content->[2]->{id};
+
+cmd 'move left';
+$content = get_ws_content($tmp);
+is(@{$content}, 2, 'only two containers on this workspace');
+my $nodes = $content->[1]->{nodes};
+is($nodes->[0]->{id}, $second, 'second container on top');
+is($nodes->[1]->{id}, $third, 'third container on bottom');
+
+######################################################################
+# move it inside the split container
+######################################################################
+
+cmd 'move up';
+$nodes = get_ws_content($tmp)->[1]->{nodes};
+is($nodes->[0]->{id}, $third, 'third container on top');
+is($nodes->[1]->{id}, $second, 'second container on bottom');
+
+# move it outside again
+cmd 'move left';
+$content = get_ws_content($tmp);
+is(@{$content}, 3, 'three nodes on this workspace');
+
+# due to automatic flattening/cleanup, the remaining split container
+# will be replaced by the con itself, so we will still have 3 nodes
+cmd 'move right';
+$content = get_ws_content($tmp);
+is(@{$content}, 2, 'two nodes on this workspace');
+
+######################################################################
+# 4) We create two v-split containers on the workspace, then we move
+# all Cons from the left v-split to the right one. The old vsplit
+# container needs to be closed. Verify that it will be closed.
+######################################################################
+
+my $otmp = fresh_workspace;
+
+cmd "open";
+cmd "open";
+cmd "split v";
+cmd "open";
+cmd 'focus left';
+cmd "split v";
+cmd "open";
+cmd "move right";
+cmd 'focus left';
+cmd "move right";
+
+$content = get_ws_content($otmp);
+is(@{$content}, 1, 'only one nodes on this workspace');
+
+######################################################################
+# 5) test moving floating containers.
+######################################################################
+
+$tmp = fresh_workspace;
+my $floatwin = open_floating_window($x);
+my ($absolute_before, $top_before) = $floatwin->rect;
+
+cmd 'move left';
+
+my ($absolute, $top) = $floatwin->rect;
+
+is($absolute->x, ($absolute_before->x - 10), 'moved 10 px to the left');
+is($absolute->y, $absolute_before->y, 'y not changed');
+is($absolute->width, $absolute_before->width, 'width not changed');
+is($absolute->height, $absolute_before->height, 'height not changed');
+
+$absolute_before = $absolute;
+$top_before = $top;
+
+cmd 'move right';
+
+($absolute, $top) = $floatwin->rect;
+
+is($absolute->x, ($absolute_before->x + 10), 'moved 10 px to the right');
+is($absolute->y, $absolute_before->y, 'y not changed');
+is($absolute->width, $absolute_before->width, 'width not changed');
+is($absolute->height, $absolute_before->height, 'height not changed');
+
+$absolute_before = $absolute;
+$top_before = $top;
+
+cmd 'move up';
+
+($absolute, $top) = $floatwin->rect;
+
+is($absolute->x, $absolute_before->x, 'x not changed');
+is($absolute->y, ($absolute_before->y - 10), 'moved 10 px up');
+is($absolute->width, $absolute_before->width, 'width not changed');
+is($absolute->height, $absolute_before->height, 'height not changed');
+
+$absolute_before = $absolute;
+$top_before = $top;
+
+cmd 'move down';
+
+($absolute, $top) = $floatwin->rect;
+
+is($absolute->x, $absolute_before->x, 'x not changed');
+is($absolute->y, ($absolute_before->y + 10), 'moved 10 px up');
+is($absolute->width, $absolute_before->width, 'width not changed');
+is($absolute->height, $absolute_before->height, 'height not changed');
+
+$absolute_before = $absolute;
+$top_before = $top;
+
+######################################################################
+# 6) test moving floating containers with a specific amount of px
+######################################################################
+
+cmd 'move left 20 px';
+
+($absolute, $top) = $floatwin->rect;
+
+is($absolute->x, ($absolute_before->x - 20), 'moved 10 px to the left');
+is($absolute->y, $absolute_before->y, 'y not changed');
+is($absolute->width, $absolute_before->width, 'width not changed');
+is($absolute->height, $absolute_before->height, 'height not changed');
+
+
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression: closing of floating clients did crash i3 when closing the
+# container which contained this client.
+#
+use i3test;
+
+fresh_workspace;
+
+cmd 'open';
+cmd 'mode toggle';
+cmd 'kill';
+cmd 'kill';
+
+does_i3_live;
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression: make a container floating, kill its parent, make it tiling again
+#
+use i3test;
+
+my $tmp = fresh_workspace;
+
+cmd 'open';
+my $left = get_focused($tmp);
+cmd 'open';
+my $old = get_focused($tmp);
+cmd 'split v';
+cmd 'open';
+my $floating = get_focused($tmp);
+diag("focused floating: " . get_focused($tmp));
+cmd 'mode toggle';
+# TODO: eliminate this race conditition
+sleep 1;
+
+# kill old container
+cmd qq|[con_id="$old"] focus|;
+is(get_focused($tmp), $old, 'old container focused');
+cmd 'kill';
+
+# kill left container
+cmd qq|[con_id="$left"] focus|;
+is(get_focused($tmp), $left, 'old container focused');
+cmd 'kill';
+
+# focus floating window, make it tiling again
+cmd qq|[con_id="$floating"] focus|;
+is(get_focused($tmp), $floating, 'floating window focused');
+
+sleep 1;
+cmd 'mode toggle';
+
+does_i3_live;
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Check if new containers are opened after the currently focused one instead
+# of always at the end
+use List::Util qw(first);
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+# Open two new container
+my $first = open_empty_con($i3);
+
+ok(@{get_ws_content($tmp)} == 1, 'containers opened');
+
+my $second = open_empty_con($i3);
+
+isnt($first, $second, 'different container focused');
+
+##############################################################
+# see if new containers open after the currently focused
+##############################################################
+
+cmd qq|[con_id="$first"] focus|;
+cmd 'open';
+$content = get_ws_content($tmp);
+ok(@{$content} == 3, 'three containers opened');
+
+is($content->[0]->{id}, $first, 'first container unmodified');
+isnt($content->[1]->{id}, $second, 'second container replaced');
+is($content->[2]->{id}, $second, 'third container unmodified');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Check if the focus is correctly restored after closing windows.
+#
+use i3test;
+use X11::XCB qw(:all);
+use List::Util qw(first);
+
+my $x = X11::XCB::Connection->new;
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $first = open_empty_con($i3);
+my $second = open_empty_con($i3);
+
+cmd 'split v';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+
+ok(!$nodes->[1]->{focused}, 'split container not focused');
+cmd 'focus parent';
+($nodes, $focus) = get_ws_content($tmp);
+ok($nodes->[1]->{focused}, 'split container focused after focus parent');
+
+my $third = open_empty_con($i3);
+
+isnt(get_focused($tmp), $second, 'different container focused');
+
+# We have the following layout now (con is focused):
+# .----------------.
+# | split | |
+# | .----. | con |
+# | | cn | | |
+# | `----' | |
+# `----------------'
+
+##############################################################
+# see if the focus goes down to $first (not to its split parent)
+# when closing $second
+##############################################################
+
+cmd 'kill';
+# TODO: this testcase sometimes has different outcomes when the
+# sleep is missing. why?
+sleep 0.25;
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{nodes}->[0]->{id}, $second, 'second container found');
+ok($nodes->[1]->{nodes}->[0]->{focused}, 'second container focused');
+
+##############################################################
+# another case, using a slightly different layout (regression)
+##############################################################
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+cmd 'split v';
+$first = open_empty_con($i3);
+my $bottom = open_empty_con($i3);
+
+cmd 'focus up';
+cmd 'split h';
+my $middle = open_empty_con($i3);
+my $right = open_empty_con($i3);
+cmd 'focus down';
+
+# We have the following layout now (second is focused):
+# .----------------------------.
+# | .------------------------. |
+# | | first | middle | right | |
+# | `------------------------' |
+# |----------------------------|
+# | |
+# | second |
+# | |
+# `----------------------------'
+
+# Check if the focus is restored to $right when we close $second
+cmd 'kill';
+
+is(get_focused($tmp), $right, 'top right container focused (in focus stack)');
+
+($nodes, $focus) = get_ws_content($tmp);
+my $tr = first { $_->{id} eq $right } @{$nodes->[0]->{nodes}};
+is($tr->{focused}, 1, 'top right container really has focus');
+
+##############################################################
+# check if focus is correct after closing an unfocused window
+##############################################################
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_empty_con($i3);
+$middle = open_empty_con($i3);
+# XXX: the $right empty con will be filled with the x11 window we are creating afterwards
+$right = open_empty_con($i3);
+my $win = open_window($x, { background_color => '#00ff00' });
+
+cmd qq|[con_id="$middle"] focus|;
+$win->destroy;
+
+sleep 0.25;
+
+is(get_focused($tmp), $middle, 'middle container focused');
+
+##############################################################
+# and now for something completely different:
+# check if the pointer position is relevant when restoring focus
+# (it should not be relevant, of course)
+##############################################################
+
+# TODO: add test code as soon as I can reproduce it
+
+done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-use List::Util qw(first);
-
-BEGIN {
- use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $tmp = fresh_workspace;
-
-#####################################################################
-# Create two windows and put them in stacking mode
-#####################################################################
-
-cmd 'split v';
-
-my $top = open_standard_window($x);
-my $bottom = open_standard_window($x);
-
-my @urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
-is(@urgent, 0, 'no window got the urgent flag');
-
-# cmd 'layout stacking';
-
-#####################################################################
-# Add the urgency hint, switch to a different workspace and back again
-#####################################################################
-$top->add_hint('urgency');
-sleep 0.5;
-
-@content = @{get_ws_content($tmp)};
-@urgent = grep { $_->{urgent} } @content;
-$top_info = first { $_->{window} == $top->id } @content;
-$bottom_info = first { $_->{window} == $bottom->id } @content;
-
-ok($top_info->{urgent}, 'top window is marked urgent');
-ok(!$bottom_info->{urgent}, 'bottom window is not marked urgent');
-is(@urgent, 1, 'exactly one window got the urgent flag');
-
-cmd '[id="' . $top->id . '"] focus';
-
-@urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
-is(@urgent, 0, 'no window got the urgent flag after focusing');
-
-$top->add_hint('urgency');
-sleep 0.5;
-
-@urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
-is(@urgent, 0, 'no window got the urgent flag after re-setting urgency hint');
-
-#####################################################################
-# Check if the workspace urgency hint gets set/cleared correctly
-#####################################################################
-my $ws = get_ws($tmp);
-ok(!$ws->{urgent}, 'urgent flag not set on workspace');
-
-my $otmp = fresh_workspace;
-
-$top->add_hint('urgency');
-sleep 0.5;
-
-$ws = get_ws($tmp);
-ok($ws->{urgent}, 'urgent flag set on workspace');
-
-cmd "workspace $tmp";
-
-$ws = get_ws($tmp);
-ok(!$ws->{urgent}, 'urgent flag not set on workspace after switching');
-
-done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Check if empty split containers are automatically closed.
+#
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $first = open_empty_con($i3);
+my $second = open_empty_con($i3);
+cmd qq|[con_id="$first"] focus|;
+
+cmd 'split v';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+is($nodes->[0]->{focused}, 0, 'split container not focused');
+
+# focus the split container
+cmd 'level up';
+($nodes, $focus) = get_ws_content($tmp);
+my $split = $focus->[0];
+cmd 'level down';
+
+my $second = open_empty_con($i3);
+
+isnt($first, $second, 'different container focused');
+
+##############################################################
+# close both windows and see if the split container still exists
+##############################################################
+
+cmd 'kill';
+cmd 'kill';
+($nodes, $focus) = get_ws_content($tmp);
+isnt($nodes->[0]->{id}, $split, 'split container closed');
+
+##############################################################
+# same thing but this time we are moving the cons away instead
+# of killing them
+##############################################################
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_empty_con($i3);
+$second = open_empty_con($i3);
+cmd qq|[con_id="$first"] focus|;
+
+cmd 'split v';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+is($nodes->[0]->{focused}, 0, 'split container not focused');
+
+# focus the split container
+cmd 'level up';
+($nodes, $focus) = get_ws_content($tmp);
+my $split = $focus->[0];
+cmd 'level down';
+
+my $second = open_empty_con($i3);
+
+isnt($first, $second, 'different container focused');
+
+##############################################################
+# close both windows and see if the split container still exists
+##############################################################
+
+my $otmp = get_unused_workspace();
+cmd "move workspace $otmp";
+cmd "move workspace $otmp";
+($nodes, $focus) = get_ws_content($tmp);
+isnt($nodes->[0]->{id}, $split, 'split container closed');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Check if stacking containers can be used independantly of
+# the split mode (horizontal/vertical) of the underlying
+# container.
+#
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+# Enforce vertical split mode
+cmd 'split v';
+
+my $first = open_empty_con($i3);
+my $second = open_empty_con($i3);
+
+isnt($first, $second, 'two different containers opened');
+
+##############################################################
+# change mode to stacking and cycle through the containers
+##############################################################
+
+cmd 'layout stacking';
+is(get_focused($tmp), $second, 'second container still focused');
+
+cmd 'focus down';
+is(get_focused($tmp), $first, 'first container focused');
+
+cmd 'focus up';
+is(get_focused($tmp), $second, 'second container focused again');
+
+##############################################################
+# now change the orientation to horizontal and cycle
+##############################################################
+
+cmd 'focus parent';
+cmd 'split h';
+cmd 'focus child';
+
+cmd 'focus down';
+is(get_focused($tmp), $first, 'first container focused');
+
+cmd 'focus up';
+is(get_focused($tmp), $second, 'second container focused again');
+
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Checks if the 'move workspace' command works correctly
+#
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+# We move the pointer out of our way to avoid a bug where the focus will
+# be set to the window under the cursor
+my $x = X11::XCB::Connection->new;
+$x->root->warp_pointer(0, 0);
+
+my $tmp = get_unused_workspace();
+my $tmp2 = get_unused_workspace();
+cmd "workspace $tmp";
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $first = open_empty_con($i3);
+my $second = open_empty_con($i3);
+ok(@{get_ws_content($tmp)} == 2, 'two containers on first ws');
+
+cmd "workspace $tmp2";
+ok(@{get_ws_content($tmp2)} == 0, 'no containers on second ws yet');
+
+cmd "workspace $tmp";
+
+cmd "move workspace $tmp2";
+ok(@{get_ws_content($tmp)} == 1, 'one container on first ws anymore');
+ok(@{get_ws_content($tmp2)} == 1, 'one container on second ws');
+my ($nodes, $focus) = get_ws_content($tmp2);
+
+is($focus->[0], $second, 'same container on different ws');
+
+($nodes, $focus) = get_ws_content($tmp);
+ok($nodes->[0]->{focused}, 'first container focused on first ws');
+
+###################################################################
+# check if 'move workspace next' and 'move workspace prev' work
+###################################################################
+
+# Open two containers on the first workspace, one container on the second
+# workspace. Because the workspaces are named, they will be sorted by order of
+# creation.
+$tmp = get_unused_workspace();
+$tmp2 = get_unused_workspace();
+cmd "workspace $tmp";
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+$first = open_empty_con($i3);
+$second = open_empty_con($i3);
+ok(@{get_ws_content($tmp)} == 2, 'two containers on first ws');
+
+cmd "workspace $tmp2";
+ok(@{get_ws_content($tmp2)} == 0, 'no containers yet');
+my $third = open_empty_con($i3);
+ok(@{get_ws_content($tmp2)} == 1, 'one container on second ws');
+
+# go back to the first workspace, move one of the containers to the next one
+cmd "workspace $tmp";
+cmd 'move workspace next';
+ok(@{get_ws_content($tmp)} == 1, 'one container on first ws');
+ok(@{get_ws_content($tmp2)} == 2, 'two containers on second ws');
+
+# go to the second workspace and move two containers to the first one
+cmd "workspace $tmp2";
+cmd 'move workspace prev';
+cmd 'move workspace prev';
+ok(@{get_ws_content($tmp)} == 3, 'three containers on first ws');
+ok(@{get_ws_content($tmp2)} == 0, 'no containers on second ws');
+
+###################################################################
+# check if floating cons are moved to new workspaces properly
+# (that is, if they are floating on the target ws, too)
+###################################################################
+
+$tmp = get_unused_workspace();
+$tmp2 = get_unused_workspace();
+cmd "workspace $tmp";
+
+cmd "open";
+cmd "floating toggle";
+
+my $ws = get_ws($tmp);
+is(@{$ws->{nodes}}, 0, 'no nodes on workspace');
+is(@{$ws->{floating_nodes}}, 1, 'one floating node on workspace');
+
+cmd "move workspace $tmp2";
+
+$ws = get_ws($tmp2);
+is(@{$ws->{nodes}}, 0, 'no nodes on workspace');
+is(@{$ws->{floating_nodes}}, 1, 'one floating node on workspace');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Checks if size hints are interpreted correctly.
+#
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $win = open_window($x, { dont_map => 1 });
+# XXX: we should check screen size. in screens with an AR of 2.0,
+# this is not a good idea.
+my $aspect = X11::XCB::Sizehints::Aspect->new;
+$aspect->min_num(600);
+$aspect->min_den(300);
+$aspect->max_num(600);
+$aspect->max_den(300);
+$win->_create;
+$win->map;
+wait_for_map $x;
+$win->hints->aspect($aspect);
+$x->flush;
+
+sync_with_i3($x);
+
+my $rect = $win->rect;
+my $ar = $rect->width / $rect->height;
+diag("Aspect ratio = $ar");
+ok(($ar > 1.90) && ($ar < 2.10), 'Aspect ratio about 2.0');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+#
+#
+use i3test;
+
+cmd 'blargh!';
+
+does_i3_live;
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+
+use i3test;
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+#############################################################################
+# 1: see if focus stays the same when toggling tiling/floating mode
+#############################################################################
+
+my $first = open_window($x);
+my $second = open_window($x);
+
+is($x->input_focus, $second->id, 'second window focused');
+
+cmd 'floating enable';
+cmd 'floating disable';
+
+is($x->input_focus, $second->id, 'second window still focused after mode toggle');
+
+#############################################################################
+# 2: see if focus stays on the current floating window if killing another
+# floating window
+#############################################################################
+
+$tmp = fresh_workspace;
+
+$first = open_window($x); # window 2
+$second = open_window($x); # window 3
+my $third = open_window($x); # window 4
+
+is($x->input_focus, $third->id, 'last container focused');
+
+cmd 'floating enable';
+
+cmd '[id="' . $second->id . '"] focus';
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second con focused');
+
+cmd 'floating enable';
+
+# now kill the third one (it's floating). focus should stay unchanged
+cmd '[id="' . $third->id . '"] kill';
+
+# TODO: wait for unmapnotify
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second con still focused after killing third');
+
+
+#############################################################################
+# 3: see if the focus gets reverted correctly when closing floating clients
+# (first to the next floating client, then to the last focused tiling client)
+#############################################################################
+
+$tmp = fresh_workspace;
+
+$first = open_window($x, { background_color => '#ff0000' }); # window 5
+$second = open_window($x, { background_color => '#00ff00' }); # window 6
+my $third = open_window($x, { background_color => '#0000ff' }); # window 7
+
+is($x->input_focus, $third->id, 'last container focused');
+
+cmd 'floating enable';
+
+cmd '[id="' . $second->id . '"] focus';
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second con focused');
+
+cmd 'floating enable';
+
+# now kill the second one. focus should fall back to the third one, which is
+# also floating
+cmd 'kill';
+
+# TODO: wait for unmapnotify
+sync_with_i3($x);
+
+is($x->input_focus, $third->id, 'third con focused');
+
+cmd 'kill';
+# TODO: wait for unmapnotify
+sync_with_i3($x);
+
+is($x->input_focus, $first->id, 'first con focused after killing all floating cons');
+
+#############################################################################
+# 4: same test as 3, but with another split con
+#############################################################################
+
+$tmp = fresh_workspace;
+
+$first = open_window($x, { background_color => '#ff0000' }); # window 5
+cmd 'split v';
+cmd 'layout stacked';
+$second = open_window($x, { background_color => '#00ff00' }); # window 6
+$third = open_window($x, { background_color => '#0000ff' }); # window 7
+
+is($x->input_focus, $third->id, 'last container focused');
+
+cmd 'floating enable';
+
+cmd '[id="' . $second->id . '"] focus';
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second con focused');
+
+cmd 'floating enable';
+
+sync_with_i3($x);
+
+# now kill the second one. focus should fall back to the third one, which is
+# also floating
+cmd 'kill';
+
+# TODO: wait for unmapnotify
+sync_with_i3($x);
+
+is($x->input_focus, $third->id, 'third con focused');
+
+cmd 'kill';
+# TODO: wait for unmapnotify
+sync_with_i3($x);
+
+is($x->input_focus, $first->id, 'first con focused after killing all floating cons');
+
+#############################################################################
+# 5: see if the 'focus tiling' and 'focus floating' commands work
+#############################################################################
+
+$tmp = fresh_workspace;
+
+$first = open_window($x, { background_color => '#ff0000' }); # window 8
+$second = open_window($x, { background_color => '#00ff00' }); # window 9
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second container focused');
+
+cmd 'floating enable';
+
+is($x->input_focus, $second->id, 'second container focused');
+
+cmd 'focus tiling';
+
+sync_with_i3($x);
+
+is($x->input_focus, $first->id, 'first (tiling) container focused');
+
+cmd 'focus floating';
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second (floating) container focused');
+
+cmd 'focus floating';
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second (floating) container still focused');
+
+cmd 'focus mode_toggle';
+
+sync_with_i3($x);
+
+is($x->input_focus, $first->id, 'first (tiling) container focused');
+
+cmd 'focus mode_toggle';
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second (floating) container focused');
+
+#############################################################################
+# 6: see if switching floating focus using the focus left/right command works
+#############################################################################
+
+$tmp = fresh_workspace;
+
+$first = open_floating_window($x, { background_color => '#ff0000' });# window 10
+$second = open_floating_window($x, { background_color => '#00ff00' }); # window 11
+$third = open_floating_window($x, { background_color => '#0000ff' }); # window 12
+
+sync_with_i3($x);
+
+is($x->input_focus, $third->id, 'third container focused');
+
+cmd 'focus left';
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second container focused');
+
+cmd 'focus left';
+
+sync_with_i3($x);
+
+is($x->input_focus, $first->id, 'first container focused');
+
+cmd 'focus left';
+
+sync_with_i3($x);
+
+is($x->input_focus, $third->id, 'focus wrapped to third container');
+
+cmd 'focus right';
+
+sync_with_i3($x);
+
+is($x->input_focus, $first->id, 'focus wrapped to first container');
+
+cmd 'focus right';
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'focus on second container');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Regression test: when only having a floating window on a workspace, it should not be deleted.
+
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+ use_ok('X11::XCB::Window');
+}
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+#############################################################################
+# 1: open a floating window, get it mapped
+#############################################################################
+
+ok(workspace_exists($tmp), "workspace $tmp exists");
+
+my $x = X11::XCB::Connection->new;
+
+# Create a floating window which is smaller than the minimum enforced size of i3
+my $window = open_floating_window($x);
+ok($window->mapped, 'Window is mapped');
+
+# switch to a different workspace, see if the window is still mapped?
+
+my $otmp = fresh_workspace;
+
+ok(workspace_exists($otmp), "new workspace $otmp exists");
+ok(workspace_exists($tmp), "old workspace $tmp still exists");
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Regression test: Floating windows were not correctly unmapped when switching
+# to a different workspace.
+
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+ use_ok('X11::XCB::Window');
+}
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+#############################################################################
+# 1: open a floating window, get it mapped
+#############################################################################
+
+my $x = X11::XCB::Connection->new;
+
+# Create a floating window which is smaller than the minimum enforced size of i3
+my $window = open_floating_window($x);
+ok($window->mapped, 'Window is mapped');
+
+# switch to a different workspace, see if the window is still mapped?
+
+my $otmp = fresh_workspace;
+
+sync_with_i3($x);
+
+ok(!$window->mapped, 'Window is not mapped after switching ws');
+
+cmd "nop testcase done";
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Regression test: New windows were attached to the container of a floating window
+# if only a floating window is present on the workspace.
+
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+ use_ok('X11::XCB::Window');
+}
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+#############################################################################
+# 1: open a floating window, get it mapped
+#############################################################################
+
+my $x = X11::XCB::Connection->new;
+
+# Create a floating window
+my $window = open_floating_window($x);
+ok($window->mapped, 'Window is mapped');
+
+my $ws = get_ws($tmp);
+my ($nodes, $focus) = get_ws_content($tmp);
+
+is(@{$ws->{floating_nodes}}, 1, 'one floating node');
+is(@{$nodes}, 0, 'no tiling nodes');
+
+# Create a tiling window
+my $twindow = open_window($x);
+
+($nodes, $focus) = get_ws_content($tmp);
+
+is(@{$nodes}, 1, 'one tiling node');
+
+#############################################################################
+# 2: similar case: floating windows should be attached at the currently focused
+# position in the workspace (for example a stack), not just at workspace level.
+#############################################################################
+
+$tmp = fresh_workspace;
+
+my $first = open_window($x);
+my $second = open_window($x);
+
+cmd 'layout stacked';
+
+$ws = get_ws($tmp);
+is(@{$ws->{floating_nodes}}, 0, 'no floating nodes so far');
+is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)');
+
+# Create a floating window
+my $window = open_floating_window($x);
+ok($window->mapped, 'Window is mapped');
+
+$ws = get_ws($tmp);
+is(@{$ws->{floating_nodes}}, 1, 'one floating nodes');
+is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)');
+
+my $third = open_window($x);
+
+
+$ws = get_ws($tmp);
+is(@{$ws->{floating_nodes}}, 1, 'one floating nodes');
+is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Check if numbered workspaces and named workspaces are sorted in the right way
+# in get_workspaces IPC output (necessary for i3bar etc.).
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+ use_ok('X11::XCB::Window');
+}
+
+my $i3 = i3(get_socket_path());
+my $x = X11::XCB::Connection->new;
+
+sub check_order {
+ my ($msg) = @_;
+
+ my @ws = @{$i3->get_workspaces->recv};
+ my @nums = map { $_->{num} } grep { defined($_->{num}) } @ws;
+ my @sorted = sort @nums;
+
+ cmp_deeply(\@nums, \@sorted, $msg);
+}
+
+check_order('workspace order alright before testing');
+
+#############################################################################
+# open a window to keep this ws open
+#############################################################################
+
+cmd "workspace 93";
+
+open_window($x);
+
+my @ws = @{$i3->get_workspaces->recv};
+my @f = grep { defined($_->{num}) && $_->{num} == 93 } @ws;
+is(@f, 1, 'ws 93 found by num');
+check_order('workspace order alright after opening 93');
+
+cmd "workspace 92";
+open_window($x);
+check_order('workspace order alright after opening 92');
+
+cmd "workspace 94";
+open_window($x);
+check_order('workspace order alright after opening 94');
+
+cmd "workspace 96";
+open_window($x);
+check_order('workspace order alright after opening 96');
+
+cmd "workspace foo";
+open_window($x);
+check_order('workspace order alright after opening foo');
+
+cmd "workspace 91";
+open_window($x);
+check_order('workspace order alright after opening 91');
+
+done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-####################################################################################
-# first part: test if a floating window will be correctly positioned above its leader
-#
-# This is verified by opening two windows, then opening a floating window above the
-# right one, then above the left one. If the floating windows are all positioned alike,
-# one of both (depending on your screen resolution) will be positioned wrong.
-####################################################################################
-
-my $left = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [0, 0, 30, 30],
- background_color => '#FF0000',
-);
-
-$left->name('Left');
-$left->map;
-
-my $right = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [0, 0, 30, 30],
- background_color => '#FF0000',
-);
-
-$right->name('Right');
-$right->map;
-
-sleep 0.25;
-
-my ($abs, $rgeom) = $right->rect;
-
-my $child = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#C0C0C0',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-$child->name('Child window');
-$child->client_leader($right);
-$child->map;
-
-sleep 0.25;
-
-my $cgeom;
-($abs, $cgeom) = $child->rect;
-cmp_ok($cgeom->x, '>=', $rgeom->x, 'Child X >= right container X');
-
-my $child2 = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#C0C0C0',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-$child2->name('Child window 2');
-$child2->client_leader($left);
-$child2->map;
-
-sleep 0.25;
-
-($abs, $cgeom) = $child2->rect;
-cmp_ok(($cgeom->x + $cgeom->width), '<', $rgeom->x, 'child above left window');
-
-# check wm_transient_for
-
-
-my $fwindow = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#FF0000',
-);
-
-$fwindow->transient_for($right);
-$fwindow->map;
-
-sleep 0.25;
-
-my ($absolute, $top) = $fwindow->rect;
-ok($absolute->{x} != 0 && $absolute->{y} != 0, 'i3 did not map it to (0x0)');
-
-SKIP: {
- skip "(workspace placement by client_leader not yet implemented)", 3;
-
-#####################################################################
-# Create a parent window
-#####################################################################
-
-my $window = $x->root->create_child(
-class => WINDOW_CLASS_INPUT_OUTPUT,
-rect => [ 0, 0, 30, 30 ],
-background_color => '#C0C0C0',
-);
-
-$window->name('Parent window');
-$window->map;
-
-sleep 0.25;
-
-#########################################################################
-# Switch to a different workspace and open a child window. It should be opened
-# on the old workspace.
-#########################################################################
-fresh_workspace;
-
-my $child = $x->root->create_child(
-class => WINDOW_CLASS_INPUT_OUTPUT,
-rect => [ 0, 0, 30, 30 ],
-background_color => '#C0C0C0',
-);
-
-$child->name('Child window');
-$child->client_leader($window);
-$child->map;
-
-sleep 0.25;
-
-isnt($x->input_focus, $child->id, "Child window focused");
-
-# Switch back
-cmd "workspace $tmp";
-
-is($x->input_focus, $child->id, "Child window focused");
-
-}
-
-done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Regression: Check if the focus stays the same when switching the layout
+# bug introduced by 77d0d42ed2d7ac8cafe267c92b35a81c1b9491eb
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+ use_ok('X11::XCB::Window');
+}
+
+my $i3 = i3(get_socket_path());
+my $x = X11::XCB::Connection->new;
+
+sub check_order {
+ my ($msg) = @_;
+
+ my @ws = @{$i3->get_workspaces->recv};
+ my @nums = map { $_->{num} } grep { defined($_->{num}) } @ws;
+ my @sorted = sort @nums;
+
+ cmp_deeply(\@nums, \@sorted, $msg);
+}
+
+my $tmp = fresh_workspace;
+
+my $left = open_window($x);
+my $mid = open_window($x);
+my $right = open_window($x);
+
+sync_with_i3($x);
+
+diag("left = " . $left->id . ", mid = " . $mid->id . ", right = " . $right->id);
+
+is($x->input_focus, $right->id, 'Right window focused');
+
+cmd 'focus left';
+
+is($x->input_focus, $mid->id, 'Mid window focused');
+
+cmd 'layout stacked';
+
+is($x->input_focus, $mid->id, 'Mid window focused');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Tests resizing tiling containers
+use i3test;
+use X11::XCB qw(:all);
+
+BEGIN {
+ use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+cmd 'split v';
+
+my $top = open_window($x);
+my $bottom = open_window($x);
+
+sync_with_i3($x);
+
+diag("top = " . $top->id . ", bottom = " . $bottom->id);
+
+is($x->input_focus, $bottom->id, 'Bottom window focused');
+
+############################################################
+# resize
+############################################################
+
+cmd 'resize grow up 10 px or 25 ppt';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+
+is($nodes->[0]->{percent}, 0.25, 'top window got only 25%');
+is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
+
+
+############################################################
+# split and check if the 'percent' factor is still correct
+############################################################
+
+cmd 'split h';
+
+($nodes, $focus) = get_ws_content($tmp);
+
+is($nodes->[0]->{percent}, 0.25, 'top window got only 25%');
+is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
+
+############################################################
+# checks that resizing within stacked/tabbed cons works
+############################################################
+
+$tmp = fresh_workspace;
+
+cmd 'split v';
+
+$top = open_window($x);
+$bottom = open_window($x);
+
+cmd 'split h';
+cmd 'layout stacked';
+
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[0]->{percent}, 0.5, 'top window got 50%');
+is($nodes->[1]->{percent}, 0.5, 'bottom window got 50%');
+
+cmd 'resize grow up 10 px or 25 ppt';
+
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[0]->{percent}, 0.25, 'top window got 25%');
+is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
+
+############################################################
+# checks that resizing floating windows works
+############################################################
+
+$tmp = fresh_workspace;
+
+$top = open_window($x);
+
+cmd 'floating enable';
+
+my @content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok(@content, '==', 1, 'one floating node on this ws');
+
+# up
+my $oldrect = $content[0]->{rect};
+
+cmd 'resize grow up 10 px or 25 ppt';
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok($content[0]->{rect}->{y}, '<', $oldrect->{y}, 'y smaller than before');
+cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y} - 10, 'y exactly 10 px smaller');
+cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
+cmp_ok($content[0]->{rect}->{height}, '>', $oldrect->{height}, 'height bigger than before');
+cmp_ok($content[0]->{rect}->{height}, '==', $oldrect->{height} + 10, 'height exactly 10 px higher');
+cmp_ok($content[0]->{rect}->{width}, '==', $oldrect->{width}, 'x untouched');
+
+# up, but with a different amount of px
+$oldrect = $content[0]->{rect};
+
+cmd 'resize grow up 12 px or 25 ppt';
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok($content[0]->{rect}->{y}, '<', $oldrect->{y}, 'y smaller than before');
+cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y} - 12, 'y exactly 10 px smaller');
+cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
+cmp_ok($content[0]->{rect}->{height}, '>', $oldrect->{height}, 'height bigger than before');
+cmp_ok($content[0]->{rect}->{height}, '==', $oldrect->{height} + 12, 'height exactly 10 px higher');
+cmp_ok($content[0]->{rect}->{width}, '==', $oldrect->{width}, 'x untouched');
+
+# left
+$oldrect = $content[0]->{rect};
+
+cmd 'resize grow left 10 px or 25 ppt';
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok($content[0]->{rect}->{x}, '<', $oldrect->{x}, 'x smaller than before');
+cmp_ok($content[0]->{rect}->{width}, '>', $oldrect->{width}, 'width bigger than before');
+
+# right
+$oldrect = $content[0]->{rect};
+
+cmd 'resize grow right 10 px or 25 ppt';
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x the same as before');
+cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y the same as before');
+cmp_ok($content[0]->{rect}->{width}, '>', $oldrect->{width}, 'width bigger than before');
+cmp_ok($content[0]->{rect}->{height}, '==', $oldrect->{height}, 'height the same as before');
+
+# down
+$oldrect = $content[0]->{rect};
+
+cmd 'resize grow down 10 px or 25 ppt';
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x the same as before');
+cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y the same as before');
+cmp_ok($content[0]->{rect}->{height}, '>', $oldrect->{height}, 'height bigger than before');
+cmp_ok($content[0]->{rect}->{width}, '==', $oldrect->{width}, 'width the same as before');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression: move a floating window to a different workspace crashes i3
+#
+use i3test;
+
+my $tmp = fresh_workspace;
+my $otmp = get_unused_workspace();
+
+cmd 'open';
+cmd 'mode toggle';
+cmd "move workspace $otmp";
+
+does_i3_live;
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression: floating windows are tiling after restarting, closing them crashes i3
+#
+use i3test;
+
+my $tmp = fresh_workspace;
+
+cmd 'open';
+cmd 'mode toggle';
+cmd 'restart';
+
+sleep 0.5;
+
+diag('Checking if i3 still lives');
+
+does_i3_live;
+
+my $ws = get_ws($tmp);
+diag('ws = ' . Dumper($ws));
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression: when resizing two containers on a workspace, opening a floating
+# client, then closing it again, i3 will re-distribute the space on the
+# workspace as if a tiling container was closed, leading to the containers
+# taking much more space than they possibly could.
+#
+use i3test;
+use List::Util qw(sum);
+
+my $tmp = fresh_workspace;
+
+cmd 'exec /usr/bin/urxvt';
+sleep 0.5;
+cmd 'exec /usr/bin/urxvt';
+sleep 0.5;
+my ($nodes, $focus) = get_ws_content($tmp);
+my $old_sum = sum map { $_->{rect}->{width} } @{$nodes};
+#cmd 'open';
+cmd 'resize grow left 10 px or 25 ppt';
+cmd 'split v';
+#cmd 'open';
+cmd 'exec /usr/bin/urxvt';
+sleep 0.5;
+cmd 'mode toggle';
+sleep 0.5;
+cmd 'kill';
+
+sleep 0.5;
+
+($nodes, $focus) = get_ws_content($tmp);
+my $new_sum = sum map { $_->{rect}->{width} } @{$nodes};
+
+is($old_sum, $new_sum, 'combined container width is still equal');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# by moving the window in the opposite orientation that its parent has, we
+# force i3 to create a new split container with the appropriate orientation.
+# However, when doing that two times in a row, we end up with two split
+# containers which are then redundant (workspace is horizontal, then v-split,
+# then h-split – we could just append the children of the latest h-split to the
+# workspace itself).
+#
+# This testcase checks that the tree is properly flattened after moving.
+#
+use X11::XCB qw(:all);
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+my $left = open_window($x);
+my $mid = open_window($x);
+my $right = open_window($x);
+
+cmd 'move before v';
+cmd 'move after h';
+my $ws = get_ws($tmp);
+
+is($ws->{orientation}, 'horizontal', 'workspace orientation is horizontal');
+is(@{$ws->{nodes}}, 3, 'all three windows on workspace level');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+ use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+my $left = open_window($x);
+my $mid = open_window($x);
+
+cmd 'split v';
+my $bottom = open_window($x);
+
+my ($nodes, $focus) = get_ws_content($tmp);
+
+#############################################################################
+# 1: open a floating window, get it mapped
+#############################################################################
+
+# Create a floating window
+my $window = open_floating_window($x);
+ok($window->mapped, 'Window is mapped');
+
+($nodes, $focus) = get_ws_content($tmp);
+is(@{$nodes->[1]->{nodes}}, 2, 'two windows in split con');
+
+#############################################################################
+# 2: make it tiling, see where it ends up
+#############################################################################
+
+cmd 'floating toggle';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+
+is(@{$nodes->[1]->{nodes}}, 3, 'three windows in split con after floating toggle');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test for moving a con outside of a floating con when there are no
+# tiling cons on a workspace
+#
+use X11::XCB qw(:all);
+use Time::HiRes qw(sleep);
+use i3test;
+
+BEGIN {
+ use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+my $left = open_window($x);
+my $mid = open_window($x);
+my $right = open_window($x);
+
+# go to workspace level
+cmd 'level up';
+sleep 0.25;
+
+# make it floating
+cmd 'mode toggle';
+sleep 0.25;
+
+# move the con outside the floating con
+cmd 'move before v';
+sleep 0.25;
+
+does_i3_live;
+
+# move another con outside
+cmd '[id="' . $mid->id . '"] focus';
+cmd 'move before v';
+sleep 0.25;
+
+does_i3_live;
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test for correct focus behaviour when moving a floating con to
+# another workspace.
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+ use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+# open a tiling window on the first workspace
+open_window($x);
+#sleep 0.25;
+my $first = get_focused($tmp);
+
+# on a different ws, open a floating window
+my $otmp = fresh_workspace;
+open_window($x);
+#sleep 0.25;
+my $float = get_focused($otmp);
+cmd 'mode toggle';
+#sleep 0.25;
+
+# move the floating con to first workspace
+cmd "move workspace $tmp";
+#sleep 0.25;
+
+# switch to the first ws and check focus
+is(get_focused($tmp), $float, 'floating client correctly focused');
+
+done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use List::MoreUtils qw(all);
-
-my $i3 = i3(get_socket_path());
-
-####################
-# Request workspaces
-####################
-
-SKIP: {
- skip "IPC API not yet stabilized", 2;
-
-my $workspaces = $i3->get_workspaces->recv;
-
-ok(@{$workspaces} > 0, "More than zero workspaces found");
-
-my $name_exists = all { defined($_->{name}) } @{$workspaces};
-ok($name_exists, "All workspaces have a name");
-
-}
-
-done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test for inplace restarting with dock clients
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+ use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# verify that there is no dock window yet
+#####################################################################
+
+# Children of all dockareas
+my @docked = get_dock_clients;
+
+is(@docked, 0, 'no dock clients yet');
+
+# open a dock client
+
+my $window = open_window($x, {
+ background_color => '#FF0000',
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+ });
+
+#####################################################################
+# check that we can find it in the layout tree at the expected position
+#####################################################################
+
+@docked = get_dock_clients;
+is(@docked, 1, 'one dock client found');
+
+# verify the height
+my $docknode = $docked[0];
+
+is($docknode->{rect}->{height}, 30, 'dock node has unchanged height');
+
+# perform an inplace-restart
+cmd 'restart';
+
+sleep 0.25;
+
+does_i3_live;
+
+
+#####################################################################
+# check that we can still find the dock client
+#####################################################################
+
+@docked = get_dock_clients;
+is(@docked, 1, 'one dock client found');
+$docknode = $docked[0];
+
+is($docknode->{rect}->{height}, 30, 'dock node has unchanged height after restart');
+
+$window->destroy;
+
+wait_for_unmap $x;
+
+@docked = get_dock_clients;
+is(@docked, 0, 'no dock clients found');
+
+#####################################################################
+# create a dock client with a 1px border
+#####################################################################
+
+$window = open_window($x, {
+ border => 1,
+ rect => [ 0, 0, 30, 20 ],
+ background_color => '#00FF00',
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+ });
+
+@docked = get_dock_clients;
+is(@docked, 1, 'one dock client found');
+$docknode = $docked[0];
+
+is($docknode->{rect}->{height}, 20, 'dock node has unchanged height');
+
+cmd 'restart';
+sleep 0.25;
+
+@docked = get_dock_clients;
+is(@docked, 1, 'one dock client found');
+$docknode = $docked[0];
+
+is($docknode->{rect}->{height}, 20, 'dock node has unchanged height');
+
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test for setting a window to floating, tiling and opening a new window
+#
+use i3test;
+
+fresh_workspace;
+
+cmd 'open';
+cmd 'mode toggle';
+cmd 'mode toggle';
+cmd 'open';
+
+does_i3_live;
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test for using level-up to get to the 'content'-container and
+# toggle floating
+#
+use i3test;
+
+fresh_workspace;
+
+cmd 'open';
+cmd 'level up';
+cmd 'level up';
+cmd 'mode toggle';
+
+does_i3_live;
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Test if the requested width/height is set after making the window floating.
+#
+use X11::XCB qw(:all);
+use i3test;
+
+my $tmp = fresh_workspace;
+
+my $x = X11::XCB::Connection->new;
+
+# Create a floating window which is smaller than the minimum enforced size of i3
+my $window = open_window($x, { rect => [ 0, 0, 400, 150 ] });
+
+my ($absolute, $top) = $window->rect;
+
+ok($window->mapped, 'Window is mapped');
+cmp_ok($absolute->{width}, '>', 400, 'i3 raised the width');
+cmp_ok($absolute->{height}, '>', 150, 'i3 raised the height');
+
+cmd 'floating toggle';
+sync_with_i3($x);
+
+($absolute, $top) = $window->rect;
+
+diag('new width: ' . $absolute->{width});
+diag('new height: ' . $absolute->{height});
+
+# we compare with a tolerance of ± 20 pixels for borders in each direction
+# (overkill, but hey)
+cmp_ok($absolute->{width}, '>', 400-20, 'width now > 380');
+cmp_ok($absolute->{width}, '<', 400+20, 'width now < 420');
+cmp_ok($absolute->{height}, '>', 150-20, 'height now > 130');
+cmp_ok($absolute->{height}, '<', 150+20, 'height now < 170');
+
+#cmp_ok($absolute->{width}, '>=', 75, 'i3 raised the width to 75');
+#cmp_ok($absolute->{height}, '>=', 50, 'i3 raised the height to 50');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test for closing one of multiple dock clients
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+ use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# verify that there is no dock window yet
+#####################################################################
+
+# Children of all dockareas
+my @docked = get_dock_clients;
+
+is(@docked, 0, 'no dock clients yet');
+
+#####################################################################
+# open a dock client
+#####################################################################
+
+my $first = open_window($x, {
+ background_color => '#FF0000',
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+ });
+
+#####################################################################
+# Open a second dock client
+#####################################################################
+
+my $second = open_window($x, {
+ background_color => '#FF0000',
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+ });
+
+#####################################################################
+# Kill the second dock client
+#####################################################################
+cmd "nop destroying dock client";
+$second->destroy;
+
+#####################################################################
+# Now issue a focus command
+#####################################################################
+cmd 'focus right';
+
+does_i3_live;
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Test to see if i3 combines the geometry of all children in a split container
+# when setting the split container to floating
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+ use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# open a window with 200x80
+#####################################################################
+
+my $first = open_window($x, {
+ rect => [ 0, 0, 200, 80],
+ background_color => '#FF0000',
+ });
+
+#####################################################################
+# Open a second window with 300x90
+#####################################################################
+
+my $second = open_window($x, {
+ rect => [ 0, 0, 300, 90],
+ background_color => '#00FF00',
+ });
+
+#####################################################################
+# Set the parent to floating
+#####################################################################
+cmd 'nop setting floating';
+cmd 'focus parent';
+cmd 'floating enable';
+
+#####################################################################
+# Get geometry of the first floating node (the split container)
+#####################################################################
+
+my @nodes = @{get_ws($tmp)->{floating_nodes}};
+my $rect = $nodes[0]->{rect};
+
+# we compare the width with ± 20 pixels for borders
+cmp_ok($rect->{width}, '>', 500-20, 'width now > 480');
+cmp_ok($rect->{width}, '<', 500+20, 'width now < 520');
+# we compare the height with ± 40 pixels for decorations
+cmp_ok($rect->{height}, '>', 90-40, 'width now > 50');
+cmp_ok($rect->{height}, '<', 90+40, 'width now < 130');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Test if new containers get focused when there is a fullscreen container at
+# the time of launching the new one.
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+ use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# open the left window
+#####################################################################
+
+my $left = open_window($x, { background_color => '#ff0000' });
+
+is($x->input_focus, $left->id, 'left window focused');
+
+diag("left = " . $left->id);
+
+#####################################################################
+# Open the right window
+#####################################################################
+
+my $right = open_window($x, { background_color => '#00ff00' });
+
+diag("right = " . $right->id);
+
+#####################################################################
+# Set the right window to fullscreen
+#####################################################################
+cmd 'nop setting fullscreen';
+cmd 'fullscreen';
+
+#####################################################################
+# Open a third window
+#####################################################################
+
+my $third = open_window($x, {
+ background_color => '#0000ff',
+ name => 'Third window',
+ dont_map => 1,
+ });
+
+$third->map;
+
+sync_with_i3 $x;
+
+diag("third = " . $third->id);
+
+# move the fullscreen window to a different ws
+
+my $tmp2 = get_unused_workspace;
+
+cmd "move workspace $tmp2";
+
+# verify that the third window has the focus
+
+sync_with_i3($x);
+
+is($x->input_focus, $third->id, 'third window focused');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test: level up should be a noop during fullscreen mode
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+ use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# open a window, verify it’s not in fullscreen mode
+#####################################################################
+
+my $win = open_window($x);
+
+my $nodes = get_ws_content $tmp;
+is(@$nodes, 1, 'exactly one client');
+is($nodes->[0]->{fullscreen_mode}, 0, 'client not fullscreen');
+
+#####################################################################
+# make it fullscreen
+#####################################################################
+
+cmd 'nop making fullscreen';
+cmd 'fullscreen';
+
+my $nodes = get_ws_content $tmp;
+is($nodes->[0]->{fullscreen_mode}, 1, 'client fullscreen now');
+
+#####################################################################
+# send level up, try to un-fullscreen
+#####################################################################
+cmd 'level up';
+cmd 'fullscreen';
+
+my $nodes = get_ws_content $tmp;
+is($nodes->[0]->{fullscreen_mode}, 0, 'client not fullscreen any longer');
+
+does_i3_live;
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests if the WM_TAKE_FOCUS protocol is correctly handled by i3
+#
+use X11::XCB qw(:all);
+use i3test;
+use v5.10;
+
+my $x = X11::XCB::Connection->new;
+
+subtest 'Window without WM_TAKE_FOCUS', sub {
+ fresh_workspace;
+
+ my $window = open_window($x);
+
+ ok(!wait_for_event($x, 1, sub { $_[0]->{response_type} == 161 }), 'did not receive ClientMessage');
+
+ done_testing;
+};
+
+subtest 'Window with WM_TAKE_FOCUS', sub {
+ fresh_workspace;
+
+ my $take_focus = $x->atom(name => 'WM_TAKE_FOCUS');
+
+ my $window = open_window($x, {
+ dont_map => 1,
+ protocols => [ $take_focus ],
+ });
+
+ $window->map;
+
+ ok(wait_for_event($x, 1, sub {
+ return 0 unless $_[0]->{response_type} == 161;
+ my ($data, $time) = unpack("L2", $_[0]->{data});
+ return ($data == $take_focus->id);
+ }), 'got ClientMessage with WM_TAKE_FOCUS atom');
+
+ done_testing;
+};
+
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# Tests if the various ipc_socket_path options are correctly handled
+#
+use i3test;
+use File::Temp qw(tempfile tempdir);
+use POSIX qw(getuid);
+use v5.10;
+
+#####################################################################
+# default case: socket will be created in /tmp/i3-<username>/ipc-socket.<pid>
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+# ensure XDG_RUNTIME_DIR is not set
+delete $ENV{XDG_RUNTIME_DIR};
+my $pid = launch_with_config($config, 1);
+
+my $folder = "/tmp/i3-" . getpwuid(getuid());
+ok(-d $folder, "folder $folder exists");
+my $socketpath = "$folder/ipc-socket." . $pid;
+ok(-S $socketpath, "file $socketpath exists and is a socket");
+
+exit_gracefully($pid);
+
+#####################################################################
+# XDG_RUNTIME_DIR case: socket gets created in $XDG_RUNTIME_DIR/i3/ipc-socket.<pid>
+#####################################################################
+
+my $rtdir = tempdir(CLEANUP => 1);
+
+ok(! -e "$rtdir/i3", "$rtdir/i3 does not exist yet");
+
+$ENV{XDG_RUNTIME_DIR} = $rtdir;
+
+$pid = launch_with_config($config, 1);
+
+ok(-d "$rtdir/i3", "$rtdir/i3 exists and is a directory");
+$socketpath = "$rtdir/i3/ipc-socket." . $pid;
+ok(-S $socketpath, "file $socketpath exists and is a socket");
+
+exit_gracefully($pid);
+
+#####################################################################
+# configuration file case: socket gets placed whereever we specify
+#####################################################################
+
+my $tmpdir = tempdir(CLEANUP => 1);
+$socketpath = $tmpdir . "/config.sock";
+ok(! -e $socketpath, "$socketpath does not exist yet");
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+ipc-socket $socketpath
+EOT
+
+$pid = launch_with_config($config, 1);
+
+ok(-S $socketpath, "file $socketpath exists and is a socket");
+
+exit_gracefully($pid);
+
+done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use List::MoreUtils qw(all none);
-use List::Util qw(first);
-
-my $i3 = i3(get_socket_path());
-
-####################
-# Request tree
-####################
-
-my $tree = $i3->get_tree->recv;
-
-my $expected = {
- fullscreen_mode => 0,
- nodes => ignore(),
- window => undef,
- name => 'root',
- orientation => ignore(),
- type => 0,
- id => ignore(),
- rect => ignore(),
- window_rect => ignore(),
- geometry => ignore(),
- swallows => ignore(),
- percent => undef,
- layout => 'default',
- focus => ignore(),
- focused => JSON::XS::false,
- urgent => JSON::XS::false,
- border => 'normal',
- 'floating_nodes' => ignore(),
-};
-
-cmp_deeply($tree, $expected, 'root node OK');
-
-my @nodes = @{$tree->{nodes}};
-
-ok(@nodes > 0, 'root node has at least one leaf');
-
-ok((all { $_->{type} == 1 } @nodes), 'all nodes are of type CT_OUTPUT');
-ok((none { defined($_->{window}) } @nodes), 'no CT_OUTPUT contains a window');
-ok((all { @{$_->{nodes}} > 0 } @nodes), 'all nodes have at least one leaf (workspace)');
-my @workspaces;
-for my $ws (@nodes) {
- my $content = first { $_->{type} == 2 } @{$ws->{nodes}};
- @workspaces = (@workspaces, @{$content->{nodes}});
-}
-
-ok((all { $_->{type} == 4 } @workspaces), 'all workspaces are of type CT_WORKSPACE');
-#ok((all { @{$_->{nodes}} == 0 } @workspaces), 'all workspaces are empty yet');
-ok((none { defined($_->{window}) } @workspaces), 'no CT_OUTPUT contains a window');
-
-# TODO: get the focused container
-
-$i3->command('open')->recv;
-
-# TODO: get the focused container, check if it changed.
-# TODO: get the old focused container, check if there is a new child
-
-#diag(Dumper(\@workspaces));
-
-#diag(Dumper($tree));
-
-
-done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test to check if borders are correctly restored after an inplace
+# restart.
+# found in eb8ad348b28e243cba1972e802ca8ee636472fc9
+#
+use X11::XCB qw(:all);
+use List::Util qw(first);
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+my $i3 = i3(get_socket_path());
+my $tmp = fresh_workspace;
+my $window = open_window($x);
+
+sub get_border_style {
+ my @content = @{get_ws_content($tmp)};
+ my $wininfo = first { $_->{window} == $window->id } @content;
+
+ return $wininfo->{border};
+}
+
+is(get_border_style(), 'normal', 'border style normal');
+
+cmd 'border 1pixel';
+
+is(get_border_style(), '1pixel', 'border style 1pixel after changing');
+
+# perform an inplace-restart
+cmd 'restart';
+
+sleep 0.25;
+
+does_i3_live;
+
+is(get_border_style(), '1pixel', 'border style still 1pixel after restart');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test for setting the urgent hint on dock clients.
+# found in 4be3178d4d360c2996217d811e61161c84d25898
+#
+use X11::XCB qw(:all);
+use i3test;
+
+BEGIN {
+ use_ok('X11::XCB::Window');
+}
+
+my $x = X11::XCB::Connection->new;
+my $i3 = i3(get_socket_path());
+
+my $tmp = fresh_workspace;
+
+#####################################################################
+# verify that there is no dock window yet
+#####################################################################
+
+# Children of all dockareas
+my @docked = get_dock_clients;
+
+is(@docked, 0, 'no dock clients yet');
+
+# open a dock client
+
+my $window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30],
+ background_color => '#FF0000',
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+);
+
+$window->map;
+
+sleep 0.25;
+
+#####################################################################
+# check that we can find it in the layout tree at the expected position
+#####################################################################
+
+@docked = get_dock_clients;
+is(@docked, 1, 'one dock client found');
+
+# verify the height
+my $docknode = $docked[0];
+
+is($docknode->{rect}->{height}, 30, 'dock node has unchanged height');
+
+$window->add_hint('urgency');
+
+sync_with_i3($x);
+
+does_i3_live;
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when
+# unmapped.
+#
+use X11::XCB qw(:all);
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+my $window = open_window($x);
+
+sync_with_i3($x);
+
+is($window->state, ICCCM_WM_STATE_NORMAL, 'WM_STATE normal');
+
+$window->unmap;
+
+wait_for_unmap $x;
+
+is($window->state, ICCCM_WM_STATE_WITHDRAWN, 'WM_STATE withdrawn');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when
+# unmapped.
+#
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+sub two_windows {
+ my $tmp = fresh_workspace;
+
+ ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+ my $first = open_window($x);
+ my $second = open_window($x);
+
+ sync_with_i3 $x;
+
+ is($x->input_focus, $second->id, 'second window focused');
+ ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
+
+ return $tmp;
+}
+
+##############################################################
+# 1: open two windows (in the same client), kill one and see if
+# the other one is still there
+##############################################################
+
+my $tmp = two_windows;
+
+cmd 'kill';
+
+sleep 0.25;
+
+ok(@{get_ws_content($tmp)} == 1, 'one container left after killing');
+
+##############################################################
+# 2: same test case as test 1, but with the explicit variant
+# 'kill window'
+##############################################################
+
+my $tmp = two_windows;
+
+cmd 'kill window';
+
+sleep 0.25;
+
+ok(@{get_ws_content($tmp)} == 1, 'one container left after killing');
+
+##############################################################
+# 3: open two windows (in the same client), use 'kill client'
+# and check if both are gone
+##############################################################
+
+my $tmp = two_windows;
+
+cmd 'kill client';
+
+sleep 0.25;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers left after killing');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+#
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+##############################################################
+# 1: test the following directive:
+# for_window [class="borderless"] border none
+# by first creating a window with a different class (should get
+# the normal border), then creating a window with the class
+# "borderless" (should get no border)
+##############################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [class="borderless"] border none
+for_window [title="special borderless title"] border none
+EOT
+
+my $pid = launch_with_config($config);
+
+my $tmp = fresh_workspace;
+
+my $window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#00ff00',
+ event_mask => [ 'structure_notify' ],
+);
+
+$window->name('Border window');
+$window->map;
+wait_for_map $x;
+
+my @content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'normal', 'normal border');
+
+$window->unmap;
+wait_for_unmap $x;
+
+my @content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 0, 'no more nodes');
+diag('content = '. Dumper(\@content));
+
+$window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#00ff00',
+ event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+
+# TODO: move this to X11::XCB::Window
+sub set_wm_class {
+ my ($id, $class, $instance) = @_;
+
+ # Add a _NET_WM_STRUT_PARTIAL hint
+ my $atomname = $x->atom(name => 'WM_CLASS');
+ my $atomtype = $x->atom(name => 'STRING');
+
+ $x->change_property(
+ PROP_MODE_REPLACE,
+ $id,
+ $atomname->id,
+ $atomtype->id,
+ 8,
+ length($class) + length($instance) + 2,
+ "$instance\x00$class\x00"
+ );
+}
+
+set_wm_class($window->id, 'borderless', 'borderless');
+$window->name('Borderless window');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'none', 'no border');
+
+$window->unmap;
+wait_for_unmap $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 0, 'no more nodes');
+
+exit_gracefully($pid);
+
+##############################################################
+# 2: match on the title, check if for_window is really executed
+# only once
+##############################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [class="borderless"] border none
+for_window [title="special borderless title"] border none
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+$window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#00ff00',
+ event_mask => [ 'structure_notify' ],
+);
+
+$window->name('special title');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'normal', 'normal border');
+
+$window->name('special borderless title');
+sync_with_i3 $x;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{border}, 'none', 'no border');
+
+$window->name('special title');
+sync_with_i3 $x;
+
+cmd 'border normal';
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{border}, 'normal', 'border reset to normal');
+
+$window->name('special borderless title');
+sync_with_i3 $x;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{border}, 'normal', 'still normal border');
+
+$window->unmap;
+wait_for_unmap $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 0, 'no more nodes');
+
+exit_gracefully($pid);
+
+##############################################################
+# 3: match on the title, set border style *and* a mark
+##############################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [class="borderless" title="usethis"] border none
+for_window [class="borderless"] border none
+for_window [title="special borderless title"] border none
+for_window [title="special mark title"] border none, mark bleh
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+$window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#00ff00',
+ event_mask => [ 'structure_notify' ],
+);
+
+$window->name('special mark title');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'none', 'no border');
+
+my $other = open_window($x);
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 2, 'two nodes');
+is($content[0]->{border}, 'none', 'no border');
+is($content[1]->{border}, 'normal', 'normal border');
+ok(!$content[0]->{focused}, 'first one not focused');
+
+cmd qq|[con_mark="bleh"] focus|;
+
+@content = @{get_ws_content($tmp)};
+ok($content[0]->{focused}, 'first node focused');
+
+exit_gracefully($pid);
+
+##############################################################
+# 4: multiple criteria for the for_window command
+##############################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [class="borderless" title="usethis"] border none
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+$window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#00ff00',
+ event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+
+set_wm_class($window->id, 'borderless', 'borderless');
+$window->name('usethis');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'none', 'no border');
+
+cmd 'kill';
+wait_for_unmap $x;
+$window->destroy;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 0, 'no nodes on this workspace now');
+
+$window->_create;
+
+set_wm_class($window->id, 'borderless', 'borderless');
+$window->name('notthis');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'normal', 'no border');
+
+
+exit_gracefully($pid);
+
+##############################################################
+# 5: check that a class criterion does not match the instance
+##############################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [class="foo"] border 1pixel
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+$window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#00ff00',
+ event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+
+set_wm_class($window->id, 'bar', 'foo');
+$window->name('usethis');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'normal', 'normal border, not matched');
+
+exit_gracefully($pid);
+
+##############################################################
+# 6: check that the 'instance' criterion works
+##############################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [class="foo"] border 1pixel
+for_window [instance="foo"] border none
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+$window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#00ff00',
+ event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+
+set_wm_class($window->id, 'bar', 'foo');
+$window->name('usethis');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'none', 'no border');
+
+exit_gracefully($pid);
+
+##############################################################
+# 7: check that invalid criteria don’t end up matching all windows
+##############################################################
+
+# this configuration is broken because "asdf" is not a valid integer
+# the for_window should therefore recognize this error and don’t add the
+# assignment
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [id="asdf"] border none
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+$window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#00ff00',
+ event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+
+set_wm_class($window->id, 'bar', 'foo');
+$window->name('usethis');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'normal', 'normal border');
+
+exit_gracefully($pid);
+
+##############################################################
+# 8: check that the role criterion works properly
+##############################################################
+
+# this configuration is broken because "asdf" is not a valid integer
+# the for_window should therefore recognize this error and don’t add the
+# assignment
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [window_role="i3test"] border none
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+$window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#00ff00',
+ event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+
+my $atomname = $x->atom(name => 'WM_WINDOW_ROLE');
+my $atomtype = $x->atom(name => 'STRING');
+$x->change_property(
+ PROP_MODE_REPLACE,
+ $window->id,
+ $atomname->id,
+ $atomtype->id,
+ 8,
+ length("i3test") + 1,
+ "i3test\x00"
+);
+
+$window->name('usethis');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'none', 'no border (window_role)');
+
+exit_gracefully($pid);
+
+##############################################################
+# 9: another test for the window_role, but this time it changes
+# *after* the window has been mapped
+##############################################################
+
+# this configuration is broken because "asdf" is not a valid integer
+# the for_window should therefore recognize this error and don’t add the
+# assignment
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [window_role="i3test"] border none
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+$window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#00ff00',
+ event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+
+$window->name('usethis');
+$window->map;
+wait_for_map $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'normal', 'normal border (window_role 2)');
+
+$atomname = $x->atom(name => 'WM_WINDOW_ROLE');
+$atomtype = $x->atom(name => 'STRING');
+$x->change_property(
+ PROP_MODE_REPLACE,
+ $window->id,
+ $atomname->id,
+ $atomtype->id,
+ 8,
+ length("i3test") + 1,
+ "i3test\x00"
+);
+
+$x->flush;
+
+sync_with_i3 $x;
+
+@content = @{get_ws_content($tmp)};
+cmp_ok(@content, '==', 1, 'one node on this workspace now');
+is($content[0]->{border}, 'none', 'no border (window_role 2)');
+
+exit_gracefully($pid);
+
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# Tests if assignments work
+#
+use i3test;
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+use v5.10;
+
+my $x = X11::XCB::Connection->new;
+
+# TODO: move to X11::XCB
+sub set_wm_class {
+ my ($id, $class, $instance) = @_;
+
+ # Add a _NET_WM_STRUT_PARTIAL hint
+ my $atomname = $x->atom(name => 'WM_CLASS');
+ my $atomtype = $x->atom(name => 'STRING');
+
+ $x->change_property(
+ PROP_MODE_REPLACE,
+ $id,
+ $atomname->id,
+ $atomtype->id,
+ 8,
+ length($class) + length($instance) + 2,
+ "$instance\x00$class\x00"
+ );
+}
+
+
+#####################################################################
+# start a window and see that it does not get assigned with an empty config
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $pid = launch_with_config($config);
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#0000ff',
+ event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+set_wm_class($window->id, 'special', 'special');
+$window->name('special window');
+$window->map;
+wait_for_map $x;
+
+ok(@{get_ws_content($tmp)} == 1, 'special window got managed to current (random) workspace');
+
+exit_gracefully($pid);
+
+$window->destroy;
+
+#####################################################################
+# start a window and see that it gets assigned to a formerly unused
+# workspace
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+assign "special" → targetws
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+my $workspaces = get_workspace_names;
+ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
+
+my $window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#0000ff',
+ event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+set_wm_class($window->id, 'special', 'special');
+$window->name('special window');
+$window->map;
+wait_for_map $x;
+
+ok(@{get_ws_content($tmp)} == 0, 'still no containers');
+ok("targetws" ~~ @{get_workspace_names()}, 'targetws exists');
+
+$window->destroy;
+
+exit_gracefully($pid);
+
+sleep 0.25;
+
+#####################################################################
+# start a window and see that it gets assigned to a workspace which has content
+# already, next to the existing node.
+#####################################################################
+
+$pid = launch_with_config($config);
+
+# initialize the target workspace, then go to a fresh one
+ok(!("targetws" ~~ @{get_workspace_names()}), 'targetws does not exist yet');
+cmd 'workspace targetws';
+cmp_ok(@{get_ws_content('targetws')}, '==', 0, 'no containers on targetws yet');
+cmd 'open';
+cmp_ok(@{get_ws_content('targetws')}, '==', 1, 'one container on targetws');
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+ok("targetws" ~~ @{get_workspace_names()}, 'targetws does not exist yet');
+
+my $window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#0000ff',
+ event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+set_wm_class($window->id, 'special', 'special');
+$window->name('special window');
+$window->map;
+
+# We use sync_with_i3 instead of wait_for_map here because i3 will not actually
+# map the window -- it will be assigned to a different workspace and will only
+# be mapped once you switch to that workspace
+sync_with_i3 $x;
+
+ok(@{get_ws_content($tmp)} == 0, 'still no containers');
+ok(@{get_ws_content('targetws')} == 2, 'two containers on targetws');
+
+exit_gracefully($pid);
+
+#####################################################################
+# start a window and see that it gets assigned to a workspace which has content
+# already, next to the existing node.
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+assign "special" → ~
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+my $workspaces = get_workspace_names;
+ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
+
+my $window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#0000ff',
+ event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+set_wm_class($window->id, 'special', 'special');
+$window->name('special window');
+$window->map;
+wait_for_map $x;
+
+my $content = get_ws($tmp);
+ok(@{$content->{nodes}} == 0, 'no tiling cons');
+ok(@{$content->{floating_nodes}} == 1, 'one floating con');
+
+$window->destroy;
+
+exit_gracefully($pid);
+
+sleep 0.25;
+
+#####################################################################
+# make sure that assignments are case-insensitive in the old syntax.
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+assign "special" → ~
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+my $workspaces = get_workspace_names;
+ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
+
+my $window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#0000ff',
+ event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+set_wm_class($window->id, 'SPEcial', 'SPEcial');
+$window->name('special window');
+$window->map;
+wait_for_map $x;
+
+my $content = get_ws($tmp);
+ok(@{$content->{nodes}} == 0, 'no tiling cons');
+ok(@{$content->{floating_nodes}} == 1, 'one floating con');
+
+$window->destroy;
+
+exit_gracefully($pid);
+
+sleep 0.25;
+
+#####################################################################
+# regression test: dock clients with floating assignments should not crash
+# (instead, nothing should happen - dock clients can’t float)
+# ticket #501
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+assign "special" → ~
+EOT
+
+$pid = launch_with_config($config);
+
+# TODO: replace this with checking the process hierarchy
+# XXX: give i3-nagbar some time to start up
+sleep 1;
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+my @docked = get_dock_clients;
+# We expect i3-nagbar as the first dock client due to using the old assign
+# syntax
+is(@docked, 1, 'one dock client yet');
+
+my $window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#0000ff',
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
+ event_mask => [ 'structure_notify' ],
+);
+
+$window->_create;
+set_wm_class($window->id, 'special', 'special');
+$window->name('special window');
+$window->map;
+wait_for_map $x;
+
+my $content = get_ws($tmp);
+ok(@{$content->{nodes}} == 0, 'no tiling cons');
+ok(@{$content->{floating_nodes}} == 0, 'one floating con');
+@docked = get_dock_clients;
+is(@docked, 2, 'two dock clients now');
+
+$window->destroy;
+
+does_i3_live;
+
+exit_gracefully($pid);
+
+sleep 0.25;
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# Tests the workspace_layout config option.
+#
+
+use i3test;
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+
+my $x = X11::XCB::Connection->new;
+
+#####################################################################
+# 1: check that with an empty config, cons are place next to each
+# other and no split containers are created
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $pid = launch_with_config($config);
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $first = open_window($x);
+my $second = open_window($x);
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second window focused');
+ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
+isnt($content[0]->{layout}, 'stacked', 'layout not stacked');
+isnt($content[1]->{layout}, 'stacked', 'layout not stacked');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 2: set workspace_layout stacked, check that when opening two cons,
+# they end up in a stacked con
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+workspace_layout stacked
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_window($x);
+$second = open_window($x);
+
+sync_with_i3($x);
+
+is($x->input_focus, $second->id, 'second window focused');
+my @content = @{get_ws_content($tmp)};
+ok(@content == 1, 'one con at workspace level');
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+
+#####################################################################
+# 3: focus parent, open two new cons, check that they end up in a stacked
+# con
+#####################################################################
+
+cmd 'focus parent';
+my $right_top = open_window($x);
+my $right_bot = open_window($x);
+
+@content = @{get_ws_content($tmp)};
+is(@content, 2, 'two cons at workspace level after focus parent');
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+is($content[1]->{layout}, 'stacked', 'layout stacked');
+
+#####################################################################
+# 4: move one of the cons to the right, check that it will end up in
+# a stacked con
+#####################################################################
+
+cmd 'move right';
+
+@content = @{get_ws_content($tmp)};
+is(@content, 3, 'three cons at workspace level after move');
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+is($content[1]->{layout}, 'stacked', 'layout stacked');
+is($content[2]->{layout}, 'stacked', 'layout stacked');
+
+#####################################################################
+# 5: move it to the left again, check that the stacked con is deleted
+#####################################################################
+
+cmd 'move left';
+
+@content = @{get_ws_content($tmp)};
+is(@content, 2, 'two cons at workspace level after moving back');
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+is($content[1]->{layout}, 'stacked', 'layout stacked');
+
+#####################################################################
+# 6: move it to a different workspace, check that it ends up in a
+# stacked con
+#####################################################################
+
+my $otmp = get_unused_workspace;
+
+cmd "move workspace $otmp";
+
+@content = @{get_ws_content($tmp)};
+is(@content, 2, 'still two cons on this workspace');
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+is($content[1]->{layout}, 'stacked', 'layout stacked');
+
+@content = @{get_ws_content($otmp)};
+is(@content, 1, 'one con on target workspace');
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+
+exit_gracefully($pid);
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Verifies that i3 survives inplace restarts with fullscreen containers
+#
+use i3test;
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+
+my $x = X11::XCB::Connection->new;
+
+fresh_workspace;
+
+open_window($x);
+open_window($x);
+
+cmd 'layout stacking';
+sleep 1;
+
+cmd 'fullscreen';
+sleep 1;
+
+cmd 'restart';
+sleep 1;
+
+does_i3_live;
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests if the 'border toggle' command works correctly
+#
+use i3test;
+
+my $tmp = fresh_workspace;
+
+cmd 'open';
+
+my @nodes = @{get_ws_content($tmp)};
+is(@nodes, 1, 'one container on this workspace');
+is($nodes[0]->{border}, 'normal', 'border style normal');
+
+cmd 'border 1pixel';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, '1pixel', 'border style 1pixel');
+
+cmd 'border none';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, 'none', 'border style none');
+
+cmd 'border normal';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, 'normal', 'border style back to normal');
+
+cmd 'border toggle';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, 'none', 'border style none');
+
+cmd 'border toggle';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, '1pixel', 'border style 1pixel');
+
+cmd 'border toggle';
+@nodes = @{get_ws_content($tmp)};
+is($nodes[0]->{border}, 'normal', 'border style back to normal');
+
+done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests whether we can switch to a non-existant workspace
-# (necessary for further tests)
-#
-use List::Util qw(first);
-use i3test;
-
-# to ensure that workspace 1 stays open
-cmd 'open';
-
-my $tmp = fresh_workspace;
-ok(workspace_exists($tmp), 'workspace created');
-# if the workspace could not be created, we cannot run any other test
-# (every test starts by creating its workspace)
-if (!workspace_exists($tmp)) {
- BAIL_OUT('Cannot create workspace, further tests make no sense');
-}
-
-my $otmp = fresh_workspace;
-diag("Other temporary workspace name: $otmp\n");
-
-# As the old workspace was empty, it should get
-# cleaned up as we switch away from it
-cmd "workspace $otmp";
-ok(!workspace_exists($tmp), 'old workspace cleaned up');
-
-# Switch to the same workspace again to make sure it doesn’t get cleaned up
-cmd "workspace $otmp";
-cmd "workspace $otmp";
-ok(workspace_exists($otmp), 'other workspace still exists');
-
-
-#####################################################################
-# check if the workspace next / prev commands work
-#####################################################################
-
-cmd 'workspace next';
-
-ok(!workspace_exists('next'), 'workspace "next" does not exist');
-
-cmd "workspace $tmp";
-cmd 'open';
-
-ok(workspace_exists($tmp), 'workspace created');
-
-cmd "workspace $otmp";
-cmd 'open';
-
-ok(workspace_exists($tmp), 'workspace tmp still exists');
-ok(workspace_exists($otmp), 'workspace otmp created');
-
-is(focused_ws(), $otmp, 'focused workspace is otmp');
-
-cmd 'workspace prev';
-is(focused_ws(), $tmp, 'focused workspace is tmp after workspace prev');
-
-cmd 'workspace next';
-is(focused_ws(), $otmp, 'focused workspace is otmp after workspace next');
-
-
-#####################################################################
-# check that wrapping works
-#####################################################################
-
-cmd 'workspace next';
-is(focused_ws(), '1', 'focused workspace is 1 after workspace next');
-
-cmd 'workspace next';
-is(focused_ws(), $tmp, 'focused workspace is tmp after workspace next');
-
-cmd 'workspace next';
-is(focused_ws(), $otmp, 'focused workspace is otmp after workspace next');
-
-
-cmd 'workspace prev';
-is(focused_ws(), $tmp, 'focused workspace is tmp after workspace prev');
-
-cmd 'workspace prev';
-is(focused_ws(), '1', 'focused workspace is tmp after workspace prev');
-
-cmd 'workspace prev';
-is(focused_ws(), $otmp, 'focused workspace is otmp after workspace prev');
-
-
-#####################################################################
-# check if we can change to "next" / "prev"
-#####################################################################
-
-cmd 'workspace "next"';
-
-ok(workspace_exists('next'), 'workspace "next" exists');
-is(focused_ws(), 'next', 'now on workspace next');
-
-cmd 'workspace "prev"';
-
-ok(workspace_exists('prev'), 'workspace "prev" exists');
-is(focused_ws(), 'prev', 'now on workspace prev');
-
-done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# Tests if the 'force_focus_wrapping' config directive works correctly.
+#
+use i3test;
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+
+my $x = X11::XCB::Connection->new;
+
+#####################################################################
+# 1: test the wrapping behaviour without force_focus_wrapping
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $pid = launch_with_config($config);
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $first = open_window($x);
+my $second = open_window($x);
+
+cmd 'layout tabbed';
+cmd 'focus parent';
+
+my $third = open_window($x);
+is($x->input_focus, $third->id, 'third window focused');
+
+cmd 'focus left';
+is($x->input_focus, $second->id, 'second window focused');
+
+cmd 'focus left';
+is($x->input_focus, $first->id, 'first window focused');
+
+# now test the wrapping
+cmd 'focus left';
+is($x->input_focus, $second->id, 'second window focused');
+
+# but focusing right should not wrap now, but instead focus the third window
+cmd 'focus right';
+is($x->input_focus, $third->id, 'third window focused');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 2: test the wrapping behaviour with force_focus_wrapping
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+force_focus_wrapping true
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_window($x);
+$second = open_window($x);
+
+cmd 'layout tabbed';
+cmd 'focus parent';
+
+$third = open_window($x);
+
+sync_with_i3($x);
+
+is($x->input_focus, $third->id, 'third window focused');
+
+cmd 'focus left';
+is($x->input_focus, $second->id, 'second window focused');
+
+cmd 'focus left';
+is($x->input_focus, $first->id, 'first window focused');
+
+# now test the wrapping
+cmd 'focus left';
+is($x->input_focus, $second->id, 'second window focused');
+
+# focusing right should now be forced to wrap
+cmd 'focus right';
+is($x->input_focus, $first->id, 'first window focused');
+
+exit_gracefully($pid);
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# Tests if i3-migrate-config-to-v4 correctly migrates all config file
+# directives and commands
+#
+use i3test;
+use Cwd qw(abs_path);
+use File::Temp qw(tempfile tempdir);
+use v5.10;
+
+# reads in a whole file
+sub slurp {
+ open my $fh, '<', shift;
+ local $/;
+ <$fh>;
+}
+
+sub migrate_config {
+ my ($config) = @_;
+
+ my ($fh, $tmpfile) = tempfile('/tmp/i3-migrate-cfg.XXXXXX', UNLINK => 1);
+ print $fh $config;
+ close($fh);
+
+ my $cmd = "sh -c 'exec " . abs_path("../i3-migrate-config-to-v4") . " --v3 <$tmpfile'";
+ return [ split /\n/, qx($cmd) ];
+}
+
+sub line_exists {
+ my ($lines, $pattern) = @_;
+
+ for my $line (@$lines) {
+ return 1 if $line =~ $pattern;
+ }
+
+ return 0
+}
+
+#####################################################################
+# check that some directives remain untouched
+#####################################################################
+
+my $input = <<EOT;
+ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $output = migrate_config($input);
+ok(line_exists($output, qr|font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1|), 'font directive unchanged');
+
+$input = <<EOT;
+ floating_Modifier Mod1
+ focus_follows_mouse true
+ ipc-socket /tmp/i3-ipc.sock
+ ipc_socket /tmp/i3-ipc.sock
+ exec /usr/bin/i3
+ set stuff Mod1
+ assign "XTerm" → 3
+ assign "XTerm" → ~5
+ client.focused #2F343A #900000 #FFFFFF
+ client.focused_inactive #FF0000 #FF0000 #FF0000
+ client.unfocused #00FF00 #00FF00 #00FF00
+ client.urgent #0000FF #0000FF #0000FF
+ client.background #000000
+EOT
+
+$output = migrate_config($input);
+ok(line_exists($output, qr|^floating_Modifier Mod1$|), 'floating_modifier unchanged');
+ok(line_exists($output, qr|^focus_follows_mouse true$|), 'focus_follows_mouse unchanged');
+ok(line_exists($output, qr|^ipc-socket /tmp/i3-ipc.sock$|), 'ipc-socket unchanged');
+ok(line_exists($output, qr|^ipc_socket /tmp/i3-ipc.sock$|), 'ipc_socket unchanged');
+ok(line_exists($output, qr|^exec /usr/bin/i3|), 'exec unchanged');
+ok(line_exists($output, qr|^set stuff Mod1|), 'set unchanged');
+ok(line_exists($output, qr|^assign "XTerm" → 3|), 'assign unchanged');
+ok(line_exists($output, qr|^assign "XTerm" → ~5|), 'assign unchanged');
+ok(line_exists($output, qr|^client\.focused #2F343A #900000 #FFFFFF$|), 'client.focused unchanged');
+ok(line_exists($output, qr|^client\.focused_inactive #FF0000 #FF0000 #FF0000$|), 'client.focused_inactive unchanged');
+ok(line_exists($output, qr|^client\.unfocused #00FF00 #00FF00 #00FF00$|), 'client.unfocused unchanged');
+ok(line_exists($output, qr|^client\.urgent #0000FF #0000FF #0000FF$|), 'client.urgent unchanged');
+ok(line_exists($output, qr|^client\.background #000000$|), 'client.background unchanged');
+
+#####################################################################
+# check whether the bar colors get removed properly
+#####################################################################
+
+$input = <<EOT;
+ bar.focused #FFFF00 #FFFF00 #FFFF00
+ bar.unfocused #FFFF00 #FFFF00 #FFFF00
+ bar.urgent #FFFF00 #FFFF00 #FFFF00
+EOT
+
+$output = migrate_config($input);
+ok(!line_exists($output, qr|^bar\.|), 'no bar. lines');
+ok(line_exists($output, qr|^#.*REMOVED bar|), 'note bar. removed');
+
+
+#####################################################################
+# check whether the other directives get converted correctly
+#####################################################################
+
+$input = <<EOT;
+ new_container stacking
+ workspace_bar no
+ new_window bb
+EOT
+
+$output = migrate_config($input);
+ok(line_exists($output, qr|^workspace_layout stacking$|), 'new_container changed');
+ok(line_exists($output, qr|REMOVED workspace_bar|), 'workspace_bar removed');
+ok(!line_exists($output, qr|^workspace_bar|), 'no workspace_bar in the output');
+ok(line_exists($output, qr|^new_window none$|), 'new_window changed');
+
+#####################################################################
+# check whether new_window's parameters get changed correctly
+#####################################################################
+
+$output = migrate_config('new_window bb');
+ok(line_exists($output, qr|^new_window none$|), 'new_window bb changed');
+
+$output = migrate_config('new_window bn');
+ok(line_exists($output, qr|^new_window normal$|), 'new_window bn changed');
+
+$output = migrate_config('new_window bp');
+ok(line_exists($output, qr|^new_window 1pixel$|), 'new_window bp changed');
+
+#####################################################################
+# check that some commands remain untouched
+#####################################################################
+
+$input = <<EOT;
+ bindsym Mod1+s exec /usr/bin/urxvt
+ bindsym Mod1+s mark foo
+ bindsym Mod1+s restart
+ bindsym Mod1+s reload
+ bindsym Mod1+s exit
+ bindsym Mod1+s stack-limit cols 2
+ bindsym Mod1+s stack-limit rows 3
+ bind Mod1+c exec /usr/bin/urxvt
+ mode "asdf" {
+ bind 36 mode default
+ }
+EOT
+
+$output = migrate_config($input);
+ok(line_exists($output, qr|^bindsym Mod1\+s exec /usr/bin/urxvt$|), 'exec unchanged');
+ok(line_exists($output, qr|^bindsym Mod1\+s mark foo$|), 'mark unchanged');
+ok(line_exists($output, qr|^bindsym Mod1\+s restart$|), 'restart unchanged');
+ok(line_exists($output, qr|^bindsym Mod1\+s reload$|), 'reload unchanged');
+ok(line_exists($output, qr|^bindsym Mod1\+s exit$|), 'exit unchanged');
+ok(line_exists($output, qr|^bindsym Mod1\+s stack-limit cols 2$|), 'stack-limit unchanged');
+ok(line_exists($output, qr|^bindsym Mod1\+s stack-limit rows 3$|), 'stack-limit unchanged');
+ok(line_exists($output, qr|^bindcode Mod1\+c exec /usr/bin/urxvt$|), 'bind changed to bindcode');
+ok(line_exists($output, qr|^mode "asdf" {$|), 'mode asdf unchanged');
+ok(line_exists($output, qr|^bindcode 36 mode \"default\"$|), 'mode default unchanged');
+ok(line_exists($output, qr|^}$|), 'closing mode bracket still there');
+
+#####################################################################
+# check the simple command replacements
+#####################################################################
+
+$input = <<EOT;
+ bindsym Mod1+s s
+ bindsym Mod1+s d
+ bindsym Mod1+s T
+
+ bindsym Mod1+s f
+ bindsym Mod1+s fg
+
+ bindsym Mod1+s t
+
+ bindsym Mod1+s h
+ bindsym Mod1+s j
+ bindsym Mod1+s k
+ bindsym Mod1+s l
+
+ bindsym Mod1+s mh
+ bindsym Mod1+s mj
+ bindsym Mod1+s mk
+ bindsym Mod1+s ml
+
+ bindsym Mod1+s bn
+ bindsym Mod1+s bp
+ bindsym Mod1+s bb
+ bindsym Mod1+s bt
+
+ bindsym Mod1+j wch
+ bindsym Mod1+j wcml
+
+ bindsym Mod1+k kill
+
+ bindsym Mod1+n nw
+ bindsym Mod1+p pw
+EOT
+
+$output = migrate_config($input);
+ok(line_exists($output, qr|^bindsym Mod1\+s layout stacking$|), 's replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s layout default$|), 'd replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s layout tabbed$|), 'T replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s fullscreen$|), 'f replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s fullscreen global$|), 'fg replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s floating toggle$|), 't replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s focus left$|), 'h replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s focus down$|), 'j replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s focus up$|), 'k replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s focus right$|), 'l replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s move left$|), 'mh replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s move down$|), 'mj replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s move up$|), 'mk replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s move right$|), 'ml replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s border normal$|), 'bn replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s border 1pixel$|), 'bp replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s border none$|), 'bb replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s border toggle$|), 'bt replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+j focus parent; focus left$|), 'with container replaced with focus parent; focus left');
+ok(line_exists($output, qr|^bindsym Mod1\+j focus parent; move right$|), 'with container replaced with focus parent; move right');
+ok(line_exists($output, qr|^bindsym Mod1\+k kill$|), 'kill unchanged');
+ok(line_exists($output, qr|^bindsym Mod1\+n workspace next$|), 'nw replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+p workspace prev$|), 'pw replaced');
+
+#####################################################################
+# check more advanced replacements
+#####################################################################
+
+$input = <<EOT;
+ bindsym Mod1+s goto foo
+EOT
+
+$output = migrate_config($input);
+ok(line_exists($output, qr|^bindsym Mod1\+s \[con_mark="foo"\] focus$|), 'goto replaced');
+
+#####################################################################
+# check whether focus's parameters get changed correctly
+#####################################################################
+
+$output = migrate_config('bindsym Mod1+f focus 3');
+ok(line_exists($output, qr|^#.*focus.*obsolete.*focus 3$|), 'focus [number] gone');
+
+$output = migrate_config('bindsym Mod1+f focus floating');
+ok(line_exists($output, qr|^bindsym Mod1\+f focus floating$|), 'focus floating unchanged');
+
+$output = migrate_config('bindsym Mod1+f focus tiling');
+ok(line_exists($output, qr|^bindsym Mod1\+f focus tiling$|), 'focus tiling unchanged');
+
+$output = migrate_config('bindsym Mod1+f focus ft');
+ok(line_exists($output, qr|^bindsym Mod1\+f focus mode_toggle$|), 'focus ft changed');
+
+#####################################################################
+# check whether resize's parameters get changed correctly
+#####################################################################
+
+$output = migrate_config('bindsym Mod1+f resize left +10');
+ok(line_exists($output, qr|^bindsym Mod1\+f resize grow left 10 px$|), 'resize left changed');
+
+$output = migrate_config('bindsym Mod1+f resize top -20');
+ok(line_exists($output, qr|^bindsym Mod1\+f resize shrink up 20 px$|), 'resize top changed');
+
+$output = migrate_config('bindsym Mod1+f resize right -20');
+ok(line_exists($output, qr|^bindsym Mod1\+f resize shrink right 20 px$|), 'resize right changed');
+
+$output = migrate_config('bindsym Mod1+f resize bottom +23');
+ok(line_exists($output, qr|^bindsym Mod1\+f resize grow down 23 px$|), 'resize bottom changed');
+
+#####################################################################
+# also resizing, but with indention this time
+#####################################################################
+
+$output = migrate_config("bindsym Mod1+f resize left \t +10");
+ok(line_exists($output, qr|^bindsym Mod1\+f resize grow left 10 px$|), 'resize left changed');
+
+#####################################################################
+# check whether jump's parameters get changed correctly
+#####################################################################
+
+$output = migrate_config('bindsym Mod1+f jump 3');
+ok(line_exists($output, qr|^#.*obsolete.*jump 3$|), 'jump to workspace removed');
+
+$output = migrate_config('bindsym Mod1+f jump 3 4 5');
+ok(line_exists($output, qr|^#.*obsolete.*jump 3 4 5$|), 'jump to workspace + col/row removed');
+
+$output = migrate_config('bindsym Mod1+f jump "XTerm"');
+ok(line_exists($output, qr|^bindsym Mod1\+f \[class="XTerm"\] focus$|), 'jump changed');
+
+$output = migrate_config('bindsym Mod1+f jump "XTerm/irssi"');
+ok(line_exists($output, qr|^bindsym Mod1\+f \[class="XTerm" title="irssi"\] focus$|), 'jump changed');
+
+#####################################################################
+# check whether workspace commands are handled correctly
+#####################################################################
+
+$output = migrate_config('workspace 3 output VGA-1');
+ok(line_exists($output, qr|^workspace 3 output VGA-1$|), 'workspace assignment unchanged');
+
+$output = migrate_config('workspace 3 work');
+ok(!line_exists($output, qr|^workspace|), 'workspace name not present');
+ok(line_exists($output, qr|#.*workspace name.*bindings|), 'note present');
+
+$input = <<EOT;
+ workspace 3 work
+ bindsym Mod1+3 3
+EOT
+$output = migrate_config($input);
+ok(!line_exists($output, qr|^workspace|), 'workspace name not present');
+ok(line_exists($output, qr|^bindsym Mod1\+3 workspace work|), 'named workspace in bindings');
+
+# The same, but in reverse order
+$input = <<EOT;
+ bindsym Mod1+3 3
+ workspace 3 work
+EOT
+$output = migrate_config($input);
+ok(!line_exists($output, qr|^workspace|), 'workspace name not present');
+ok(line_exists($output, qr|^bindsym Mod1\+3 workspace work|), 'named workspace in bindings');
+
+$output = migrate_config('bindsym Mod1+3 3');
+ok(line_exists($output, qr|^bindsym Mod1\+3 workspace 3|), 'workspace changed');
+
+$output = migrate_config('bindsym Mod1+3 m3');
+ok(line_exists($output, qr|^bindsym Mod1\+3 move workspace 3|), 'move workspace changed');
+
+$input = <<EOT;
+ workspace 3 work
+ bindsym Mod1+3 m3
+EOT
+$output = migrate_config($input);
+ok(!line_exists($output, qr|^workspace|), 'workspace name not present');
+ok(line_exists($output, qr|^bindsym Mod1\+3 move workspace work|), 'move to named workspace in bindings');
+
+#####################################################################
+# check whether an i3bar call is added if the workspace bar bar was enabled
+#####################################################################
+
+$output = migrate_config('');
+ok(line_exists($output, qr|bar {|), 'i3bar added');
+
+$output = migrate_config('workspace_bar enable');
+ok(line_exists($output, qr|bar {|), 'i3bar added');
+
+$output = migrate_config('workspace_bar no');
+ok(!line_exists($output, qr|bar {|), 'no i3bar added');
+
+#####################################################################
+# check whether the mode command gets quotes
+#####################################################################
+
+$output = migrate_config('bindsym Mod1+m mode foobar');
+ok(line_exists($output, qr|^bindsym Mod1\+m mode "foobar"|), 'mode got quotes');
+
+done_testing();
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# checks if i3 starts up on workspace '1' or the first configured named workspace
+#
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+##############################################################
+# 1: i3 should start with workspace '1'
+##############################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $pid = launch_with_config($config);
+
+my @names = @{get_workspace_names()};
+cmp_deeply(\@names, [ '1' ], 'i3 starts on workspace 1 without any configuration');
+
+exit_gracefully($pid);
+
+##############################################################
+# 2: with named workspaces, i3 should start on the first named one
+##############################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+bindsym Mod1+1 workspace foobar
+EOT
+
+$pid = launch_with_config($config);
+
+my @names = @{get_workspace_names()};
+cmp_deeply(\@names, [ 'foobar' ], 'i3 starts on named workspace foobar');
+
+exit_gracefully($pid);
+
+##############################################################
+# 3: the same test as 2, but with a quoted workspace name
+##############################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+bindsym Mod1+1 workspace "foobar"
+EOT
+
+$pid = launch_with_config($config);
+
+my @names = @{get_workspace_names()};
+cmp_deeply(\@names, [ 'foobar' ], 'i3 starts on named workspace foobar');
+
+exit_gracefully($pid);
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# checks if the IPC message type get_marks works correctly
+#
+use i3test;
+
+sub get_marks {
+ return i3(get_socket_path())->get_marks->recv;
+}
+
+##############################################################
+# 1: check that get_marks returns no marks yet
+##############################################################
+
+my $tmp = fresh_workspace;
+
+my $marks = get_marks();
+cmp_deeply($marks, [], 'no marks set so far');
+
+##############################################################
+# 2: check that setting a mark is reflected in the get_marks reply
+##############################################################
+
+cmd 'open';
+cmd 'mark foo';
+
+cmp_deeply(get_marks(), [ 'foo' ], 'mark foo set');
+
+##############################################################
+# 3: check that the mark is gone after killing the container
+##############################################################
+
+cmd 'kill';
+
+cmp_deeply(get_marks(), [ ], 'mark gone');
+
+##############################################################
+# 4: check that duplicate marks are included twice in the get_marks reply
+##############################################################
+
+cmd 'open';
+cmd 'mark bar';
+
+cmd 'open';
+cmd 'mark bar';
+
+cmp_deeply(get_marks(), [ 'bar', 'bar' ], 'duplicate mark found twice');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# Regression: Checks if focus is stolen when a window is managed which is
+# assigned to an invisible workspace
+#
+use i3test;
+use X11::XCB qw(:all);
+use X11::XCB::Connection;
+use v5.10;
+
+my $x = X11::XCB::Connection->new;
+
+# TODO: move to X11::XCB
+sub set_wm_class {
+ my ($id, $class, $instance) = @_;
+
+ # Add a _NET_WM_STRUT_PARTIAL hint
+ my $atomname = $x->atom(name => 'WM_CLASS');
+ my $atomtype = $x->atom(name => 'STRING');
+
+ $x->change_property(
+ PROP_MODE_REPLACE,
+ $id,
+ $atomname->id,
+ $atomtype->id,
+ 8,
+ length($class) + length($instance) + 2,
+ "$instance\x00$class\x00"
+ );
+}
+
+
+#####################################################################
+# start a window and see that it does not get assigned with an empty config
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+assign "special" → targetws
+EOT
+
+my $pid = launch_with_config($config);
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+ok(get_ws($tmp)->{focused}, 'current workspace focused');
+
+my $window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#0000ff',
+);
+
+$window->_create;
+set_wm_class($window->id, 'special', 'special');
+$window->name('special window');
+$window->map;
+sleep 0.25;
+
+
+ok(@{get_ws_content($tmp)} == 0, 'special window not on current workspace');
+ok(@{get_ws_content('targetws')} == 1, 'special window on targetws');
+ok(get_ws($tmp)->{focused}, 'current workspace still focused');
+
+#####################################################################
+# the same test, but with a floating window
+#####################################################################
+
+$window = $x->root->create_child(
+ class => WINDOW_CLASS_INPUT_OUTPUT,
+ rect => [ 0, 0, 30, 30 ],
+ background_color => '#0000ff',
+ window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
+);
+
+$window->_create;
+set_wm_class($window->id, 'special', 'special');
+$window->name('special window');
+$window->map;
+sleep 0.25;
+
+
+ok(@{get_ws_content($tmp)} == 0, 'special window not on current workspace');
+ok(@{get_ws_content('targetws')} == 1, 'special window on targetws');
+ok(get_ws($tmp)->{focused}, 'current workspace still focused');
+
+exit_gracefully($pid);
+
+$window->destroy;
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# Tests the new_window and new_float config option.
+#
+
+use i3test;
+
+my $x = X11::XCB::Connection->new;
+
+#####################################################################
+# 1: check that new windows start with 'normal' border unless configured
+# otherwise
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $pid = launch_with_config($config);
+
+my $tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $first = open_window($x);
+
+my @content = @{get_ws_content($tmp)};
+ok(@content == 1, 'one container opened');
+is($content[0]->{border}, 'normal', 'border normal by default');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 2: check that new tiling windows start with '1pixel' border when
+# configured
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+new_window 1pixel
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_window($x);
+
+@content = @{get_ws_content($tmp)};
+ok(@content == 1, 'one container opened');
+is($content[0]->{border}, '1pixel', 'border normal by default');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 3: check that new floating windows start with 'normal' border unless
+# configured otherwise
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_floating_window($x);
+
+my $wscontent = get_ws($tmp);
+my @floating = @{$wscontent->{floating_nodes}};
+ok(@floating == 1, 'one floating container opened');
+my $floatingcon = $floating[0];
+is($floatingcon->{nodes}->[0]->{border}, 'normal', 'border normal by default');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 4: check that new floating windows start with '1pixel' border when
+# configured
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+new_float 1pixel
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+$first = open_floating_window($x);
+
+$wscontent = get_ws($tmp);
+@floating = @{$wscontent->{floating_nodes}};
+ok(@floating == 1, 'one floating container opened');
+$floatingcon = $floating[0];
+is($floatingcon->{nodes}->[0]->{border}, '1pixel', 'border normal by default');
+
+exit_gracefully($pid);
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression: Checks if i3 still lives after using 'focus mode_toggle' on an
+# empty workspace. This regression was fixed in
+# 0848844f2d41055f6ffc69af1149d7a873460976.
+#
+use i3test;
+use v5.10;
+
+my $tmp = fresh_workspace;
+
+cmd 'focus mode_toggle';
+
+does_i3_live;
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Test for the startup notification protocol.
+#
+
+use i3test;
+use POSIX qw(mkfifo);
+use File::Temp qw(:POSIX);
+
+my $x = X11::XCB::Connection->new;
+use ExtUtils::PkgConfig;
+
+# setup dependency on libstartup-notification using pkg-config
+my %sn_config;
+BEGIN {
+ %sn_config = ExtUtils::PkgConfig->find('libstartup-notification-1.0');
+}
+
+use Inline C => Config => LIBS => $sn_config{libs}, CCFLAGS => $sn_config{cflags};
+use Inline C => <<'END_OF_C_CODE';
+
+#include <xcb/xcb.h>
+
+#define SN_API_NOT_YET_FROZEN 1
+#include <libsn/sn-common.h>
+#include <libsn/sn-launchee.h>
+
+static SnDisplay *sndisplay;
+static SnLauncheeContext *ctx;
+static xcb_connection_t *conn;
+
+// TODO: this should use $x
+void init_ctx() {
+ int screen;
+ if ((conn = xcb_connect(NULL, &screen)) == NULL ||
+ xcb_connection_has_error(conn))
+ errx(1, "x11 conn failed");
+
+ printf("screen = %d\n", screen);
+ sndisplay = sn_xcb_display_new(conn, NULL, NULL);
+ ctx = sn_launchee_context_new_from_environment(sndisplay, screen);
+}
+
+const char *get_startup_id() {
+ return sn_launchee_context_get_startup_id(ctx);
+}
+
+void mark_window(int window) {
+ sn_launchee_context_setup_window(ctx, (Window)window);
+ xcb_flush(conn);
+}
+
+void complete_startup() {
+ /* mark the startup process complete */
+ sn_launchee_context_complete(ctx);
+}
+END_OF_C_CODE
+
+my $first_ws = fresh_workspace;
+
+is(@{get_ws_content($first_ws)}, 0, 'no containers on this workspace yet');
+
+######################################################################
+# 1) initiate startup, switch workspace, create window
+# (should be placed on the original workspace)
+######################################################################
+
+# Start a new process via i3 (to initialize a new startup notification
+# context), then steal its DESKTOP_STARTUP_ID variable. We handle the startup
+# notification in the testcase from there on.
+#
+# This works by setting up a FIFO in which the process (started by i3) will
+# echo its $DESKTOP_STARTUP_ID. We (blockingly) read the variable into
+# $startup_id in the testcase.
+my $tmp = tmpnam();
+mkfifo($tmp, 0600) or die "Could not create FIFO in $tmp";
+
+cmd qq|exec echo \$DESKTOP_STARTUP_ID >$tmp|;
+
+open(my $fh, '<', $tmp);
+chomp(my $startup_id = <$fh>);
+close($fh);
+
+unlink($tmp);
+
+isnt($startup_id, '', 'startup_id not empty');
+
+$ENV{DESKTOP_STARTUP_ID} = $startup_id;
+
+# Create a new libstartup-notification launchee context
+init_ctx();
+
+# Make sure the context was set up successfully
+is(get_startup_id(), $startup_id, 'libstartup-notification returns the same id');
+
+my $second_ws = fresh_workspace;
+
+is(@{get_ws_content($second_ws)}, 0, 'no containers on the second workspace yet');
+
+my $win = open_window($x, { dont_map => 1 });
+mark_window($win->id);
+$win->map;
+# We don’t use wait_for_map because the window will not get mapped -- it is on
+# a different workspace.
+# We sync with i3 here to make sure $x->input_focus is updated.
+sync_with_i3($x);
+
+is(@{get_ws_content($second_ws)}, 0, 'still no containers on the second workspace');
+is(@{get_ws_content($first_ws)}, 1, 'one container on the first workspace');
+
+######################################################################
+# same thing, but with _NET_STARTUP_ID set on the leader
+######################################################################
+
+my $leader = open_window($x, { dont_map => 1 });
+mark_window($leader->id);
+
+$win = open_window($x, { dont_map => 1, client_leader => $leader });
+$win->map;
+sync_with_i3($x);
+
+is(@{get_ws_content($second_ws)}, 0, 'still no containers on the second workspace');
+is(@{get_ws_content($first_ws)}, 2, 'two containers on the first workspace');
+
+######################################################################
+# 2) open another window after the startup process is completed
+# (should be placed on the current workspace)
+######################################################################
+
+complete_startup();
+sync_with_i3($x);
+
+my $otherwin = open_window($x);
+is(@{get_ws_content($second_ws)}, 1, 'one container on the second workspace');
+
+######################################################################
+# 3) test that the --no-startup-id flag for exec leads to no DESKTOP_STARTUP_ID
+# environment variable.
+######################################################################
+
+mkfifo($tmp, 0600) or die "Could not create FIFO in $tmp";
+
+cmd qq|exec --no-startup-id echo \$DESKTOP_STARTUP_ID >$tmp|;
+
+open($fh, '<', $tmp);
+chomp($startup_id = <$fh>);
+close($fh);
+
+unlink($tmp);
+
+is($startup_id, '', 'startup_id empty');
+
+######################################################################
+# 4) same thing, but with double quotes in exec
+######################################################################
+
+mkfifo($tmp, 0600) or die "Could not create FIFO in $tmp";
+
+cmd qq|exec --no-startup-id "echo \$DESKTOP_STARTUP_ID >$tmp"|;
+
+open($fh, '<', $tmp);
+chomp($startup_id = <$fh>);
+close($fh);
+
+unlink($tmp);
+
+is($startup_id, '', 'startup_id empty');
+
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# Checks if the 'workspace back_and_forth' command and the
+# 'workspace_auto_back_and_forth' config directive work correctly.
+#
+
+use i3test;
+use X11::XCB::Connection;
+
+my $x = X11::XCB::Connection->new;
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $pid = launch_with_config($config);
+
+my $first_ws = fresh_workspace;
+ok(get_ws($first_ws)->{focused}, 'first workspace focused');
+
+my $second_ws = fresh_workspace;
+ok(get_ws($second_ws)->{focused}, 'second workspace focused');
+
+my $third_ws = fresh_workspace;
+ok(get_ws($third_ws)->{focused}, 'third workspace focused');
+
+cmd 'workspace back_and_forth';
+ok(get_ws($second_ws)->{focused}, 'second workspace focused');
+
+#####################################################################
+# test that without workspace_auto_back_and_forth switching to the same
+# workspace that is currently focused is a no-op
+#####################################################################
+
+cmd qq|workspace "$second_ws"|;
+ok(get_ws($second_ws)->{focused}, 'second workspace still focused');
+
+exit_gracefully($pid);
+
+#####################################################################
+# the same test, but with workspace_auto_back_and_forth
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+workspace_auto_back_and_forth yes
+EOT
+
+$pid = launch_with_config($config);
+
+$first_ws = fresh_workspace;
+ok(get_ws($first_ws)->{focused}, 'first workspace focused');
+
+$second_ws = fresh_workspace;
+ok(get_ws($second_ws)->{focused}, 'second workspace focused');
+
+$third_ws = fresh_workspace;
+ok(get_ws($third_ws)->{focused}, 'third workspace focused');
+
+cmd qq|workspace "$third_ws"|;
+ok(get_ws($second_ws)->{focused}, 'second workspace focused');
+
+exit_gracefully($pid);
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
+#
+# Checks that the bar config is parsed correctly.
+#
+
+use i3test;
+
+#####################################################################
+# test a config without any bars
+#####################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+my $pid = launch_with_config($config);
+
+my $i3 = i3(get_socket_path(0));
+my $bars = $i3->get_bar_config()->recv;
+is(@$bars, 0, 'no bars configured');
+
+exit_gracefully($pid);
+
+#####################################################################
+# now provide a simple bar configuration
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+bar {
+ # Start a default instance of i3bar which provides workspace buttons.
+ # Additionally, i3status will provide a statusline.
+ status_command i3status --foo
+}
+EOT
+
+$pid = launch_with_config($config);
+
+$i3 = i3(get_socket_path(0));
+$bars = $i3->get_bar_config()->recv;
+is(@$bars, 1, 'one bar configured');
+
+my $bar_id = shift @$bars;
+
+my $bar_config = $i3->get_bar_config($bar_id)->recv;
+is($bar_config->{status_command}, 'i3status --foo', 'status_command correct');
+ok(!$bar_config->{verbose}, 'verbose off by default');
+ok($bar_config->{workspace_buttons}, 'workspace buttons enabled per default');
+is($bar_config->{mode}, 'dock', 'dock mode by default');
+is($bar_config->{position}, 'bottom', 'position bottom by default');
+
+#####################################################################
+# ensure that reloading cleans up the old bar configs
+#####################################################################
+
+cmd 'reload';
+$bars = $i3->get_bar_config()->recv;
+is(@$bars, 1, 'still one bar configured');
+
+exit_gracefully($pid);
+
+#####################################################################
+# validate a more complex configuration
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+bar {
+ # Start a default instance of i3bar which provides workspace buttons.
+ # Additionally, i3status will provide a statusline.
+ status_command i3status --bar
+
+ output HDMI1
+ output HDMI2
+
+ tray_output LVDS1
+ tray_output HDMI2
+ position top
+ mode dock
+ font Terminus
+ workspace_buttons no
+ verbose yes
+ socket_path /tmp/foobar
+
+ colors {
+ background #ff0000
+ statusline #00ff00
+
+ focused_workspace #ffffff #285577
+ active_workspace #888888 #222222
+ inactive_workspace #888888 #222222
+ urgent_workspace #ffffff #900000
+ }
+}
+EOT
+
+$pid = launch_with_config($config);
+
+$i3 = i3(get_socket_path(0));
+$bars = $i3->get_bar_config()->recv;
+is(@$bars, 1, 'one bar configured');
+
+$bar_id = shift @$bars;
+
+$bar_config = $i3->get_bar_config($bar_id)->recv;
+is($bar_config->{status_command}, 'i3status --bar', 'status_command correct');
+ok($bar_config->{verbose}, 'verbose on');
+ok(!$bar_config->{workspace_buttons}, 'workspace buttons disabled');
+is($bar_config->{mode}, 'dock', 'dock mode');
+is($bar_config->{position}, 'top', 'position top');
+is_deeply($bar_config->{outputs}, [ 'HDMI1', 'HDMI2' ], 'outputs ok');
+is($bar_config->{tray_output}, 'HDMI2', 'tray_output ok');
+is($bar_config->{font}, 'Terminus', 'font ok');
+is($bar_config->{socket_path}, '/tmp/foobar', 'socket_path ok');
+is_deeply($bar_config->{colors},
+ {
+ background => '#ff0000',
+ statusline => '#00ff00',
+ focused_workspace_text => '#ffffff',
+ focused_workspace_bg => '#285577',
+ active_workspace_text => '#888888',
+ active_workspace_bg => '#222222',
+ inactive_workspace_text => '#888888',
+ inactive_workspace_bg => '#222222',
+ urgent_workspace_text => '#ffffff',
+ urgent_workspace_bg => '#900000',
+ }, 'colors ok');
+
+exit_gracefully($pid);
+
+#####################################################################
+# ensure that multiple bars get different IDs
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+bar {
+ # Start a default instance of i3bar which provides workspace buttons.
+ # Additionally, i3status will provide a statusline.
+ status_command i3status --bar
+
+ output HDMI1
+}
+
+bar {
+ output VGA1
+}
+EOT
+
+$pid = launch_with_config($config);
+
+$i3 = i3(get_socket_path(0));
+$bars = $i3->get_bar_config()->recv;
+is(@$bars, 2, 'two bars configured');
+isnt($bars->[0], $bars->[1], 'bar IDs are different');
+
+my $bar1_config = $i3->get_bar_config($bars->[0])->recv;
+my $bar2_config = $i3->get_bar_config($bars->[1])->recv;
+
+isnt($bar1_config->{outputs}, $bar2_config->{outputs}, 'outputs different');
+
+exit_gracefully($pid);
+
+#####################################################################
+# make sure comments work properly
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+bar {
+ # Start a default instance of i3bar which provides workspace buttons.
+ # Additionally, i3status will provide a statusline.
+ status_command i3status --bar
+ #status_command i3status --qux
+#status_command i3status --qux
+
+ output HDMI1
+ colors {
+ background #000000
+ #background #ffffff
+ }
+}
+EOT
+
+$pid = launch_with_config($config);
+
+$i3 = i3(get_socket_path(0));
+$bars = $i3->get_bar_config()->recv;
+$bar_id = shift @$bars;
+
+$bar_config = $i3->get_bar_config($bar_id)->recv;
+is($bar_config->{status_command}, 'i3status --bar', 'status_command correct');
+is($bar_config->{colors}->{background}, '#000000', 'background color ok');
+
+exit_gracefully($pid);
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests if empty workspaces are closed when the last child
+# exits, as long as they're not empty.
+#
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+# Get a workspace and open a container
+my $ws = fresh_workspace;
+my $con = open_empty_con($i3);
+
+# Go to a second workspace, kill the container
+fresh_workspace;
+cmd "[con_id=\"$con\"] kill";
+
+# The first workspace should have been closed
+ok(!workspace_exists($ws), 'workspace closed');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# The command "move workspace prev; workspace prev" will lead to an error.
+# This regression is present in 7f9b65f6a752e454c492447be4e21e2ee8faf8fd
+use i3test;
+
+my $i3 = i3(get_socket_path());
+
+# Open one workspace to move the con to
+my $old = fresh_workspace;
+my $keep_open_con = open_empty_con($i3);
+
+# Get a workspace and open a container
+my $tmp = fresh_workspace;
+my $con = open_empty_con($i3);
+
+is(@{get_ws_content($tmp)}, 1, 'one container');
+is(@{get_ws_content($old)}, 1, 'one container on old ws');
+
+cmd 'move workspace prev; workspace prev';
+
+is(@{get_ws_content($old)}, 2, 'container moved away');
+
+done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests whether opening an empty container and killing it again works
-#
-use List::Util qw(first);
-use i3test;
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-# Open a new container
-cmd 'open';
-
-ok(@{get_ws_content($tmp)} == 1, 'container opened');
-
-cmd 'kill';
-ok(@{get_ws_content($tmp)} == 0, 'container killed');
-
-##############################################################
-# open two containers and kill the one which is not focused
-# by its ID to test if the parser correctly matches the window
-##############################################################
-
-cmd 'open';
-cmd 'open';
-ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
-
-my $content = get_ws_content($tmp);
-my $not_focused = first { !$_->{focused} } @{$content};
-my $id = $not_focused->{id};
-
-cmd "[con_id=\"$id\"] kill";
-
-$content = get_ws_content($tmp);
-ok(@{$content} == 1, 'one container killed');
-ok($content->[0]->{id} != $id, 'correct window killed');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests all kinds of matching methods
-#
-use i3test;
-use X11::XCB qw(:all);
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-# Open a new window
-my $x = X11::XCB::Connection->new;
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#C0C0C0',
-);
-
-$window->map;
-# give it some time to be picked up by the window manager
-# TODO: better check for $window->mapped or something like that?
-# maybe we can even wait for getting mapped?
-my $c = 0;
-while (@{get_ws_content($tmp)} == 0 and $c++ < 5) {
- sleep 0.25;
-}
-my $content = get_ws_content($tmp);
-ok(@{$content} == 1, 'window mapped');
-my $win = $content->[0];
-
-######################################################################
-# first test that matches which should not match this window really do
-# not match it
-######################################################################
-# TODO: use PCRE expressions
-# TODO: specify more match types
-cmd q|[class="*"] kill|;
-cmd q|[con_id="99999"] kill|;
-
-$content = get_ws_content($tmp);
-ok(@{$content} == 1, 'window still there');
-
-# now kill the window
-cmd 'nop now killing the window';
-my $id = $win->{id};
-cmd qq|[con_id="$id"] kill|;
-
-# give i3 some time to pick up the UnmapNotify event
-sleep 0.25;
-
-cmd 'nop checking if its gone';
-$content = get_ws_content($tmp);
-ok(@{$content} == 0, 'window killed');
-
-# TODO: same test, but with pcre expressions
-
-######################################################################
-# check that multiple criteria work are checked with a logical AND,
-# not a logical OR (that is, matching is not cancelled after the first
-# criterion matches).
-######################################################################
-
-$tmp = fresh_workspace;
-
-# TODO: move to X11::XCB
-sub set_wm_class {
- my ($id, $class, $instance) = @_;
-
- # Add a _NET_WM_STRUT_PARTIAL hint
- my $atomname = $x->atom(name => 'WM_CLASS');
- my $atomtype = $x->atom(name => 'STRING');
-
- $x->change_property(
- PROP_MODE_REPLACE,
- $id,
- $atomname->id,
- $atomtype->id,
- 8,
- length($class) + length($instance) + 2,
- "$instance\x00$class\x00"
- );
-}
-
-my $left = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#0000ff',
-);
-
-$left->_create;
-set_wm_class($left->id, 'special', 'special');
-$left->name('left');
-$left->map;
-sleep 0.25;
-
-my $right = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#0000ff',
-);
-
-$right->_create;
-set_wm_class($right->id, 'special', 'special');
-$right->name('right');
-$right->map;
-sleep 0.25;
-
-# two windows should be here
-$content = get_ws_content($tmp);
-ok(@{$content} == 2, 'two windows opened');
-
-cmd '[class="special" title="left"] kill';
-
-sleep 0.25;
-
-$content = get_ws_content($tmp);
-is(@{$content}, 1, 'one window still there');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests multiple commands (using ';') and multiple operations (using ',')
-#
-use i3test;
-
-my $tmp = fresh_workspace;
-
-sub multiple_cmds {
- my ($cmd) = @_;
-
- cmd 'open';
- cmd 'open';
- ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
-
- cmd $cmd;
- ok(@{get_ws_content($tmp)} == 0, "both containers killed (cmd = $cmd)");
-}
-multiple_cmds('kill;kill');
-multiple_cmds('kill; kill');
-multiple_cmds('kill ; kill');
-multiple_cmds('kill ;kill');
-multiple_cmds('kill ;kill');
-multiple_cmds('kill ; kill');
-multiple_cmds("kill;\tkill");
-multiple_cmds("kill\t;kill");
-multiple_cmds("kill\t;\tkill");
-multiple_cmds("kill\t ;\tkill");
-multiple_cmds("kill\t ;\t kill");
-multiple_cmds("kill \t ; \t kill");
-
-#####################################################################
-# test if un-quoted strings are handled correctly
-#####################################################################
-
-$tmp = fresh_workspace;
-cmd 'open';
-my $unused = get_unused_workspace;
-ok(!($unused ~~ @{get_workspace_names()}), 'workspace does not exist yet');
-cmd "move workspace $unused; nop parser test";
-ok(($unused ~~ @{get_workspace_names()}), 'workspace exists after moving');
-
-#####################################################################
-# quote the workspace name and use a ; (command separator) in its name
-#####################################################################
-
-$unused = get_unused_workspace;
-$unused .= ';a';
-ok(!($unused ~~ @{get_workspace_names()}), 'workspace does not exist yet');
-cmd qq|move workspace "$unused"; nop parser test|;
-ok(($unused ~~ @{get_workspace_names()}), 'workspace exists after moving');
-
-# TODO: need a non-invasive command before implementing a test which uses ','
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests focus switching (next/prev)
-#
-use i3test;
-
-my $tmp = fresh_workspace;
-
-######################################################################
-# Open one container, verify that 'focus down' and 'focus right' do nothing
-######################################################################
-cmd 'open';
-
-my ($nodes, $focus) = get_ws_content($tmp);
-my $old_focused = $focus->[0];
-
-cmd 'focus down';
-($nodes, $focus) = get_ws_content($tmp);
-is($focus->[0], $old_focused, 'focus did not change with only one con');
-
-cmd 'focus right';
-($nodes, $focus) = get_ws_content($tmp);
-is($focus->[0], $old_focused, 'focus did not change with only one con');
-
-######################################################################
-# Open another container, verify that 'focus right' switches
-######################################################################
-my $left = $old_focused;
-
-cmd 'open';
-($nodes, $focus) = get_ws_content($tmp);
-isnt($old_focused, $focus->[0], 'new container is focused');
-my $mid = $focus->[0];
-
-cmd 'open';
-($nodes, $focus) = get_ws_content($tmp);
-isnt($old_focused, $focus->[0], 'new container is focused');
-my $right = $focus->[0];
-
-cmd 'focus right';
-($nodes, $focus) = get_ws_content($tmp);
-isnt($focus->[0], $right, 'focus did change');
-is($focus->[0], $left, 'left container focused (wrapping)');
-
-cmd 'focus right';
-($nodes, $focus) = get_ws_content($tmp);
-is($focus->[0], $mid, 'middle container focused');
-
-cmd 'focus right';
-($nodes, $focus) = get_ws_content($tmp);
-is($focus->[0], $right, 'right container focused');
-
-cmd 'focus left';
-($nodes, $focus) = get_ws_content($tmp);
-is($focus->[0], $mid, 'middle container focused');
-
-cmd 'focus left';
-($nodes, $focus) = get_ws_content($tmp);
-is($focus->[0], $left, 'left container focused');
-
-cmd 'focus left';
-($nodes, $focus) = get_ws_content($tmp);
-is($focus->[0], $right, 'right container focused');
-
-
-######################################################################
-# Test focus command
-######################################################################
-
-cmd qq|[con_id="$mid"] focus|;
-($nodes, $focus) = get_ws_content($tmp);
-is($focus->[0], $mid, 'middle container focused');
-
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests splitting
-#
-use i3test;
-use X11::XCB qw(:all);
-
-my $tmp = fresh_workspace;
-
-my $ws = get_ws($tmp);
-is($ws->{orientation}, 'horizontal', 'orientation horizontal by default');
-cmd 'split v';
-$ws = get_ws($tmp);
-is($ws->{orientation}, 'vertical', 'split v changes workspace orientation');
-
-######################################################################
-# Open two containers, split, open another container. Then verify
-# the layout is like we expect it to be
-######################################################################
-cmd 'open';
-cmd 'open';
-my $content = get_ws_content($tmp);
-
-is(@{$content}, 2, 'two containers on workspace level');
-my $first = $content->[0];
-my $second = $content->[1];
-
-is(@{$first->{nodes}}, 0, 'first container has no children');
-is(@{$second->{nodes}}, 0, 'second container has no children (yet)');
-my $old_name = $second->{name};
-
-
-cmd 'split h';
-cmd 'open';
-
-$content = get_ws_content($tmp);
-
-is(@{$content}, 2, 'two containers on workspace level');
-$first = $content->[0];
-$second = $content->[1];
-
-is(@{$first->{nodes}}, 0, 'first container has no children');
-isnt($second->{name}, $old_name, 'second container was replaced');
-is($second->{orientation}, 'horizontal', 'orientation is horizontal');
-is(@{$second->{nodes}}, 2, 'second container has 2 children');
-is($second->{nodes}->[0]->{name}, $old_name, 'found old second container');
-
-# TODO: extend this test-case (test next/prev)
-# - wrapping (no horizontal switch possible, goes level-up)
-# - going level-up "manually"
-
-######################################################################
-# Test splitting multiple times without actually creating windows
-######################################################################
-
-$tmp = fresh_workspace;
-
-$ws = get_ws($tmp);
-is($ws->{orientation}, 'horizontal', 'orientation horizontal by default');
-cmd 'split v';
-$ws = get_ws($tmp);
-is($ws->{orientation}, 'vertical', 'split v changes workspace orientation');
-
-cmd 'open';
-my @content = @{get_ws_content($tmp)};
-
-# recursively sums up all nodes and their children
-sub sum_nodes {
- my ($nodes) = @_;
-
- return 0 if !@{$nodes};
-
- my @children = (map { @{$_->{nodes}} } @{$nodes},
- map { @{$_->{'floating_nodes'}} } @{$nodes});
-
- return @{$nodes} + sum_nodes(\@children);
-}
-
-my $old_count = sum_nodes(\@content);
-cmd 'split v';
-
-@content = @{get_ws_content($tmp)};
-$old_count = sum_nodes(\@content);
-
-cmd 'split v';
-
-@content = @{get_ws_content($tmp)};
-my $count = sum_nodes(\@content);
-is($count, $old_count, 'not more windows after splitting again');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests moving. Basically, there are four different code-paths:
-# 1) move a container which cannot be moved (single container on a workspace)
-# 2) move a container before another single container
-# 3) move a container inside another container
-# 4) move a container in a different direction so that we need to go up in tree
-#
-use i3test;
-
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-######################################################################
-# 1) move a container which cannot be moved
-######################################################################
-
-cmd 'open';
-
-my $old_content = get_ws_content($tmp);
-is(@{$old_content}, 1, 'one container on this workspace');
-
-my $first = $old_content->[0]->{id};
-
-#cmd 'move before h';
-#cmd 'move before v';
-#cmd 'move after v';
-#cmd 'move after h';
-
-my $content = get_ws_content($tmp);
-#is_deeply($old_content, $content, 'workspace unmodified after useless moves');
-
-######################################################################
-# 2) move a container before another single container
-######################################################################
-
-cmd 'open';
-$content = get_ws_content($tmp);
-is(@{$content}, 2, 'two containers on this workspace');
-my $second = $content->[1]->{id};
-
-is($content->[0]->{id}, $first, 'first container unmodified');
-
-# Move the second container before the first one (→ swap them)
-cmd 'move left';
-$content = get_ws_content($tmp);
-is($content->[0]->{id}, $second, 'first container modified');
-
-# We should not be able to move any further
-cmd 'move left';
-$content = get_ws_content($tmp);
-is($content->[0]->{id}, $second, 'first container unmodified');
-
-# Now move in the other direction
-cmd 'move right';
-$content = get_ws_content($tmp);
-is($content->[0]->{id}, $first, 'first container modified');
-
-# We should not be able to move any further
-cmd 'move right';
-$content = get_ws_content($tmp);
-is($content->[0]->{id}, $first, 'first container unmodified');
-
-######################################################################
-# 3) move a container inside another container
-######################################################################
-
-# Split the current (second) container and create a new container on workspace
-# level. Our layout looks like this now:
-# --------------------------
-# | | second | |
-# | first | ------ | third |
-# | | | |
-# --------------------------
-cmd 'split v';
-cmd 'focus parent';
-cmd 'open';
-
-$content = get_ws_content($tmp);
-is(@{$content}, 3, 'three containers on this workspace');
-my $third = $content->[2]->{id};
-
-cmd 'move left';
-$content = get_ws_content($tmp);
-is(@{$content}, 2, 'only two containers on this workspace');
-my $nodes = $content->[1]->{nodes};
-is($nodes->[0]->{id}, $second, 'second container on top');
-is($nodes->[1]->{id}, $third, 'third container on bottom');
-
-######################################################################
-# move it inside the split container
-######################################################################
-
-cmd 'move up';
-$nodes = get_ws_content($tmp)->[1]->{nodes};
-is($nodes->[0]->{id}, $third, 'third container on top');
-is($nodes->[1]->{id}, $second, 'second container on bottom');
-
-# move it outside again
-cmd 'move left';
-$content = get_ws_content($tmp);
-is(@{$content}, 3, 'three nodes on this workspace');
-
-# due to automatic flattening/cleanup, the remaining split container
-# will be replaced by the con itself, so we will still have 3 nodes
-cmd 'move right';
-$content = get_ws_content($tmp);
-is(@{$content}, 2, 'two nodes on this workspace');
-
-######################################################################
-# 4) We create two v-split containers on the workspace, then we move
-# all Cons from the left v-split to the right one. The old vsplit
-# container needs to be closed. Verify that it will be closed.
-######################################################################
-
-my $otmp = fresh_workspace;
-
-cmd "open";
-cmd "open";
-cmd "split v";
-cmd "open";
-cmd 'focus left';
-cmd "split v";
-cmd "open";
-cmd "move right";
-cmd 'focus left';
-cmd "move right";
-
-$content = get_ws_content($otmp);
-is(@{$content}, 1, 'only one nodes on this workspace');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression: closing of floating clients did crash i3 when closing the
-# container which contained this client.
-#
-use i3test;
-
-fresh_workspace;
-
-cmd 'open';
-cmd 'mode toggle';
-cmd 'kill';
-cmd 'kill';
-
-does_i3_live;
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression: make a container floating, kill its parent, make it tiling again
-#
-use i3test;
-
-my $tmp = fresh_workspace;
-
-cmd 'open';
-my $left = get_focused($tmp);
-cmd 'open';
-my $old = get_focused($tmp);
-cmd 'split v';
-cmd 'open';
-my $floating = get_focused($tmp);
-diag("focused floating: " . get_focused($tmp));
-cmd 'mode toggle';
-# TODO: eliminate this race conditition
-sleep 1;
-
-# kill old container
-cmd qq|[con_id="$old"] focus|;
-is(get_focused($tmp), $old, 'old container focused');
-cmd 'kill';
-
-# kill left container
-cmd qq|[con_id="$left"] focus|;
-is(get_focused($tmp), $left, 'old container focused');
-cmd 'kill';
-
-# focus floating window, make it tiling again
-cmd qq|[con_id="$floating"] focus|;
-is(get_focused($tmp), $floating, 'floating window focused');
-
-sleep 1;
-cmd 'mode toggle';
-
-does_i3_live;
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Check if new containers are opened after the currently focused one instead
-# of always at the end
-use List::Util qw(first);
-use i3test;
-
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-# Open two new container
-my $first = open_empty_con($i3);
-
-ok(@{get_ws_content($tmp)} == 1, 'containers opened');
-
-my $second = open_empty_con($i3);
-
-isnt($first, $second, 'different container focused');
-
-##############################################################
-# see if new containers open after the currently focused
-##############################################################
-
-cmd qq|[con_id="$first"] focus|;
-cmd 'open';
-$content = get_ws_content($tmp);
-ok(@{$content} == 3, 'three containers opened');
-
-is($content->[0]->{id}, $first, 'first container unmodified');
-isnt($content->[1]->{id}, $second, 'second container replaced');
-is($content->[2]->{id}, $second, 'third container unmodified');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Check if the focus is correctly restored after closing windows.
-#
-use i3test;
-use X11::XCB qw(:all);
-use List::Util qw(first);
-
-my $x = X11::XCB::Connection->new;
-
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-my $first = open_empty_con($i3);
-my $second = open_empty_con($i3);
-
-cmd 'split v';
-
-my ($nodes, $focus) = get_ws_content($tmp);
-
-ok(!$nodes->[1]->{focused}, 'split container not focused');
-cmd 'focus parent';
-($nodes, $focus) = get_ws_content($tmp);
-ok($nodes->[1]->{focused}, 'split container focused after focus parent');
-
-my $third = open_empty_con($i3);
-
-isnt(get_focused($tmp), $second, 'different container focused');
-
-# We have the following layout now (con is focused):
-# .----------------.
-# | split | |
-# | .----. | con |
-# | | cn | | |
-# | `----' | |
-# `----------------'
-
-##############################################################
-# see if the focus goes down to $first (not to its split parent)
-# when closing $second
-##############################################################
-
-cmd 'kill';
-# TODO: this testcase sometimes has different outcomes when the
-# sleep is missing. why?
-sleep 0.25;
-($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[1]->{nodes}->[0]->{id}, $second, 'second container found');
-ok($nodes->[1]->{nodes}->[0]->{focused}, 'second container focused');
-
-##############################################################
-# another case, using a slightly different layout (regression)
-##############################################################
-
-$tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-cmd 'split v';
-$first = open_empty_con($i3);
-my $bottom = open_empty_con($i3);
-
-cmd 'focus up';
-cmd 'split h';
-my $middle = open_empty_con($i3);
-my $right = open_empty_con($i3);
-cmd 'focus down';
-
-# We have the following layout now (second is focused):
-# .----------------------------.
-# | .------------------------. |
-# | | first | middle | right | |
-# | `------------------------' |
-# |----------------------------|
-# | |
-# | second |
-# | |
-# `----------------------------'
-
-# Check if the focus is restored to $right when we close $second
-cmd 'kill';
-
-is(get_focused($tmp), $right, 'top right container focused (in focus stack)');
-
-($nodes, $focus) = get_ws_content($tmp);
-my $tr = first { $_->{id} eq $right } @{$nodes->[0]->{nodes}};
-is($tr->{focused}, 1, 'top right container really has focus');
-
-##############################################################
-# check if focus is correct after closing an unfocused window
-##############################################################
-
-$tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-$first = open_empty_con($i3);
-$middle = open_empty_con($i3);
-# XXX: the $right empty con will be filled with the x11 window we are creating afterwards
-$right = open_empty_con($i3);
-my $win = open_standard_window($x, '#00ff00');
-
-cmd qq|[con_id="$middle"] focus|;
-$win->destroy;
-
-sleep 0.25;
-
-is(get_focused($tmp), $middle, 'middle container focused');
-
-##############################################################
-# and now for something completely different:
-# check if the pointer position is relevant when restoring focus
-# (it should not be relevant, of course)
-##############################################################
-
-# TODO: add test code as soon as I can reproduce it
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Check if empty split containers are automatically closed.
-#
-use i3test;
-
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-my $first = open_empty_con($i3);
-my $second = open_empty_con($i3);
-cmd qq|[con_id="$first"] focus|;
-
-cmd 'split v';
-
-($nodes, $focus) = get_ws_content($tmp);
-
-is($nodes->[0]->{focused}, 0, 'split container not focused');
-
-# focus the split container
-cmd 'level up';
-($nodes, $focus) = get_ws_content($tmp);
-my $split = $focus->[0];
-cmd 'level down';
-
-my $second = open_empty_con($i3);
-
-isnt($first, $second, 'different container focused');
-
-##############################################################
-# close both windows and see if the split container still exists
-##############################################################
-
-cmd 'kill';
-cmd 'kill';
-($nodes, $focus) = get_ws_content($tmp);
-isnt($nodes->[0]->{id}, $split, 'split container closed');
-
-##############################################################
-# same thing but this time we are moving the cons away instead
-# of killing them
-##############################################################
-
-$tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-$first = open_empty_con($i3);
-$second = open_empty_con($i3);
-cmd qq|[con_id="$first"] focus|;
-
-cmd 'split v';
-
-($nodes, $focus) = get_ws_content($tmp);
-
-is($nodes->[0]->{focused}, 0, 'split container not focused');
-
-# focus the split container
-cmd 'level up';
-($nodes, $focus) = get_ws_content($tmp);
-my $split = $focus->[0];
-cmd 'level down';
-
-my $second = open_empty_con($i3);
-
-isnt($first, $second, 'different container focused');
-
-##############################################################
-# close both windows and see if the split container still exists
-##############################################################
-
-my $otmp = get_unused_workspace();
-cmd "move workspace $otmp";
-cmd "move workspace $otmp";
-($nodes, $focus) = get_ws_content($tmp);
-isnt($nodes->[0]->{id}, $split, 'split container closed');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Check if stacking containers can be used independantly of
-# the split mode (horizontal/vertical) of the underlying
-# container.
-#
-use i3test;
-
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-# Enforce vertical split mode
-cmd 'split v';
-
-my $first = open_empty_con($i3);
-my $second = open_empty_con($i3);
-
-isnt($first, $second, 'two different containers opened');
-
-##############################################################
-# change mode to stacking and cycle through the containers
-##############################################################
-
-cmd 'layout stacking';
-is(get_focused($tmp), $second, 'second container still focused');
-
-cmd 'focus down';
-is(get_focused($tmp), $first, 'first container focused');
-
-cmd 'focus up';
-is(get_focused($tmp), $second, 'second container focused again');
-
-##############################################################
-# now change the orientation to horizontal and cycle
-##############################################################
-
-cmd 'focus parent';
-cmd 'split h';
-cmd 'focus child';
-
-cmd 'focus down';
-is(get_focused($tmp), $first, 'first container focused');
-
-cmd 'focus up';
-is(get_focused($tmp), $second, 'second container focused again');
-
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Checks if the 'move workspace' command works correctly
-#
-use i3test;
-
-my $i3 = i3(get_socket_path());
-
-# We move the pointer out of our way to avoid a bug where the focus will
-# be set to the window under the cursor
-my $x = X11::XCB::Connection->new;
-$x->root->warp_pointer(0, 0);
-
-my $tmp = get_unused_workspace();
-my $tmp2 = get_unused_workspace();
-cmd "workspace $tmp";
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-my $first = open_empty_con($i3);
-my $second = open_empty_con($i3);
-ok(@{get_ws_content($tmp)} == 2, 'two containers on first ws');
-
-cmd "workspace $tmp2";
-ok(@{get_ws_content($tmp2)} == 0, 'no containers on second ws yet');
-
-cmd "workspace $tmp";
-
-cmd "move workspace $tmp2";
-ok(@{get_ws_content($tmp)} == 1, 'one container on first ws anymore');
-ok(@{get_ws_content($tmp2)} == 1, 'one container on second ws');
-my ($nodes, $focus) = get_ws_content($tmp2);
-
-is($focus->[0], $second, 'same container on different ws');
-
-($nodes, $focus) = get_ws_content($tmp);
-ok($nodes->[0]->{focused}, 'first container focused on first ws');
-
-###################################################################
-# check if floating cons are moved to new workspaces properly
-# (that is, if they are floating on the target ws, too)
-###################################################################
-
-$tmp = get_unused_workspace();
-$tmp2 = get_unused_workspace();
-cmd "workspace $tmp";
-
-cmd "open";
-cmd "floating toggle";
-
-my $ws = get_ws($tmp);
-is(@{$ws->{nodes}}, 0, 'no nodes on workspace');
-is(@{$ws->{floating_nodes}}, 1, 'one floating node on workspace');
-
-cmd "move workspace $tmp2";
-
-$ws = get_ws($tmp2);
-is(@{$ws->{nodes}}, 0, 'no nodes on workspace');
-is(@{$ws->{floating_nodes}}, 1, 'one floating node on workspace');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Checks if size hints are interpreted correctly.
-#
-use i3test;
-
-my $i3 = i3(get_socket_path());
-
-my $x = X11::XCB::Connection->new;
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-my $win = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30),
- background_color => '#C0C0C0',
-);
-
-# XXX: we should check screen size. in screens with an AR of 2.0,
-# this is not a good idea.
-my $aspect = X11::XCB::Sizehints::Aspect->new;
-$aspect->min_num(600);
-$aspect->min_den(300);
-$aspect->max_num(600);
-$aspect->max_den(300);
-$win->_create;
-$win->map;
-sleep 0.25;
-$win->hints->aspect($aspect);
-$x->flush;
-
-sleep 0.25;
-
-my $rect = $win->rect;
-my $ar = $rect->width / $rect->height;
-diag("Aspect ratio = $ar");
-ok(($ar > 1.90) && ($ar < 2.10), 'Aspect ratio about 2.0');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-#
-#
-use i3test;
-
-cmd 'blargh!';
-
-does_i3_live;
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-
-use i3test;
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-
-my $x = X11::XCB::Connection->new;
-
-my $tmp = fresh_workspace;
-
-#############################################################################
-# 1: see if focus stays the same when toggling tiling/floating mode
-#############################################################################
-
-my $first = open_standard_window($x);
-my $second = open_standard_window($x);
-
-is($x->input_focus, $second->id, 'second window focused');
-
-cmd 'floating enable';
-cmd 'floating disable';
-
-is($x->input_focus, $second->id, 'second window still focused after mode toggle');
-
-#############################################################################
-# 2: see if focus stays on the current floating window if killing another
-# floating window
-#############################################################################
-
-$tmp = fresh_workspace;
-
-$first = open_standard_window($x); # window 2
-$second = open_standard_window($x); # window 3
-my $third = open_standard_window($x); # window 4
-
-is($x->input_focus, $third->id, 'last container focused');
-
-cmd 'floating enable';
-
-cmd '[id="' . $second->id . '"] focus';
-
-is($x->input_focus, $second->id, 'second con focused');
-
-cmd 'floating enable';
-
-# now kill the third one (it's floating). focus should stay unchanged
-cmd '[id="' . $third->id . '"] kill';
-
-sleep 0.25;
-
-is($x->input_focus, $second->id, 'second con still focused after killing third');
-
-
-#############################################################################
-# 3: see if the focus gets reverted correctly when closing floating clients
-# (first to the next floating client, then to the last focused tiling client)
-#############################################################################
-
-$tmp = fresh_workspace;
-
-$first = open_standard_window($x, '#ff0000'); # window 5
-$second = open_standard_window($x, '#00ff00'); # window 6
-my $third = open_standard_window($x, '#0000ff'); # window 7
-
-is($x->input_focus, $third->id, 'last container focused');
-
-cmd 'floating enable';
-
-cmd '[id="' . $second->id . '"] focus';
-
-is($x->input_focus, $second->id, 'second con focused');
-
-cmd 'floating enable';
-
-# now kill the second one. focus should fall back to the third one, which is
-# also floating
-cmd 'kill';
-
-sleep 0.25;
-
-is($x->input_focus, $third->id, 'third con focused');
-
-cmd 'kill';
-
-sleep 0.25;
-
-is($x->input_focus, $first->id, 'first con focused after killing all floating cons');
-
-#############################################################################
-# 4: same test as 3, but with another split con
-#############################################################################
-
-$tmp = fresh_workspace;
-
-$first = open_standard_window($x, '#ff0000'); # window 5
-cmd 'split v';
-cmd 'layout stacked';
-$second = open_standard_window($x, '#00ff00'); # window 6
-$third = open_standard_window($x, '#0000ff'); # window 7
-
-is($x->input_focus, $third->id, 'last container focused');
-
-cmd 'floating enable';
-
-cmd '[id="' . $second->id . '"] focus';
-
-is($x->input_focus, $second->id, 'second con focused');
-
-cmd 'floating enable';
-
-sleep 0.5;
-
-# now kill the second one. focus should fall back to the third one, which is
-# also floating
-cmd 'kill';
-
-sleep 0.25;
-
-is($x->input_focus, $third->id, 'second con focused');
-
-cmd 'kill';
-
-sleep 0.25;
-
-is($x->input_focus, $first->id, 'first con focused after killing all floating cons');
-
-#############################################################################
-# 5: see if the 'focus tiling' and 'focus floating' commands work
-#############################################################################
-
-$tmp = fresh_workspace;
-
-$first = open_standard_window($x, '#ff0000'); # window 8
-$second = open_standard_window($x, '#00ff00'); # window 9
-
-is($x->input_focus, $second->id, 'second container focused');
-
-cmd 'floating enable';
-
-is($x->input_focus, $second->id, 'second container focused');
-
-cmd 'focus tiling';
-
-sleep 0.25;
-
-is($x->input_focus, $first->id, 'first (tiling) container focused');
-
-cmd 'focus floating';
-
-sleep 0.25;
-
-is($x->input_focus, $second->id, 'second (floating) container focused');
-
-cmd 'focus floating';
-
-sleep 0.25;
-
-is($x->input_focus, $second->id, 'second (floating) container still focused');
-
-cmd 'focus mode_toggle';
-
-sleep 0.25;
-
-is($x->input_focus, $first->id, 'first (tiling) container focused');
-
-cmd 'focus mode_toggle';
-
-sleep 0.25;
-
-is($x->input_focus, $second->id, 'second (floating) container focused');
-
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-# Regression test: when only having a floating window on a workspace, it should not be deleted.
-
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-#############################################################################
-# 1: open a floating window, get it mapped
-#############################################################################
-
-ok(workspace_exists($tmp), "workspace $tmp exists");
-
-my $x = X11::XCB::Connection->new;
-
-# Create a floating window which is smaller than the minimum enforced size of i3
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#C0C0C0',
- # replace the type with 'utility' as soon as the coercion works again in X11::XCB
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
-ok($window->mapped, 'Window is mapped');
-
-# switch to a different workspace, see if the window is still mapped?
-
-my $otmp = fresh_workspace;
-
-ok(workspace_exists($otmp), "new workspace $otmp exists");
-ok(workspace_exists($tmp), "old workspace $tmp still exists");
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-# Regression test: Floating windows were not correctly unmapped when switching
-# to a different workspace.
-
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-#############################################################################
-# 1: open a floating window, get it mapped
-#############################################################################
-
-my $x = X11::XCB::Connection->new;
-
-# Create a floating window which is smaller than the minimum enforced size of i3
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#C0C0C0',
- # replace the type with 'utility' as soon as the coercion works again in X11::XCB
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
-ok($window->mapped, 'Window is mapped');
-
-# switch to a different workspace, see if the window is still mapped?
-
-my $otmp = fresh_workspace;
-
-sleep 0.25;
-
-ok(!$window->mapped, 'Window is not mapped after switching ws');
-
-cmd "nop testcase done";
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-# Regression test: New windows were attached to the container of a floating window
-# if only a floating window is present on the workspace.
-
-use i3test;
-use X11::XCB qw(:all);
-use Time::HiRes qw(sleep);
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-#############################################################################
-# 1: open a floating window, get it mapped
-#############################################################################
-
-my $x = X11::XCB::Connection->new;
-
-# Create a floating window
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#C0C0C0',
- # replace the type with 'utility' as soon as the coercion works again in X11::XCB
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
-ok($window->mapped, 'Window is mapped');
-
-my $ws = get_ws($tmp);
-my ($nodes, $focus) = get_ws_content($tmp);
-
-is(@{$ws->{floating_nodes}}, 1, 'one floating node');
-is(@{$nodes}, 0, 'no tiling nodes');
-
-# Create a tiling window
-my $twindow = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#C0C0C0',
-);
-
-isa_ok($twindow, 'X11::XCB::Window');
-
-$twindow->map;
-
-sleep 0.25;
-
-($nodes, $focus) = get_ws_content($tmp);
-
-is(@{$nodes}, 1, 'one tiling node');
-
-#############################################################################
-# 2: similar case: floating windows should be attached at the currently focused
-# position in the workspace (for example a stack), not just at workspace level.
-#############################################################################
-
-$tmp = fresh_workspace;
-
-my $first = open_standard_window($x);
-my $second = open_standard_window($x);
-
-cmd 'layout stacked';
-
-$ws = get_ws($tmp);
-is(@{$ws->{floating_nodes}}, 0, 'no floating nodes so far');
-is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)');
-
-# Create a floating window
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#C0C0C0',
- # replace the type with 'utility' as soon as the coercion works again in X11::XCB
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
-ok($window->mapped, 'Window is mapped');
-
-$ws = get_ws($tmp);
-is(@{$ws->{floating_nodes}}, 1, 'one floating nodes');
-is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)');
-
-my $third = open_standard_window($x);
-
-
-$ws = get_ws($tmp);
-is(@{$ws->{floating_nodes}}, 1, 'one floating nodes');
-is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-# Check if numbered workspaces and named workspaces are sorted in the right way
-# in get_workspaces IPC output (necessary for i3bar etc.).
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $i3 = i3(get_socket_path());
-my $x = X11::XCB::Connection->new;
-
-sub check_order {
- my ($msg) = @_;
-
- my @ws = @{$i3->get_workspaces->recv};
- my @nums = map { $_->{num} } grep { defined($_->{num}) } @ws;
- my @sorted = sort @nums;
-
- cmp_deeply(\@nums, \@sorted, $msg);
-}
-
-check_order('workspace order alright before testing');
-
-#############################################################################
-# open a window to keep this ws open
-#############################################################################
-
-cmd "workspace 93";
-
-open_standard_window($x);
-
-my @ws = @{$i3->get_workspaces->recv};
-my @f = grep { defined($_->{num}) && $_->{num} == 93 } @ws;
-is(@f, 1, 'ws 93 found by num');
-check_order('workspace order alright after opening 93');
-
-cmd "workspace 92";
-open_standard_window($x);
-check_order('workspace order alright after opening 92');
-
-cmd "workspace 94";
-open_standard_window($x);
-check_order('workspace order alright after opening 94');
-
-cmd "workspace 96";
-open_standard_window($x);
-check_order('workspace order alright after opening 96');
-
-cmd "workspace foo";
-open_standard_window($x);
-check_order('workspace order alright after opening foo');
-
-cmd "workspace 91";
-open_standard_window($x);
-check_order('workspace order alright after opening 91');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-# Regression: Check if the focus stays the same when switching the layout
-# bug introduced by 77d0d42ed2d7ac8cafe267c92b35a81c1b9491eb
-use i3test;
-use X11::XCB qw(:all);
-use Time::HiRes qw(sleep);
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $i3 = i3(get_socket_path());
-my $x = X11::XCB::Connection->new;
-
-sub check_order {
- my ($msg) = @_;
-
- my @ws = @{$i3->get_workspaces->recv};
- my @nums = map { $_->{num} } grep { defined($_->{num}) } @ws;
- my @sorted = sort @nums;
-
- cmp_deeply(\@nums, \@sorted, $msg);
-}
-
-my $tmp = fresh_workspace;
-
-my $left = open_standard_window($x);
-sleep 0.25;
-my $mid = open_standard_window($x);
-sleep 0.25;
-my $right = open_standard_window($x);
-sleep 0.25;
-
-diag("left = " . $left->id . ", mid = " . $mid->id . ", right = " . $right->id);
-
-is($x->input_focus, $right->id, 'Right window focused');
-
-cmd 'focus left';
-
-is($x->input_focus, $mid->id, 'Mid window focused');
-
-cmd 'layout stacked';
-
-is($x->input_focus, $mid->id, 'Mid window focused');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-# Tests resizing tiling containers
-use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $tmp = fresh_workspace;
-
-cmd 'split v';
-
-my $top = open_standard_window($x);
-sleep 0.25;
-my $bottom = open_standard_window($x);
-sleep 0.25;
-
-diag("top = " . $top->id . ", bottom = " . $bottom->id);
-
-is($x->input_focus, $bottom->id, 'Bottom window focused');
-
-############################################################
-# resize
-############################################################
-
-cmd 'resize grow up 10 px or 25 ppt';
-
-my ($nodes, $focus) = get_ws_content($tmp);
-
-is($nodes->[0]->{percent}, 0.25, 'top window got only 25%');
-is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
-
-
-############################################################
-# split and check if the 'percent' factor is still correct
-############################################################
-
-cmd 'split h';
-
-($nodes, $focus) = get_ws_content($tmp);
-
-is($nodes->[0]->{percent}, 0.25, 'top window got only 25%');
-is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
-
-############################################################
-# checks that resizing within stacked/tabbed cons works
-############################################################
-
-$tmp = fresh_workspace;
-
-cmd 'split v';
-
-$top = open_standard_window($x);
-sleep 0.25;
-$bottom = open_standard_window($x);
-sleep 0.25;
-
-cmd 'split h';
-cmd 'layout stacked';
-
-($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[0]->{percent}, 0.5, 'top window got 50%');
-is($nodes->[1]->{percent}, 0.5, 'bottom window got 50%');
-
-cmd 'resize grow up 10 px or 25 ppt';
-
-($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[0]->{percent}, 0.25, 'top window got 25%');
-is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
-
-############################################################
-# checks that resizing floating windows works
-############################################################
-
-$tmp = fresh_workspace;
-
-$top = open_standard_window($x);
-sleep 0.25;
-
-cmd 'floating enable';
-
-my @content = @{get_ws($tmp)->{floating_nodes}};
-cmp_ok(@content, '==', 1, 'one floating node on this ws');
-
-# up
-my $oldrect = $content[0]->{rect};
-
-cmd 'resize grow up 10 px or 25 ppt';
-
-@content = @{get_ws($tmp)->{floating_nodes}};
-cmp_ok($content[0]->{rect}->{y}, '<', $oldrect->{y}, 'y smaller than before');
-cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y} - 10, 'y exactly 10 px smaller');
-cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
-cmp_ok($content[0]->{rect}->{height}, '>', $oldrect->{height}, 'height bigger than before');
-cmp_ok($content[0]->{rect}->{height}, '==', $oldrect->{height} + 10, 'height exactly 10 px higher');
-cmp_ok($content[0]->{rect}->{width}, '==', $oldrect->{width}, 'x untouched');
-
-# up, but with a different amount of px
-$oldrect = $content[0]->{rect};
-
-cmd 'resize grow up 12 px or 25 ppt';
-
-@content = @{get_ws($tmp)->{floating_nodes}};
-cmp_ok($content[0]->{rect}->{y}, '<', $oldrect->{y}, 'y smaller than before');
-cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y} - 12, 'y exactly 10 px smaller');
-cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
-cmp_ok($content[0]->{rect}->{height}, '>', $oldrect->{height}, 'height bigger than before');
-cmp_ok($content[0]->{rect}->{height}, '==', $oldrect->{height} + 12, 'height exactly 10 px higher');
-cmp_ok($content[0]->{rect}->{width}, '==', $oldrect->{width}, 'x untouched');
-
-# left
-$oldrect = $content[0]->{rect};
-
-cmd 'resize grow left 10 px or 25 ppt';
-
-@content = @{get_ws($tmp)->{floating_nodes}};
-cmp_ok($content[0]->{rect}->{x}, '<', $oldrect->{x}, 'x smaller than before');
-cmp_ok($content[0]->{rect}->{width}, '>', $oldrect->{width}, 'width bigger than before');
-
-# right
-$oldrect = $content[0]->{rect};
-
-cmd 'resize grow right 10 px or 25 ppt';
-
-@content = @{get_ws($tmp)->{floating_nodes}};
-cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x the same as before');
-cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y the same as before');
-cmp_ok($content[0]->{rect}->{width}, '>', $oldrect->{width}, 'width bigger than before');
-cmp_ok($content[0]->{rect}->{height}, '==', $oldrect->{height}, 'height the same as before');
-
-# down
-$oldrect = $content[0]->{rect};
-
-cmd 'resize grow down 10 px or 25 ppt';
-
-@content = @{get_ws($tmp)->{floating_nodes}};
-cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x the same as before');
-cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y the same as before');
-cmp_ok($content[0]->{rect}->{height}, '>', $oldrect->{height}, 'height bigger than before');
-cmp_ok($content[0]->{rect}->{width}, '==', $oldrect->{width}, 'width the same as before');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression: move a floating window to a different workspace crashes i3
-#
-use i3test;
-
-my $tmp = fresh_workspace;
-my $otmp = get_unused_workspace();
-
-cmd 'open';
-cmd 'mode toggle';
-cmd "move workspace $otmp";
-
-does_i3_live;
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression: floating windows are tiling after restarting, closing them crashes i3
-#
-use i3test;
-
-my $tmp = fresh_workspace;
-
-cmd 'open';
-cmd 'mode toggle';
-cmd 'restart';
-
-sleep 0.5;
-
-diag('Checking if i3 still lives');
-
-does_i3_live;
-
-my $ws = get_ws($tmp);
-diag('ws = ' . Dumper($ws));
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression: when resizing two containers on a workspace, opening a floating
-# client, then closing it again, i3 will re-distribute the space on the
-# workspace as if a tiling container was closed, leading to the containers
-# taking much more space than they possibly could.
-#
-use i3test;
-use List::Util qw(sum);
-
-my $tmp = fresh_workspace;
-
-cmd 'exec /usr/bin/urxvt';
-sleep 0.5;
-cmd 'exec /usr/bin/urxvt';
-sleep 0.5;
-my ($nodes, $focus) = get_ws_content($tmp);
-my $old_sum = sum map { $_->{rect}->{width} } @{$nodes};
-#cmd 'open';
-cmd 'resize grow left 10 px or 25 ppt';
-cmd 'split v';
-#cmd 'open';
-cmd 'exec /usr/bin/urxvt';
-sleep 0.5;
-cmd 'mode toggle';
-sleep 0.5;
-cmd 'kill';
-
-sleep 0.5;
-
-($nodes, $focus) = get_ws_content($tmp);
-my $new_sum = sum map { $_->{rect}->{width} } @{$nodes};
-
-is($old_sum, $new_sum, 'combined container width is still equal');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# by moving the window in the opposite orientation that its parent has, we
-# force i3 to create a new split container with the appropriate orientation.
-# However, when doing that two times in a row, we end up with two split
-# containers which are then redundant (workspace is horizontal, then v-split,
-# then h-split – we could just append the children of the latest h-split to the
-# workspace itself).
-#
-# This testcase checks that the tree is properly flattened after moving.
-#
-use X11::XCB qw(:all);
-use i3test;
-
-my $x = X11::XCB::Connection->new;
-
-my $tmp = fresh_workspace;
-
-my $left = open_standard_window($x);
-sleep 0.25;
-my $mid = open_standard_window($x);
-sleep 0.25;
-my $right = open_standard_window($x);
-sleep 0.25;
-
-cmd 'move before v';
-cmd 'move after h';
-my $ws = get_ws($tmp);
-
-is($ws->{orientation}, 'horizontal', 'workspace orientation is horizontal');
-is(@{$ws->{nodes}}, 3, 'all three windows on workspace level');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-use X11::XCB qw(:all);
-use Time::HiRes qw(sleep);
-use i3test;
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $tmp = fresh_workspace;
-
-my $left = open_standard_window($x);
-sleep 0.25;
-my $mid = open_standard_window($x);
-sleep 0.25;
-
-cmd 'split v';
-my $bottom = open_standard_window($x);
-sleep 0.25;
-
-my ($nodes, $focus) = get_ws_content($tmp);
-
-#############################################################################
-# 1: open a floating window, get it mapped
-#############################################################################
-
-my $x = X11::XCB::Connection->new;
-
-# Create a floating window
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#C0C0C0',
- # replace the type with 'utility' as soon as the coercion works again in X11::XCB
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
-ok($window->mapped, 'Window is mapped');
-
-($nodes, $focus) = get_ws_content($tmp);
-is(@{$nodes->[1]->{nodes}}, 2, 'two windows in split con');
-
-#############################################################################
-# 2: make it tiling, see where it ends up
-#############################################################################
-
-cmd 'floating toggle';
-
-my ($nodes, $focus) = get_ws_content($tmp);
-
-is(@{$nodes->[1]->{nodes}}, 3, 'three windows in split con after floating toggle');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression test for moving a con outside of a floating con when there are no
-# tiling cons on a workspace
-#
-use X11::XCB qw(:all);
-use Time::HiRes qw(sleep);
-use i3test;
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $tmp = fresh_workspace;
-
-my $left = open_standard_window($x);
-sleep 0.25;
-my $mid = open_standard_window($x);
-sleep 0.25;
-my $right = open_standard_window($x);
-sleep 0.25;
-
-# go to workspace level
-cmd 'level up';
-sleep 0.25;
-
-# make it floating
-cmd 'mode toggle';
-sleep 0.25;
-
-# move the con outside the floating con
-cmd 'move before v';
-sleep 0.25;
-
-does_i3_live;
-
-# move another con outside
-cmd '[id="' . $mid->id . '"] focus';
-cmd 'move before v';
-sleep 0.25;
-
-does_i3_live;
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression test for correct focus behaviour when moving a floating con to
-# another workspace.
-#
-use X11::XCB qw(:all);
-use i3test;
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $tmp = fresh_workspace;
-
-# open a tiling window on the first workspace
-open_standard_window($x);
-sleep 0.25;
-my $first = get_focused($tmp);
-
-# on a different ws, open a floating window
-my $otmp = fresh_workspace;
-open_standard_window($x);
-sleep 0.25;
-my $float = get_focused($otmp);
-cmd 'mode toggle';
-sleep 0.25;
-
-# move the floating con to first workspace
-cmd "move workspace $tmp";
-sleep 0.25;
-
-# switch to the first ws and check focus
-is(get_focused($tmp), $float, 'floating client correctly focused');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression test for inplace restarting with dock clients
-#
-use X11::XCB qw(:all);
-use i3test;
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-#####################################################################
-# verify that there is no dock window yet
-#####################################################################
-
-# Children of all dockareas
-my @docked = get_dock_clients;
-
-is(@docked, 0, 'no dock clients yet');
-
-# open a dock client
-
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#FF0000',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$window->map;
-
-sleep 0.25;
-
-#####################################################################
-# check that we can find it in the layout tree at the expected position
-#####################################################################
-
-@docked = get_dock_clients;
-is(@docked, 1, 'one dock client found');
-
-# verify the height
-my $docknode = $docked[0];
-
-is($docknode->{rect}->{height}, 30, 'dock node has unchanged height');
-
-# perform an inplace-restart
-cmd 'restart';
-
-sleep 0.25;
-
-does_i3_live;
-
-
-#####################################################################
-# check that we can still find the dock client
-#####################################################################
-
-@docked = get_dock_clients;
-is(@docked, 1, 'one dock client found');
-$docknode = $docked[0];
-
-is($docknode->{rect}->{height}, 30, 'dock node has unchanged height after restart');
-
-$window->destroy;
-
-sleep 0.25;
-
-@docked = get_dock_clients;
-is(@docked, 0, 'no dock clients found');
-
-#####################################################################
-# create a dock client with a 1px border
-#####################################################################
-
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- border => 1,
- rect => [ 0, 0, 30, 20],
- background_color => '#00FF00',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$window->map;
-
-sleep 0.25;
-
-@docked = get_dock_clients;
-is(@docked, 1, 'one dock client found');
-$docknode = $docked[0];
-
-is($docknode->{rect}->{height}, 20, 'dock node has unchanged height');
-
-cmd 'restart';
-sleep 0.25;
-
-@docked = get_dock_clients;
-is(@docked, 1, 'one dock client found');
-$docknode = $docked[0];
-
-is($docknode->{rect}->{height}, 20, 'dock node has unchanged height');
-
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression test for setting a window to floating, tiling and opening a new window
-#
-use i3test;
-
-fresh_workspace;
-
-cmd 'open';
-cmd 'mode toggle';
-cmd 'mode toggle';
-cmd 'open';
-
-does_i3_live;
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression test for using level-up to get to the 'content'-container and
-# toggle floating
-#
-use i3test;
-
-fresh_workspace;
-
-cmd 'open';
-cmd 'level up';
-cmd 'level up';
-cmd 'mode toggle';
-
-does_i3_live;
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Test if the requested width/height is set after making the window floating.
-#
-use X11::XCB qw(:all);
-use i3test;
-
-my $tmp = fresh_workspace;
-
-my $x = X11::XCB::Connection->new;
-
-# Create a floating window which is smaller than the minimum enforced size of i3
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 400, 150],
- background_color => '#C0C0C0',
-);
-
-isa_ok($window, 'X11::XCB::Window');
-
-$window->map;
-
-sleep 0.25;
-
-my ($absolute, $top) = $window->rect;
-
-ok($window->mapped, 'Window is mapped');
-cmp_ok($absolute->{width}, '>', 400, 'i3 raised the width');
-cmp_ok($absolute->{height}, '>', 150, 'i3 raised the height');
-
-cmd 'floating toggle';
-sleep 0.25;
-
-($absolute, $top) = $window->rect;
-
-diag('new width: ' . $absolute->{width});
-diag('new height: ' . $absolute->{height});
-
-# we compare with a tolerance of ± 20 pixels for borders in each direction
-# (overkill, but hey)
-cmp_ok($absolute->{width}, '>', 400-20, 'width now > 380');
-cmp_ok($absolute->{width}, '<', 400+20, 'width now < 420');
-cmp_ok($absolute->{height}, '>', 150-20, 'height now > 130');
-cmp_ok($absolute->{height}, '<', 150+20, 'height now < 170');
-
-#cmp_ok($absolute->{width}, '>=', 75, 'i3 raised the width to 75');
-#cmp_ok($absolute->{height}, '>=', 50, 'i3 raised the height to 50');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression test for closing one of multiple dock clients
-#
-use X11::XCB qw(:all);
-use i3test;
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-#####################################################################
-# verify that there is no dock window yet
-#####################################################################
-
-# Children of all dockareas
-my @docked = get_dock_clients;
-
-is(@docked, 0, 'no dock clients yet');
-
-#####################################################################
-# open a dock client
-#####################################################################
-
-my $first = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#FF0000',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$first->map;
-
-sleep 0.25;
-
-#####################################################################
-# Open a second dock client
-#####################################################################
-
-my $second = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#FF0000',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$second->map;
-
-sleep 0.25;
-
-#####################################################################
-# Kill the second dock client
-#####################################################################
-cmd "nop destroying dock client";
-$second->destroy;
-
-#####################################################################
-# Now issue a focus command
-#####################################################################
-cmd 'next v';
-
-does_i3_live;
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Test to see if i3 combines the geometry of all children in a split container
-# when setting the split container to floating
-#
-use X11::XCB qw(:all);
-use i3test;
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-#####################################################################
-# open a window with 200x80
-#####################################################################
-
-my $first = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 200, 80],
- background_color => '#FF0000',
-);
-
-$first->map;
-
-sleep 0.25;
-
-#####################################################################
-# Open a second window with 300x90
-#####################################################################
-
-my $second = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 300, 90],
- background_color => '#00FF00',
-);
-
-$second->map;
-
-sleep 0.25;
-
-#####################################################################
-# Set the parent to floating
-#####################################################################
-cmd 'nop setting floating';
-cmd 'focus parent';
-cmd 'floating enable';
-
-#####################################################################
-# Get geometry of the first floating node (the split container)
-#####################################################################
-
-my @nodes = @{get_ws($tmp)->{floating_nodes}};
-my $rect = $nodes[0]->{rect};
-
-# we compare the width with ± 20 pixels for borders
-cmp_ok($rect->{width}, '>', 500-20, 'width now > 480');
-cmp_ok($rect->{width}, '<', 500+20, 'width now < 520');
-# we compare the height with ± 40 pixels for decorations
-cmp_ok($rect->{height}, '>', 90-40, 'width now > 50');
-cmp_ok($rect->{height}, '<', 90+40, 'width now < 130');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Test if new containers get focused when there is a fullscreen container at
-# the time of launching the new one.
-#
-use X11::XCB qw(:all);
-use i3test;
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-#####################################################################
-# open the left window
-#####################################################################
-
-my $left = open_standard_window($x, '#ff0000');
-
-is($x->input_focus, $left->id, 'left window focused');
-
-diag("left = " . $left->id);
-
-#####################################################################
-# Open the right window
-#####################################################################
-
-my $right = open_standard_window($x, '#00ff00');
-
-diag("right = " . $right->id);
-
-#####################################################################
-# Set the right window to fullscreen
-#####################################################################
-cmd 'nop setting fullscreen';
-cmd 'fullscreen';
-
-#####################################################################
-# Open a third window
-#####################################################################
-
-my $third = open_standard_window($x, '#0000ff');
-
-diag("third = " . $third->id);
-
-# move the fullscreen window to a different ws
-
-my $tmp2 = get_unused_workspace;
-
-cmd "move workspace $tmp2";
-
-# verify that the third window has the focus
-
-sleep 0.25;
-
-is($x->input_focus, $third->id, 'third window focused');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression test: level up should be a noop during fullscreen mode
-#
-use X11::XCB qw(:all);
-use i3test;
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-#####################################################################
-# open a window, verify it’s not in fullscreen mode
-#####################################################################
-
-my $win = open_standard_window($x);
-
-my $nodes = get_ws_content $tmp;
-is(@$nodes, 1, 'exactly one client');
-is($nodes->[0]->{fullscreen_mode}, 0, 'client not fullscreen');
-
-#####################################################################
-# make it fullscreen
-#####################################################################
-
-cmd 'nop making fullscreen';
-cmd 'fullscreen';
-
-my $nodes = get_ws_content $tmp;
-is($nodes->[0]->{fullscreen_mode}, 1, 'client fullscreen now');
-
-#####################################################################
-# send level up, try to un-fullscreen
-#####################################################################
-cmd 'level up';
-cmd 'fullscreen';
-
-my $nodes = get_ws_content $tmp;
-is($nodes->[0]->{fullscreen_mode}, 0, 'client not fullscreen any longer');
-
-does_i3_live;
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests if the WM_TAKE_FOCUS protocol is correctly handled by i3
-#
-use X11::XCB qw(:all);
-use i3test;
-use v5.10;
-
-BEGIN {
- use_ok('EV');
- use_ok('AnyEvent');
- use_ok('X11::XCB::Window');
- use_ok('X11::XCB::Event::Generic');
- use_ok('X11::XCB::Event::MapNotify');
- use_ok('X11::XCB::Event::ClientMessage');
-}
-
-my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
-
-subtest 'Window without WM_TAKE_FOCUS', sub {
-
- my $tmp = fresh_workspace;
-
- my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
- event_mask => [ 'structure_notify' ],
- );
-
- $window->name('Window 1');
- $window->map;
-
- my $cv = AE::cv;
-
- my $prep = EV::prepare sub {
- $x->flush;
- };
-
- my $check = EV::check sub {
- while (defined(my $event = $x->poll_for_event)) {
- if ($event->response_type == 161) {
- # clientmessage
- $cv->send(0);
- }
- }
- };
-
- my $w = EV::io $x->get_file_descriptor, EV::READ, sub {
- # do nothing, we only need this watcher so that EV picks up the events
- };
-
- # Trigger timeout after 1 second
- my $t = AE::timer 1, 0, sub {
- $cv->send(1);
- };
-
- my $result = $cv->recv;
- ok($result, 'cv result');
-
- done_testing;
-};
-
-subtest 'Window with WM_TAKE_FOCUS', sub {
-
- my $tmp = fresh_workspace;
-
- my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
- event_mask => [ 'structure_notify' ],
- protocols => [ $x->atom(name => 'WM_TAKE_FOCUS') ],
- );
-
- $window->name('Window 1');
- $window->map;
-
- my $cv = AE::cv;
-
- my $prep = EV::prepare sub {
- $x->flush;
- };
-
- my $check = EV::check sub {
- while (defined(my $event = $x->poll_for_event)) {
- if ($event->response_type == 161) {
- $cv->send($event->data);
- }
- }
- };
-
- my $w = EV::io $x->get_file_descriptor, EV::READ, sub {
- # do nothing, we only need this watcher so that EV picks up the events
- };
-
- my $t = AE::timer 1, 0, sub {
- say "timer!";
- $cv->send(undef);
- };
-
- my $result = $cv->recv;
- ok(defined($result), 'got a ClientMessage');
- if (defined($result)) {
- my ($data, $time) = unpack("L2", $result);
- is($data, $x->atom(name => 'WM_TAKE_FOCUS')->id, 'first uint32_t contains WM_TAKE_FOCUS atom');
- }
-
- done_testing;
-};
-
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
-#
-# Tests if the various ipc_socket_path options are correctly handled
-#
-use i3test;
-use Cwd qw(abs_path);
-use Proc::Background;
-use File::Temp qw(tempfile tempdir);
-use POSIX qw(getuid);
-use v5.10;
-
-# assuming we are run by complete-run.pl
-my $i3_path = abs_path("../i3");
-
-#####################################################################
-# default case: socket will be created in /tmp/i3-<username>/ipc-socket.<pid>
-#####################################################################
-
-my ($fh, $tmpfile) = tempfile();
-say $fh "# i3 config file (v4)";
-say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
-close($fh);
-
-diag("Starting i3");
-my $i3cmd = "unset XDG_RUNTIME_DIR; exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null";
-my $process = Proc::Background->new($i3cmd);
-sleep 1;
-
-diag("pid = " . $process->pid);
-
-my $folder = "/tmp/i3-" . getpwuid(getuid());
-ok(-d $folder, "folder $folder exists");
-my $socketpath = "$folder/ipc-socket." . $process->pid;
-ok(-S $socketpath, "file $socketpath exists and is a socket");
-
-exit_gracefully($process->pid, $socketpath);
-
-sleep 0.25;
-
-#####################################################################
-# XDG_RUNTIME_DIR case: socket gets created in $XDG_RUNTIME_DIR/i3/ipc-socket.<pid>
-#####################################################################
-
-my $rtdir = tempdir(CLEANUP => 1);
-
-ok(! -e "$rtdir/i3", "$rtdir/i3 does not exist yet");
-
-$i3cmd = "export XDG_RUNTIME_DIR=$rtdir; exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null";
-$process = Proc::Background->new($i3cmd);
-sleep 1;
-
-ok(-d "$rtdir/i3", "$rtdir/i3 exists and is a directory");
-$socketpath = "$rtdir/i3/ipc-socket." . $process->pid;
-ok(-S $socketpath, "file $socketpath exists and is a socket");
-
-exit_gracefully($process->pid, $socketpath);
-
-sleep 0.25;
-
-#####################################################################
-# configuration file case: socket gets placed whereever we specify
-#####################################################################
-
-my $tmpdir = tempdir(CLEANUP => 1);
-$socketpath = $tmpdir . "/config.sock";
-ok(! -e $socketpath, "$socketpath does not exist yet");
-
-($fh, $tmpfile) = tempfile();
-say $fh "# i3 config file (v4)";
-say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
-say $fh "ipc-socket $socketpath";
-close($fh);
-
-$i3cmd = "export XDG_RUNTIME_DIR=$rtdir; exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null";
-$process = Proc::Background->new($i3cmd);
-sleep 1;
-
-ok(-S $socketpath, "file $socketpath exists and is a socket");
-
-exit_gracefully($process->pid, $socketpath);
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression test to check if borders are correctly restored after an inplace
-# restart.
-# found in eb8ad348b28e243cba1972e802ca8ee636472fc9
-#
-use X11::XCB qw(:all);
-use List::Util qw(first);
-use i3test;
-
-my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
-my $tmp = fresh_workspace;
-my $window = open_standard_window($x);
-
-sub get_border_style {
- my @content = @{get_ws_content($tmp)};
- my $wininfo = first { $_->{window} == $window->id } @content;
-
- return $wininfo->{border};
-}
-
-is(get_border_style(), 'normal', 'border style normal');
-
-cmd 'border 1pixel';
-
-is(get_border_style(), '1pixel', 'border style 1pixel after changing');
-
-# perform an inplace-restart
-cmd 'restart';
-
-sleep 0.25;
-
-does_i3_live;
-
-is(get_border_style(), '1pixel', 'border style still 1pixel after restart');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression test for setting the urgent hint on dock clients.
-# found in 4be3178d4d360c2996217d811e61161c84d25898
-#
-use X11::XCB qw(:all);
-use i3test;
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-my $i3 = i3(get_socket_path());
-
-my $tmp = fresh_workspace;
-
-#####################################################################
-# verify that there is no dock window yet
-#####################################################################
-
-# Children of all dockareas
-my @docked = get_dock_clients;
-
-is(@docked, 0, 'no dock clients yet');
-
-# open a dock client
-
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#FF0000',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$window->map;
-
-sleep 0.25;
-
-#####################################################################
-# check that we can find it in the layout tree at the expected position
-#####################################################################
-
-@docked = get_dock_clients;
-is(@docked, 1, 'one dock client found');
-
-# verify the height
-my $docknode = $docked[0];
-
-is($docknode->{rect}->{height}, 30, 'dock node has unchanged height');
-
-$window->add_hint('urgency');
-
-sleep 0.25;
-
-does_i3_live;
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when
-# unmapped.
-#
-use X11::XCB qw(:all);
-use i3test;
-
-BEGIN {
- use_ok('X11::XCB::Window');
- use_ok('X11::XCB::Event::Generic');
- use_ok('X11::XCB::Event::MapNotify');
- use_ok('X11::XCB::Event::ClientMessage');
-}
-
-my $x = X11::XCB::Connection->new;
-
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
- event_mask => [ 'structure_notify' ],
-);
-
-$window->name('Window 1');
-$window->map;
-
-diag('window mapped');
-
-sleep 0.5;
-
-is($window->state, ICCCM_WM_STATE_NORMAL, 'WM_STATE normal');
-
-$window->unmap;
-
-sleep 0.5;
-
-is($window->state, ICCCM_WM_STATE_WITHDRAWN, 'WM_STATE withdrawn');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when
-# unmapped.
-#
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-use i3test;
-
-my $x = X11::XCB::Connection->new;
-
-sub two_windows {
- my $tmp = fresh_workspace;
-
- ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
- my $first = open_standard_window($x);
- my $second = open_standard_window($x);
-
- is($x->input_focus, $second->id, 'second window focused');
- ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
-
- return $tmp;
-}
-
-##############################################################
-# 1: open two windows (in the same client), kill one and see if
-# the other one is still there
-##############################################################
-
-my $tmp = two_windows;
-
-cmd 'kill';
-
-sleep 0.25;
-
-ok(@{get_ws_content($tmp)} == 1, 'one container left after killing');
-
-##############################################################
-# 2: same test case as test 1, but with the explicit variant
-# 'kill window'
-##############################################################
-
-my $tmp = two_windows;
-
-cmd 'kill window';
-
-sleep 0.25;
-
-ok(@{get_ws_content($tmp)} == 1, 'one container left after killing');
-
-##############################################################
-# 3: open two windows (in the same client), use 'kill client'
-# and check if both are gone
-##############################################################
-
-my $tmp = two_windows;
-
-cmd 'kill client';
-
-sleep 0.25;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers left after killing');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
-#
-#
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-use i3test;
-
-my $x = X11::XCB::Connection->new;
-
-##############################################################
-# 1: test the following directive:
-# for_window [class="borderless"] border none
-# by first creating a window with a different class (should get
-# the normal border), then creating a window with the class
-# "borderless" (should get no border)
-##############################################################
-
-my $config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-for_window [class="borderless"] border none
-for_window [title="special borderless title"] border none
-EOT
-
-my $process = launch_with_config($config);
-
-my $tmp = fresh_workspace;
-
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
-);
-
-$window->name('Border window');
-$window->map;
-sleep 0.25;
-
-my @content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 1, 'one node on this workspace now');
-is($content[0]->{border}, 'normal', 'normal border');
-
-$window->unmap;
-sleep 0.25;
-
-my @content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 0, 'no more nodes');
-diag('content = '. Dumper(\@content));
-
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
-);
-
-$window->_create;
-
-# TODO: move this to X11::XCB::Window
-sub set_wm_class {
- my ($id, $class, $instance) = @_;
-
- # Add a _NET_WM_STRUT_PARTIAL hint
- my $atomname = $x->atom(name => 'WM_CLASS');
- my $atomtype = $x->atom(name => 'STRING');
-
- $x->change_property(
- PROP_MODE_REPLACE,
- $id,
- $atomname->id,
- $atomtype->id,
- 8,
- length($class) + length($instance) + 2,
- "$instance\x00$class\x00"
- );
-}
-
-set_wm_class($window->id, 'borderless', 'borderless');
-$window->name('Borderless window');
-$window->map;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 1, 'one node on this workspace now');
-is($content[0]->{border}, 'none', 'no border');
-
-$window->unmap;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 0, 'no more nodes');
-
-exit_gracefully($process->pid);
-
-##############################################################
-# 2: match on the title, check if for_window is really executed
-# only once
-##############################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-for_window [class="borderless"] border none
-for_window [title="special borderless title"] border none
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
-);
-
-$window->name('special title');
-$window->map;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 1, 'one node on this workspace now');
-is($content[0]->{border}, 'normal', 'normal border');
-
-$window->name('special borderless title');
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-is($content[0]->{border}, 'none', 'no border');
-
-$window->name('special title');
-sleep 0.25;
-
-cmd 'border normal';
-
-@content = @{get_ws_content($tmp)};
-is($content[0]->{border}, 'normal', 'border reset to normal');
-
-$window->name('special borderless title');
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-is($content[0]->{border}, 'normal', 'still normal border');
-
-$window->unmap;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 0, 'no more nodes');
-
-exit_gracefully($process->pid);
-
-##############################################################
-# 3: match on the title, set border style *and* a mark
-##############################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-for_window [class="borderless" title="usethis"] border none
-for_window [class="borderless"] border none
-for_window [title="special borderless title"] border none
-for_window [title="special mark title"] border none, mark bleh
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
-);
-
-$window->name('special mark title');
-$window->map;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 1, 'one node on this workspace now');
-is($content[0]->{border}, 'none', 'no border');
-
-my $other = open_standard_window($x);
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 2, 'two nodes');
-is($content[0]->{border}, 'none', 'no border');
-is($content[1]->{border}, 'normal', 'normal border');
-ok(!$content[0]->{focused}, 'first one not focused');
-
-cmd qq|[con_mark="bleh"] focus|;
-
-@content = @{get_ws_content($tmp)};
-ok($content[0]->{focused}, 'first node focused');
-
-exit_gracefully($process->pid);
-
-##############################################################
-# 4: multiple criteria for the for_window command
-##############################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-for_window [class="borderless" title="usethis"] border none
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
-);
-
-$window->_create;
-
-set_wm_class($window->id, 'borderless', 'borderless');
-$window->name('usethis');
-$window->map;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 1, 'one node on this workspace now');
-is($content[0]->{border}, 'none', 'no border');
-
-$window->unmap;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 0, 'no nodes on this workspace now');
-
-set_wm_class($window->id, 'borderless', 'borderless');
-$window->name('notthis');
-$window->map;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 1, 'one node on this workspace now');
-is($content[0]->{border}, 'normal', 'no border');
-
-
-exit_gracefully($process->pid);
-
-##############################################################
-# 5: check that a class criterion does not match the instance
-##############################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-for_window [class="foo"] border 1pixel
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
-);
-
-$window->_create;
-
-set_wm_class($window->id, 'bar', 'foo');
-$window->name('usethis');
-$window->map;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 1, 'one node on this workspace now');
-is($content[0]->{border}, 'normal', 'normal border, not matched');
-
-exit_gracefully($process->pid);
-
-##############################################################
-# 6: check that the 'instance' criterion works
-##############################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-for_window [class="foo"] border 1pixel
-for_window [instance="foo"] border none
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
-);
-
-$window->_create;
-
-set_wm_class($window->id, 'bar', 'foo');
-$window->name('usethis');
-$window->map;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 1, 'one node on this workspace now');
-is($content[0]->{border}, 'none', 'no border');
-
-exit_gracefully($process->pid);
-
-##############################################################
-# 7: check that invalid criteria don’t end up matching all windows
-##############################################################
-
-# this configuration is broken because "asdf" is not a valid integer
-# the for_window should therefore recognize this error and don’t add the
-# assignment
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-for_window [id="asdf"] border none
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
-);
-
-$window->_create;
-
-set_wm_class($window->id, 'bar', 'foo');
-$window->name('usethis');
-$window->map;
-sleep 0.25;
-
-@content = @{get_ws_content($tmp)};
-cmp_ok(@content, '==', 1, 'one node on this workspace now');
-is($content[0]->{border}, 'normal', 'normal border');
-
-exit_gracefully($process->pid);
-
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
-#
-# Tests if assignments work
-#
-use i3test;
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-use v5.10;
-
-my $x = X11::XCB::Connection->new;
-
-# TODO: move to X11::XCB
-sub set_wm_class {
- my ($id, $class, $instance) = @_;
-
- # Add a _NET_WM_STRUT_PARTIAL hint
- my $atomname = $x->atom(name => 'WM_CLASS');
- my $atomtype = $x->atom(name => 'STRING');
-
- $x->change_property(
- PROP_MODE_REPLACE,
- $id,
- $atomname->id,
- $atomtype->id,
- 8,
- length($class) + length($instance) + 2,
- "$instance\x00$class\x00"
- );
-}
-
-
-#####################################################################
-# start a window and see that it does not get assigned with an empty config
-#####################################################################
-
-my $config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-EOT
-
-my $process = launch_with_config($config);
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#0000ff',
-);
-
-$window->_create;
-set_wm_class($window->id, 'special', 'special');
-$window->name('special window');
-$window->map;
-sleep 0.25;
-
-ok(@{get_ws_content($tmp)} == 1, 'special window got managed to current (random) workspace');
-
-exit_gracefully($process->pid);
-
-$window->destroy;
-
-sleep 0.25;
-
-#####################################################################
-# start a window and see that it gets assigned to a formerly unused
-# workspace
-#####################################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-assign "special" → targetws
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-my $workspaces = get_workspace_names;
-ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
-
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#0000ff',
-);
-
-$window->_create;
-set_wm_class($window->id, 'special', 'special');
-$window->name('special window');
-$window->map;
-sleep 0.25;
-
-ok(@{get_ws_content($tmp)} == 0, 'still no containers');
-ok("targetws" ~~ @{get_workspace_names()}, 'targetws exists');
-
-$window->destroy;
-
-exit_gracefully($process->pid);
-
-sleep 0.25;
-
-#####################################################################
-# start a window and see that it gets assigned to a workspace which has content
-# already, next to the existing node.
-#####################################################################
-
-$process = launch_with_config($config);
-
-# initialize the target workspace, then go to a fresh one
-ok(!("targetws" ~~ @{get_workspace_names()}), 'targetws does not exist yet');
-cmd 'workspace targetws';
-cmp_ok(@{get_ws_content('targetws')}, '==', 0, 'no containers on targetws yet');
-cmd 'open';
-cmp_ok(@{get_ws_content('targetws')}, '==', 1, 'one container on targetws');
-$tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-ok("targetws" ~~ @{get_workspace_names()}, 'targetws does not exist yet');
-
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#0000ff',
-);
-
-$window->_create;
-set_wm_class($window->id, 'special', 'special');
-$window->name('special window');
-$window->map;
-sleep 0.25;
-
-ok(@{get_ws_content($tmp)} == 0, 'still no containers');
-ok(@{get_ws_content('targetws')} == 2, 'two containers on targetws');
-
-exit_gracefully($process->pid);
-
-#####################################################################
-# start a window and see that it gets assigned to a workspace which has content
-# already, next to the existing node.
-#####################################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-assign "special" → ~
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-my $workspaces = get_workspace_names;
-ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
-
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#0000ff',
-);
-
-$window->_create;
-set_wm_class($window->id, 'special', 'special');
-$window->name('special window');
-$window->map;
-sleep 0.25;
-
-my $content = get_ws($tmp);
-ok(@{$content->{nodes}} == 0, 'no tiling cons');
-ok(@{$content->{floating_nodes}} == 1, 'one floating con');
-
-$window->destroy;
-
-exit_gracefully($process->pid);
-
-sleep 0.25;
-
-#####################################################################
-# regression test: dock clients with floating assignments should not crash
-# (instead, nothing should happen - dock clients can’t float)
-# ticket #501
-#####################################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-assign "special" → ~
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-my @docked = get_dock_clients;
-is(@docked, 0, 'no dock clients yet');
-
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#0000ff',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
-);
-
-$window->_create;
-set_wm_class($window->id, 'special', 'special');
-$window->name('special window');
-$window->map;
-sleep 0.25;
-
-my $content = get_ws($tmp);
-ok(@{$content->{nodes}} == 0, 'no tiling cons');
-ok(@{$content->{floating_nodes}} == 0, 'one floating con');
-@docked = get_dock_clients;
-is(@docked, 1, 'no dock clients yet');
-
-$window->destroy;
-
-does_i3_live;
-
-exit_gracefully($process->pid);
-
-sleep 0.25;
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
-#
-# Tests the workspace_layout config option.
-#
-
-use i3test;
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-
-my $x = X11::XCB::Connection->new;
-
-#####################################################################
-# 1: check that with an empty config, cons are place next to each
-# other and no split containers are created
-#####################################################################
-
-my $config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-EOT
-
-my $process = launch_with_config($config);
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-my $first = open_standard_window($x);
-my $second = open_standard_window($x);
-
-is($x->input_focus, $second->id, 'second window focused');
-ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
-isnt($content[0]->{layout}, 'stacked', 'layout not stacked');
-isnt($content[1]->{layout}, 'stacked', 'layout not stacked');
-
-exit_gracefully($process->pid);
-
-#####################################################################
-# 2: set workspace_layout stacked, check that when opening two cons,
-# they end up in a stacked con
-#####################################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-workspace_layout stacked
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-$first = open_standard_window($x);
-$second = open_standard_window($x);
-
-is($x->input_focus, $second->id, 'second window focused');
-my @content = @{get_ws_content($tmp)};
-ok(@content == 1, 'one con at workspace level');
-is($content[0]->{layout}, 'stacked', 'layout stacked');
-
-#####################################################################
-# 3: focus parent, open two new cons, check that they end up in a stacked
-# con
-#####################################################################
-
-cmd 'focus parent';
-my $right_top = open_standard_window($x);
-my $right_bot = open_standard_window($x);
-
-@content = @{get_ws_content($tmp)};
-is(@content, 2, 'two cons at workspace level after focus parent');
-is($content[0]->{layout}, 'stacked', 'layout stacked');
-is($content[1]->{layout}, 'stacked', 'layout stacked');
-
-#####################################################################
-# 4: move one of the cons to the right, check that it will end up in
-# a stacked con
-#####################################################################
-
-cmd 'move right';
-
-@content = @{get_ws_content($tmp)};
-is(@content, 3, 'three cons at workspace level after move');
-is($content[0]->{layout}, 'stacked', 'layout stacked');
-is($content[1]->{layout}, 'stacked', 'layout stacked');
-is($content[2]->{layout}, 'stacked', 'layout stacked');
-
-#####################################################################
-# 5: move it to the left again, check that the stacked con is deleted
-#####################################################################
-
-cmd 'move left';
-
-@content = @{get_ws_content($tmp)};
-is(@content, 2, 'two cons at workspace level after moving back');
-is($content[0]->{layout}, 'stacked', 'layout stacked');
-is($content[1]->{layout}, 'stacked', 'layout stacked');
-
-#####################################################################
-# 6: move it to a different workspace, check that it ends up in a
-# stacked con
-#####################################################################
-
-my $otmp = get_unused_workspace;
-
-cmd "move workspace $otmp";
-
-@content = @{get_ws_content($tmp)};
-is(@content, 2, 'still two cons on this workspace');
-is($content[0]->{layout}, 'stacked', 'layout stacked');
-is($content[1]->{layout}, 'stacked', 'layout stacked');
-
-@content = @{get_ws_content($otmp)};
-is(@content, 1, 'one con on target workspace');
-is($content[0]->{layout}, 'stacked', 'layout stacked');
-
-exit_gracefully($process->pid);
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Verifies that i3 survives inplace restarts with fullscreen containers
-#
-use i3test;
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-
-my $x = X11::XCB::Connection->new;
-
-fresh_workspace;
-
-open_standard_window($x);
-open_standard_window($x);
-
-cmd 'layout stacking';
-sleep 1;
-
-cmd 'fullscreen';
-sleep 1;
-
-cmd 'restart';
-sleep 1;
-
-does_i3_live;
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Tests if the 'border toggle' command works correctly
-#
-use i3test;
-
-my $tmp = fresh_workspace;
-
-cmd 'open';
-
-my @nodes = @{get_ws_content($tmp)};
-is(@nodes, 1, 'one container on this workspace');
-is($nodes[0]->{border}, 'normal', 'border style normal');
-
-cmd 'border 1pixel';
-@nodes = @{get_ws_content($tmp)};
-is($nodes[0]->{border}, '1pixel', 'border style 1pixel');
-
-cmd 'border none';
-@nodes = @{get_ws_content($tmp)};
-is($nodes[0]->{border}, 'none', 'border style none');
-
-cmd 'border normal';
-@nodes = @{get_ws_content($tmp)};
-is($nodes[0]->{border}, 'normal', 'border style back to normal');
-
-cmd 'border toggle';
-@nodes = @{get_ws_content($tmp)};
-is($nodes[0]->{border}, 'none', 'border style none');
-
-cmd 'border toggle';
-@nodes = @{get_ws_content($tmp)};
-is($nodes[0]->{border}, '1pixel', 'border style 1pixel');
-
-cmd 'border toggle';
-@nodes = @{get_ws_content($tmp)};
-is($nodes[0]->{border}, 'normal', 'border style back to normal');
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
-#
-# Tests if the 'force_focus_wrapping' config directive works correctly.
-#
-use i3test;
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-
-my $x = X11::XCB::Connection->new;
-
-#####################################################################
-# 1: test the wrapping behaviour without force_focus_wrapping
-#####################################################################
-
-my $config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-EOT
-
-my $process = launch_with_config($config);
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-my $first = open_standard_window($x);
-my $second = open_standard_window($x);
-
-cmd 'layout tabbed';
-cmd 'focus parent';
-
-my $third = open_standard_window($x);
-is($x->input_focus, $third->id, 'third window focused');
-
-cmd 'focus left';
-is($x->input_focus, $second->id, 'second window focused');
-
-cmd 'focus left';
-is($x->input_focus, $first->id, 'first window focused');
-
-# now test the wrapping
-cmd 'focus left';
-is($x->input_focus, $second->id, 'second window focused');
-
-# but focusing right should not wrap now, but instead focus the third window
-cmd 'focus right';
-is($x->input_focus, $third->id, 'third window focused');
-
-exit_gracefully($process->pid);
-
-#####################################################################
-# 2: test the wrapping behaviour with force_focus_wrapping
-#####################################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-force_focus_wrapping true
-EOT
-
-$process = launch_with_config($config);
-
-$tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-
-$first = open_standard_window($x);
-$second = open_standard_window($x);
-
-cmd 'layout tabbed';
-cmd 'focus parent';
-
-$third = open_standard_window($x);
-is($x->input_focus, $third->id, 'third window focused');
-
-cmd 'focus left';
-is($x->input_focus, $second->id, 'second window focused');
-
-cmd 'focus left';
-is($x->input_focus, $first->id, 'first window focused');
-
-# now test the wrapping
-cmd 'focus left';
-is($x->input_focus, $second->id, 'second window focused');
-
-# focusing right should now be forced to wrap
-cmd 'focus right';
-is($x->input_focus, $first->id, 'first window focused');
-
-exit_gracefully($process->pid);
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
-#
-# Tests if i3-migrate-config-to-v4 correctly migrates all config file
-# directives and commands
-#
-use i3test;
-use Cwd qw(abs_path);
-use Proc::Background;
-use File::Temp qw(tempfile tempdir);
-use POSIX qw(getuid);
-use Data::Dumper;
-use v5.10;
-
-# reads in a whole file
-sub slurp {
- open my $fh, '<', shift;
- local $/;
- <$fh>;
-}
-
-sub migrate_config {
- my ($config) = @_;
-
- my ($fh, $tmpfile) = tempfile();
- print $fh $config;
- close($fh);
-
- my $cmd = "sh -c 'exec " . abs_path("../i3-migrate-config-to-v4") . " --v3 <$tmpfile'";
- return [ split /\n/, qx($cmd) ];
-}
-
-sub line_exists {
- my ($lines, $pattern) = @_;
-
- for my $line (@$lines) {
- return 1 if $line =~ $pattern;
- }
-
- return 0
-}
-
-#####################################################################
-# check that some directives remain untouched
-#####################################################################
-
-my $input = <<EOT;
- font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-EOT
-
-my $output = migrate_config($input);
-ok(line_exists($output, qr|font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1|), 'font directive unchanged');
-
-$input = <<EOT;
- floating_Modifier Mod1
- focus_follows_mouse true
- ipc-socket /tmp/i3-ipc.sock
- ipc_socket /tmp/i3-ipc.sock
- exec /usr/bin/i3
- set stuff Mod1
- assign "XTerm" → 3
- assign "XTerm" → ~5
- client.focused #2F343A #900000 #FFFFFF
- client.focused_inactive #FF0000 #FF0000 #FF0000
- client.unfocused #00FF00 #00FF00 #00FF00
- client.urgent #0000FF #0000FF #0000FF
- client.background #000000
-EOT
-
-$output = migrate_config($input);
-ok(line_exists($output, qr|^floating_Modifier Mod1$|), 'floating_modifier unchanged');
-ok(line_exists($output, qr|^focus_follows_mouse true$|), 'focus_follows_mouse unchanged');
-ok(line_exists($output, qr|^ipc-socket /tmp/i3-ipc.sock$|), 'ipc-socket unchanged');
-ok(line_exists($output, qr|^ipc_socket /tmp/i3-ipc.sock$|), 'ipc_socket unchanged');
-ok(line_exists($output, qr|^exec /usr/bin/i3|), 'exec unchanged');
-ok(line_exists($output, qr|^set stuff Mod1|), 'set unchanged');
-ok(line_exists($output, qr|^assign "XTerm" → 3|), 'assign unchanged');
-ok(line_exists($output, qr|^assign "XTerm" → ~5|), 'assign unchanged');
-ok(line_exists($output, qr|^client\.focused #2F343A #900000 #FFFFFF$|), 'client.focused unchanged');
-ok(line_exists($output, qr|^client\.focused_inactive #FF0000 #FF0000 #FF0000$|), 'client.focused_inactive unchanged');
-ok(line_exists($output, qr|^client\.unfocused #00FF00 #00FF00 #00FF00$|), 'client.unfocused unchanged');
-ok(line_exists($output, qr|^client\.urgent #0000FF #0000FF #0000FF$|), 'client.urgent unchanged');
-ok(line_exists($output, qr|^client\.background #000000$|), 'client.background unchanged');
-
-#####################################################################
-# check whether the bar colors get removed properly
-#####################################################################
-
-$input = <<EOT;
- bar.focused #FFFF00 #FFFF00 #FFFF00
- bar.unfocused #FFFF00 #FFFF00 #FFFF00
- bar.urgent #FFFF00 #FFFF00 #FFFF00
-EOT
-
-$output = migrate_config($input);
-ok(!line_exists($output, qr|^bar\.|), 'no bar. lines');
-ok(line_exists($output, qr|^#.*REMOVED bar|), 'note bar. removed');
-
-
-#####################################################################
-# check whether the other directives get converted correctly
-#####################################################################
-
-$input = <<EOT;
- new_container stacking
- workspace_bar no
- new_window bb
-EOT
-
-$output = migrate_config($input);
-ok(line_exists($output, qr|^workspace_layout stacking$|), 'new_container changed');
-ok(line_exists($output, qr|REMOVED workspace_bar|), 'workspace_bar removed');
-ok(!line_exists($output, qr|^workspace_bar|), 'no workspace_bar in the output');
-ok(line_exists($output, qr|^new_window none$|), 'new_window changed');
-
-#####################################################################
-# check whether new_window's parameters get changed correctly
-#####################################################################
-
-$output = migrate_config('new_window bb');
-ok(line_exists($output, qr|^new_window none$|), 'new_window bb changed');
-
-$output = migrate_config('new_window bn');
-ok(line_exists($output, qr|^new_window normal$|), 'new_window bn changed');
-
-$output = migrate_config('new_window bp');
-ok(line_exists($output, qr|^new_window 1pixel$|), 'new_window bp changed');
-
-#####################################################################
-# check that some commands remain untouched
-#####################################################################
-
-$input = <<EOT;
- bindsym Mod1+s exec /usr/bin/urxvt
- bindsym Mod1+s mark foo
- bindsym Mod1+s restart
- bindsym Mod1+s reload
- bindsym Mod1+s exit
- bindsym Mod1+s stack-limit cols 2
- bindsym Mod1+s stack-limit rows 3
- bind Mod1+c exec /usr/bin/urxvt
- mode "asdf" {
- bind 36 mode default
- }
-EOT
-
-$output = migrate_config($input);
-ok(line_exists($output, qr|^bindsym Mod1\+s exec /usr/bin/urxvt$|), 'exec unchanged');
-ok(line_exists($output, qr|^bindsym Mod1\+s mark foo$|), 'mark unchanged');
-ok(line_exists($output, qr|^bindsym Mod1\+s restart$|), 'restart unchanged');
-ok(line_exists($output, qr|^bindsym Mod1\+s reload$|), 'reload unchanged');
-ok(line_exists($output, qr|^bindsym Mod1\+s exit$|), 'exit unchanged');
-ok(line_exists($output, qr|^bindsym Mod1\+s stack-limit cols 2$|), 'stack-limit unchanged');
-ok(line_exists($output, qr|^bindsym Mod1\+s stack-limit rows 3$|), 'stack-limit unchanged');
-ok(line_exists($output, qr|^bindcode Mod1\+c exec /usr/bin/urxvt$|), 'bind changed to bindcode');
-ok(line_exists($output, qr|^mode "asdf" {$|), 'mode asdf unchanged');
-ok(line_exists($output, qr|^bindcode 36 mode \"default\"$|), 'mode default unchanged');
-ok(line_exists($output, qr|^}$|), 'closing mode bracket still there');
-
-#####################################################################
-# check the simple command replacements
-#####################################################################
-
-$input = <<EOT;
- bindsym Mod1+s s
- bindsym Mod1+s d
- bindsym Mod1+s T
-
- bindsym Mod1+s f
- bindsym Mod1+s fg
-
- bindsym Mod1+s t
-
- bindsym Mod1+s h
- bindsym Mod1+s j
- bindsym Mod1+s k
- bindsym Mod1+s l
-
- bindsym Mod1+s mh
- bindsym Mod1+s mj
- bindsym Mod1+s mk
- bindsym Mod1+s ml
-
- bindsym Mod1+s bn
- bindsym Mod1+s bp
- bindsym Mod1+s bb
- bindsym Mod1+s bt
-
- bindsym Mod1+j wch
- bindsym Mod1+j wcml
-
- bindsym Mod1+k kill
-
- bindsym Mod1+n nw
- bindsym Mod1+p pw
-EOT
-
-$output = migrate_config($input);
-ok(line_exists($output, qr|^bindsym Mod1\+s layout stacking$|), 's replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s layout default$|), 'd replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s layout tabbed$|), 'T replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s fullscreen$|), 'f replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s fullscreen global$|), 'fg replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s floating toggle$|), 't replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s focus left$|), 'h replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s focus down$|), 'j replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s focus up$|), 'k replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s focus right$|), 'l replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s move left$|), 'mh replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s move down$|), 'mj replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s move up$|), 'mk replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s move right$|), 'ml replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s border normal$|), 'bn replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s border 1pixel$|), 'bp replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s border none$|), 'bb replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s border toggle$|), 'bt replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+j focus parent; focus left$|), 'with container replaced with focus parent; focus left');
-ok(line_exists($output, qr|^bindsym Mod1\+j focus parent; move right$|), 'with container replaced with focus parent; move right');
-ok(line_exists($output, qr|^bindsym Mod1\+k kill$|), 'kill unchanged');
-ok(line_exists($output, qr|^bindsym Mod1\+n workspace next$|), 'nw replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+p workspace prev$|), 'pw replaced');
-
-#####################################################################
-# check more advanced replacements
-#####################################################################
-
-$input = <<EOT;
- bindsym Mod1+s goto foo
-EOT
-
-$output = migrate_config($input);
-ok(line_exists($output, qr|^bindsym Mod1\+s \[con_mark="foo"\] focus$|), 'goto replaced');
-
-#####################################################################
-# check whether focus's parameters get changed correctly
-#####################################################################
-
-$output = migrate_config('bindsym Mod1+f focus 3');
-ok(line_exists($output, qr|^#.*focus.*obsolete.*focus 3$|), 'focus [number] gone');
-
-$output = migrate_config('bindsym Mod1+f focus floating');
-ok(line_exists($output, qr|^bindsym Mod1\+f focus floating$|), 'focus floating unchanged');
-
-$output = migrate_config('bindsym Mod1+f focus tiling');
-ok(line_exists($output, qr|^bindsym Mod1\+f focus tiling$|), 'focus tiling unchanged');
-
-$output = migrate_config('bindsym Mod1+f focus ft');
-ok(line_exists($output, qr|^bindsym Mod1\+f focus mode_toggle$|), 'focus ft changed');
-
-#####################################################################
-# check whether resize's parameters get changed correctly
-#####################################################################
-
-$output = migrate_config('bindsym Mod1+f resize left +10');
-ok(line_exists($output, qr|^bindsym Mod1\+f resize grow left 10 px$|), 'resize left changed');
-
-$output = migrate_config('bindsym Mod1+f resize top -20');
-ok(line_exists($output, qr|^bindsym Mod1\+f resize shrink up 20 px$|), 'resize top changed');
-
-$output = migrate_config('bindsym Mod1+f resize right -20');
-ok(line_exists($output, qr|^bindsym Mod1\+f resize shrink right 20 px$|), 'resize right changed');
-
-$output = migrate_config('bindsym Mod1+f resize bottom +23');
-ok(line_exists($output, qr|^bindsym Mod1\+f resize grow down 23 px$|), 'resize bottom changed');
-
-#####################################################################
-# also resizing, but with indention this time
-#####################################################################
-
-$output = migrate_config("bindsym Mod1+f resize left \t +10");
-ok(line_exists($output, qr|^bindsym Mod1\+f resize grow left 10 px$|), 'resize left changed');
-
-#####################################################################
-# check whether jump's parameters get changed correctly
-#####################################################################
-
-$output = migrate_config('bindsym Mod1+f jump 3');
-ok(line_exists($output, qr|^#.*obsolete.*jump 3$|), 'jump to workspace removed');
-
-$output = migrate_config('bindsym Mod1+f jump 3 4 5');
-ok(line_exists($output, qr|^#.*obsolete.*jump 3 4 5$|), 'jump to workspace + col/row removed');
-
-$output = migrate_config('bindsym Mod1+f jump "XTerm"');
-ok(line_exists($output, qr|^bindsym Mod1\+f \[class="XTerm"\] focus$|), 'jump changed');
-
-$output = migrate_config('bindsym Mod1+f jump "XTerm/irssi"');
-ok(line_exists($output, qr|^bindsym Mod1\+f \[class="XTerm" title="irssi"\] focus$|), 'jump changed');
-
-#####################################################################
-# check whether workspace commands are handled correctly
-#####################################################################
-
-$output = migrate_config('workspace 3 output VGA-1');
-ok(line_exists($output, qr|^workspace 3 output VGA-1$|), 'workspace assignment unchanged');
-
-$output = migrate_config('workspace 3 work');
-ok(!line_exists($output, qr|^workspace|), 'workspace name not present');
-ok(line_exists($output, qr|#.*workspace name.*bindings|), 'note present');
-
-$input = <<EOT;
- workspace 3 work
- bindsym Mod1+3 3
-EOT
-$output = migrate_config($input);
-ok(!line_exists($output, qr|^workspace|), 'workspace name not present');
-ok(line_exists($output, qr|^bindsym Mod1\+3 workspace work|), 'named workspace in bindings');
-
-# The same, but in reverse order
-$input = <<EOT;
- bindsym Mod1+3 3
- workspace 3 work
-EOT
-$output = migrate_config($input);
-ok(!line_exists($output, qr|^workspace|), 'workspace name not present');
-ok(line_exists($output, qr|^bindsym Mod1\+3 workspace work|), 'named workspace in bindings');
-
-$output = migrate_config('bindsym Mod1+3 3');
-ok(line_exists($output, qr|^bindsym Mod1\+3 workspace 3|), 'workspace changed');
-
-$output = migrate_config('bindsym Mod1+3 m3');
-ok(line_exists($output, qr|^bindsym Mod1\+3 move workspace 3|), 'move workspace changed');
-
-$input = <<EOT;
- workspace 3 work
- bindsym Mod1+3 m3
-EOT
-$output = migrate_config($input);
-ok(!line_exists($output, qr|^workspace|), 'workspace name not present');
-ok(line_exists($output, qr|^bindsym Mod1\+3 move workspace work|), 'move to named workspace in bindings');
-
-#####################################################################
-# check whether an i3bar call is added if the workspace bar bar was enabled
-#####################################################################
-
-$output = migrate_config('');
-ok(line_exists($output, qr|i3bar|), 'i3bar added');
-
-$output = migrate_config('workspace_bar enable');
-ok(line_exists($output, qr|i3bar|), 'i3bar added');
-
-$output = migrate_config('workspace_bar no');
-ok(!line_exists($output, qr|i3bar|), 'no i3bar added');
-
-#####################################################################
-# check whether the mode command gets quotes
-#####################################################################
-
-$output = migrate_config('bindsym Mod1+m mode foobar');
-ok(line_exists($output, qr|^bindsym Mod1\+m mode "foobar"|), 'mode got quotes');
-
-done_testing();
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
-#
-# checks if i3 starts up on workspace '1' or the first configured named workspace
-#
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-use i3test;
-
-my $x = X11::XCB::Connection->new;
-
-##############################################################
-# 1: i3 should start with workspace '1'
-##############################################################
-
-my $config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-EOT
-
-my $process = launch_with_config($config);
-
-my @names = @{get_workspace_names()};
-cmp_deeply(\@names, [ '1' ], 'i3 starts on workspace 1 without any configuration');
-
-exit_gracefully($process->pid);
-
-##############################################################
-# 2: with named workspaces, i3 should start on the first named one
-##############################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-
-bindsym Mod1+1 workspace foobar
-EOT
-
-$process = launch_with_config($config);
-
-my @names = @{get_workspace_names()};
-cmp_deeply(\@names, [ 'foobar' ], 'i3 starts on named workspace foobar');
-
-exit_gracefully($process->pid);
-
-##############################################################
-# 3: the same test as 2, but with a quoted workspace name
-##############################################################
-
-$config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-
-bindsym Mod1+1 workspace "foobar"
-EOT
-
-$process = launch_with_config($config);
-
-my @names = @{get_workspace_names()};
-cmp_deeply(\@names, [ 'foobar' ], 'i3 starts on named workspace foobar');
-
-exit_gracefully($process->pid);
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
-#
-# Regression: Checks if focus is stolen when a window is managed which is
-# assigned to an invisible workspace
-#
-use i3test;
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-use v5.10;
-
-my $x = X11::XCB::Connection->new;
-
-# TODO: move to X11::XCB
-sub set_wm_class {
- my ($id, $class, $instance) = @_;
-
- # Add a _NET_WM_STRUT_PARTIAL hint
- my $atomname = $x->atom(name => 'WM_CLASS');
- my $atomtype = $x->atom(name => 'STRING');
-
- $x->change_property(
- PROP_MODE_REPLACE,
- $id,
- $atomname->id,
- $atomtype->id,
- 8,
- length($class) + length($instance) + 2,
- "$instance\x00$class\x00"
- );
-}
-
-
-#####################################################################
-# start a window and see that it does not get assigned with an empty config
-#####################################################################
-
-my $config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-assign "special" → targetws
-EOT
-
-my $process = launch_with_config($config);
-
-my $tmp = fresh_workspace;
-
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-ok(get_ws($tmp)->{focused}, 'current workspace focused');
-
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#0000ff',
-);
-
-$window->_create;
-set_wm_class($window->id, 'special', 'special');
-$window->name('special window');
-$window->map;
-sleep 0.25;
-
-
-ok(@{get_ws_content($tmp)} == 0, 'special window not on current workspace');
-ok(@{get_ws_content('targetws')} == 1, 'special window on targetws');
-ok(get_ws($tmp)->{focused}, 'current workspace still focused');
-
-#####################################################################
-# the same test, but with a floating window
-#####################################################################
-
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#0000ff',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
-);
-
-$window->_create;
-set_wm_class($window->id, 'special', 'special');
-$window->name('special window');
-$window->map;
-sleep 0.25;
-
-
-ok(@{get_ws_content($tmp)} == 0, 'special window not on current workspace');
-ok(@{get_ws_content('targetws')} == 1, 'special window on targetws');
-ok(get_ws($tmp)->{focused}, 'current workspace still focused');
-
-exit_gracefully($process->pid);
-
-$window->destroy;
-
-done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression: Checks if i3 still lives after using 'focus mode_toggle' on an
-# empty workspace. This regression was fixed in
-# 0848844f2d41055f6ffc69af1149d7a873460976.
-#
-use i3test;
-use v5.10;
-
-my $tmp = fresh_workspace;
-
-cmd 'focus mode_toggle';
-
-does_i3_live;
-
-done_testing;
+++ /dev/null
-package i3test;
-# vim:ts=4:sw=4:expandtab
-
-use File::Temp qw(tmpnam tempfile tempdir);
-use Test::Builder;
-use X11::XCB::Rect;
-use X11::XCB::Window;
-use X11::XCB qw(:all);
-use AnyEvent::I3;
-use List::Util qw(first);
-use List::MoreUtils qw(lastval);
-use Time::HiRes qw(sleep);
-use Try::Tiny;
-use Cwd qw(abs_path);
-use Proc::Background;
-
-use v5.10;
-
-use Exporter ();
-our @EXPORT = qw(get_workspace_names get_unused_workspace fresh_workspace get_ws_content get_ws get_focused open_empty_con open_standard_window get_dock_clients cmd does_i3_live exit_gracefully workspace_exists focused_ws get_socket_path launch_with_config);
-
-my $tester = Test::Builder->new();
-my $_cached_socket_path = undef;
-my $tmp_socket_path = undef;
-
-BEGIN {
- my $window_count = 0;
- sub counter_window {
- return $window_count++;
- }
-}
-
-sub import {
- my $class = shift;
- my $pkg = caller;
- eval "package $pkg;
-use Test::Most" . (@_ > 0 ? " qw(@_)" : "") . ";
-use Data::Dumper;
-use AnyEvent::I3;
-use Time::HiRes qw(sleep);
-use Test::Deep qw(eq_deeply cmp_deeply cmp_set cmp_bag cmp_methods useclass noclass set bag subbagof superbagof subsetof supersetof superhashof subhashof bool str arraylength Isa ignore methods regexprefonly regexpmatches num regexponly scalref reftype hashkeysonly blessed array re hash regexpref hash_each shallow array_each code arrayelementsonly arraylengthonly scalarrefonly listmethods any hashkeys isa);
-use v5.10;
-use strict;
-use warnings;
-";
- @_ = ($class);
- goto \&Exporter::import;
-}
-
-sub open_standard_window {
- my ($x, $color) = @_;
-
- $color ||= '#c0c0c0';
-
- my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => $color,
- );
-
- $window->name('Window ' . counter_window());
- $window->map;
-
- sleep(0.25);
-
- return $window;
-}
-
-sub open_empty_con {
- my ($i3) = @_;
-
- my $reply = $i3->command('open')->recv;
- return $reply->{id};
-}
-
-sub get_workspace_names {
- my $i3 = i3(get_socket_path());
- my $tree = $i3->get_tree->recv;
- my @outputs = @{$tree->{nodes}};
- my @cons;
- for my $output (@outputs) {
- # get the first CT_CON of each output
- my $content = first { $_->{type} == 2 } @{$output->{nodes}};
- @cons = (@cons, @{$content->{nodes}});
- }
- [ map { $_->{name} } @cons ]
-}
-
-sub get_unused_workspace {
- my @names = get_workspace_names();
- my $tmp;
- do { $tmp = tmpnam() } while ($tmp ~~ @names);
- $tmp
-}
-
-sub fresh_workspace {
- my $unused = get_unused_workspace;
- cmd("workspace $unused");
- $unused
-}
-
-sub get_ws {
- my ($name) = @_;
- my $i3 = i3(get_socket_path());
- my $tree = $i3->get_tree->recv;
-
- my @outputs = @{$tree->{nodes}};
- my @workspaces;
- for my $output (@outputs) {
- # get the first CT_CON of each output
- my $content = first { $_->{type} == 2 } @{$output->{nodes}};
- @workspaces = (@workspaces, @{$content->{nodes}});
- }
-
- # as there can only be one workspace with this name, we can safely
- # return the first entry
- return first { $_->{name} eq $name } @workspaces;
-}
-
-#
-# returns the content (== tree, starting from the node of a workspace)
-# of a workspace. If called in array context, also includes the focus
-# stack of the workspace
-#
-sub get_ws_content {
- my ($name) = @_;
- my $con = get_ws($name);
- return wantarray ? ($con->{nodes}, $con->{focus}) : $con->{nodes};
-}
-
-sub get_focused {
- my ($ws) = @_;
- my $con = get_ws($ws);
-
- my @focused = @{$con->{focus}};
- my $lf;
- while (@focused > 0) {
- $lf = $focused[0];
- last unless defined($con->{focus});
- @focused = @{$con->{focus}};
- @cons = grep { $_->{id} == $lf } (@{$con->{nodes}}, @{$con->{'floating_nodes'}});
- $con = $cons[0];
- }
-
- return $lf;
-}
-
-sub get_dock_clients {
- my $which = shift;
-
- my $tree = i3(get_socket_path())->get_tree->recv;
- my @outputs = @{$tree->{nodes}};
- # Children of all dockareas
- my @docked;
- for my $output (@outputs) {
- if (!defined($which)) {
- @docked = (@docked, map { @{$_->{nodes}} }
- grep { $_->{type} == 5 }
- @{$output->{nodes}});
- } elsif ($which eq 'top') {
- my $first = first { $_->{type} == 5 } @{$output->{nodes}};
- @docked = (@docked, @{$first->{nodes}});
- } elsif ($which eq 'bottom') {
- my $last = lastval { $_->{type} == 5 } @{$output->{nodes}};
- @docked = (@docked, @{$last->{nodes}});
- }
- }
- return @docked;
-}
-
-sub cmd {
- i3(get_socket_path())->command(@_)->recv
-}
-
-sub workspace_exists {
- my ($name) = @_;
- ($name ~~ @{get_workspace_names()})
-}
-
-sub focused_ws {
- my $i3 = i3(get_socket_path());
- my $tree = $i3->get_tree->recv;
- my @outputs = @{$tree->{nodes}};
- my @cons;
- for my $output (@outputs) {
- # get the first CT_CON of each output
- my $content = first { $_->{type} == 2 } @{$output->{nodes}};
- my $first = first { $_->{fullscreen_mode} == 1 } @{$content->{nodes}};
- return $first->{name}
- }
-}
-
-sub does_i3_live {
- my $tree = i3(get_socket_path())->get_tree->recv;
- my @nodes = @{$tree->{nodes}};
- my $ok = (@nodes > 0);
- $tester->ok($ok, 'i3 still lives');
- return $ok;
-}
-
-# Tries to exit i3 gracefully (with the 'exit' cmd) or kills the PID if that fails
-sub exit_gracefully {
- my ($pid, $socketpath) = @_;
- $socketpath ||= get_socket_path();
-
- my $exited = 0;
- try {
- say "Exiting i3 cleanly...";
- i3($socketpath)->command('exit')->recv;
- $exited = 1;
- };
-
- if (!$exited) {
- kill(9, $pid) or die "could not kill i3";
- }
-}
-
-# Gets the socket path from the I3_SOCKET_PATH atom stored on the X11 root window
-sub get_socket_path {
- my ($cache) = @_;
- $cache ||= 1;
-
- if ($cache && defined($_cached_socket_path)) {
- return $_cached_socket_path;
- }
-
- my $x = X11::XCB::Connection->new;
- my $atom = $x->atom(name => 'I3_SOCKET_PATH');
- my $cookie = $x->get_property(0, $x->get_root_window(), $atom->id, GET_PROPERTY_TYPE_ANY, 0, 256);
- my $reply = $x->get_property_reply($cookie->{sequence});
- my $socketpath = $reply->{value};
- $_cached_socket_path = $socketpath;
- return $socketpath;
-}
-
-#
-# launches a new i3 process with the given string as configuration file.
-# useful for tests which test specific config file directives.
-#
-# be sure to use !NO_I3_INSTANCE! somewhere in the file to signal
-# complete-run.pl that it should not create an instance of i3
-#
-sub launch_with_config {
- my ($config) = @_;
-
- if (!defined($tmp_socket_path)) {
- $tmp_socket_path = File::Temp::tempnam('/tmp', 'i3-test-socket-');
- }
-
- my ($fh, $tmpfile) = tempfile('i3-test-config-XXXXX', UNLINK => 1);
- say $fh $config;
- say $fh "ipc-socket $tmp_socket_path";
- close($fh);
-
- my $i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null";
- my $process = Proc::Background->new($i3cmd);
- sleep 1;
-
- # force update of the cached socket path in lib/i3test
- get_socket_path(0);
-
- return $process;
-}
-
-1