*.o
tags
-include/loglevels.h
include/GENERATED_*.h
-loglevels.tmp
*.swp
*.gcda
*.gcno
i3-msg/i3-msg
i3-config-wizard/i3-config-wizard
i3-dump-log/i3-dump-log
-libi3/libi3.a
+libi3.a
docs/*.pdf
include $(TOPDIR)/common.mk
-# Depend on the object files of all source-files in src/*.c and on all header files
-AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c
-FILES:=$(filter-out $(AUTOGENERATED),$(wildcard src/*.c))
-FILES:=$(FILES:.c=.o)
-HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h))
-CMDPARSE_HEADERS:=include/GENERATED_call.h include/GENERATED_enums.h include/GENERATED_tokens.h
+SUBDIRS:=
-# Recursively generate loglevels.h by explicitly calling make
-# We need this step because we need to ensure that loglevels.h will be
-# updated if necessary, but we also want to save rebuilds of the object
-# files, so we cannot let the object files depend on loglevels.h.
-ifeq ($(MAKECMDGOALS),loglevels.h)
-#UNUSED:=$(warning Generating loglevels.h)
-else
-UNUSED:=$(shell $(MAKE) loglevels.h)
-endif
+ALL_TARGETS =
+INSTALL_TARGETS =
+CLEAN_TARGETS =
+DISTCLEAN_TARGETS =
-SUBDIRS:=i3-msg i3-input i3-nagbar i3-config-wizard i3bar i3-dump-log
+all: real-all
-# Depend on the specific file (.c for each .o) and on all headers
-src/%.o: src/%.c ${HEADERS}
- echo "[i3] CC $<"
- $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR; exit 0; }' loglevels.tmp))" -c -o $@ $<
+include libi3/libi3.mk
+include src/i3.mk
+include i3-config-wizard/i3-config-wizard.mk
+include i3-msg/i3-msg.mk
+include i3-input/i3-input.mk
+include i3-nagbar/i3-nagbar.mk
+include i3bar/i3bar.mk
+include i3-dump-log/i3-dump-log.mk
+include docs/docs.mk
+include man/man.mk
-all: i3 subdirs
+real-all: $(ALL_TARGETS)
-i3: libi3/libi3.a src/cfgparse.y.o src/cfgparse.yy.o ${FILES}
- echo "[i3] LINK i3"
- $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS)
-
-libi3/%.a: libi3/*.c
- $(MAKE) -C libi3
-
-subdirs:
- for dir in $(SUBDIRS); do \
- echo ""; \
- echo "MAKE $$dir"; \
- $(MAKE) -C $$dir; \
- done
-
-loglevels.h:
- echo "[i3] LOGLEVELS"
- for file in $$(ls src/*.c src/*.y src/*.l | grep -v 'cfgparse.\(tab\|yy\).c'); \
- do \
- echo $$(basename $$file .c); \
- done > loglevels.tmp
- (echo "char *loglevels[] = {"; for file in $$(cat loglevels.tmp); \
- do \
- echo "\"$$file\", "; \
- done; \
- echo "};") > include/loglevels.h;
-
-# The GENERATED_* files are actually all created from a single pass, so all
-# files just depend on the first one.
-include/GENERATED_call.h: generate-command-parser.pl parser-specs/commands.spec
- echo "[i3] Generating command parser"
- (cd include; ../generate-command-parser.pl)
-include/GENERATED_enums.h: include/GENERATED_call.h
-include/GENERATED_tokens.h: include/GENERATED_call.h
-
-# This target compiles the command parser twice:
-# Once with -DTEST_PARSER, creating a stand-alone executable used for tests,
-# and once as an object file for i3.
-src/commands_parser.o: src/commands_parser.c ${HEADERS} ${CMDPARSE_HEADERS}
- echo "[i3] CC $<"
- $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -DTEST_PARSER -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR; exit 0; }' loglevels.tmp))" -o test.commands_parser $< $(LIBS)
- $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR; exit 0; }' loglevels.tmp))" -c -o $@ $<
-
-src/cfgparse.yy.o: src/cfgparse.l src/cfgparse.y.o ${HEADERS}
- echo "[i3] LEX $<"
- $(FLEX) -i -o$(@:.o=.c) $<
- $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c)
-
-
-src/cfgparse.y.o: src/cfgparse.y ${HEADERS}
- echo "[i3] YACC $<"
- $(BISON) --debug --verbose -b $(basename $< .y) -d $<
- $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c)
-
-
-install: all
- echo "[i3] INSTALL"
- $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
- $(INSTALL) -d -m 0755 $(DESTDIR)$(SYSCONFDIR)/i3
- $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/include/i3
- $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/xsessions
- $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/applications
- $(INSTALL) -m 0755 i3 $(DESTDIR)$(PREFIX)/bin/
- $(INSTALL) -m 0755 i3-migrate-config-to-v4 $(DESTDIR)$(PREFIX)/bin/
- $(INSTALL) -m 0755 i3-sensible-editor $(DESTDIR)$(PREFIX)/bin/
- $(INSTALL) -m 0755 i3-sensible-pager $(DESTDIR)$(PREFIX)/bin/
- $(INSTALL) -m 0755 i3-sensible-terminal $(DESTDIR)$(PREFIX)/bin/
- test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config
- test -e $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes || $(INSTALL) -m 0644 i3.config.keycodes $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes
- $(INSTALL) -m 0644 i3.xsession.desktop $(DESTDIR)$(PREFIX)/share/xsessions/i3.desktop
- $(INSTALL) -m 0644 i3.applications.desktop $(DESTDIR)$(PREFIX)/share/applications/i3.desktop
- $(INSTALL) -m 0644 include/i3/ipc.h $(DESTDIR)$(PREFIX)/include/i3/
- for dir in $(SUBDIRS); do \
- $(MAKE) -C $$dir install; \
- done
+install: $(INSTALL_TARGETS)
dist: distclean
[ ! -d i3-${VERSION} ] || rm -rf i3-${VERSION}
[ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2
mkdir i3-${VERSION}
- cp i3-migrate-config-to-v4 generate-command-parser.pl i3-sensible-* i3.config.keycodes DEPENDS LICENSE PACKAGE-MAINTAINER RELEASE-NOTES-${VERSION} i3.config i3.xsession.desktop i3.applications.desktop pseudo-doc.doxygen Makefile i3-${VERSION}
+ cp i3-migrate-config-to-v4 generate-command-parser.pl i3-sensible-* i3.config.keycodes DEPENDS LICENSE PACKAGE-MAINTAINER RELEASE-NOTES-${VERSION} i3.config i3.xsession.desktop i3.applications.desktop pseudo-doc.doxygen common.mk Makefile i3-${VERSION}
cp -r src libi3 i3-msg i3-nagbar i3-config-wizard i3bar i3-dump-log yajl-fallback include man parser-specs i3-${VERSION}
# Only copy toplevel documentation (important stuff)
mkdir i3-${VERSION}/docs
# Pre-generate documentation
- $(MAKE) -C docs
- $(MAKE) -C i3bar/doc
+ $(MAKE) docs
# Cleanup τεχ output files
find docs -regex ".*\.\(aux\|out\|log\|toc\|bm\|dvi\|log\)" -exec rm '{}' \;
find docs -maxdepth 1 -type f ! \( -name "*.xcf" -or -name "*.svg" \) -exec cp '{}' i3-${VERSION}/docs \;
# Only copy source code from i3-input
mkdir i3-${VERSION}/i3-input
- find i3-input -maxdepth 1 -type f \( -name "*.c" -or -name "*.h" -or -name "Makefile" \) -exec cp '{}' i3-${VERSION}/i3-input \;
- sed -e 's/^GIT_VERSION:=\(.*\)/GIT_VERSION:=$(shell /bin/echo '${GIT_VERSION}' | sed 's/\\/\\\\/g')/g;s/^VERSION:=\(.*\)/VERSION:=${VERSION}/g' common.mk > i3-${VERSION}/common.mk
+ find i3-input -maxdepth 1 -type f \( -name "*.c" -or -name "*.mk" -or -name "*.h" -or -name "Makefile" \) -exec cp '{}' i3-${VERSION}/i3-input \;
+ echo -n ${I3_VERSION} > i3-${VERSION}/VERSION
# Pre-generate a manpage to allow distributors to skip this step and save some dependencies
- $(MAKE) -C man
+ $(MAKE) mans
cp man/*.1 i3-${VERSION}/man/
- cp i3bar/doc/*.1 i3-${VERSION}/i3bar/doc/
tar cfj i3-${VERSION}.tar.bz2 i3-${VERSION}
rm -rf i3-${VERSION}
-clean:
- rm -f src/*.o src/*.gcno src/cmdparse.* src/cfgparse.tab.{c,h} src/cfgparse.yy.c src/cfgparse.{output,dot} loglevels.tmp include/loglevels.h include/GENERATED_*
+clean: $(CLEAN_TARGETS)
(which lcov >/dev/null 2>&1 && lcov -d . --zerocounters) || true
- $(MAKE) -C libi3 clean
- $(MAKE) -C docs clean
- $(MAKE) -C man clean
- for dir in $(SUBDIRS); do \
- echo ""; \
- echo "CLEAN $$dir"; \
- $(MAKE) TOPDIR=$(TOPDIR) -C $$dir distclean; \
- done
-distclean: clean
- rm -f i3
- for dir in $(SUBDIRS); do \
- echo ""; \
- echo "DISTCLEAN $$dir"; \
- $(MAKE) TOPDIR=$(TOPDIR) -C $$dir distclean; \
- done
+distclean: clean $(DISTCLEAN_TARGETS)
coverage:
rm -f /tmp/i3-coverage.info
SYSCONFDIR=$(PREFIX)/etc
endif
endif
-# The escaping is absurd, but we need to escape for shell, sed, make, define
-GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch $(shell [ -f $(TOPDIR)/.git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' $(TOPDIR)/.git/HEAD || echo 'unknown'))"
-VERSION:=$(shell git describe --tags --abbrev=0)
+
+I3_VERSION := '$(shell [ -f $(TOPDIR)/VERSION ] && cat $(TOPDIR)/VERSION)'
+ifeq ('',$(I3_VERSION))
+VERSION := $(shell git describe --tags --abbrev=0)
+I3_VERSION := '$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch \"$(shell git describe --tags --always --all | sed s:heads/::)\")'
+else
+VERSION := ${I3_VERSION}
+endif
+
+
+## Generic flags
+
+# Default CFLAGS that users should be able to override
+ifeq ($(DEBUG),1)
+# Extended debugging flags, macros shall be available in gcc
+CFLAGS ?= -pipe -gdwarf-2 -g3
+else
+CFLAGS ?= -pipe -O2 -freorder-blocks-and-partition
+endif
+
+# Default LDFLAGS that users should be able to override
+LDFLAGS ?= $(as_needed_LDFLAG)
+
+# Common CFLAGS for all i3 related binaries
+I3_CFLAGS = -std=c99
+I3_CFLAGS += -Wall
+# unused-function, unused-label, unused-variable are turned on by -Wall
+# We don’t want unused-parameter because of the use of many callbacks
+I3_CFLAGS += -Wunused-value
+I3_CFLAGS += -Iinclude
+
+I3_CPPFLAGS = -DI3_VERSION=\"${I3_VERSION}\"
+I3_CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\"
+
+
+## Libraries flags
ifeq ($(shell which pkg-config 2>/dev/null 1>/dev/null || echo 1),1)
$(error "pkg-config was not found")
cflags_for_lib = $(shell pkg-config --silence-errors --cflags $(1) 2>/dev/null)
ldflags_for_lib = $(shell pkg-config --exists 2>/dev/null $(1) && pkg-config --libs $(1) 2>/dev/null || echo -l$(2))
-CFLAGS += -std=c99
-CFLAGS += -pipe
-CFLAGS += -Wall
-# unused-function, unused-label, unused-variable are turned on by -Wall
-# We don’t want unused-parameter because of the use of many callbacks
-CFLAGS += -Wunused-value
-CFLAGS += -Iinclude
-CFLAGS += $(call cflags_for_lib, xcb-keysyms)
+# XCB common stuff
+XCB_CFLAGS := $(call cflags_for_lib, xcb)
+XCB_CFLAGS += $(call cflags_for_lib, xcb-event)
+XCB_LIBS := $(call ldflags_for_lib, xcb,xcb)
+XCB_LIBS += $(call ldflags_for_lib, xcb-event,xcb-event)
ifeq ($(shell pkg-config --exists xcb-util 2>/dev/null || echo 1),1)
-CPPFLAGS += -DXCB_COMPAT
-CFLAGS += $(call cflags_for_lib, xcb-atom)
-CFLAGS += $(call cflags_for_lib, xcb-aux)
+XCB_CFLAGS += $(call cflags_for_lib, xcb-atom)
+XCB_CFLAGS += $(call cflags_for_lib, xcb-aux)
+XCB_LIBS += $(call ldflags_for_lib, xcb-atom,xcb-atom)
+XCB_LIBS += $(call ldflags_for_lib, xcb-aux,xcb-aux)
else
-CFLAGS += $(call cflags_for_lib, xcb-util)
+XCB_CFLAGS += $(call cflags_for_lib, xcb-util)
+XCB_LIBS += $(call ldflags_for_lib, xcb-util)
endif
-CFLAGS += $(call cflags_for_lib, xcb-icccm)
-CFLAGS += $(call cflags_for_lib, xcb-xinerama)
-CFLAGS += $(call cflags_for_lib, xcb-randr)
-CFLAGS += $(call cflags_for_lib, xcb)
-CFLAGS += $(call cflags_for_lib, xcursor)
-CFLAGS += $(call cflags_for_lib, x11)
-CFLAGS += $(call cflags_for_lib, yajl)
-CFLAGS += $(call cflags_for_lib, libev)
-CFLAGS += $(call cflags_for_lib, libpcre)
-CFLAGS += $(call cflags_for_lib, libstartup-notification-1.0)
-CPPFLAGS += -DI3_VERSION=\"${GIT_VERSION}\"
-CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\"
+# XCB keyboard stuff
+XCB_KBD_CFLAGS := $(call cflags_for_lib, xcb-keysyms)
+XCB_KBD_LIBS := $(call ldflags_for_lib, xcb-keysyms,xcb-keysyms)
+
+# XCB WM stuff
+XCB_WM_CFLAGS := $(call cflags_for_lib, xcb-icccm)
+XCB_WM_CFLAGS += $(call cflags_for_lib, xcb-xinerama)
+XCB_WM_CFLAGS += $(call cflags_for_lib, xcb-randr)
+XCB_WM_LIBS := $(call ldflags_for_lib, xcb-icccm,xcb-icccm)
+XCB_WM_LIBS += $(call ldflags_for_lib, xcb-xinerama,xcb-xinerama)
+XCB_WM_LIBS += $(call ldflags_for_lib, xcb-randr,xcb-randr)
+
+# Xlib
+X11_CFLAGS := $(call cflags_for_lib, x11)
+X11_LIBS := $(call ldflags_for_lib, x11,X11)
+
+# Xcursor
+XCURSOR_CFLAGS := $(call cflags_for_lib, xcursor)
+XCURSOR_LIBS := $(call ldflags_for_lib, xcursor,Xcursor)
+
+# yajl
+YAJL_CFLAGS := $(call cflags_for_lib, yajl)
+# Fallback for libyajl 1 which did not include yajl_version.h. We need
+# YAJL_MAJOR from that file to decide which code path should be used.
+YAJL_CFLAGS += -idirafter $(TOPDIR)/yajl-fallback
+YAJL_LIBS := $(call ldflags_for_lib, yajl,yajl)
+
+#libev
+LIBEV_CFLAGS := $(call cflags_for_lib, libev)
+LIBEV_LIBS := $(call ldflags_for_lib, libev,ev)
+
+# libpcre
+PCRE_CFLAGS := $(call cflags_for_lib, libpcre)
ifeq ($(shell pkg-config --atleast-version=8.10 libpcre 2>/dev/null && echo 1),1)
-CPPFLAGS += -DPCRE_HAS_UCP=1
+I3_CPPFLAGS += -DPCRE_HAS_UCP=1
endif
+PCRE_LIBS := $(call ldflags_for_lib, libpcre,pcre)
-LIBS += -lm
-# Darwin (Mac OS X) doesn’t have librt
-ifneq ($(UNAME),Darwin)
-LIBS += -lrt
-endif
-LIBS += -L $(TOPDIR)/libi3 -li3
-LIBS += $(call ldflags_for_lib, xcb-event,xcb-event)
-LIBS += $(call ldflags_for_lib, xcb-keysyms,xcb-keysyms)
-ifeq ($(shell pkg-config --exists xcb-util 2>/dev/null || echo 1),1)
-LIBS += $(call ldflags_for_lib, xcb-atom,xcb-atom)
-LIBS += $(call ldflags_for_lib, xcb-aux,xcb-aux)
-else
-LIBS += $(call ldflags_for_lib, xcb-util)
-endif
-LIBS += $(call ldflags_for_lib, xcb-icccm,xcb-icccm)
-LIBS += $(call ldflags_for_lib, xcb-xinerama,xcb-xinerama)
-LIBS += $(call ldflags_for_lib, xcb-randr,xcb-randr)
-LIBS += $(call ldflags_for_lib, xcb,xcb)
-LIBS += $(call ldflags_for_lib, xcursor,Xcursor)
-LIBS += $(call ldflags_for_lib, x11,X11)
-LIBS += $(call ldflags_for_lib, yajl,yajl)
-LIBS += $(call ldflags_for_lib, libev,ev)
-LIBS += $(call ldflags_for_lib, libpcre,pcre)
-LIBS += $(call ldflags_for_lib, libstartup-notification-1.0,startup-notification-1)
+# startup-notification
+LIBSN_CFLAGS := $(call cflags_for_lib, libstartup-notification-1.0)
+LIBSN_LIBS := $(call ldflags_for_lib, libstartup-notification-1.0,startup-notification-1)
+
+# libi3
+LIBS = -L$(TOPDIR) -li3
+
+## Platform-specific flags
# Please test if -Wl,--as-needed works on your platform and send me a patch.
# it is known not to work on Darwin (Mac OS X)
ifneq (,$(filter Linux GNU GNU/%, $(UNAME)))
-LDFLAGS += -Wl,--as-needed
+as_needed_LDFLAG = -Wl,--as-needed
endif
ifeq ($(UNAME),NetBSD)
# We need -idirafter instead of -I to prefer the system’s iconv over GNU libiconv
-CFLAGS += -idirafter /usr/pkg/include
-LDFLAGS += -Wl,-rpath,/usr/local/lib -Wl,-rpath,/usr/pkg/lib
+I3_CFLAGS += -idirafter /usr/pkg/include
+I3_LDFLAGS += -Wl,-rpath,/usr/local/lib -Wl,-rpath,/usr/pkg/lib
endif
ifeq ($(UNAME),OpenBSD)
-CFLAGS += -I${X11BASE}/include
+I3_CFLAGS += -I${X11BASE}/include
LIBS += -liconv
-LDFLAGS += -L${X11BASE}/lib
+I3_LDFLAGS += -L${X11BASE}/lib
endif
ifeq ($(UNAME),FreeBSD)
ifeq ($(UNAME),Darwin)
LIBS += -liconv
+else
+# Darwin (Mac OS X) doesn’t have librt
+LIBS += -lrt
endif
-# Fallback for libyajl 1 which did not include yajl_version.h. We need
-# YAJL_MAJOR from that file to decide which code path should be used.
-CFLAGS += -idirafter $(TOPDIR)/yajl-fallback
-
ifneq (,$(filter Linux GNU GNU/%, $(UNAME)))
-CPPFLAGS += -D_GNU_SOURCE
+I3_CPPFLAGS += -D_GNU_SOURCE
endif
-ifeq ($(DEBUG),1)
-# Extended debugging flags, macros shall be available in gcc
-CFLAGS += -gdwarf-2
-CFLAGS += -g3
-else
-CFLAGS += -O2
-CFLAGS += -freorder-blocks-and-partition
-endif
ifeq ($(COVERAGE),1)
-CFLAGS += -fprofile-arcs -ftest-coverage
+I3_CFLAGS += -fprofile-arcs -ftest-coverage
LIBS += -lgcov
endif
+V ?= 0
+ifeq ($(V),0)
# Don’t print command lines which are run
.SILENT:
+# echo-ing vars
+V_ASCIIDOC = echo ASCIIDOC $@;
+V_A2X = echo A2X $@;
+endif
+
# Always remake the following targets
.PHONY: install clean dist distclean
man/i3-sensible-pager.1
man/i3-sensible-editor.1
man/i3-sensible-terminal.1
-i3bar/doc/i3bar.1
+man/i3bar.1
-# To pass additional parameters for asciidoc
-ASCIIDOC=asciidoc
-
-ASCIIDOC_TARGETS:=hacking-howto.html debugging.html debugging-release-version.html userguide.html ipc.html multi-monitor.html wsbar.html testsuite.html i3bar-protocol.html
-
-all: ${ASCIIDOC_TARGETS}
-
-hacking-howto.html: hacking-howto
- $(ASCIIDOC) -a toc -n $<
-
-i3bar-protocol.html: i3bar-protocol
- $(ASCIIDOC) -a toc -n $<
-
-debugging.html: debugging
- $(ASCIIDOC) -n $<
-
-debugging-release-version.html: debugging-release-version
- $(ASCIIDOC) -n $<
-
-userguide.html: userguide
- $(ASCIIDOC) -a toc -n $<
-
-testsuite.html: testsuite
- $(ASCIIDOC) -a toc -n $<
-
-ipc.html: ipc
- $(ASCIIDOC) -a toc -n $<
-
-multi-monitor.html: multi-monitor
- $(ASCIIDOC) -a toc -n $<
-
-wsbar.html: wsbar
- $(ASCIIDOC) -a toc -n $<
+all:
+ $(MAKE) -C .. docs
clean:
- rm -f ${ASCIIDOC_TARGETS}
+ $(MAKE) -C .. clean-docs
+
+.PHONY: all clean
--- /dev/null
+DISTCLEAN_TARGETS += clean-docs
+
+# To pass additional parameters for asciidoc
+ASCIIDOC = asciidoc
+
+ASCIIDOC_NOTOC_TARGETS = \
+ docs/debugging.html \
+ docs/debugging-release-version.html
+
+ASCIIDOC_TOC_TARGETS = \
+ docs/hacking-howto.html \
+ docs/userguide.html \
+ docs/ipc.html \
+ docs/multi-monitor.html \
+ docs/wsbar.html \
+ docs/testsuite.html \
+ docs/i3bar-protocol.html
+
+ASCIIDOC_TARGETS = \
+ $(ASCIIDOC_TOC_TARGETS) \
+ $(ASCIIDOC_NOTOC_TARGETS)
+
+ASCIIDOC_CALL = $(V_ASCIIDOC)$(ASCIIDOC) -n $(ASCIIDOC_FLAGS) -o $@ $<
+ASCIIDOC_TOC_CALL = $(V_ASCIIDOC)$(ASCIIDOC) -a toc -n $(ASCIIDOC_FLAGS) -o $@ $<
+
+docs: $(ASCIIDOC_TARGETS)
+
+$(ASCIIDOC_TOC_TARGETS): docs/%.html: docs/%
+ $(ASCIIDOC_TOC_CALL)
+
+$(ASCIIDOC_NOTOC_TARGETS): docs/%.html: docs/%
+ $(ASCIIDOC_CALL)
+
+clean-docs:
+ rm -f $(ASCIIDOC_TARGETS)
Contains code for loading layouts from JSON files.
src/log.c::
-Handles the setting of loglevels, contains the logging functions.
+Contains the logging functions.
src/main.c::
Initializes the window manager.
IPC interface (interprocess communication)
==========================================
Michael Stapelberg <michael@i3wm.org>
-February 2012
+August 2012
This document describes how to interface with i3 from a separate process. This
is useful for example to remote-control i3 (to write test cases for example) or
Can be either "normal", "none" or "1pixel", dependending on the
container’s border style.
layout (string)::
- Can be either "default", "stacked", "tabbed", "dockarea" or "output".
+ Can be either "splith", "splitv", "stacked", "tabbed", "dockarea" or
+ "output".
Other values might be possible in the future, should we add new
layouts.
orientation (string)::
Can be either "none" (for non-split containers), "horizontal" or
"vertical".
+ THIS FIELD IS OBSOLETE. It is still present, but your code should not
+ use it. Instead, rely on the layout field.
percent (float)::
The percentage which this container takes in its parent. A value of
+null+ means that the percent property does not make sense for this
geometry (map)::
The original geometry the window specified when i3 mapped it. Used when
switching a window to floating mode, for example.
+window (integer)::
+ The X11 window ID of the *actual client window* inside this container.
+ This field is set to null for split containers or otherwise empty
+ containers. This ID corresponds to what xwininfo(1) and other
+ X11-related tools display (usually in hex).
urgent (bool)::
Whether this container (window or workspace) has the urgency hint set.
focused (bool)::
Ruby::
http://github.com/badboy/i3-ipc
Perl::
- http://search.cpan.org/search?query=AnyEvent::I3
+ https://metacpan.org/module/AnyEvent::I3
Python::
- https://github.com/whitelynx/i3ipc
- https://github.com/ziberna/i3-py (includes higher-level features)
+ * https://github.com/whitelynx/i3ipc
+ * https://github.com/ziberna/i3-py (includes higher-level features)
</p>
</header>
+
<section>
<h2>Basics</h2>
<table>
<tr>
- <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd></kbd>
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd>
<td>open new terminal
-
<tr>
- <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd>j</kbd>
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>j</kbd>
<td>focus left
<tr>
- <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd>k</kbd>
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>k</kbd>
<td>focus down
<tr>
- <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd>l</kbd>
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>l</kbd>
<td>focus up
<tr>
- <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd>;</kbd>
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>;</kbd>
<td>focus right
+ <tr>
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd>
+ <td>toggle focus mode
</table>
</section>
-
<section>
- <h2>Changing the container layout</h2>
+ <h2>Moving windows</h2>
<table>
<tr>
- <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd>e</kbd>
- <td>default
-
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd> + <kbd>j</kbd>
+ <td>move window left
<tr>
- <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd>s</kbd>
- <td>stacking
-
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd> + <kbd>k</kbd>
+ <td>move window down
<tr>
- <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd>w</kbd>
- <td>tabbed
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd> + <kbd>l</kbd>
+ <td>move window up
+ <tr>
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd> + <kbd>;</kbd>
+ <td>move window right
</table>
</section>
</div><div>
<section>
- <h2>Fullscreen mode</h2>
+ <h2>Modifying windows</h2>
<table>
<tr>
- <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd>f</kbd>
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>f</kbd>
<td>toggle fullscreen
+ <tr>
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>v</kbd>
+ <td>split a window vertically
+ <tr>
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>h</kbd>
+ <td>split a window horizontally
+ <tr>
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>r</kbd>
+ <td>resize mode
</table>
+ <p class="ref">Look at the “Resizing containers / windows” section of the user guide.</p>
</section>
-
<section>
- <h2>Opening other applications</h2>
+ <h2>Changing the container layout</h2>
<table>
<tr>
- <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd>d</kbd>
- <td>open application (with dmenu)
- </table>
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>e</kbd>
+ <td>default
+ <tr>
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>s</kbd>
+ <td>stacking
- <section>
- <h2>Closing windows</h2>
- <table>
<tr>
- <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd> </kbd>+ <kbd>q</kbd>
- <td>kill a window
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>w</kbd>
+ <td>tabbed
</table>
</section>
-
<section>
- <h2>Using workspaces</h2>
+ <h2>Floating</h2>
<table>
<tr>
- <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd>1</kbd>–<kbd>9</kbd>
- <td>switch to another workspace
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd> + <kbd></kbd>
+ <td>toggle floating
+ <tr>
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd>
+ <td>drag floating
</table>
</section>
<section>
- <h2>Moving windows to workspaces</h2>
+ <h2>Using workspaces</h2>
<table>
<tr>
- <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd></kbd> + <kbd>1</kbd>–<kbd>9</kbd>
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>0</kbd>-<kbd>9</kbd>
+ <td>switch to another workspace
+ <tr>
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd> + <kbd>0</kbd>-<kbd>9</kbd>
<td>move a window to another workspace
</table>
</section>
</div><div>
<section>
- <h2>Resizing</h2>
- <p class="ref">Look at “Resizing containers / windows” section of the user guide.</p>
- </section>
-
-
- <section>
- <h2>Restart / Exit</h2>
+ <h2>Opening applications / Closing windows</h2>
<table>
<tr>
- <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd></kbd> + <kbd>r</kbd>
- <td>restart i3 inplace
-
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd>d</kbd>
+ <td>open application launcher (dmenu)
<tr>
- <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd></kbd> + <kbd>e</kbd>
-
- </section><td>exit i3
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd> + <kbd>q</kbd>
+ <td>kill a window
</table>
-
+ </section>
<section>
- <h2>Floating</h2>
+ <h2>Restart / Exit</h2>
<table>
<tr>
- <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd></kbd> + <kbd></kbd>
- <td>toggle floating
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd> + <kbd>c</kbd>
+ <td>reload the configuration file
+ <tr>
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd> + <kbd>r</kbd>
+ <td>restart i3 inplace
<tr>
- <td><img class="i3mod" src="logo-30.png" alt="" />+<kbd></kbd>
- <td>drag floating
- </table>
+ <td><img class="i3mod" src="logo-30.png" alt="" /> + <kbd></kbd> + <kbd>e</kbd>
+ <td>exit i3
</section>
+ </table>
+
<!-- footer -->
<p id="copyright">
<br />
All rights reserved
<br />
- Designed by Zeus Panchenko
+ Designed by Zeus Panchenko, updated by Moritz Bandemer
</p>
<p id="licence">
Permission is granted to copy, distribute and/or modify this document provided
i3 User’s Guide
===============
-Michael Stapelberg <michael+i3@stapelberg.de>
-April 2012
+Michael Stapelberg <michael@i3wm.org>
+August 2012
This document contains all the information you need to configure and use the i3
window manager. If it does not, please contact us on IRC (preferred) or post your
A split container can have one of the following layouts:
-default::
+splith/splitv::
Windows are sized so that every window gets an equal amount of space in the
-container.
+container. splith distributes the windows horizontally (windows are right next
+to each other), splitv distributes them vertically (windows are on top of each
+other).
stacking::
Only the focused window in the container is displayed. You get a list of
windows at the top of the container.
The same principle as +stacking+, but the list of windows at the top is only
a single line which is vertically split.
-To switch modes, press +mod+e+ for default, +mod+s+ for stacking and
-+mod+w+ for tabbed.
+To switch modes, press +mod+e+ for splith/splitv (it toggles), +mod+s+ for
+stacking and +mod+w+ for tabbed.
image:modes.png[Container modes]
It is only natural to use so-called +Split Containers+ in order to build a
layout when using a tree as data structure. In i3, every +Container+ has an
-orientation (horizontal, vertical or unspecified). So, in our example with the
-workspace, the default orientation of the workspace +Container+ is horizontal
-(most monitors are widescreen nowadays). If you change the orientation to
-vertical (+mod+v+ in the default config) and *then* open two terminals, i3 will
-configure your windows like this:
+orientation (horizontal, vertical or unspecified) and the orientation depends
+on the layout the container is in (vertical for splitv and stacking, horizontal
+for splith and tabbed). So, in our example with the workspace, the default
+layout of the workspace +Container+ is splith (most monitors are widescreen
+nowadays). If you change the layout to splitv (+mod+l+ in the default config)
+and *then* open two terminals, i3 will configure your windows like this:
image::tree-shot2.png["shot2",title="Vertical Workspace Orientation"]
-An interesting new feature of the tree branch is the ability to split anything:
-Let’s assume you have two terminals on a workspace (with horizontal
-orientation), focus is on the right terminal. Now you want to open another
-terminal window below the current one. If you would just open a new terminal
-window, it would show up to the right due to the horizontal workspace
-orientation. Instead, press +mod+v+ to create a +Vertical Split Container+ (to
+An interesting new feature of i3 since version 4 is the ability to split anything:
+Let’s assume you have two terminals on a workspace (with splith layout, that is
+horizontal orientation), focus is on the right terminal. Now you want to open
+another terminal window below the current one. If you would just open a new
+terminal window, it would show up to the right due to the splith layout.
+Instead, press +mod+v+ to split the container with the splitv layout (to
open a +Horizontal Split Container+, use +mod+h+). Now you can open a new
terminal and it will open below the current one:
=== Splitting containers
The split command makes the current window a split container. Split containers
-can contain multiple windows. Every split container has an orientation, it is
-either split horizontally (a new window gets placed to the right of the current
-one) or vertically (a new window gets placed below the current one).
+can contain multiple windows. Depending on the layout of the split container,
+new windows get placed to the right of the current one (splith) or new windows
+get placed below the current one (splitv).
If you apply this command to a split container with the same orientation,
nothing will happen. If you use a different orientation, the split container’s
-orientation will be changed (if it does not have more than one window).
+orientation will be changed (if it does not have more than one window). Use
++layout toggle split+ to change the layout of any split container from splitv
+to splith or vice-versa.
*Syntax*:
---------------------------
=== Manipulating layout
-Use +layout default+, +layout stacking+ or +layout tabbed+ to change the
-current container layout to default, stacking or tabbed layout, respectively.
+Use +layout toggle split+, +layout stacking+ or +layout tabbed+ to change the
+current container layout to splith/splitv, stacking or tabbed layout,
+respectively.
To make the current window (!) fullscreen, use +fullscreen+, to make
it floating (or tiling again) use +floating enable+ respectively +floating disable+
(or +floating toggle+):
+*Syntax*:
+--------------
+layout <tabbed|stacking>
+layout toggle [split|all]
+--------------
+
*Examples*:
--------------
bindsym mod+s layout stacking
-bindsym mod+l layout default
+bindsym mod+l layout toggle split
bindsym mod+w layout tabbed
+# Toggle between stacking/tabbed/split:
+bindsym mod+x layout toggle
+
+# Toggle between stacking/tabbed/splith/splitv:
+bindsym mod+x layout toggle all
+
# Toggle fullscreen
bindsym mod+f fullscreen
workspace 1, 3, 4 and 9 and you want to cycle through them with a single key
combination. To restrict those to the current output, use +workspace
next_on_output+ and +workspace prev_on_output+. Similarly, you can use +move
-container to workspace next+ and +move container to workspace prev+ to move a
-container to the next/previous workspace.
+container to workspace next+, +move container to workspace prev+ to move a
+container to the next/previous workspace and +move container to workspace current+
+(the last one makes sense only when used with criteria).
[[back_and_forth]]
To switch back to the previously focused workspace, use +workspace
target output. You may also use +left+, +right+, +up+, +down+ instead of the
xrandr output name to move to the next output in the specified direction.
+*Syntax*:
+-----------------------------------
+workspace <next|prev|next_on_output|prev_on_output>
+workspace back_and_forth
+workspace <name>
+workspace number <number>
+
+move [window|container] [to] workspace <name>
+move [window|container] [to] workspace number <number>
+move [window|container] [to] workspace <prev|next|current>
+-----------------------------------
+
*Examples*:
-------------------------
bindsym mod+1 workspace 1
# move the whole workspace to the next output
bindsym mod+x move workspace to output right
+
+# move firefox to current workspace
+bindsym mod+F1 [class="Firefox"] move workspace current
-------------------------
==== Named workspaces
$cmd =~ s/[^(]+\(//;
$cmd =~ s/\)$//;
$cmd = ", $cmd" if length($cmd) > 0;
- say $callfh qq| printf("$fmt\\n"$cmd);|;
+ say $callfh qq| fprintf(stderr, "$fmt\\n"$cmd);|;
say $callfh '#endif';
say $callfh " state = $next_state;";
say $callfh " break;";
-# Default value so one can compile i3-input standalone
-TOPDIR=..
+all:
+ $(MAKE) -C .. i3-config-wizard/i3-config-wizard
-include $(TOPDIR)/common.mk
-
-# Depend on the object files of all source-files in src/*.c and on all header files
-AUTOGENERATED:=cfgparse.tab.c cfgparse.yy.c
-FILES:=$(patsubst %.c,%.o,$(filter-out $(AUTOGENERATED),$(wildcard *.c)))
-HEADERS:=$(wildcard *.h)
-
-CPPFLAGS += -I$(TOPDIR)/include
-
-# Depend on the specific file (.c for each .o) and on all headers
-%.o: %.c ${HEADERS}
- echo "[i3-config-wizard] CC $<"
- $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
-
-all: i3-config-wizard
-
-i3-config-wizard: $(TOPDIR)/libi3/libi3.a cfgparse.y.o cfgparse.yy.o ${FILES}
- echo "[i3-config-wizard] LINK i3-config-wizard"
- $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS)
-
-$(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c
- $(MAKE) -C $(TOPDIR)/libi3
-
-cfgparse.yy.o: cfgparse.l cfgparse.y.o ${HEADERS}
- echo "[i3-config-wizard] LEX $<"
- $(FLEX) -i -o$(@:.o=.c) $<
- $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(@:.o=.c)
-
-cfgparse.y.o: cfgparse.y ${HEADERS}
- echo "[i3-config-wizard] YACC $<"
- $(BISON) --debug --verbose -b $(basename $< .y) -d $<
- $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(<:.y=.tab.c)
-
-
-install: all
- echo "[i3-config-wizard] INSTALL"
- $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
- $(INSTALL) -m 0755 i3-config-wizard $(DESTDIR)$(PREFIX)/bin/
+install:
+ $(MAKE) -C .. install-i3-config-wizard
clean:
- rm -f *.o cfgparse.tab.{c,h} cfgparse.output cfgparse.yy.c
+ $(MAKE) -C .. clean-i3-config-wizard
-distclean: clean
- rm -f i3-config-wizard
+.PHONY: all install clean
YY_BUFFER_STATE yy_scan_string(const char *);
static struct context *context;
+static xcb_connection_t *conn;
+static xcb_key_symbols_t *keysyms;
/* We don’t need yydebug for now, as we got decent error messages using
* yyerror(). Should you ever want to extend the parser, it might be handy
char *rewrite_binding(const char *bindingline) {
char *result = NULL;
+ conn = xcb_connect(NULL, NULL);
+ if (conn == NULL || xcb_connection_has_error(conn)) {
+ fprintf(stderr, "Cannot open display\n");
+ exit(1);
+ }
+ keysyms = xcb_key_symbols_alloc(conn);
+
context = calloc(sizeof(struct context), 1);
yy_scan_string(bindingline);
if (context->line_copy)
free(context->line_copy);
free(context);
+ xcb_key_symbols_free(keysyms);
+ xcb_disconnect(conn);
return result;
}
else return strdup("");
}
+/*
+ * Returns true if sym is bound to any key except for 'except_keycode' on the
+ * first four layers (normal, shift, mode_switch, mode_switch + shift).
+ *
+ */
+static bool keysym_used_on_other_key(KeySym sym, xcb_keycode_t except_keycode) {
+ xcb_keycode_t i,
+ min_keycode = xcb_get_setup(conn)->min_keycode,
+ max_keycode = xcb_get_setup(conn)->max_keycode;
+
+ for (i = min_keycode; i && i <= max_keycode; i++) {
+ if (i == except_keycode)
+ continue;
+ for (int level = 0; level < 4; level++) {
+ if (xcb_key_symbols_get_keysym(keysyms, i, level) != sym)
+ continue;
+ return true;
+ }
+ }
+ return false;
+}
+
%}
%error-verbose
* different key than the upper-case one (unlikely for letters, but
* more likely for special characters). */
level = 1;
+
+ /* Try to use the keysym on the first level (lower-case). In case
+ * this doesn’t make it ambiguous (think of a keyboard layout
+ * having '1' on two different keys, but '!' only on keycode 10),
+ * we’ll stick with the keysym of the first level.
+ *
+ * This reduces a lot of confusion for users who switch keyboard
+ * layouts from qwerty to qwertz or other slight variations of
+ * qwerty (yes, that happens quite often). */
+ KeySym sym = XkbKeycodeToKeysym(dpy, $4, 0, 0);
+ if (!keysym_used_on_other_key(sym, $4))
+ level = 0;
}
KeySym sym = XkbKeycodeToKeysym(dpy, $4, 0, level);
char *str = XKeysymToString(sym);
char *modifiers = modifier_to_string($<number>3);
- // TODO: modifier to string
sasprintf(&(context->result), "bindsym %s%s %s\n", modifiers, str, $<string>6);
free(modifiers);
}
--- /dev/null
+ALL_TARGETS += i3-config-wizard/i3-config-wizard
+INSTALL_TARGETS += install-i3-config-wizard
+CLEAN_TARGETS += clean-i3-config-wizard
+
+i3_config_wizard_SOURCES_GENERATED = i3-config-wizard/cfgparse.tab.c i3-config-wizard/cfgparse.yy.c
+i3_config_wizard_SOURCES := $(filter-out $(i3_config_wizard_SOURCES_GENERATED),$(wildcard i3-config-wizard/*.c))
+i3_config_wizard_HEADERS := $(wildcard i3-config-wizard/*.h)
+i3_config_wizard_CFLAGS = $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(X11_CFLAGS)
+i3_config_wizard_LIBS = $(XCB_LIBS) $(XCB_KBD_LIBS) $(X11_LIBS)
+
+i3_config_wizard_OBJECTS := $(i3_config_wizard_SOURCES_GENERATED:.c=.o) $(i3_config_wizard_SOURCES:.c=.o)
+
+
+i3-config-wizard/%.o: i3-config-wizard/%.c $(i3_config_wizard_HEADERS)
+ echo "[i3-config-wizard] CC $<"
+ $(CC) $(I3_CPPFLAGS) $(CPPFLAGS) $(i3_config_wizard_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $<
+
+i3-config-wizard/cfgparse.yy.c: i3-config-wizard/cfgparse.l i3-config-wizard/cfgparse.tab.o $(i3_config_wizard_HEADERS)
+ echo "[i3-config-wizard] LEX $<"
+ $(FLEX) -i -o $@ $<
+
+i3-config-wizard/cfgparse.tab.c: i3-config-wizard/cfgparse.y $(i3_config_wizard_HEADERS)
+ echo "[i3-config-wizard] YACC $<"
+ $(BISON) --debug --verbose -b $(basename $< .y) -d $<
+
+i3-config-wizard/i3-config-wizard: libi3.a $(i3_config_wizard_OBJECTS)
+ echo "[i3-config-wizard] Link i3-config-wizard"
+ $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(i3_config_wizard_LIBS) $(LIBS)
+
+install-i3-config-wizard: i3-config-wizard/i3-config-wizard
+ echo "[i3-config-wizard] Install"
+ $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
+ $(INSTALL) -m 0755 i3-config-wizard/i3-config-wizard $(DESTDIR)$(PREFIX)/bin/
+
+clean-i3-config-wizard:
+ echo "[i3-config-wizard] Clean"
+ rm -f $(i3_config_wizard_OBJECTS) $(i3_config_wizard_SOURCES_GENERATED) i3-config-wizard/i3-config-wizard i3-config-wizard/cfgparse.{output,dot}
-# Default value so one can compile i3-dump-log standalone
-TOPDIR=..
+all:
+ $(MAKE) -C .. i3-dump-log/i3-dump-log
-include $(TOPDIR)/common.mk
-
-CFLAGS += -I$(TOPDIR)/include
-
-# Depend on the object files of all source-files in src/*.c and on all header files
-FILES=$(patsubst %.c,%.o,$(wildcard *.c))
-HEADERS=$(wildcard *.h)
-
-# Depend on the specific file (.c for each .o) and on all headers
-%.o: %.c ${HEADERS}
- echo "[i3-dump-log] CC $<"
- $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
-
-all: i3-dump-log
-
-i3-dump-log: ${FILES}
- echo "[i3-dump-log] LINK i3-dump-log"
- $(CC) $(LDFLAGS) -o i3-dump-log ${FILES} $(LIBS)
-
-install: all
- echo "[i3-dump-log] INSTALL"
- $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
- $(INSTALL) -m 0755 i3-dump-log $(DESTDIR)$(PREFIX)/bin/
+install:
+ $(MAKE) -C .. install-i3-dump-log
clean:
- rm -f *.o
+ $(MAKE) -C .. clean-i3-dump-log
-distclean: clean
- rm -f i3-dump-log
+.PHONY: all install clean
--- /dev/null
+ALL_TARGETS += i3-dump-log/i3-dump-log
+INSTALL_TARGETS += install-i3-dump-log
+CLEAN_TARGETS += clean-i3-dump-log
+
+i3_dump_log_SOURCES := $(wildcard i3-dump-log/*.c)
+i3_dump_log_HEADERS := $(wildcard i3-dump-log/*.h)
+i3_dump_log_CFLAGS = $(XCB_CFLAGS)
+i3_dump_log_LIBS = $(XCB_LIBS)
+
+i3_dump_log_OBJECTS := $(i3_dump_log_SOURCES:.c=.o)
+
+
+i3-dump-log/%.o: i3-dump-log/%.c $(i3_dump_log_HEADERS)
+ echo "[i3-dump-log] CC $<"
+ $(CC) $(I3_CPPFLAGS) $(CPPFLAGS) $(i3_dump_log_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $<
+
+i3-dump-log/i3-dump-log: libi3.a $(i3_dump_log_OBJECTS)
+ echo "[i3-dump-log] Link i3-dump-log"
+ $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(LIBS) $(i3_dump_log_LIBS)
+
+install-i3-dump-log: i3-dump-log/i3-dump-log
+ echo "[i3-dump-log] Install"
+ $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
+ $(INSTALL) -m 0755 i3-dump-log/i3-dump-log $(DESTDIR)$(PREFIX)/bin/
+
+clean-i3-dump-log:
+ echo "[i3-dump-log] Clean"
+ rm -f $(i3_dump_log_OBJECTS) i3-dump-log/i3-dump-log
-# Default value so one can compile i3-input standalone
-TOPDIR=..
+all:
+ $(MAKE) -C .. i3-input/i3-input
-include $(TOPDIR)/common.mk
-
-CPPFLAGS += -I$(TOPDIR)/include
-
-# Depend on the object files of all source-files in src/*.c and on all header files
-FILES=$(patsubst %.c,%.o,$(wildcard *.c))
-HEADERS=$(wildcard *.h)
-
-# Depend on the specific file (.c for each .o) and on all headers
-%.o: %.c ${HEADERS}
- echo "[i3-input] CC $<"
- $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
-
-all: i3-input
-
-i3-input: $(TOPDIR)/libi3/libi3.a ${FILES}
- echo "[i3-input] LINK i3-input"
- $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS)
-
-$(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c
- $(MAKE) -C $(TOPDIR)/libi3
-
-install: all
- echo "[i3-input] INSTALL"
- $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
- $(INSTALL) -m 0755 i3-input $(DESTDIR)$(PREFIX)/bin/
+install:
+ $(MAKE) -C .. install-i3-input
clean:
- rm -f *.o
+ $(MAKE) -C .. clean-i3-input
-distclean: clean
- rm -f i3-input
+.PHONY: all install clean
--- /dev/null
+ALL_TARGETS += i3-input/i3-input
+INSTALL_TARGETS += install-i3-input
+CLEAN_TARGETS += clean-i3-input
+
+i3_input_SOURCES := $(wildcard i3-input/*.c)
+i3_input_HEADERS := $(wildcard i3-input/*.h)
+i3_input_CFLAGS = $(XCB_CFLAGS) $(XCB_KBD_CFLAGS)
+i3_input_LIBS = $(XCB_LIBS) $(XCB_KBD_LIBS)
+
+i3_input_OBJECTS := $(i3_input_SOURCES:.c=.o)
+
+
+i3-input/%.o: i3-input/%.c $(i3_input_HEADERS)
+ echo "[i3-input] CC $<"
+ $(CC) $(I3_CPPFLAGS) $(CPPFLAGS) $(i3_input_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $<
+
+i3-input/i3-input: libi3.a $(i3_input_OBJECTS)
+ echo "[i3-input] Link i3-input"
+ $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(i3_input_LIBS) $(LIBS)
+
+install-i3-input: i3-input/i3-input
+ echo "[i3-input] Install"
+ $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
+ $(INSTALL) -m 0755 i3-input/i3-input $(DESTDIR)$(PREFIX)/bin/
+
+clean-i3-input:
+ echo "[i3-input] Clean"
+ rm -f $(i3_input_OBJECTS) i3-input/i3-input
# simple replacements
my @replace = (
qr/^s/ => 'layout stacking',
- qr/^d/ => 'layout default',
+ qr/^d/ => 'layout toggle split',
qr/^T/ => 'layout tabbed',
qr/^f($|[^go])/ => 'fullscreen',
qr/^fg/ => 'fullscreen global',
-# Default value so one can compile i3-msg standalone
-TOPDIR=..
+all:
+ $(MAKE) -C .. i3-msg/i3-msg
-include $(TOPDIR)/common.mk
-
-CFLAGS += -I$(TOPDIR)/include
-
-# Depend on the object files of all source-files in src/*.c and on all header files
-FILES=$(patsubst %.c,%.o,$(wildcard *.c))
-HEADERS=$(wildcard *.h)
-
-# Depend on the specific file (.c for each .o) and on all headers
-%.o: %.c ${HEADERS}
- echo "[i3-msg] CC $<"
- $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
-
-all: i3-msg
-
-i3-msg: ${FILES}
- echo "[i3-msg] LINK i3-msg"
- $(CC) $(LDFLAGS) -o i3-msg ${FILES} $(LIBS)
-
-install: all
- echo "[i3-msg] INSTALL"
- $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
- $(INSTALL) -m 0755 i3-msg $(DESTDIR)$(PREFIX)/bin/
+install:
+ $(MAKE) -C .. install-i3-msg
clean:
- rm -f *.o
+ $(MAKE) -C .. clean-i3-msg
-distclean: clean
- rm -f i3-msg
+.PHONY: all install clean
--- /dev/null
+ALL_TARGETS += i3-msg/i3-msg
+INSTALL_TARGETS += install-i3-msg
+CLEAN_TARGETS += clean-i3-msg
+
+i3_msg_SOURCES := $(wildcard i3-msg/*.c)
+i3_msg_HEADERS := $(wildcard i3-msg/*.h)
+i3_msg_CFLAGS = $(XCB_CFLAGS)
+i3_msg_LIBS = $(XCB_LIBS)
+
+i3_msg_OBJECTS := $(i3_msg_SOURCES:.c=.o)
+
+
+i3-msg/%.o: i3-msg/%.c $(i3_msg_HEADERS)
+ echo "[i3-msg] CC $<"
+ $(CC) $(I3_CPPFLAGS) $(CPPFLAGS) $(i3_msg_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $<
+
+i3-msg/i3-msg: libi3.a $(i3_msg_OBJECTS)
+ echo "[i3-msg] Link i3-msg"
+ $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(LIBS) $(i3_msg_LIBS)
+
+install-i3-msg: i3-msg/i3-msg
+ echo "[i3-msg] Install"
+ $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
+ $(INSTALL) -m 0755 i3-msg/i3-msg $(DESTDIR)$(PREFIX)/bin/
+
+clean-i3-msg:
+ echo "[i3-msg] Clean"
+ rm -f $(i3_msg_OBJECTS) i3-msg/i3-msg
-# Default value so one can compile i3-nagbar standalone
-TOPDIR=..
+all:
+ $(MAKE) -C .. i3-nagbar/i3-nagbar
-include $(TOPDIR)/common.mk
-
-CPPFLAGS += -I$(TOPDIR)/include
-
-# Depend on the object files of all source-files in src/*.c and on all header files
-FILES=$(patsubst %.c,%.o,$(wildcard *.c))
-HEADERS=$(wildcard *.h)
-
-# Depend on the specific file (.c for each .o) and on all headers
-%.o: %.c ${HEADERS}
- echo "[i3-nagbar] CC $<"
- $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
-
-all: i3-nagbar
-
-i3-nagbar: $(TOPDIR)/libi3/libi3.a ${FILES}
- echo "[i3-nagbar] LINK i3-nagbar"
- $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS)
-
-$(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c
- $(MAKE) -C $(TOPDIR)/libi3
-
-install: all
- echo "[i3-nagbar] INSTALL"
- $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
- $(INSTALL) -m 0755 i3-nagbar $(DESTDIR)$(PREFIX)/bin/
+install:
+ $(MAKE) -C .. install-i3-nagbar
clean:
- rm -f *.o
+ $(MAKE) -C .. clean-i3-nagbar
-distclean: clean
- rm -f i3-nagbar
+.PHONY: all install clean
--- /dev/null
+ALL_TARGETS += i3-nagbar/i3-nagbar
+INSTALL_TARGETS += install-i3-nagbar
+CLEAN_TARGETS += clean-i3-nagbar
+
+i3_nagbar_SOURCES := $(wildcard i3-nagbar/*.c)
+i3_nagbar_HEADERS := $(wildcard i3-nagbar/*.h)
+i3_nagbar_CFLAGS = $(XCB_CFLAGS)
+i3_nagbar_LIBS = $(XCB_LIBS)
+
+i3_nagbar_OBJECTS := $(i3_nagbar_SOURCES:.c=.o)
+
+
+i3-nagbar/%.o: i3-nagbar/%.c $(i3_nagbar_HEADERS)
+ echo "[i3-nagbar] CC $<"
+ $(CC) $(I3_CPPFLAGS) $(CPPFLAGS) $(i3_nagbar_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $<
+
+i3-nagbar/i3-nagbar: libi3.a $(i3_nagbar_OBJECTS)
+ echo "[i3-nagbar] Link i3-nagbar"
+ $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(i3_nagbar_LIBS) $(LIBS)
+
+install-i3-nagbar: i3-nagbar/i3-nagbar
+ echo "[i3-nagbar] Install"
+ $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
+ $(INSTALL) -m 0755 i3-nagbar/i3-nagbar $(DESTDIR)$(PREFIX)/bin/
+
+clean-i3-nagbar:
+ echo "[i3-nagbar] Clean"
+ rm -f $(i3_nagbar_OBJECTS) i3-nagbar/i3-nagbar
# Distributions/packagers should enhance this script with a
# distribution-specific mechanism to find the preferred terminal emulator. On
# Debian, there is the x-terminal-emulator symlink for example.
-for terminal in $TERMINAL urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm; do
+for terminal in $TERMINAL urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal; do
if which $terminal > /dev/null 2>&1; then
exec $terminal "$@"
fi
# enter fullscreen mode for the focused container
bindsym Mod1+f fullscreen
-# change container layout (stacked, tabbed, default)
+# change container layout (stacked, tabbed, toggle split)
bindsym Mod1+s layout stacking
bindsym Mod1+w layout tabbed
-bindsym Mod1+e layout default
+bindsym Mod1+e layout toggle split
# toggle tiling / floating
bindsym Mod1+Shift+space floating toggle
# restart i3 inplace (preserves your layout/session, can be used to upgrade i3)
bindsym Mod1+Shift+r restart
# exit i3 (logs you out of your X session)
-bindsym Mod1+Shift+e exit
+bindsym Mod1+Shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'"
# resize window (you can also use the mouse for that)
mode "resize" {
# enter fullscreen mode for the focused container
bindcode $mod+41 fullscreen
-# change container layout (stacked, tabbed, default)
+# change container layout (stacked, tabbed, toggle split)
bindcode $mod+39 layout stacking
bindcode $mod+25 layout tabbed
-bindcode $mod+26 layout default
+bindcode $mod+26 layout toggle split
# toggle tiling / floating
bindcode $mod+Shift+65 floating toggle
# restart i3 inplace (preserves your layout/session, can be used to upgrade i3)
bindcode $mod+Shift+27 restart
# exit i3 (logs you out of your X session)
-bindcode $mod+Shift+26 exit
+bindcode $mod+Shift+26 exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'"
# resize window (you can also use the mouse for that)
mode "resize" {
-TOPDIR=..
+all:
+ $(MAKE) -C .. i3bar/i3bar
-include $(TOPDIR)/common.mk
-
-FILES:=$(wildcard src/*.c)
-FILES:=$(FILES:.c=.o)
-HEADERS:=$(wildcard include/*.h)
-
-CPPFLAGS += -I$(TOPDIR)/include
-
-all: i3bar doc
-
-i3bar: $(TOPDIR)/libi3/libi3.a ${FILES}
- echo "[i3bar] LINK"
- $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS)
-
-$(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c
- $(MAKE) -C $(TOPDIR)/libi3
-
-doc:
- echo ""
- echo "[i3bar] SUBDIR doc"
- $(MAKE) -C doc
-
-src/%.o: src/%.c ${HEADERS}
- echo "[i3bar] CC $<"
- $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
-
-install: all
- echo "[i3bar] INSTALL"
- $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
- $(INSTALL) -m 0755 i3bar $(DESTDIR)$(PREFIX)/bin
+install:
+ $(MAKE) -C .. install-i3bar
clean:
- rm -f src/*.o
- $(MAKE) -C doc clean
-
-distclean: clean
- rm -f i3bar
- $(MAKE) -C doc distclean
+ $(MAKE) -C .. clean-i3bar
-.PHONY: install clean distclean doc
+.PHONY: all install clean
+++ /dev/null
-all: i3bar.1
-
-i3bar.1: i3bar.man
- echo "A2X i3bar"
- a2x --no-xmllint -f manpage i3bar.man
-clean:
- rm -f i3bar.xml i3bar.1 i3bar.html
-
-distclean: clean
+++ /dev/null
-i3bar(1)
-========
-Axel Wagner <mail+i3bar@merovius.de>
-v4.1, October 2011
-
-== NAME
-
-i3bar - xcb-based status- and workspace-bar
-
-== SYNOPSIS
-
-*i3bar* [*-s* 'sock_path'] [*-b* 'bar_id'] [*-v*] [*-h*]
-
-== WARNING
-
-i3bar will automatically be invoked by i3 for every 'bar' configuration block.
-
-Starting it manually is usually not what you want to do.
-
-You have been warned!
-
-== OPTIONS
-
-*-s, --socket* 'sock_path'::
-Overwrites the path to the i3 IPC socket.
-
-*-b, --bar_id* 'bar_id'::
-Specifies the bar ID for which to get the configuration from i3.
-
-*-v, --version*::
-Display version number and exit.
-
-*-h, --help*::
-Display a short help-message and exit
-
-== DESCRIPTION
-
-*i3bar* displays a bar at the bottom (or top) of your monitor(s) containing
-workspace switching buttons and a statusline generated by i3status(1) or
-similar. It is automatically invoked (and configured through) i3.
-
-i3bar does not support any color or other markups, so stdin should be plain
-utf8, one line at a time. If you use *i3status*(1), you therefore should
-specify 'output_format = none' in the general section of its config file.
-
-== ENVIRONMENT
-
-=== I3SOCK
-
-Used as a fallback for the i3 IPC socket path if neither the commandline
-contains an argument nor the I3_SOCKET_PATH property is set on the X11 root
-window.
-
-== EXAMPLES
-
-Nothing to see here, move along. As stated above, you should not run i3bar manually.
-
-Instead, see the i3 documentation, especially the User’s Guide.
-
-== SEE ALSO
-
-+i3status(1)+ or +conky(1)+ for programs generating a statusline.
-
-+dzen2(1)+ or +xmobar(1)+ for similar programs to i3bar.
-
-== AUTHORS
-
-Axel Wagner and contributors
--- /dev/null
+ALL_TARGETS += i3bar/i3bar
+INSTALL_TARGETS += install-i3bar
+CLEAN_TARGETS += clean-i3bar
+
+i3bar_SOURCES := $(wildcard i3bar/src/*.c)
+i3bar_HEADERS := $(wildcard i3bar/include/*.h)
+i3bar_CFLAGS = $(XCB_CFLAGS) $(X11_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS)
+i3bar_LIBS = $(XCB_LIBS) $(X11_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS)
+
+i3bar_OBJECTS := $(i3bar_SOURCES:.c=.o)
+
+
+i3bar/src/%.o: i3bar/src/%.c $(i3bar_HEADERS)
+ echo "[i3bar] CC $<"
+ $(CC) $(I3_CPPFLAGS) $(CPPFLAGS) $(i3bar_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -Ii3bar/include -c -o $@ $<
+
+i3bar/i3bar: libi3.a $(i3bar_OBJECTS)
+ echo "[i3bar] Link i3bar"
+ $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(i3bar_LIBS) $(LIBS)
+
+install-i3bar: i3bar/i3bar
+ echo "[i3bar] Install"
+ $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
+ $(INSTALL) -m 0755 i3bar/i3bar $(DESTDIR)$(PREFIX)/bin/
+
+clean-i3bar:
+ echo "[i3bar] Clean"
+ rm -f $(i3bar_OBJECTS) i3bar/i3bar
}
i3_ws *ws_walk;
+ static char *last_urgent_ws = NULL;
+ bool has_urgent = false, walks_away = true;
+
TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) {
DLOG("Drawing Button for WS %s at x = %d, len = %d\n", ws_walk->name, i, ws_walk->name_width);
uint32_t fg_color = colors.inactive_ws_fg;
fg_color = colors.focus_ws_fg;
bg_color = colors.focus_ws_bg;
border_color = colors.focus_ws_border;
+ if (last_urgent_ws && strcmp(ws_walk->name, last_urgent_ws) == 0)
+ walks_away = false;
}
}
if (ws_walk->urgent) {
fg_color = colors.urgent_ws_fg;
bg_color = colors.urgent_ws_bg;
border_color = colors.urgent_ws_border;
+ has_urgent = true;
+ if (!ws_walk->focused) {
+ FREE(last_urgent_ws);
+ last_urgent_ws = sstrdup(ws_walk->name);
+ }
/* The urgent-hint should get noticed, so we unhide the bars shortly */
unhide_bars();
}
i += 10 + ws_walk->name_width + 1;
}
+ if (!has_urgent && !mod_pressed && walks_away) {
+ FREE(last_urgent_ws);
+ hide_bars();
+ }
+
i = 0;
}
#include "i3.h"
#include "x.h"
#include "click.h"
+#include "key_press.h"
#include "floating.h"
#include "config.h"
#include "handlers.h"
void cmd_move_direction(I3_CMD, char *direction, char *move_px);
/**
- * Implementation of 'layout default|stacked|stacking|tabbed'.
+ * Implementation of 'layout default|stacked|stacking|tabbed|splitv|splith'.
*
*/
void cmd_layout(I3_CMD, char *layout_str);
+/**
+ * Implementation of 'layout toggle [all|split]'.
+ *
+ */
+void cmd_layout_toggle(I3_CMD, char *toggle_mode);
+
/**
* Implementaiton of 'exit'.
*
*/
void con_set_layout(Con *con, int layout);
+/**
+ * This function toggles the layout of a given container. toggle_mode can be
+ * either 'default' (toggle only between stacked/tabbed/last_split_layout),
+ * 'split' (toggle only between splitv/splith) or 'all' (toggle between all
+ * layouts).
+ *
+ */
+void con_toggle_layout(Con *con, const char *toggle_mode);
+
/**
* Determines the minimum size of the given con by looking at its children (for
* split/stacked/tabbed cons). Will be called when resizing floating cons
*/
Rect con_minimum_size(Con *con);
+/**
+ * Returns true if changing the focus to con would be allowed considering
+ * the fullscreen focus constraints. Specifically, if a fullscreen container or
+ * any of its descendants is focused, this function returns true if and only if
+ * focusing con would mean that focus would still be visible on screen, i.e.,
+ * the newly focused container would not be obscured by a fullscreen container.
+ *
+ * In the simplest case, if a fullscreen container or any of its descendants is
+ * fullscreen, this functions returns true if con is the fullscreen container
+ * itself or any of its descendants, as this means focus wouldn't escape the
+ * boundaries of the fullscreen container.
+ *
+ * In case the fullscreen container is of type CF_OUTPUT, this function returns
+ * true if con is on a different workspace, as focus wouldn't be obscured by
+ * the fullscreen container that is constrained to a different workspace.
+ *
+ * Note that this same logic can be applied to moving containers. If a
+ * container can be focused under the fullscreen focus constraints, it can also
+ * become a parent or sibling to the currently focused container.
+ *
+ */
+bool con_fullscreen_permits_focusing(Con *con);
+
#endif
*/
struct Con {
bool mapped;
+ /** whether this is a split container or not */
+ bool split;
enum {
CT_ROOT = 0,
CT_OUTPUT = 1,
CT_WORKSPACE = 4,
CT_DOCKAREA = 5
} type;
- orientation_t orientation;
struct Con *parent;
struct Rect rect;
TAILQ_HEAD(swallow_head, Match) swallow_head;
enum { CF_NONE = 0, CF_OUTPUT = 1, CF_GLOBAL = 2 } fullscreen_mode;
- enum { L_DEFAULT = 0, L_STACKED = 1, L_TABBED = 2, L_DOCKAREA = 3, L_OUTPUT = 4 } layout;
+ enum {
+ L_DEFAULT = 0,
+ L_STACKED = 1,
+ L_TABBED = 2,
+ L_DOCKAREA = 3,
+ L_OUTPUT = 4,
+ L_SPLITV = 5,
+ L_SPLITH = 6
+ } layout, last_split_layout;
border_style_t border_style;
/** floating? (= not in tiling layout) This cannot be simply a bool
* because we want to keep track of whether the status was set by the
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * key_press.c: key press handler
+ *
+ */
+#ifndef _KEY_PRESS_H
+#define _KEY_PRESS_H
+
+/**
+ * There was a key press. We compare this key code with our bindings table and pass
+ * the bound action to parse_command().
+ *
+ */
+void handle_key_press(xcb_key_press_event_t *event);
+
+/**
+ * Kills the commanderror i3-nagbar process, if any.
+ *
+ * Called when reloading/restarting, since the user probably fixed his wrong
+ * keybindings.
+ *
+ * If wait_for_it is set (restarting), this function will waitpid(), otherwise,
+ * ev is assumed to handle it (reloading).
+ *
+ */
+void kill_commanderror_nagbar(bool wait_for_it);
+
+#endif
* i3 - an improved dynamic tiling window manager
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * log.c: Setting of loglevels, logging functions.
+ * log.c: Logging functions.
*
*/
#ifndef _LOG_H
is, delete the preceding comma */
#define LOG(fmt, ...) verboselog(fmt, ##__VA_ARGS__)
#define ELOG(fmt, ...) errorlog("ERROR: " fmt, ##__VA_ARGS__)
-#define DLOG(fmt, ...) debuglog(LOGLEVEL, "%s:%s:%d - " fmt, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__)
+#define DLOG(fmt, ...) debuglog("%s:%s:%d - " fmt, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__)
-extern char *loglevels[];
extern char *errorfilename;
extern char *shmlogname;
extern int shmlog_size;
void init_logging(void);
/**
- * Enables the given loglevel.
+ * Set debug logging.
*
*/
-void add_loglevel(const char *level);
+void set_debug_logging(const bool _debug_logging);
/**
* Set verbosity of i3. If verbose is set to true, informative messages will
/**
* Logs the given message to stdout while prefixing the current time to it,
- * but only if the corresponding debug loglevel was activated.
+ * but only if debug logging was activated.
*
*/
-void debuglog(uint64_t lev, char *fmt, ...);
+void debuglog(char *fmt, ...);
/**
* Logs the given message to stdout while prefixing the current time to it.
/**
* Checks if the given regular expression matches the given input and returns
* true if it does. In either case, it logs the outcome using LOG(), so it will
- * be visible without any debug loglevel.
+ * be visible without debug logging.
*
*/
bool regex_matches(struct regex *regex, const char *input);
void tree_split(Con *con, orientation_t orientation);
/**
- * Moves focus one level up.
+ * Moves focus one level up. Returns true if focus changed.
*
*/
-void level_up(void);
+bool level_up(void);
/**
- * Moves focus one level down.
+ * Moves focus one level down. Returns true if focus changed.
*
*/
-void level_down(void);
+bool level_down(void);
/**
* Renders the tree, that is rendering all outputs using render_con() and
-# Default value so one can compile i3-msg standalone
-TOPDIR=..
-
-include $(TOPDIR)/common.mk
-
-CFLAGS += -I$(TOPDIR)/include
-
-# Depend on the object files of all source-files in src/*.c and on all header files
-FILES=$(patsubst %.c,%.o,$(wildcard *.c))
-HEADERS=$(wildcard *.h)
-
-# Depend on the specific file (.c for each .o) and on all headers
-%.o: %.c ${HEADERS}
- echo "[libi3] CC $<"
- $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
-
-all: libi3.a
-
-libi3.a: ${FILES}
- echo "[libi3] AR libi3.a"
- ar rcs libi3.a ${FILES}
+all:
+ $(MAKE) -C .. libi3.a
clean:
- rm -f *.o libi3.a
+ $(MAKE) -C .. clean-libi3
-distclean: clean
+.PHONY: all clean
--- /dev/null
+CLEAN_TARGETS += clean-libi3
+
+libi3_SOURCES := $(wildcard libi3/*.c)
+libi3_HEADERS := $(wildcard libi3/*.h)
+libi3_CFLAGS =
+libi3_LIBS =
+
+libi3_OBJECTS := $(libi3_SOURCES:.c=.o)
+
+
+libi3/%.o: libi3/%.c $(libi3_HEADERS)
+ echo "[libi3] CC $<"
+ $(CC) $(I3_CPPFLAGS) $(CPPFLAGS) $(libi3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $<
+
+libi3.a: $(libi3_OBJECTS)
+ echo "[libi3] AR libi3.a"
+ ar rcs $@ $^ $(libi3_LIBS)
+
+clean-libi3:
+ echo "[libi3] Clean"
+ rm -f $(libi3_OBJECTS) libi3.a
-A2M:=a2x -f manpage --asciidoc-opts="-f asciidoc.conf"
-
-all: i3.1 i3-msg.1 i3-input.1 i3-nagbar.1 i3-config-wizard.1 i3-migrate-config-to-v4.1 i3-sensible-editor.1 i3-sensible-pager.1 i3-sensible-terminal.1 i3-dump-log.1
-
-%.1: %.man asciidoc.conf
- ${A2M} $<
+all:
+ $(MAKE) -C .. mans
clean:
- for file in $$(echo i3 i3-msg i3-input i3-nagbar i3-wsbar i3-config-wizard i3-migrate-config-to-v4 i3-sensible-editor i3-sensible-pager i3-sensible-terminal i3-dump-log); \
- do \
- rm -f $${file}.1 $${file}.html $${file}.xml; \
- done
+ $(MAKE) -C .. clean-mans
-distclean: clean
- rm -f *.1
+.PHONY: all clean
== DESCRIPTION
Debug versions of i3 automatically use 1% of your RAM (but 25 MiB max) to store
-full debug loglevel log output. This is extremely helpful for bugreports and
+full debug log output. This is extremely helpful for bugreports and
figuring out what is going on, without permanently logging to a file.
With i3-dump-log, you can dump the SHM log to stdout.
i3-sensible-terminal(1)
=======================
-Michael Stapelberg <michael+i3@stapelberg.de>
-v4.1, November 2011
+Michael Stapelberg <michael@i3wm.org>
+v4.2, August 2012
== NAME
* xterm
* gnome-terminal
* roxterm
+* xfce4-terminal
Please don’t complain about the order: If the user has any preference, he will
have $TERMINAL set or modified his i3 configuration file.
== SYNOPSIS
-i3 [-a] [-c configfile] [-C] [-d <loglevel>] [-v] [-V]
+i3 [-a] [-c configfile] [-C] [-d all] [-v] [-V]
== OPTIONS
-C::
Check the configuration file for validity and exit.
--d::
-Specifies the debug loglevel. To see the most output, use -d all.
+-d all::
+Enables debug logging.
+The 'all' parameter is present for historical reasons.
-v::
Display version number (and date of the last commit).
--- /dev/null
+i3bar(1)
+========
+Axel Wagner <mail+i3bar@merovius.de>
+v4.1, October 2011
+
+== NAME
+
+i3bar - xcb-based status- and workspace-bar
+
+== SYNOPSIS
+
+*i3bar* [*-s* 'sock_path'] [*-b* 'bar_id'] [*-v*] [*-h*]
+
+== WARNING
+
+i3bar will automatically be invoked by i3 for every 'bar' configuration block.
+
+Starting it manually is usually not what you want to do.
+
+You have been warned!
+
+== OPTIONS
+
+*-s, --socket* 'sock_path'::
+Overwrites the path to the i3 IPC socket.
+
+*-b, --bar_id* 'bar_id'::
+Specifies the bar ID for which to get the configuration from i3.
+
+*-v, --version*::
+Display version number and exit.
+
+*-h, --help*::
+Display a short help-message and exit
+
+== DESCRIPTION
+
+*i3bar* displays a bar at the bottom (or top) of your monitor(s) containing
+workspace switching buttons and a statusline generated by i3status(1) or
+similar. It is automatically invoked (and configured through) i3.
+
+i3bar does not support any color or other markups, so stdin should be plain
+utf8, one line at a time. If you use *i3status*(1), you therefore should
+specify 'output_format = none' in the general section of its config file.
+
+== ENVIRONMENT
+
+=== I3SOCK
+
+Used as a fallback for the i3 IPC socket path if neither the commandline
+contains an argument nor the I3_SOCKET_PATH property is set on the X11 root
+window.
+
+== EXAMPLES
+
+Nothing to see here, move along. As stated above, you should not run i3bar manually.
+
+Instead, see the i3 documentation, especially the User’s Guide.
+
+== SEE ALSO
+
++i3status(1)+ or +conky(1)+ for programs generating a statusline.
+
++dzen2(1)+ or +xmobar(1)+ for similar programs to i3bar.
+
+== AUTHORS
+
+Axel Wagner and contributors
--- /dev/null
+DISTCLEAN_TARGETS += clean-mans
+
+A2X = a2x
+
+A2X_MAN_CALL = $(V_A2X)$(A2X) -f manpage --asciidoc-opts="-f man/asciidoc.conf" $(A2X_FLAGS) $<
+
+MANS_1 = \
+ man/i3.1 \
+ man/i3bar.1 \
+ man/i3-msg.1 \
+ man/i3-input.1 \
+ man/i3-nagbar.1 \
+ man/i3-config-wizard.1 \
+ man/i3-migrate-config-to-v4.1 \
+ man/i3-sensible-editor.1 \
+ man/i3-sensible-pager.1 \
+ man/i3-sensible-terminal.1 \
+ man/i3-dump-log.1
+
+MANS = \
+ $(MANS_1)
+
+mans: $(MANS)
+
+$(MANS_1): %.1: %.man man/asciidoc.conf
+ $(A2X_MAN_CALL)
+
+clean-mans:
+ for file in $(notdir $(MANS)); \
+ do \
+ rm -f man/$${file} man/$${file%.*}.html man/$${file%.*}.xml; \
+ done
border_style = 'normal', 'none', '1pixel', 'toggle'
-> call cmd_border($border_style)
-# layout default|stacked|stacking|tabbed
+# layout default|stacked|stacking|tabbed|splitv|splith
+# layout toggle [split|all]
state LAYOUT:
- layout_mode = 'default', 'stacked', 'stacking', 'tabbed'
+ layout_mode = 'default', 'stacked', 'stacking', 'tabbed', 'splitv', 'splith'
-> call cmd_layout($layout_mode)
+ 'toggle'
+ -> LAYOUT_TOGGLE
+
+# layout toggle [split|all]
+state LAYOUT_TOGGLE:
+ end
+ -> call cmd_layout_toggle($toggle_mode)
+ toggle_mode = 'split', 'all'
+ -> call cmd_layout_toggle($toggle_mode)
# append_layout <path>
state APPEND_LAYOUT:
-> call cmd_rename_workspace($old_name, $new_name)
# move <direction> [<pixels> [px]]
-# move [window|container] [to] workspace <str>
+# move [window|container] [to] workspace [<str>|next|prev|current]
# move [window|container] [to] output <str>
# move [window|container] [to] scratchpad
# move workspace to [output] <str>
state MOVE_WORKSPACE:
'to'
-> MOVE_WORKSPACE_TO_OUTPUT
- workspace = 'next', 'prev', 'next_on_output', 'prev_on_output'
+ workspace = 'next', 'prev', 'next_on_output', 'prev_on_output', 'current'
-> call cmd_move_con_to_workspace($workspace)
'number'
-> MOVE_WORKSPACE_NUMBER
--- /dev/null
+all:
+ $(MAKE) -C .. i3
+
+install:
+ $(MAKE) -C .. install-i3
+
+clean:
+ $(MAKE) -C .. clean-i3
+
+.PHONY: all install clean
* store this in a separate variable because in the i3 config struct we just
* store the i3Font. */
static char *font_pattern;
+/* The path to the temporary script files used by i3-nagbar. We need to keep
+ * them around to delete the files in the i3-nagbar SIGCHLD handler. */
+static char *edit_script_path, *pager_script_path;
typedef struct yy_buffer_state *YY_BUFFER_STATE;
extern int yylex(struct context *context);
*/
static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
ev_child_stop(EV_A_ watcher);
+
+ if (unlink(edit_script_path) != 0)
+ warn("Could not delete temporary i3-nagbar script %s", edit_script_path);
+ if (unlink(pager_script_path) != 0)
+ warn("Could not delete temporary i3-nagbar script %s", pager_script_path);
+
if (!WIFEXITED(watcher->rstatus)) {
fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n");
return;
}
#endif
+/*
+ * Writes the given command as a shell script to path.
+ * Returns true unless something went wrong.
+ *
+ */
+static bool write_nagbar_script(const char *path, const char *command) {
+ int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IXUSR);
+ if (fd == -1) {
+ warn("Could not create temporary script to store the nagbar command");
+ return false;
+ }
+ write(fd, "#!/bin/sh\n", strlen("#!/bin/sh\n"));
+ write(fd, command, strlen(command));
+ close(fd);
+ return true;
+}
+
/*
* Starts an i3-nagbar process which alerts the user that his configuration
* file contains one or more errors. Also offers two buttons: One to launch an
return;
fprintf(stderr, "Starting i3-nagbar due to configuration errors\n");
+
+ /* We need to create a custom script containing our actual command
+ * since not every terminal emulator which is contained in
+ * i3-sensible-terminal supports -e with multiple arguments (and not
+ * all of them support -e with one quoted argument either).
+ *
+ * NB: The paths need to be unique, that is, don’t assume users close
+ * their nagbars at any point in time (and they still need to work).
+ * */
+ edit_script_path = get_process_filename("nagbar-cfgerror-edit");
+ pager_script_path = get_process_filename("nagbar-cfgerror-pager");
+
configerror_pid = fork();
if (configerror_pid == -1) {
warn("Could not fork()");
/* child */
if (configerror_pid == 0) {
+ char *edit_command, *pager_command;
+ sasprintf(&edit_command, "i3-sensible-editor \"%s\" && i3-msg reload\n", config_path);
+ sasprintf(&pager_command, "i3-sensible-pager \"%s\"\n", errorfilename);
+ if (!write_nagbar_script(edit_script_path, edit_command) ||
+ !write_nagbar_script(pager_script_path, pager_command))
+ return;
+
char *editaction,
*pageraction;
- sasprintf(&editaction, "i3-sensible-terminal -e sh -c \"i3-sensible-editor \\\"%s\\\" && i3-msg reload\"", config_path);
- sasprintf(&pageraction, "i3-sensible-terminal -e i3-sensible-pager \"%s\"", errorfilename);
+ sasprintf(&editaction, "i3-sensible-terminal -e \"%s\"", edit_script_path);
+ sasprintf(&pageraction, "i3-sensible-terminal -e \"%s\"", pager_script_path);
char *argv[] = {
NULL, /* will be replaced by the executable path */
"-t",
Con *resize_con = con;
while (resize_con->type != CT_WORKSPACE &&
resize_con->type != CT_FLOATING_CON &&
- resize_con->parent->orientation != orientation)
+ con_orientation(resize_con->parent) != orientation)
resize_con = resize_con->parent;
DLOG("resize_con = %p\n", resize_con);
if (resize_con->type != CT_WORKSPACE &&
resize_con->type != CT_FLOATING_CON &&
- resize_con->parent->orientation == orientation) {
+ con_orientation(resize_con->parent) == orientation) {
first = resize_con;
second = (way == 'n') ? TAILQ_NEXT(first, nodes) : TAILQ_PREV(first, nodes_head, nodes);
if (second == TAILQ_END(&(first->nodes_head))) {
if ((check_con->layout == L_STACKED ||
check_con->layout == L_TABBED ||
- check_con->orientation == HORIZ) &&
+ con_orientation(check_con) == HORIZ) &&
con_num_children(check_con) > 1) {
DLOG("Not handling this resize, this container has > 1 child.\n");
return false;
} while (0)
/** When the command did not include match criteria (!), we use the currently
- * focused command. Do not confuse this case with a command which included
+ * focused container. Do not confuse this case with a command which included
* criteria but which did not match any windows. This macro has to be called in
* every command.
*/
/*
* Implementation of 'move [window|container] [to] workspace
- * next|prev|next_on_output|prev_on_output'.
+ * next|prev|next_on_output|prev_on_output|current'.
*
*/
void cmd_move_con_to_workspace(I3_CMD, char *which) {
DLOG("which=%s\n", which);
+ /* We have nothing to move:
+ * when criteria was specified but didn't match any window or
+ * when criteria wasn't specified and we don't have any window focused. */
+ if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
+ (match_is_empty(current_match) && focused->type == CT_WORKSPACE)) {
+ ysuccess(false);
+ return;
+ }
+
HANDLE_EMPTY_MATCH;
/* get the workspace */
ws = workspace_next_on_output();
else if (strcmp(which, "prev_on_output") == 0)
ws = workspace_prev_on_output();
+ else if (strcmp(which, "current") == 0)
+ ws = con_get_workspace(focused);
else {
ELOG("BUG: called with which=%s\n", which);
ysuccess(false);
owindow *current;
- /* Error out early to not create a non-existing workspace (in
- * workspace_get()) if we are not actually able to move anything. */
- if (match_is_empty(current_match) && focused->type == CT_WORKSPACE) {
+ /* We have nothing to move:
+ * when criteria was specified but didn't match any window or
+ * when criteria wasn't specified and we don't have any window focused. */
+ if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
+ (match_is_empty(current_match) && focused->type == CT_WORKSPACE)) {
ysuccess(false);
return;
}
void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
owindow *current;
- /* Error out early to not create a non-existing workspace (in
- * workspace_get()) if we are not actually able to move anything. */
- if (match_is_empty(current_match) && focused->type == CT_WORKSPACE) {
+ /* We have nothing to move:
+ * when criteria was specified but didn't match any window or
+ * when criteria wasn't specified and we don't have any window focused. */
+ if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
+ (match_is_empty(current_match) && focused->type == CT_WORKSPACE)) {
ysuccess(false);
return;
}
LOG("tiling resize\n");
/* get the appropriate current container (skip stacked/tabbed cons) */
Con *current = focused;
+ Con *other = NULL;
+ double percentage = 0;
while (current->parent->layout == L_STACKED ||
current->parent->layout == L_TABBED)
current = current->parent;
orientation_t search_orientation =
(strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0 ? HORIZ : VERT);
- while (current->type != CT_WORKSPACE &&
- current->type != CT_FLOATING_CON &&
- current->parent->orientation != search_orientation)
- current = current->parent;
+ do {
+ if (con_orientation(current->parent) != search_orientation) {
+ current = current->parent;
+ continue;
+ }
- /* get the default percentage */
- int children = con_num_children(current->parent);
- Con *other;
- LOG("ins. %d children\n", children);
- double percentage = 1.0 / children;
- LOG("default percentage = %f\n", percentage);
+ /* get the default percentage */
+ int children = con_num_children(current->parent);
+ LOG("ins. %d children\n", children);
+ percentage = 1.0 / children;
+ LOG("default percentage = %f\n", percentage);
- orientation_t orientation = current->parent->orientation;
+ orientation_t orientation = con_orientation(current->parent);
- if ((orientation == HORIZ &&
- (strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0)) ||
- (orientation == VERT &&
- (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0))) {
- LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
- (orientation == HORIZ ? "horizontal" : "vertical"));
- ysuccess(false);
- return false;
- }
+ if ((orientation == HORIZ &&
+ (strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0)) ||
+ (orientation == VERT &&
+ (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0))) {
+ LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
+ (orientation == HORIZ ? "horizontal" : "vertical"));
+ ysuccess(false);
+ return false;
+ }
- if (strcmp(direction, "up") == 0 || strcmp(direction, "left") == 0) {
- other = TAILQ_PREV(current, nodes_head, nodes);
- } else {
- other = TAILQ_NEXT(current, nodes);
- }
- if (other == TAILQ_END(workspaces)) {
- LOG("No other container in this direction found, cannot resize.\n");
+ if (strcmp(direction, "up") == 0 || strcmp(direction, "left") == 0) {
+ other = TAILQ_PREV(current, nodes_head, nodes);
+ } else {
+ other = TAILQ_NEXT(current, nodes);
+ }
+ if (other == TAILQ_END(workspaces)) {
+ LOG("No other container in this direction found, trying to look further up in the tree...\n");
+ current = current->parent;
+ continue;
+ }
+ break;
+ } while (current->type != CT_WORKSPACE &&
+ current->type != CT_FLOATING_CON);
+
+ if (other == NULL) {
+ LOG("No other container in this direction found, trying to look further up in the tree...\n");
ysuccess(false);
return false;
}
+
LOG("other->percent = %f\n", other->percent);
LOG("current->percent before = %f\n", current->percent);
if (current->percent == 0.0)
while (current->type != CT_WORKSPACE &&
current->type != CT_FLOATING_CON &&
- current->parent->orientation != search_orientation)
+ con_orientation(current->parent) != search_orientation)
current = current->parent;
/* get the default percentage */
double percentage = 1.0 / children;
LOG("default percentage = %f\n", percentage);
- orientation_t orientation = current->parent->orientation;
+ orientation_t orientation = con_orientation(current->parent);
if ((orientation == HORIZ &&
strcmp(direction, "height") == 0) ||
*
*/
void cmd_focus_level(I3_CMD, char *level) {
- if (focused &&
- focused->type != CT_WORKSPACE &&
- focused->fullscreen_mode != CF_NONE) {
- LOG("Cannot change focus while in fullscreen mode.\n");
- ysuccess(false);
- return;
- }
-
DLOG("level = %s\n", level);
+ bool success = false;
+
+ /* Focusing the parent can only be allowed if the newly
+ * focused container won't escape the fullscreen container. */
+ if (strcmp(level, "parent") == 0) {
+ if (focused && focused->parent) {
+ if (con_fullscreen_permits_focusing(focused->parent))
+ success = level_up();
+ else
+ LOG("Currently in fullscreen, not going up\n");
+ }
+ }
- if (strcmp(level, "parent") == 0)
- level_up();
- else level_down();
+ /* Focusing a child should always be allowed. */
+ else success = level_down();
- cmd_output->needs_tree_render = true;
+ cmd_output->needs_tree_render = success;
// XXX: default reply for now, make this a better reply
- ysuccess(true);
+ ysuccess(success);
}
/*
if (!ws)
continue;
- /* Don't allow the focus switch if the focused and current
- * containers are in the same workspace. */
- if (focused &&
- focused->type != CT_WORKSPACE &&
- focused->fullscreen_mode != CF_NONE &&
- con_get_workspace(focused) == ws) {
- LOG("Cannot change focus while in fullscreen mode (same workspace).\n");
+ /* Check the fullscreen focus constraints. */
+ if (!con_fullscreen_permits_focusing(current->con)) {
+ LOG("Cannot change focus while in fullscreen mode (fullscreen rules).\n");
ysuccess(false);
return;
}
}
/*
- * Implementation of 'layout default|stacked|stacking|tabbed'.
+ * Implementation of 'layout default|stacked|stacking|tabbed|splitv|splith'.
*
*/
void cmd_layout(I3_CMD, char *layout_str) {
if (strcmp(layout_str, "stacking") == 0)
layout_str = "stacked";
- DLOG("changing layout to %s\n", layout_str);
owindow *current;
- int layout = (strcmp(layout_str, "default") == 0 ? L_DEFAULT :
- (strcmp(layout_str, "stacked") == 0 ? L_STACKED :
- L_TABBED));
+ int layout;
+ /* default is a special case which will be handled in con_set_layout(). */
+ if (strcmp(layout_str, "default") == 0)
+ layout = L_DEFAULT;
+ else if (strcmp(layout_str, "stacked") == 0)
+ layout = L_STACKED;
+ else if (strcmp(layout_str, "tabbed") == 0)
+ layout = L_TABBED;
+ else if (strcmp(layout_str, "splitv") == 0)
+ layout = L_SPLITV;
+ else if (strcmp(layout_str, "splith") == 0)
+ layout = L_SPLITH;
+
+ DLOG("changing layout to %s (%d)\n", layout_str, layout);
/* check if the match is empty, not if the result is empty */
if (match_is_empty(current_match))
ysuccess(true);
}
+/*
+ * Implementation of 'layout toggle [all|split]'.
+ *
+ */
+void cmd_layout_toggle(I3_CMD, char *toggle_mode) {
+ owindow *current;
+
+ if (toggle_mode == NULL)
+ toggle_mode = "default";
+
+ DLOG("toggling layout (mode = %s)\n", toggle_mode);
+
+ /* check if the match is empty, not if the result is empty */
+ if (match_is_empty(current_match))
+ con_toggle_layout(focused->parent, toggle_mode);
+ else {
+ TAILQ_FOREACH(current, &owindows, owindows) {
+ DLOG("matching: %p / %s\n", current->con, current->con->name);
+ con_toggle_layout(current->con, toggle_mode);
+ }
+ }
+
+ cmd_output->needs_tree_render = true;
+ // XXX: default reply for now, make this a better reply
+ ysuccess(true);
+}
+
/*
* Implementaiton of 'exit'.
*
*/
void cmd_exit(I3_CMD) {
LOG("Exiting due to user command.\n");
+ xcb_disconnect(conn);
exit(0);
/* unreached */
void cmd_reload(I3_CMD) {
LOG("reloading\n");
kill_configerror_nagbar(false);
+ kill_commanderror_nagbar(false);
load_configuration(conn, NULL, true);
x_set_i3_atoms();
/* Send an IPC event just in case the ws names have changed */
void cmd_open(I3_CMD) {
LOG("opening new container\n");
Con *con = tree_open_con(NULL, NULL);
+ con->layout = L_SPLITH;
con_focus(con);
y(map_open);
position[(copywalk - input)] = (copywalk >= walk ? '^' : ' ');
position[len] = '\0';
- printf("%s\n", errormessage);
- printf("Your command: %s\n", input);
- printf(" %s\n", position);
+ ELOG("%s\n", errormessage);
+ ELOG("Your command: %s\n", input);
+ ELOG(" %s\n", position);
/* Format this error message as a JSON reply. */
y(map_open);
/*
* Logs the given message to stdout while prefixing the current time to it,
- * but only if the corresponding debug loglevel was activated.
+ * but only if debug logging was activated.
* This is to be called by DLOG() which includes filename/linenumber
*
*/
-void debuglog(uint64_t lev, char *fmt, ...) {
+void debuglog(char *fmt, ...) {
+ va_list args;
+
+ va_start(args, fmt);
+ fprintf(stdout, "# ");
+ vfprintf(stdout, fmt, args);
+ va_end(args);
+}
+
+void errorlog(char *fmt, ...) {
va_list args;
va_start(args, fmt);
- fprintf(stderr, "# ");
vfprintf(stderr, fmt, args);
va_end(args);
}
if (con->type == CT_WORKSPACE)
return false;
- if (con->orientation != NO_ORIENTATION) {
- DLOG("container %p does not accepts windows, orientation != NO_ORIENTATION\n", con);
+ if (con->split) {
+ DLOG("container %p does not accept windows, it is a split container.\n", con);
return false;
}
while (con_orientation(parent) != orientation) {
DLOG("Need to go one level further up\n");
parent = parent->parent;
- /* Abort when we reach a floating con */
- if (parent && parent->type == CT_FLOATING_CON)
+ /* Abort when we reach a floating con, or an output con */
+ if (parent &&
+ (parent->type == CT_FLOATING_CON ||
+ parent->type == CT_OUTPUT ||
+ (parent->parent && parent->parent->type == CT_OUTPUT)))
parent = NULL;
if (parent == NULL)
break;
return;
}
+ /* Prevent moving if this would violate the fullscreen focus restrictions. */
+ if (!con_fullscreen_permits_focusing(workspace)) {
+ LOG("Cannot move out of a fullscreen container");
+ return;
+ }
+
if (con_is_floating(con)) {
DLOG("Using FLOATINGCON instead\n");
con = con->parent;
}
+ Con *source_ws = con_get_workspace(con);
+ if (workspace == source_ws) {
+ DLOG("Not moving, already there\n");
+ return;
+ }
+
+ /* Save the current workspace. So we can call workspace_show() by the end
+ * of this function. */
+ Con *current_ws = con_get_workspace(focused);
+
Con *source_output = con_get_output(con),
*dest_output = con_get_output(workspace);
/* Descend focus stack in case focus_next is a workspace which can
* occur if we move to the same workspace. Also show current workspace
* to ensure it is focused. */
- workspace_show(con_get_workspace(focus_next));
- con_focus(con_descend_focused(focus_next));
+ workspace_show(current_ws);
+
+ /* Set focus only if con was on current workspace before moving.
+ * Otherwise we would give focus to some window on different workspace. */
+ if (source_ws == current_ws)
+ con_focus(con_descend_focused(focus_next));
}
CALL(parent, on_remove_child);
*
*/
int con_orientation(Con *con) {
- /* stacking containers behave like they are in vertical orientation */
- if (con->layout == L_STACKED)
- return VERT;
-
- if (con->layout == L_TABBED)
- return HORIZ;
-
- return con->orientation;
+ switch (con->layout) {
+ case L_SPLITV:
+ /* stacking containers behave like they are in vertical orientation */
+ case L_STACKED:
+ return VERT;
+
+ case L_SPLITH:
+ /* tabbed containers behave like they are in vertical orientation */
+ case L_TABBED:
+ return HORIZ;
+
+ case L_DEFAULT:
+ DLOG("Someone called con_orientation() on a con with L_DEFAULT, this is a bug in the code.\n");
+ assert(false);
+ return HORIZ;
+
+ case L_DOCKAREA:
+ case L_OUTPUT:
+ DLOG("con_orientation() called on dockarea/output (%d) container %p\n", con->layout, con);
+ assert(false);
+ return HORIZ;
+
+ default:
+ DLOG("con_orientation() ran into default\n");
+ assert(false);
+ }
}
/*
Con *new = con_new(NULL, NULL);
new->parent = con;
- /* 2: set the requested layout on the split con */
- new->layout = layout;
-
- /* 3: While the layout is irrelevant in stacked/tabbed mode, it needs
- * to be set. Otherwise, this con will not be interpreted as a split
- * container. */
- if (config.default_orientation == NO_ORIENTATION) {
- new->orientation = (con->rect.height > con->rect.width) ? VERT : HORIZ;
- } else {
- new->orientation = config.default_orientation;
- }
+ /* 2: Set the requested layout on the split container and mark it as
+ * split. */
+ con_set_layout(new, layout);
+ new->split = true;
Con *old_focused = TAILQ_FIRST(&(con->focus_head));
if (old_focused == TAILQ_END(&(con->focus_head)))
old_focused = NULL;
- /* 4: move the existing cons of this workspace below the new con */
+ /* 3: move the existing cons of this workspace below the new con */
DLOG("Moving cons\n");
Con *child;
while (!TAILQ_EMPTY(&(con->nodes_head))) {
return;
}
- con->layout = layout;
+ if (layout == L_DEFAULT) {
+ /* Special case: the layout formerly known as "default" (in combination
+ * with an orientation). Since we switched to splith/splitv layouts,
+ * using the "default" layout (which "only" should happen when using
+ * legacy configs) is using the last split layout (either splith or
+ * splitv) in order to still do the same thing.
+ *
+ * Starting from v4.6 though, we will nag users about using "layout
+ * default", and in v4.9 we will remove it entirely (with an
+ * appropriate i3-migrate-config mechanism). */
+ con->layout = con->last_split_layout;
+ /* In case last_split_layout was not initialized… */
+ if (con->layout == L_DEFAULT)
+ con->layout = L_SPLITH;
+ } else {
+ /* We fill in last_split_layout when switching to a different layout
+ * since there are many places in the code that don’t use
+ * con_set_layout(). */
+ if (con->layout == L_SPLITH || con->layout == L_SPLITV)
+ con->last_split_layout = con->layout;
+ con->layout = layout;
+ }
+}
+
+/*
+ * This function toggles the layout of a given container. toggle_mode can be
+ * either 'default' (toggle only between stacked/tabbed/last_split_layout),
+ * 'split' (toggle only between splitv/splith) or 'all' (toggle between all
+ * layouts).
+ *
+ */
+void con_toggle_layout(Con *con, const char *toggle_mode) {
+ if (strcmp(toggle_mode, "split") == 0) {
+ /* Toggle between splits. When the current layout is not a split
+ * layout, we just switch back to last_split_layout. Otherwise, we
+ * change to the opposite split layout. */
+ if (con->layout != L_SPLITH && con->layout != L_SPLITV)
+ con_set_layout(con, con->last_split_layout);
+ else {
+ if (con->layout == L_SPLITH)
+ con_set_layout(con, L_SPLITV);
+ else con_set_layout(con, L_SPLITH);
+ }
+ } else {
+ if (con->layout == L_STACKED)
+ con_set_layout(con, L_TABBED);
+ else if (con->layout == L_TABBED) {
+ if (strcmp(toggle_mode, "all") == 0)
+ con_set_layout(con, L_SPLITH);
+ else con_set_layout(con, con->last_split_layout);
+ } else if (con->layout == L_SPLITH || con->layout == L_SPLITV) {
+ if (strcmp(toggle_mode, "all") == 0) {
+ /* When toggling through all modes, we toggle between
+ * splith/splitv, whereas normally we just directly jump to
+ * stacked. */
+ if (con->layout == L_SPLITH)
+ con_set_layout(con, L_SPLITV);
+ else con_set_layout(con, L_STACKED);
+ } else {
+ con_set_layout(con, L_STACKED);
+ }
+ }
+ }
}
/*
/* For horizontal/vertical split containers we sum up the width (h-split)
* or height (v-split) and use the maximum of the height (h-split) or width
* (v-split) as minimum size. */
- if (con->orientation == HORIZ || con->orientation == VERT) {
+ if (con->split) {
uint32_t width = 0, height = 0;
Con *child;
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
Rect min = con_minimum_size(child);
- if (con->orientation == HORIZ) {
+ if (con->layout == L_SPLITH) {
width += min.width;
height = max(height, min.height);
} else {
return (Rect){ 0, 0, width, height };
}
- ELOG("Unhandled case, type = %d, layout = %d, orientation = %d\n",
- con->type, con->layout, con->orientation);
+ ELOG("Unhandled case, type = %d, layout = %d, split = %d\n",
+ con->type, con->layout, con->split);
assert(false);
}
+
+/*
+ * Returns true if changing the focus to con would be allowed considering
+ * the fullscreen focus constraints. Specifically, if a fullscreen container or
+ * any of its descendants is focused, this function returns true if and only if
+ * focusing con would mean that focus would still be visible on screen, i.e.,
+ * the newly focused container would not be obscured by a fullscreen container.
+ *
+ * In the simplest case, if a fullscreen container or any of its descendants is
+ * fullscreen, this functions returns true if con is the fullscreen container
+ * itself or any of its descendants, as this means focus wouldn't escape the
+ * boundaries of the fullscreen container.
+ *
+ * In case the fullscreen container is of type CF_OUTPUT, this function returns
+ * true if con is on a different workspace, as focus wouldn't be obscured by
+ * the fullscreen container that is constrained to a different workspace.
+ *
+ * Note that this same logic can be applied to moving containers. If a
+ * container can be focused under the fullscreen focus constraints, it can also
+ * become a parent or sibling to the currently focused container.
+ *
+ */
+bool con_fullscreen_permits_focusing(Con *con) {
+ /* No focus, no problem. */
+ if (!focused)
+ return true;
+
+ /* Find the first fullscreen ascendent. */
+ Con *fs = focused;
+ while (fs && fs->fullscreen_mode == CF_NONE)
+ fs = fs->parent;
+
+ /* The most common case is we hit the workspace level. In this
+ * situation, changing focus is also harmless. */
+ assert(fs->fullscreen_mode != CF_NONE);
+ if (fs->type == CT_WORKSPACE)
+ return true;
+
+ /* Allow it if the container itself is the fullscreen container. */
+ if (con == fs)
+ return true;
+
+ /* If fullscreen is per-output, the focus being in a different workspace is
+ * sufficient to guarantee that change won't leave fullscreen in bad shape. */
+ if (fs->fullscreen_mode == CF_OUTPUT &&
+ con_get_workspace(con) != con_get_workspace(fs)) {
+ return true;
+ }
+
+ /* Allow it only if the container to be focused is contained within the
+ * current fullscreen container. */
+ do {
+ if (con->parent == fs)
+ return true;
+ con = con->parent;
+ } while (con);
+
+ /* Focusing con would hide it behind a fullscreen window, disallow it. */
+ return false;
+}
}
/* 1: If the container is a workspace container, we need to create a new
- * split-container with the same orientation and make that one floating. We
+ * split-container with the same layout and make that one floating. We
* cannot touch the workspace container itself because floating containers
* are children of the workspace. */
if (con->type == CT_WORKSPACE) {
/* TODO: refactor this with src/con.c:con_set_layout */
Con *new = con_new(NULL, NULL);
new->parent = con;
- new->orientation = con->orientation;
+ new->layout = con->layout;
/* since the new container will be set into floating mode directly
* afterwards, we need to copy the workspace rect. */
* otherwise. */
Con *ws = con_get_workspace(con);
nc->parent = ws;
- nc->orientation = NO_ORIENTATION;
+ nc->split = true;
nc->type = CT_FLOATING_CON;
+ nc->layout = L_SPLITH;
/* We insert nc already, even though its rect is not yet calculated. This
* is necessary because otherwise the workspace might be empty (and get
* closed in tree_close()) even though it’s not. */
int32_t rel_y = (con->rect.y - old_rect->y);
/* Then we calculate a fraction, for example 0.63 for a window
* which is at y = 1212 of a 1920 px high output */
- double fraction_x = ((double)rel_x / (int32_t)old_rect->width);
- double fraction_y = ((double)rel_y / (int32_t)old_rect->height);
DLOG("rel_x = %d, rel_y = %d, fraction_x = %f, fraction_y = %f, output->w = %d, output->h = %d\n",
- rel_x, rel_y, fraction_x, fraction_y, old_rect->width, old_rect->height);
- con->rect.x = (int32_t)new_rect->x + (fraction_x * (int32_t)new_rect->width);
- con->rect.y = (int32_t)new_rect->y + (fraction_y * (int32_t)new_rect->height);
+ rel_x, rel_y, (double)rel_x / old_rect->width, (double)rel_y / old_rect->height,
+ old_rect->width, old_rect->height);
+ /* Here we have to multiply at first. Or we will lose precision when not compiled with -msse2 */
+ con->rect.x = (int32_t)new_rect->x + (double)(rel_x * (int32_t)new_rect->width) / (int32_t)old_rect->width;
+ con->rect.y = (int32_t)new_rect->y + (double)(rel_y * (int32_t)new_rect->height) / (int32_t)old_rect->height;
DLOG("Resulting coordinates: x = %d, y = %d\n", con->rect.x, con->rect.y);
}
return false;
}
-
-/*
- * There was a key press. We compare this key code with our bindings table and pass
- * the bound action to parse_command().
- *
- */
-static void handle_key_press(xcb_key_press_event_t *event) {
-
- last_timestamp = event->time;
-
- DLOG("Keypress %d, state raw = %d\n", event->detail, event->state);
-
- /* Remove the numlock bit, all other bits are modifiers we can bind to */
- uint16_t state_filtered = event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK);
- DLOG("(removed numlock, state = %d)\n", state_filtered);
- /* Only use the lower 8 bits of the state (modifier masks) so that mouse
- * button masks are filtered out */
- state_filtered &= 0xFF;
- DLOG("(removed upper 8 bits, state = %d)\n", state_filtered);
-
- if (xkb_current_group == XkbGroup2Index)
- state_filtered |= BIND_MODE_SWITCH;
-
- DLOG("(checked mode_switch, state %d)\n", state_filtered);
-
- /* Find the binding */
- Binding *bind = get_binding(state_filtered, event->detail);
-
- /* No match? Then the user has Mode_switch enabled but does not have a
- * specific keybinding. Fall back to the default keybindings (without
- * Mode_switch). Makes it much more convenient for users of a hybrid
- * layout (like us, ru). */
- if (bind == NULL) {
- state_filtered &= ~(BIND_MODE_SWITCH);
- DLOG("no match, new state_filtered = %d\n", state_filtered);
- if ((bind = get_binding(state_filtered, event->detail)) == NULL) {
- ELOG("Could not lookup key binding (modifiers %d, keycode %d)\n",
- state_filtered, event->detail);
- return;
- }
- }
-
- char *command_copy = sstrdup(bind->command);
- struct CommandResult *command_output = parse_command(command_copy);
- free(command_copy);
-
- if (command_output->needs_tree_render)
- tree_render();
-
- yajl_gen_free(command_output->json_gen);
-}
-
/*
* Called with coordinates of an enter_notify event or motion_notify event
* to check if the user crossed virtual screen boundaries and adjust the
--- /dev/null
+ALL_TARGETS += i3
+INSTALL_TARGETS += install-i3
+CLEAN_TARGETS += clean-i3
+
+i3_SOURCES_GENERATED = src/cfgparse.tab.c src/cfgparse.yy.c
+i3_SOURCES := $(filter-out $(i3_SOURCES_GENERATED),$(wildcard src/*.c))
+i3_HEADERS_CMDPARSER := $(wildcard include/GENERATED_*.h)
+i3_HEADERS := $(filter-out $(i3_HEADERS_CMDPARSER),$(wildcard include/*.h))
+i3_CFLAGS = $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(XCB_WM_CFLAGS) $(X11_CFLAGS) $(XCURSOR_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) $(PCRE_CFLAGS) $(LIBSN_CFLAGS)
+i3_LIBS = $(XCB_LIBS) $(XCB_KBD_LIBS) $(XCB_WM_LIBS) $(X11_LIBS) $(XCURSOR_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) $(PCRE_LIBS) $(LIBSN_LIBS) -lm
+
+i3_OBJECTS := $(i3_SOURCES_GENERATED:.c=.o) $(i3_SOURCES:.c=.o)
+
+src/%.o: src/%.c $(i3_HEADERS)
+ echo "[i3] CC $<"
+ $(CC) $(I3_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $<
+
+src/cfgparse.yy.c: src/cfgparse.l src/cfgparse.tab.o $(i3_HEADERS)
+ echo "[i3] LEX $<"
+ $(FLEX) -i -o $@ $<
+
+src/cfgparse.tab.c: src/cfgparse.y $(i3_HEADERS)
+ echo "[i3] YACC $<"
+ $(BISON) --debug --verbose -b $(basename $< .y) -d $<
+
+# This target compiles the command parser twice:
+# Once with -DTEST_PARSER, creating a stand-alone executable used for tests,
+# and once as an object file for i3.
+src/commands_parser.o: src/commands_parser.c $(i3_HEADERS) i3-command-parser.stamp
+ echo "[i3] CC $<"
+ $(CC) $(I3_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(I3_LDFLAGS) $(LDFLAGS) -DTEST_PARSER -o test.commands_parser $< $(i3_LIBS) $(LIBS)
+ $(CC) $(I3_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $<
+
+i3-command-parser.stamp: generate-command-parser.pl parser-specs/commands.spec
+ echo "[i3] Generating command parser"
+ (cd include; ../generate-command-parser.pl)
+ touch $@
+
+i3: libi3.a $(i3_OBJECTS)
+ echo "[i3] Link i3"
+ $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(i3_LIBS) $(LIBS)
+
+install-i3: i3
+ echo "[i3] Install"
+ $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
+ $(INSTALL) -d -m 0755 $(DESTDIR)$(SYSCONFDIR)/i3
+ $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/include/i3
+ $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/xsessions
+ $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/applications
+ $(INSTALL) -m 0755 i3 $(DESTDIR)$(PREFIX)/bin/
+ $(INSTALL) -m 0755 i3-migrate-config-to-v4 $(DESTDIR)$(PREFIX)/bin/
+ $(INSTALL) -m 0755 i3-sensible-editor $(DESTDIR)$(PREFIX)/bin/
+ $(INSTALL) -m 0755 i3-sensible-pager $(DESTDIR)$(PREFIX)/bin/
+ $(INSTALL) -m 0755 i3-sensible-terminal $(DESTDIR)$(PREFIX)/bin/
+ test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config
+ test -e $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes || $(INSTALL) -m 0644 i3.config.keycodes $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes
+ $(INSTALL) -m 0644 i3.xsession.desktop $(DESTDIR)$(PREFIX)/share/xsessions/i3.desktop
+ $(INSTALL) -m 0644 i3.applications.desktop $(DESTDIR)$(PREFIX)/share/applications/i3.desktop
+ $(INSTALL) -m 0644 include/i3/ipc.h $(DESTDIR)$(PREFIX)/include/i3/
+
+clean-i3:
+ echo "[i3] Clean"
+ rm -f $(i3_OBJECTS) $(i3_SOURCES_GENERATED) $(i3_HEADERS_CMDPARSER) i3-command-parser.stamp i3 src/*.gcno src/cfgparse.{output,dot}
ystr("type");
y(integer, con->type);
+ /* provided for backwards compatibility only. */
ystr("orientation");
- switch (con->orientation) {
- case NO_ORIENTATION:
- ystr("none");
- break;
- case HORIZ:
+ if (!con->split)
+ ystr("none");
+ else {
+ if (con_orientation(con) == HORIZ)
ystr("horizontal");
- break;
- case VERT:
- ystr("vertical");
- break;
+ else ystr("vertical");
}
ystr("scratchpad_state");
ystr("focused");
y(bool, (con == focused));
+ ystr("split");
+ y(bool, con->split);
+
ystr("layout");
switch (con->layout) {
case L_DEFAULT:
- ystr("default");
+ DLOG("About to dump layout=default, this is a bug in the code.\n");
+ assert(false);
+ break;
+ case L_SPLITV:
+ ystr("splitv");
+ break;
+ case L_SPLITH:
+ ystr("splith");
break;
case L_STACKED:
ystr("stacked");
break;
}
+ ystr("last_split_layout");
+ switch (con->layout) {
+ case L_SPLITV:
+ ystr("splitv");
+ break;
+ default:
+ ystr("splith");
+ break;
+ }
+
ystr("border");
switch (con->border_style) {
case BS_NORMAL:
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * key_press.c: key press handler
+ *
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include "all.h"
+
+static int current_nesting_level;
+static bool success_key;
+static bool command_failed;
+
+/* XXX: I don’t want to touch too much of the nagbar code at once, but we
+ * should refactor this with src/cfgparse.y into a clean generic nagbar
+ * interface. It might come in handy in other situations within i3, too. */
+static char *pager_script_path;
+static pid_t nagbar_pid = -1;
+
+/*
+ * Handler which will be called when we get a SIGCHLD for the nagbar, meaning
+ * it exited (or could not be started, depending on the exit code).
+ *
+ */
+static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
+ ev_child_stop(EV_A_ watcher);
+
+ if (unlink(pager_script_path) != 0)
+ warn("Could not delete temporary i3-nagbar script %s", pager_script_path);
+
+ if (!WIFEXITED(watcher->rstatus)) {
+ fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n");
+ return;
+ }
+
+ int exitcode = WEXITSTATUS(watcher->rstatus);
+ printf("i3-nagbar process exited with status %d\n", exitcode);
+ if (exitcode == 2) {
+ fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n");
+ }
+
+ nagbar_pid = -1;
+}
+
+/* We need ev >= 4 for the following code. Since it is not *that* important (it
+ * only makes sure that there are no i3-nagbar instances left behind) we still
+ * support old systems with libev 3. */
+#if EV_VERSION_MAJOR >= 4
+/*
+ * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal
+ * SIGKILL (9) to make sure there are no left-over i3-nagbar processes.
+ *
+ */
+static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) {
+ if (nagbar_pid != -1) {
+ LOG("Sending SIGKILL (%d) to i3-nagbar with PID %d\n", SIGKILL, nagbar_pid);
+ kill(nagbar_pid, SIGKILL);
+ }
+}
+#endif
+
+/*
+ * Writes the given command as a shell script to path.
+ * Returns true unless something went wrong.
+ *
+ */
+static bool write_nagbar_script(const char *path, const char *command) {
+ int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IXUSR);
+ if (fd == -1) {
+ warn("Could not create temporary script to store the nagbar command");
+ return false;
+ }
+ write(fd, "#!/bin/sh\n", strlen("#!/bin/sh\n"));
+ write(fd, command, strlen(command));
+ close(fd);
+ return true;
+}
+
+/*
+ * Starts an i3-nagbar process which alerts the user that his configuration
+ * file contains one or more errors. Also offers two buttons: One to launch an
+ * $EDITOR on the config file and another one to launch a $PAGER on the error
+ * logfile.
+ *
+ */
+static void start_commanderror_nagbar(void) {
+ if (nagbar_pid != -1) {
+ DLOG("i3-nagbar for command error already running, not starting again.\n");
+ return;
+ }
+
+ DLOG("Starting i3-nagbar due to command error\n");
+
+ /* We need to create a custom script containing our actual command
+ * since not every terminal emulator which is contained in
+ * i3-sensible-terminal supports -e with multiple arguments (and not
+ * all of them support -e with one quoted argument either).
+ *
+ * NB: The paths need to be unique, that is, don’t assume users close
+ * their nagbars at any point in time (and they still need to work).
+ * */
+ pager_script_path = get_process_filename("nagbar-cfgerror-pager");
+
+ nagbar_pid = fork();
+ if (nagbar_pid == -1) {
+ warn("Could not fork()");
+ return;
+ }
+
+ /* child */
+ if (nagbar_pid == 0) {
+ char *pager_command;
+ sasprintf(&pager_command, "i3-sensible-pager \"%s\"\n", errorfilename);
+ if (!write_nagbar_script(pager_script_path, pager_command))
+ return;
+
+ char *pageraction;
+ sasprintf(&pageraction, "i3-sensible-terminal -e \"%s\"", pager_script_path);
+ char *argv[] = {
+ NULL, /* will be replaced by the executable path */
+ "-t",
+ "error",
+ "-m",
+ "The configured command for this shortcut could not be run successfully.",
+ "-b",
+ "show errors",
+ pageraction,
+ NULL
+ };
+ exec_i3_utility("i3-nagbar", argv);
+ }
+
+ /* parent */
+ /* install a child watcher */
+ ev_child *child = smalloc(sizeof(ev_child));
+ ev_child_init(child, &nagbar_exited, nagbar_pid, 0);
+ ev_child_start(main_loop, child);
+
+/* We need ev >= 4 for the following code. Since it is not *that* important (it
+ * only makes sure that there are no i3-nagbar instances left behind) we still
+ * support old systems with libev 3. */
+#if EV_VERSION_MAJOR >= 4
+ /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is
+ * still running) */
+ ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup));
+ ev_cleanup_init(cleanup, nagbar_cleanup);
+ ev_cleanup_start(main_loop, cleanup);
+#endif
+}
+
+/*
+ * Kills the commanderror i3-nagbar process, if any.
+ *
+ * Called when reloading/restarting, since the user probably fixed his wrong
+ * keybindings.
+ *
+ * If wait_for_it is set (restarting), this function will waitpid(), otherwise,
+ * ev is assumed to handle it (reloading).
+ *
+ */
+void kill_commanderror_nagbar(bool wait_for_it) {
+ if (nagbar_pid == -1)
+ return;
+
+ if (kill(nagbar_pid, SIGTERM) == -1)
+ warn("kill(configerror_nagbar) failed");
+
+ if (!wait_for_it)
+ return;
+
+ /* When restarting, we don’t enter the ev main loop anymore and after the
+ * exec(), our old pid is no longer watched. So, ev won’t handle SIGCHLD
+ * for us and we would end up with a <defunct> process. Therefore we
+ * waitpid() here. */
+ waitpid(nagbar_pid, NULL, 0);
+}
+
+static int json_boolean(void *ctx, int boolval) {
+ DLOG("Got bool: %d, success_key %d, nesting_level %d\n", boolval, success_key, current_nesting_level);
+
+ if (success_key && current_nesting_level == 1 && !boolval)
+ command_failed = true;
+
+ return 1;
+}
+
+#if YAJL_MAJOR >= 2
+static int json_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) {
+#else
+static int json_map_key(void *ctx, const unsigned char *stringval, unsigned int stringlen) {
+#endif
+ success_key = (stringlen >= strlen("success") &&
+ strncmp((const char*)stringval, "success", strlen("success")) == 0);
+ return 1;
+}
+
+static int json_start_map(void *ctx) {
+ current_nesting_level++;
+ return 1;
+}
+
+static int json_end_map(void *ctx) {
+ current_nesting_level--;
+ return 1;
+}
+
+static yajl_callbacks command_error_callbacks = {
+ NULL,
+ &json_boolean,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &json_start_map,
+ &json_map_key,
+ &json_end_map,
+ NULL,
+ NULL
+};
+
+/*
+ * There was a key press. We compare this key code with our bindings table and pass
+ * the bound action to parse_command().
+ *
+ */
+void handle_key_press(xcb_key_press_event_t *event) {
+
+ last_timestamp = event->time;
+
+ DLOG("Keypress %d, state raw = %d\n", event->detail, event->state);
+
+ /* Remove the numlock bit, all other bits are modifiers we can bind to */
+ uint16_t state_filtered = event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK);
+ DLOG("(removed numlock, state = %d)\n", state_filtered);
+ /* Only use the lower 8 bits of the state (modifier masks) so that mouse
+ * button masks are filtered out */
+ state_filtered &= 0xFF;
+ DLOG("(removed upper 8 bits, state = %d)\n", state_filtered);
+
+ if (xkb_current_group == XkbGroup2Index)
+ state_filtered |= BIND_MODE_SWITCH;
+
+ DLOG("(checked mode_switch, state %d)\n", state_filtered);
+
+ /* Find the binding */
+ Binding *bind = get_binding(state_filtered, event->detail);
+
+ /* No match? Then the user has Mode_switch enabled but does not have a
+ * specific keybinding. Fall back to the default keybindings (without
+ * Mode_switch). Makes it much more convenient for users of a hybrid
+ * layout (like us, ru). */
+ if (bind == NULL) {
+ state_filtered &= ~(BIND_MODE_SWITCH);
+ DLOG("no match, new state_filtered = %d\n", state_filtered);
+ if ((bind = get_binding(state_filtered, event->detail)) == NULL) {
+ ELOG("Could not lookup key binding (modifiers %d, keycode %d)\n",
+ state_filtered, event->detail);
+ return;
+ }
+ }
+
+ char *command_copy = sstrdup(bind->command);
+ struct CommandResult *command_output = parse_command(command_copy);
+ free(command_copy);
+
+ if (command_output->needs_tree_render)
+ tree_render();
+
+ /* We parse the JSON reply to figure out whether there was an error
+ * ("success" being false in on of the returned dictionaries). */
+ const unsigned char *reply;
+#if YAJL_MAJOR >= 2
+ size_t length;
+ yajl_handle handle = yajl_alloc(&command_error_callbacks, NULL, NULL);
+#else
+ unsigned int length;
+ yajl_parser_config parse_conf = { 0, 0 };
+
+ yajl_handle handle = yajl_alloc(&command_error_callbacks, &parse_conf, NULL, NULL);
+#endif
+ yajl_gen_get_buf(command_output->json_gen, &reply, &length);
+
+ current_nesting_level = 0;
+ success_key = false;
+ command_failed = false;
+ yajl_status state = yajl_parse(handle, reply, length);
+ if (state != yajl_status_ok) {
+ ELOG("Could not parse my own reply. That's weird. reply is %.*s\n", length, reply);
+ } else {
+ if (command_failed)
+ start_commanderror_nagbar();
+ }
+
+ yajl_free(handle);
+
+ yajl_gen_free(command_output->json_gen);
+}
memcpy(json_node->sticky_group, val, len);
LOG("sticky_group of this container is %s\n", json_node->sticky_group);
} else if (strcasecmp(last_key, "orientation") == 0) {
+ /* Upgrade path from older versions of i3 (doing an inplace restart
+ * to a newer version):
+ * "orientation" is dumped before "layout". Therefore, we store
+ * whether the orientation was horizontal or vertical in the
+ * last_split_layout. When we then encounter layout == "default",
+ * we will use the last_split_layout as layout instead. */
char *buf = NULL;
sasprintf(&buf, "%.*s", (int)len, val);
- if (strcasecmp(buf, "none") == 0)
- json_node->orientation = NO_ORIENTATION;
- else if (strcasecmp(buf, "horizontal") == 0)
- json_node->orientation = HORIZ;
+ if (strcasecmp(buf, "none") == 0 ||
+ strcasecmp(buf, "horizontal") == 0)
+ json_node->last_split_layout = L_SPLITH;
else if (strcasecmp(buf, "vertical") == 0)
- json_node->orientation = VERT;
+ json_node->last_split_layout = L_SPLITV;
else LOG("Unhandled orientation: %s\n", buf);
+
+ /* What used to be an implicit check whether orientation !=
+ * NO_ORIENTATION is now a proper separate flag. */
+ if (strcasecmp(buf, "none") != 0)
+ json_node->split = true;
free(buf);
} else if (strcasecmp(last_key, "border") == 0) {
char *buf = NULL;
char *buf = NULL;
sasprintf(&buf, "%.*s", (int)len, val);
if (strcasecmp(buf, "default") == 0)
- json_node->layout = L_DEFAULT;
+ /* This set above when we read "orientation". */
+ json_node->layout = json_node->last_split_layout;
else if (strcasecmp(buf, "stacked") == 0)
json_node->layout = L_STACKED;
else if (strcasecmp(buf, "tabbed") == 0)
json_node->layout = L_TABBED;
- else if (strcasecmp(buf, "dockarea") == 0)
+ else if (strcasecmp(buf, "dockarea") == 0) {
json_node->layout = L_DOCKAREA;
- else if (strcasecmp(buf, "output") == 0)
+ /* Necessary for migrating from older versions of i3. */
+ json_node->split = false;
+ } else if (strcasecmp(buf, "output") == 0)
json_node->layout = L_OUTPUT;
+ else if (strcasecmp(buf, "splith") == 0)
+ json_node->layout = L_SPLITH;
+ else if (strcasecmp(buf, "splitv") == 0)
+ json_node->layout = L_SPLITV;
else LOG("Unhandled \"layout\": %s\n", buf);
free(buf);
+ } else if (strcasecmp(last_key, "last_split_layout") == 0) {
+ char *buf = NULL;
+ sasprintf(&buf, "%.*s", (int)len, val);
+ if (strcasecmp(buf, "splith") == 0)
+ json_node->last_split_layout = L_SPLITH;
+ else if (strcasecmp(buf, "splitv") == 0)
+ json_node->last_split_layout = L_SPLITV;
+ else LOG("Unhandled \"last_splitlayout\": %s\n", buf);
+ free(buf);
} else if (strcasecmp(last_key, "mark") == 0) {
char *buf = NULL;
sasprintf(&buf, "%.*s", (int)len, val);
to_focus = json_node;
}
+ if (strcasecmp(last_key, "split") == 0)
+ json_node->split = val;
+
if (parsing_swallows) {
if (strcasecmp(last_key, "restart_mode") == 0)
current_swallow->restart_mode = val;
* i3 - an improved dynamic tiling window manager
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
- * log.c: Setting of loglevels, logging functions.
+ * log.c: Logging functions.
*
*/
#include <stdarg.h>
#include "libi3.h"
#include "shmlog.h"
-/* loglevels.h is autogenerated at make time */
-#include "loglevels.h"
-
-static uint64_t loglevel = 0;
+static bool debug_logging = false;
static bool verbose = false;
static FILE *errorfile;
char *errorfilename;
}
/*
- * Enables the given loglevel.
+ * Set debug logging.
*
*/
-void add_loglevel(const char *level) {
- /* Handle the special loglevel "all" */
- if (strcasecmp(level, "all") == 0) {
- loglevel = UINT64_MAX;
- return;
- }
-
- for (int i = 0; i < sizeof(loglevels) / sizeof(char*); i++) {
- if (strcasecmp(loglevels[i], level) != 0)
- continue;
-
- /* The position in the array (plus one) is the amount of times
- * which we need to shift 1 to the left to get our bitmask for
- * the specific loglevel. */
- loglevel |= (1 << (i+1));
- break;
- }
+void set_debug_logging(const bool _debug_logging) {
+ debug_logging = _debug_logging;
}
/*
/*
* Logs the given message to stdout while prefixing the current time to it,
- * but only if the corresponding debug loglevel was activated.
+ * but only if debug logging was activated.
* This is to be called by DLOG() which includes filename/linenumber
*
*/
-void debuglog(uint64_t lev, char *fmt, ...) {
+void debuglog(char *fmt, ...) {
va_list args;
- if (!logbuffer && !(loglevel & lev))
+ if (!logbuffer && !(debug_logging))
return;
va_start(args, fmt);
- vlog((loglevel & lev), fmt, args);
+ vlog(debug_logging, fmt, args);
va_end(args);
}
set_verbosity(true);
break;
case 'd':
- LOG("Enabling debug loglevel %s\n", optarg);
- add_loglevel(optarg);
+ LOG("Enabling debug logging\n");
+ set_debug_logging(true);
break;
case 'l':
/* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */
}
/* fall-through */
default:
- fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V] [-C]\n", argv[0]);
+ fprintf(stderr, "Usage: %s [-c configfile] [-d all] [-a] [-v] [-V] [-C]\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "\t-a disable autostart ('exec' lines in config)\n");
fprintf(stderr, "\t-c <file> use the provided configfile instead\n");
fprintf(stderr, "\t-C validate configuration file and exit\n");
- fprintf(stderr, "\t-d <level> enable debug output with the specified loglevel\n");
+ fprintf(stderr, "\t-d all enable debug output\n");
fprintf(stderr, "\t-L <file> path to the serialized layout during restarts\n");
fprintf(stderr, "\t-v display version and exit\n");
fprintf(stderr, "\t-V enable verbose mode\n");
while (above->parent != same_orientation)
above = above->parent;
+ /* Enforce the fullscreen focus restrictions. */
+ if (!con_fullscreen_permits_focusing(above->parent)) {
+ LOG("Cannot move out of fullscreen container\n");
+ return;
+ }
+
DLOG("above = %p\n", above);
Con *next;
position_t position;
Con *topdock = con_new(NULL, NULL);
topdock->type = CT_DOCKAREA;
topdock->layout = L_DOCKAREA;
- topdock->orientation = VERT;
/* this container swallows dock clients */
Match *match = scalloc(sizeof(Match));
match_init(match);
DLOG("adding main content container\n");
Con *content = con_new(NULL, NULL);
content->type = CT_CON;
+ content->layout = L_SPLITH;
FREE(content->name);
content->name = sstrdup("content");
Con *bottomdock = con_new(NULL, NULL);
bottomdock->type = CT_DOCKAREA;
bottomdock->layout = L_DOCKAREA;
- bottomdock->orientation = VERT;
/* this container swallows dock clients */
match = scalloc(sizeof(Match));
match_init(match);
if (con_num_children(workspace) > 1)
continue;
- workspace->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ;
- DLOG("Setting workspace [%d,%s]'s orientation to %d.\n", workspace->num, workspace->name, workspace->orientation);
+ workspace->layout = (output->rect.height > output->rect.width) ? L_SPLITV : L_SPLITH;
+ DLOG("Setting workspace [%d,%s]'s layout to %d.\n", workspace->num, workspace->name, workspace->layout);
if ((child = TAILQ_FIRST(&(workspace->nodes_head)))) {
- child->orientation = workspace->orientation;
- DLOG("Setting child [%d,%s]'s orientation to %d.\n", child->num, child->name, child->orientation);
+ child->layout = workspace->layout;
+ DLOG("Setting child [%d,%s]'s layout to %d.\n", child->num, child->name, child->layout);
}
}
}
/*
* Checks if the given regular expression matches the given input and returns
* true if it does. In either case, it logs the outcome using LOG(), so it will
- * be visible without any debug loglevel.
+ * be visible without debug logging.
*
*/
bool regex_matches(struct regex *regex, const char *input) {
*/
void render_con(Con *con, bool render_fullscreen) {
int children = con_num_children(con);
- DLOG("Rendering %snode %p / %s / layout %d / children %d / orient %d\n",
+ DLOG("Rendering %snode %p / %s / layout %d / children %d\n",
(render_fullscreen ? "fullscreen " : ""), con, con->name, con->layout,
- children, con->orientation);
+ children);
/* Copy container rect, subtract container border */
/* This is the actually usable space inside this container for clients */
/* precalculate the sizes to be able to correct rounding errors */
int sizes[children];
- if (con->layout == L_DEFAULT && children > 0) {
+ if ((con->layout == L_SPLITH || con->layout == L_SPLITV) && children > 0) {
assert(!TAILQ_EMPTY(&con->nodes_head));
Con *child;
int i = 0, assigned = 0;
- int total = con->orientation == HORIZ ? rect.width : rect.height;
+ int total = con_orientation(con) == HORIZ ? rect.width : rect.height;
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
double percentage = child->percent > 0.0 ? child->percent : 1.0 / children;
assigned += sizes[i++] = percentage * total;
assert(children > 0);
/* default layout */
- if (con->layout == L_DEFAULT) {
- if (con->orientation == HORIZ) {
+ if (con->layout == L_SPLITH || con->layout == L_SPLITV) {
+ if (con->layout == L_SPLITH) {
child->rect.x = x;
child->rect.y = y;
child->rect.width = sizes[i];
content->type = CT_CON;
FREE(content->name);
content->name = sstrdup("content");
+ content->layout = L_SPLITH;
x_set_name(content, "[i3 con] content __i3");
con_attach(content, __i3, false);
ws->type = CT_WORKSPACE;
ws->num = -1;
ws->name = sstrdup("__i3_scratch");
+ ws->layout = L_SPLITH;
con_attach(ws, content, false);
x_set_name(ws, "[i3 con] workspace __i3_scratch");
ws->fullscreen_mode = CF_OUTPUT;
FREE(croot->name);
croot->name = "root";
croot->type = CT_ROOT;
+ croot->layout = L_SPLITH;
croot->rect = (Rect){
geometry->x,
geometry->y,
/* 3. create the container and attach it to its parent */
Con *new = con_new(con, window);
+ new->layout = L_SPLITH;
/* 4: re-calculate child->percent for each child */
con_fix_percent(con);
}
}
else {
- DLOG("not focusing because we're not killing anybody");
+ DLOG("not focusing because we're not killing anybody\n");
}
} else {
DLOG("not focusing, was not mapped\n");
/* for a workspace, we just need to change orientation */
if (con->type == CT_WORKSPACE) {
DLOG("Workspace, simply changing orientation to %d\n", orientation);
- con->orientation = orientation;
+ con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
return;
}
* child (its split functionality is unused so far), we just change the
* orientation (more intuitive than splitting again) */
if (con_num_children(parent) == 1 &&
- parent->layout == L_DEFAULT) {
- parent->orientation = orientation;
+ (parent->layout == L_SPLITH ||
+ parent->layout == L_SPLITV)) {
+ parent->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
DLOG("Just changing orientation of existing container\n");
return;
}
TAILQ_REPLACE(&(parent->nodes_head), con, new, nodes);
TAILQ_REPLACE(&(parent->focus_head), con, new, focused);
new->parent = parent;
- new->orientation = orientation;
+ new->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
+ new->split = true;
/* 3: swap 'percent' (resize factor) */
new->percent = con->percent;
}
/*
- * Moves focus one level up.
+ * Moves focus one level up. Returns true if focus changed.
*
*/
-void level_up(void) {
- /* We cannot go up when we are in fullscreen mode at the moment, that would
- * be totally not intuitive */
- if (focused->fullscreen_mode != CF_NONE) {
- LOG("Currently in fullscreen, not going up\n");
- return;
- }
+bool level_up(void) {
/* We can focus up to the workspace, but not any higher in the tree */
if ((focused->parent->type != CT_CON &&
focused->parent->type != CT_WORKSPACE) ||
focused->type == CT_WORKSPACE) {
LOG("Cannot go up any further\n");
- return;
+ return false;
}
con_focus(focused->parent);
+ return true;
}
/*
- * Moves focus one level down.
+ * Moves focus one level down. Returns true if focus changed.
*
*/
-void level_down(void) {
+bool level_down(void) {
/* Go down the focus stack of the current node */
Con *next = TAILQ_FIRST(&(focused->focus_head));
if (next == TAILQ_END(&(focused->focus_head))) {
printf("cannot go down\n");
- return;
+ return false;
}
con_focus(next);
+ return true;
}
static void mark_unmapped(Con *con) {
else next = TAILQ_LAST(&(parent->nodes_head), nodes_head);
}
+ /* Don't violate fullscreen focus restrictions. */
+ if (!con_fullscreen_permits_focusing(next))
+ return false;
+
/* 3: focus choice comes in here. at the moment we will go down
* until we find a window */
/* TODO: check for window, atm we only go down as far as possible */
DLOG("Checking if I can flatten con = %p / %s\n", con, con->name);
/* We only consider normal containers without windows */
- if (con->type != CT_CON || con->window != NULL)
+ if (con->type != CT_CON ||
+ parent->layout == L_OUTPUT || /* con == "content" */
+ con->window != NULL)
goto recurse;
/* Ensure it got only one child */
if (child == NULL || TAILQ_NEXT(child, nodes) != NULL)
goto recurse;
+ DLOG("child = %p, con = %p, parent = %p\n", child, con, parent);
+
/* The child must have a different orientation than the con but the same as
* the con’s parent to be redundant */
- if (con->orientation == NO_ORIENTATION ||
- child->orientation == NO_ORIENTATION ||
- con->orientation == child->orientation ||
- child->orientation != parent->orientation)
+ if (con->split ||
+ child->split ||
+ con_orientation(con) == con_orientation(child) ||
+ con_orientation(child) != con_orientation(parent))
goto recurse;
DLOG("Alright, I have to flatten this situation now. Stay calm.\n");
char *restart_filename = forget_layout ? NULL : store_restart_layout();
kill_configerror_nagbar(true);
+ kill_commanderror_nagbar(true);
restore_geometry();
* back-and-forth switching. */
static char *previous_workspace_name = NULL;
+/*
+ * Sets ws->layout to splith/splitv if default_orientation was specified in the
+ * configfile. Otherwise, it uses splith/splitv depending on whether the output
+ * is higher than wide.
+ *
+ */
+static void _workspace_apply_default_orientation(Con *ws) {
+ /* If default_orientation is set to NO_ORIENTATION we determine
+ * orientation depending on output resolution. */
+ if (config.default_orientation == NO_ORIENTATION) {
+ Con *output = con_get_output(ws);
+ ws->layout = (output->rect.height > output->rect.width) ? L_SPLITV : L_SPLITH;
+ DLOG("Auto orientation. Workspace size set to (%d,%d), setting layout to %d.\n",
+ output->rect.width, output->rect.height, ws->layout);
+ } else {
+ ws->layout = (config.default_orientation == HORIZ) ? L_SPLITH : L_SPLITV;
+ }
+}
+
/*
* Returns a pointer to the workspace with the given number (starting at 0),
* creating the workspace if necessary (by allocating the necessary amount of
else workspace->num = parsed_num;
LOG("num = %d\n", workspace->num);
- /* If default_orientation is set to NO_ORIENTATION we
- * determine workspace orientation from workspace size.
- * Otherwise we just set the orientation to default_orientation. */
- if (config.default_orientation == NO_ORIENTATION) {
- workspace->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ;
- DLOG("Auto orientation. Output resolution set to (%d,%d), setting orientation to %d.\n",
- workspace->rect.width, workspace->rect.height, workspace->orientation);
- } else {
- workspace->orientation = config.default_orientation;
- }
+ workspace->parent = content;
+ _workspace_apply_default_orientation(workspace);
con_attach(workspace, content, false);
/* We check if this is the workspace
* next/prev/next_on_output/prev_on_output/back_and_forth/number command.
* Beware: The workspace names "next", "prev", "next_on_output",
- * "prev_on_output", "number" and "back_and_forth" are OK, so we check
- * before stripping the double quotes */
+ * "prev_on_output", "number", "back_and_forth" and "current" are OK,
+ * so we check before stripping the double quotes */
if (strncasecmp(target, "next", strlen("next")) == 0 ||
strncasecmp(target, "prev", strlen("prev")) == 0 ||
strncasecmp(target, "next_on_output", strlen("next_on_output")) == 0 ||
strncasecmp(target, "prev_on_output", strlen("prev_on_output")) == 0 ||
strncasecmp(target, "number", strlen("number")) == 0 ||
- strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0)
+ strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0 ||
+ strncasecmp(target, "current", strlen("current")) == 0)
continue;
if (*target == '"')
target++;
ws->fullscreen_mode = CF_OUTPUT;
- /* If default_orientation is set to NO_ORIENTATION we determine
- * orientation depending on output resolution. */
- if (config.default_orientation == NO_ORIENTATION) {
- ws->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ;
- DLOG("Auto orientation. Workspace size set to (%d,%d), setting orientation to %d.\n",
- output->rect.width, output->rect.height, ws->orientation);
- } else {
- ws->orientation = config.default_orientation;
- }
+ _workspace_apply_default_orientation(ws);
return ws;
}
+
/*
* Returns true if the workspace is currently visible. Especially important for
* multi-monitor environments, as they can have multiple currenlty active
/*
* 'Forces' workspace orientation by moving all cons into a new split-con with
- * the same orientation as the workspace and then changing the workspace
- * orientation.
+ * the same layout as the workspace and then changing the workspace layout.
*
*/
void ws_force_orientation(Con *ws, orientation_t orientation) {
Con *split = con_new(NULL, NULL);
split->parent = ws;
- /* 2: copy layout and orientation from workspace */
+ /* 2: copy layout from workspace */
split->layout = ws->layout;
- split->orientation = ws->orientation;
Con *old_focused = TAILQ_FIRST(&(ws->focus_head));
con_attach(child, split, true);
}
- /* 4: switch workspace orientation */
- ws->orientation = orientation;
+ /* 4: switch workspace layout */
+ ws->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
/* 5: attach the new split container to the workspace */
DLOG("Attaching new split to ws\n");
/* 1: create a new split container */
Con *new = con_new(NULL, NULL);
new->parent = ws;
+ new->split = true;
/* 2: set the requested layout on the split con */
new->layout = config.default_layout;
- /* 3: While the layout is irrelevant in stacked/tabbed mode, it needs
- * to be set. Otherwise, this con will not be interpreted as a split
- * container. */
- if (config.default_orientation == NO_ORIENTATION) {
- new->orientation = (ws->rect.height > ws->rect.width) ? VERT : HORIZ;
- } else {
- new->orientation = config.default_orientation;
- }
-
/* 4: attach the new split container to the workspace */
DLOG("Attaching new split %p to workspace %p\n", new, ws);
con_attach(new, ws, false);
my %options = (
valgrind => 0,
strace => 0,
+ xtrace => 0,
coverage => 0,
restart => 0,
);
"coverage-testing" => \$options{coverage},
"valgrind" => \$options{valgrind},
"strace" => \$options{strace},
+ "xtrace" => \$options{xtrace},
"display=s" => \@displays,
"parallel=i" => \$parallel,
"help|?" => \$help,
Runs i3 under strace to trace system calls. The output will be available in
C<latest/strace-for-$test.log>.
+=item B<--xtrace>
+
+Runs i3 under xtrace to trace X11 requests/replies. The output will be
+available in C<latest/xtrace-for-$test.log>.
+
=item B<--coverage-testing>
Exits i3 cleanly (instead of kill -9) to make coverage testing work properly.
'sh -c "export LISTEN_PID=\$\$; ' . $cmd . '"';
}
+ if ($args{xtrace}) {
+ my $out = "$outdir/xtrace-for-$test.log";
+
+ # See comment in $args{strace} branch.
+ $cmd = qq|xtrace -n -o "$out" -- | .
+ 'sh -c "export LISTEN_PID=\$\$; ' . $cmd . '"';
+ }
+
# We need to use the shell due to using output redirections.
exec '/bin/sh', '-c', $cmd;
$test->failure_output(\*STDERR);
$test->todo_output(\*STDOUT);
- @ENV{qw(DISPLAY TESTNAME OUTDIR VALGRIND STRACE COVERAGE RESTART)}
+ @ENV{qw(DISPLAY TESTNAME OUTDIR VALGRIND STRACE XTRACE COVERAGE RESTART)}
= ($self->{display},
basename($file),
$outdir,
$options->{valgrind},
$options->{strace},
+ $options->{xtrace},
$options->{coverage},
$options->{restart});
open_floating_window
get_dock_clients
cmd
+ cmp_float
sync_with_i3
does_i3_live
exit_gracefully
testname => $ENV{TESTNAME},
valgrind => $ENV{VALGRIND},
strace => $ENV{STRACE},
+ xtrace => $ENV{XTRACE},
restart => $ENV{RESTART},
cv => $cv,
dont_create_temp_dir => $args{dont_create_temp_dir},
return $i3_pid;
}
+# compares two floats and return true if they differ less
+# then 1e-6
+sub cmp_float {
+ my ($a, $b) = @_;
+
+ return abs($a - $b) < 1e-6;
+}
+
package i3test::X11;
use parent 'X11::XCB::Connection';
name => 'root',
orientation => $ignore,
type => 0,
+ split => JSON::XS::false,
id => $ignore,
rect => $ignore,
window_rect => $ignore,
geometry => $ignore,
swallows => $ignore,
percent => undef,
- layout => 'default',
+ layout => 'splith',
floating => 'auto_off',
+ last_split_layout => 'splith',
scratchpad_state => 'none',
focus => $ignore,
focused => JSON::XS::false,
$tmp = fresh_workspace;
$ws = get_ws($tmp);
- is($ws->{orientation}, 'horizontal', 'orientation horizontal by default');
+ is($ws->{layout}, 'splith', 'orientation horizontal by default');
cmd 'split v';
$ws = get_ws($tmp);
- is($ws->{orientation}, 'vertical', 'split v changes workspace orientation');
+ is($ws->{layout}, 'splitv', 'split v changes workspace orientation');
cmd 'open';
cmd 'open';
is(@{$first->{nodes}}, 0, 'first container has no children');
isnt($second->{name}, $old_name, 'second container was replaced');
- is($second->{orientation}, 'horizontal', 'orientation is horizontal');
+ is($second->{layout}, 'splith', 'orientation is horizontal');
is(@{$second->{nodes}}, 2, 'second container has 2 children');
is($second->{nodes}->[0]->{name}, $old_name, 'found old second container');
}
$tmp = fresh_workspace;
$ws = get_ws($tmp);
-is($ws->{orientation}, 'horizontal', 'orientation horizontal by default');
+is($ws->{layout}, 'splith', 'orientation horizontal by default');
cmd 'split v';
$ws = get_ws($tmp);
-is($ws->{orientation}, 'vertical', 'split v changes workspace orientation');
+is($ws->{layout}, 'splitv', 'split v changes workspace orientation');
cmd 'open';
my @content = @{get_ws_content($tmp)};
ok(@{get_ws_content($tmp)} == 3, 'three containers on first ws');
ok(@{get_ws_content($tmp2)} == 0, 'no containers on second ws');
+###################################################################
+# check if 'move workspace current' works
+###################################################################
+
+$tmp = get_unused_workspace();
+$tmp2 = get_unused_workspace();
+
+cmd "workspace $tmp";
+$first = open_window(name => 'win-name');
+ok(@{get_ws_content($tmp)} == 1, 'one container on first ws');
+
+cmd "workspace $tmp2";
+ok(@{get_ws_content($tmp2)} == 0, 'no containers yet');
+
+cmd qq|[title="win-name"] move workspace $tmp2|;
+ok(@{get_ws_content($tmp2)} == 1, 'one container on second ws');
+
+cmd qq|[title="win-name"] move workspace $tmp|;
+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)
my ($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[0]->{percent}, 0.25, 'top window got only 25%');
-is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
+ok(cmp_float($nodes->[0]->{percent}, 0.25), 'top window got only 25%');
+ok(cmp_float($nodes->[1]->{percent}, 0.75), 'bottom window got 75%');
############################################################
($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[0]->{percent}, 0.25, 'top window got only 25%');
-is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
+ok(cmp_float($nodes->[0]->{percent}, 0.25), 'top window got only 25%');
+ok(cmp_float($nodes->[1]->{percent}, 0.75), 'bottom window got 75%');
############################################################
# checks that resizing within stacked/tabbed cons works
cmd 'layout stacked';
($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[0]->{percent}, 0.5, 'top window got 50%');
-is($nodes->[1]->{percent}, 0.5, 'bottom window got 50%');
+ok(cmp_float($nodes->[0]->{percent}, 0.5), 'top window got 50%');
+ok(cmp_float($nodes->[1]->{percent}, 0.5), 'bottom window got 50%');
cmd 'resize grow up 10 px or 25 ppt';
($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[0]->{percent}, 0.25, 'top window got 25%');
-is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
+ok(cmp_float($nodes->[0]->{percent}, 0.25), 'top window got 25%');
+ok(cmp_float($nodes->[1]->{percent}, 0.75), 'bottom window got 75%');
############################################################
# Checks that resizing in the parent's parent's orientation works.
$bottom = open_window;
($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[0]->{percent}, 0.5, 'left window got 50%');
-is($nodes->[1]->{percent}, 0.5, 'right window got 50%');
+ok(cmp_float($nodes->[0]->{percent}, 0.5), 'left window got 50%');
+ok(cmp_float($nodes->[1]->{percent}, 0.5), 'right window got 50%');
cmd 'resize grow left 10 px or 25 ppt';
($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[0]->{percent}, 0.25, 'left window got 25%');
-is($nodes->[1]->{percent}, 0.75, 'right window got 75%');
+ok(cmp_float($nodes->[0]->{percent}, 0.25), 'left window got 25%');
+ok(cmp_float($nodes->[1]->{percent}, 0.75), 'right window got 75%');
################################################################################
# Check that the resize grow/shrink width/height syntax works.
cmd 'resize grow width 10 px or 25 ppt';
($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[0]->{percent}, 0.25, 'left window got 25%');
-is($nodes->[1]->{percent}, 0.75, 'right window got 75%');
+ok(cmp_float($nodes->[0]->{percent}, 0.25), 'left window got 25%');
+ok(cmp_float($nodes->[1]->{percent}, 0.75), 'right window got 75%');
# Now test it with four windows
$tmp = fresh_workspace;
cmd 'resize grow width 10 px or 25 ppt';
($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[0]->{percent}, 0.166666666666667, 'first window got 16%');
-is($nodes->[1]->{percent}, 0.166666666666667, 'second window got 16%');
-is($nodes->[2]->{percent}, 0.166666666666667, 'third window got 16%');
-is($nodes->[3]->{percent}, 0.50, 'fourth window got 50%');
+ok(cmp_float($nodes->[0]->{percent}, 0.166666666666667), 'first window got 16%');
+ok(cmp_float($nodes->[1]->{percent}, 0.166666666666667), 'second window got 16%');
+ok(cmp_float($nodes->[2]->{percent}, 0.166666666666667), 'third window got 16%');
+ok(cmp_float($nodes->[3]->{percent}, 0.50), 'fourth window got 50%');
# height should be a no-op in this situation
cmd 'resize grow height 10 px or 25 ppt';
($nodes, $focus) = get_ws_content($tmp);
-is($nodes->[0]->{percent}, 0.166666666666667, 'first window got 16%');
-is($nodes->[1]->{percent}, 0.166666666666667, 'second window got 16%');
-is($nodes->[2]->{percent}, 0.166666666666667, 'third window got 16%');
-is($nodes->[3]->{percent}, 0.50, 'fourth window got 50%');
+ok(cmp_float($nodes->[0]->{percent}, 0.166666666666667), 'first window got 16%');
+ok(cmp_float($nodes->[1]->{percent}, 0.166666666666667), 'second window got 16%');
+ok(cmp_float($nodes->[2]->{percent}, 0.166666666666667), 'third window got 16%');
+ok(cmp_float($nodes->[3]->{percent}, 0.50), 'fourth window got 50%');
############################################################
cmd 'move right';
my $ws = get_ws($tmp);
-is($ws->{orientation}, 'horizontal', 'workspace orientation is horizontal');
+is($ws->{layout}, 'splith', 'workspace layout is splith');
is(@{$ws->{nodes}}, 3, 'all three windows on workspace level');
done_testing;
my $tmp = fresh_workspace;
-#####################################################################
-# open the left window
-#####################################################################
+################################################################################
+# Open the left window.
+################################################################################
my $left = open_window({ background_color => '#ff0000' });
diag("left = " . $left->id);
-#####################################################################
-# Open the right window
-#####################################################################
+################################################################################
+# Open the right window.
+################################################################################
my $right = open_window({ background_color => '#00ff00' });
diag("right = " . $right->id);
-#####################################################################
-# Set the right window to fullscreen
-#####################################################################
+################################################################################
+# Set the right window to fullscreen.
+################################################################################
+
cmd 'nop setting fullscreen';
cmd 'fullscreen';
-#####################################################################
-# Open a third window
-#####################################################################
+################################################################################
+# Open a third window. Since we're fullscreen, the window won't be # mapped, so
+# don't wait for it to be mapped. Instead, just send the map request and sync
+# with i3 to make sure i3 recognizes it.
+################################################################################
my $third = open_window({
background_color => '#0000ff',
diag("third = " . $third->id);
-# move the fullscreen window to a different ws
+################################################################################
+# Move the window to a different workspace, and verify that the third window now
+# gets focused in the current workspace.
+################################################################################
my $tmp2 = get_unused_workspace;
cmd "move workspace $tmp2";
-# verify that the third window has the focus
is($x->input_focus, $third->id, 'third window focused');
################################################################################
is($nodes->[0]->{focused}, 1, 'fullscreen window focused');
################################################################################
-# Make sure it's possible to focus a container in a different workspace even if
-# we are currently focusing a fullscreen container.
+# Ensure it's possible to change focus if it doesn't escape the fullscreen
+# container with fullscreen global. We can't even focus a container in a
+# different workspace.
################################################################################
+cmd 'fullscreen';
+
+$tmp = fresh_workspace;
+cmd "workspace $tmp";
+my $diff_ws = open_window;
+
$tmp2 = fresh_workspace;
-my $focusable_window = open_window;
+cmd "workspace $tmp2";
+cmd 'split h';
+
+$left = open_window;
+my $right1 = open_window;
+cmd 'split v';
+my $right2 = open_window;
+
+cmd 'focus parent';
+cmd 'fullscreen global';
+
+cmd '[id="' . $right1->id . '"] focus';
+is($x->input_focus, $right1->id, 'upper right window focused');
+
+cmd '[id="' . $right2->id . '"] focus';
+is($x->input_focus, $right2->id, 'bottom right window focused');
+
+cmd 'focus parent';
+isnt($x->input_focus, $right2->id, 'bottom right window no longer focused');
+
+cmd 'focus child';
+is($x->input_focus, $right2->id, 'bottom right window focused again');
+
+cmd '[id="' . $left->id . '"] focus';
+is($x->input_focus, $right2->id, 'prevented focus change to left window');
+
+cmd 'focus up';
+is($x->input_focus, $right1->id, 'allowed focus up');
+
+cmd 'focus down';
+is($x->input_focus, $right2->id, 'allowed focus down');
+
+cmd 'focus left';
+is($x->input_focus, $right2->id, 'prevented focus left');
+
+cmd 'focus right';
+is($x->input_focus, $right2->id, 'prevented focus right');
+
+cmd 'focus down';
+is($x->input_focus, $right1->id, 'allowed focus wrap (down)');
+
+cmd 'focus up';
+is($x->input_focus, $right2->id, 'allowed focus wrap (up)');
+
+cmd '[id="' . $diff_ws->id . '"] focus';
+is($x->input_focus, $right2->id, 'prevented focus change to different ws');
+
+################################################################################
+# Same tests when we're in non-global fullscreen mode. It should now be possible
+# to focus a container in a different workspace.
+################################################################################
+
+cmd 'focus parent';
+cmd 'fullscreen global';
+cmd 'fullscreen';
+
+cmd '[id="' . $right1->id . '"] focus';
+is($x->input_focus, $right1->id, 'upper right window focused');
+
+cmd '[id="' . $right2->id . '"] focus';
+is($x->input_focus, $right2->id, 'bottom right window focused');
+
+cmd 'focus parent';
+isnt($x->input_focus, $right2->id, 'bottom right window no longer focused');
+
+cmd 'focus child';
+is($x->input_focus, $right2->id, 'bottom right window focused again');
+
+cmd '[id="' . $left->id . '"] focus';
+is($x->input_focus, $right2->id, 'prevented focus change to left window');
+
+cmd 'focus up';
+is($x->input_focus, $right1->id, 'allowed focus up');
+
+cmd 'focus down';
+is($x->input_focus, $right2->id, 'allowed focus down');
+
+cmd 'focus left';
+is($x->input_focus, $right2->id, 'prevented focus left');
+
+cmd 'focus right';
+is($x->input_focus, $right2->id, 'prevented focus right');
+
+cmd 'focus down';
+is($x->input_focus, $right1->id, 'allowed focus wrap (down)');
+
+cmd 'focus up';
+is($x->input_focus, $right2->id, 'allowed focus wrap (up)');
+
+cmd '[id="' . $diff_ws->id . '"] focus';
+is($x->input_focus, $diff_ws->id, 'allowed focus change to different ws');
+
+################################################################################
+# More testing of the interaction between wrapping and the fullscreen focus
+# restrictions.
+################################################################################
+
+cmd '[id="' . $right1->id . '"] focus';
+is($x->input_focus, $right1->id, 'upper right window focused');
+
+cmd 'focus parent';
+cmd 'fullscreen';
+cmd 'focus child';
+
+cmd 'split v';
+my $right12 = open_window;
+
+cmd 'focus down';
+is($x->input_focus, $right2->id, 'bottom right window focused');
+
+cmd 'split v';
+my $right22 = open_window;
+
+cmd 'focus parent';
+cmd 'fullscreen';
+cmd 'focus child';
+
+cmd 'focus down';
+is($x->input_focus, $right2->id, 'focus did not leave parent container (1)');
+
+cmd 'focus down';
+is($x->input_focus, $right22->id, 'focus did not leave parent container (2)');
+
+cmd 'focus up';
+is($x->input_focus, $right2->id, 'focus did not leave parent container (3)');
+
+cmd 'focus up';
+is($x->input_focus, $right22->id, 'focus did not leave parent container (4)');
+
+################################################################################
+# Ensure that moving in a direction doesn't violate the focus restrictions.
+################################################################################
+
+sub verify_move {
+ my $num = shift;
+ my $msg = shift;
+ my $nodes = get_ws_content($tmp2);
+ my $split = $nodes->[1];
+ my $fs = $split->{nodes}->[1];
+ is(scalar @{$fs->{nodes}}, $num, $msg);
+}
+
+cmd 'move left';
+verify_move(2, 'prevented move left');
+cmd 'move right';
+verify_move(2, 'prevented move right');
+cmd 'move down';
+verify_move(2, 'prevented move down');
+cmd 'move up';
+cmd 'move up';
+verify_move(2, 'prevented move up');
+
+################################################################################
+# Moving to a different workspace is allowed with per-output fullscreen
+# containers.
+################################################################################
+
+cmd "move to workspace $tmp";
+verify_move(1, 'did not prevent move to workspace by name');
cmd "workspace $tmp";
-cmd '[id="' . $focusable_window->id . '"] focus';
+cmd "move to workspace $tmp2";
+cmd "workspace $tmp2";
-is(focused_ws(), $tmp2, 'focus went to a different workspace');
+cmd "move to workspace prev";
+verify_move(1, 'did not prevent move to workspace by position');
-$nodes = get_ws_content($tmp2);
-is(scalar @$nodes, 1, 'precisely one window');
-is($nodes->[0]->{focused}, 1, 'focusable window focused');
+################################################################################
+# Ensure that is not allowed with global fullscreen containers.
+################################################################################
+
+cmd "workspace $tmp";
+cmd "move to workspace $tmp2";
+cmd "workspace $tmp2";
+
+cmd 'focus parent';
+cmd 'fullscreen';
+cmd 'fullscreen global';
+cmd 'focus child';
+
+cmd "move to workspace $tmp";
+verify_move(2, 'prevented move to workspace by name');
+
+cmd "move to workspace prev";
+verify_move(2, 'prevented move to workspace by position');
+
+# TODO: Tests for "move to output" and "move workspace to output".
done_testing;
+++ /dev/null
-#!perl
-# vim:ts=4:sw=4:expandtab
-#
-# Regression test: level up should be a noop during fullscreen mode
-#
-use i3test;
-
-my $tmp = fresh_workspace;
-
-#####################################################################
-# open a window, verify it’s not in fullscreen mode
-#####################################################################
-
-my $win = open_window;
-
-my $nodes = get_ws_content $tmp;
-is(@$nodes, 1, 'exactly one client');
-is($nodes->[0]->{fullscreen_mode}, 0, 'client not fullscreen');
-
-#####################################################################
-# make it fullscreen
-#####################################################################
-
-cmd 'nop making fullscreen';
-cmd 'fullscreen';
-
-$nodes = get_ws_content $tmp;
-is($nodes->[0]->{fullscreen_mode}, 1, 'client fullscreen now');
-
-#####################################################################
-# send level up, try to un-fullscreen
-#####################################################################
-cmd 'focus parent';
-cmd 'fullscreen';
-
-$nodes = get_ws_content $tmp;
-is($nodes->[0]->{fullscreen_mode}, 0, 'client not fullscreen any longer');
-
-does_i3_live;
-
-done_testing;
#
use i3test i3_autostart => 0;
use File::Temp qw(tempfile tempdir);
+use File::Basename;
use POSIX qw(getuid);
use v5.10;
# ensure XDG_RUNTIME_DIR is not set
delete $ENV{XDG_RUNTIME_DIR};
-# See which files exist in /tmp before to not mistakenly check an already
-# existing tmpdir of another i3 instance.
-my @files_before = </tmp/i3-*>;
my $pid = launch_with_config($config, dont_add_socket_path => 1, dont_create_temp_dir => 1);
-my @files_after = </tmp/i3-*>;
-@files_after = grep { !($_ ~~ @files_before) } @files_after;
-
-is(@files_after, 1, 'one new temp directory');
-
+my $socketpath = get_socket_path(0);
my $folder = "/tmp/i3-" . getpwuid(getuid());
-like($files_after[0], qr/^$folder/, 'temp directory matches expected pattern');
-$folder = $files_after[0];
+like(dirname($socketpath), qr/^$folder/, 'temp directory matches expected pattern');
+$folder = dirname($socketpath);
ok(-d $folder, "folder $folder exists");
-my $socketpath = "$folder/ipc-socket." . $pid;
+$socketpath = "$folder/ipc-socket." . $pid;
ok(-S $socketpath, "file $socketpath exists and is a socket");
exit_gracefully($pid);
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
my $window = open_special;
+wait_for_map($window);
ok(@{get_ws_content($tmp)} == 1, 'special window got managed to current (random) workspace');
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-assign "special" → ~
+for_window [title="special"] floating enable
EOT
$pid = launch_with_config($config);
-# Ensure that i3-nagbar is running. It should be started pretty quickly, so we
-# busy-loop with a short delay.
-while (!i3nagbar_running($pid)) {
- sleep 0.05;
-}
-
$tmp = fresh_workspace;
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
my @docked = get_dock_clients;
-# We expect i3-nagbar as the first dock client due to using the old assign
-# syntax
-is(@docked, 1, 'one dock client yet');
+is(@docked, 0, 'one dock client yet');
$window = open_special(
window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
ok(@{$content->{nodes}} == 0, 'no tiling cons');
ok(@{$content->{floating_nodes}} == 0, 'one floating con');
@docked = get_dock_clients;
-is(@docked, 2, 'two dock clients now');
+is(@docked, 1, 'one dock client now');
$window->destroy;
$output = migrate_config($input);
ok(line_exists($output, qr|^bindsym Mod1\+s layout stacking$|), 's replaced');
-ok(line_exists($output, qr|^bindsym Mod1\+s layout default$|), 'd replaced');
+ok(line_exists($output, qr|^bindsym Mod1\+s layout toggle split$|), 'd replaced');
ok(line_exists($output, qr|^bindsym Mod1\+s layout tabbed$|), 'T replaced');
ok(line_exists($output, qr|^bindsym Mod1\+s fullscreen$|), 'f replaced');
ok(line_exists($output, qr|^bindsym Mod1\+s fullscreen global$|), 'fg replaced');
# TODO: use a timeout, so that we can error out if it doesn’t terminate
# TODO: better way of passing arguments
- my $stdout = qx(../test.commands_parser '$command' 2>&-);
+ my $stdout = qx(../test.commands_parser '$command' 2>&1 >&-);
# Filter out all debugging output.
my @lines = split("\n", $stdout);
################################################################################
is(parser_calls('unknown_literal'),
- "Expected one of these tokens: <end>, '[', 'move', 'exec', 'exit', 'restart', 'reload', 'border', 'layout', 'append_layout', 'workspace', 'focus', 'kill', 'open', 'fullscreen', 'split', 'floating', 'mark', 'resize', 'rename', 'nop', 'scratchpad', 'mode'\n" .
- "Your command: unknown_literal\n" .
- " ^^^^^^^^^^^^^^^",
+ "ERROR: Expected one of these tokens: <end>, '[', 'move', 'exec', 'exit', 'restart', 'reload', 'border', 'layout', 'append_layout', 'workspace', 'focus', 'kill', 'open', 'fullscreen', 'split', 'floating', 'mark', 'resize', 'rename', 'nop', 'scratchpad', 'mode'\n" .
+ "ERROR: Your command: unknown_literal\n" .
+ "ERROR: ^^^^^^^^^^^^^^^",
'error for unknown literal ok');
is(parser_calls('move something to somewhere'),
- "Expected one of these tokens: 'window', 'container', 'to', 'workspace', 'output', 'scratchpad', 'left', 'right', 'up', 'down', 'position', 'absolute'\n" .
- "Your command: move something to somewhere\n" .
- " ^^^^^^^^^^^^^^^^^^^^^^",
+ "ERROR: Expected one of these tokens: 'window', 'container', 'to', 'workspace', 'output', 'scratchpad', 'left', 'right', 'up', 'down', 'position', 'absolute'\n" .
+ "ERROR: Your command: move something to somewhere\n" .
+ "ERROR: ^^^^^^^^^^^^^^^^^^^^^^",
'error for unknown literal ok');
################################################################################
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Verifies that you can resize across different levels of containers even when
+# they are all of the same orientation.
+# (Ticket #754)
+use i3test;
+
+my $tmp = fresh_workspace;
+
+open_window;
+open_window;
+cmd 'split v';
+my $middle = open_window;
+open_window;
+cmd 'focus parent';
+cmd 'split h';
+open_window;
+
+cmd '[id="' . $middle->id . '"] focus';
+is($x->input_focus, $middle->id, 'middle window focused');
+
+cmd 'resize grow left 10px or 25ppt';
+
+my ($nodes, $focus) = get_ws_content($tmp);
+
+ok(cmp_float($nodes->[0]->{percent}, 0.25), 'left container got only 25%');
+ok(cmp_float($nodes->[1]->{percent}, 0.75), 'right container got 75%');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+# Verifies that switching between the different layouts works as expected.
+use i3test;
+
+my $tmp = fresh_workspace;
+
+open_window;
+open_window;
+cmd 'split v';
+open_window;
+
+my ($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout is splitv currently');
+
+cmd 'layout stacked';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'stacked', 'layout now stacked');
+
+cmd 'layout tabbed';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed');
+
+cmd 'layout toggle split';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv again');
+
+cmd 'layout toggle split';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splith', 'layout now splith');
+
+cmd 'layout toggle split';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv');
+
+cmd 'layout toggle split';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splith', 'layout now splith');
+
+cmd 'layout toggle';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'stacked', 'layout now stacked');
+
+cmd 'layout toggle';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed');
+
+cmd 'layout toggle';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splith', 'layout now splith');
+
+cmd 'layout toggle';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'stacked', 'layout now stacked');
+
+cmd 'layout toggle all';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed');
+
+cmd 'layout toggle all';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splith', 'layout now splith');
+
+cmd 'layout toggle all';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv');
+
+cmd 'layout toggle all';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'stacked', 'layout now stacked');
+
+cmd 'layout toggle all';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed');
+
+cmd 'layout toggle all';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splith', 'layout now splith');
+
+cmd 'layout toggle all';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv');
+
+done_testing;
# TODO:
# introduce 'move workspace 3 to output <output>' with synonym 'move workspace 3 to <output>'
+# Ensure the pointer is at (0, 0) so that we really start on the first
+# (the left) workspace.
+$x->root->warp_pointer(0, 0);
+
my $config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1