From: Michael Stapelberg Date: Sun, 16 Sep 2012 15:59:55 +0000 (+0200) Subject: Merge branch 'master' into next X-Git-Tag: 4.3~9 X-Git-Url: https://git.sur5r.net/?p=i3%2Fi3;a=commitdiff_plain;h=d660c4bcffeb56d8a967bf9de747b8fafa691445;hp=4df409bae642cbf51582ef6746b388b85c0b3f8d Merge branch 'master' into next --- diff --git a/.gitignore b/.gitignore index 705314b2..e50eb4fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,33 +1,25 @@ *.o tags -include/loglevels.h include/GENERATED_*.h -loglevels.tmp +include/all.h.pch *.swp *.gcda *.gcno testcases/testsuite-* testcases/latest testcases/Makefile +testcases/Makefile.old +testcases/.last_run_timings.json +testcases/_Inline +testcases/inc +testcases/META.yml test.commands_parser *.output *.tab.* *.yy.c -man/i3-msg.1 -man/i3-msg.xml -man/i3-msg.html -man/i3-nagbar.1 -man/i3-nagbar.xml -man/i3-nagbar.html -man/i3-wsbar.1 -man/i3-wsbar.xml -man/i3-wsbar.html -man/i3-input.1 -man/i3-input.xml -man/i3-input.html -man/i3.1 -man/i3.xml -man/i3.html +man/*.1 +man/*.xml +man/*.html *.tar.bz2* i3 i3-input/i3-input @@ -35,5 +27,7 @@ i3-nagbar/i3-nagbar i3-msg/i3-msg i3-config-wizard/i3-config-wizard i3-dump-log/i3-dump-log -libi3/libi3.a +libi3.a docs/*.pdf +docs/*.html +!/docs/refcard.html diff --git a/DEPENDS b/DEPENDS index 61fb9586..fe9ba17f 100644 --- a/DEPENDS +++ b/DEPENDS @@ -8,7 +8,6 @@ │ dependency │ min. │ lkgv │ URL │ ├─────────────┼────────┼────────┼────────────────────────────────────────┤ │ pkg-config │ 0.25 │ 0.26 │ http://pkgconfig.freedesktop.org/ │ -│ xcb-proto │ 1.3 │ 1.6 │ http://xcb.freedesktop.org/dist/ │ │ libxcb │ 1.1.93 │ 1.7 │ http://xcb.freedesktop.org/dist/ │ │ xcb-util │ 0.3.3 │ 0.3.8 │ http://xcb.freedesktop.org/dist/ │ │ libev │ 4.0 │ 4.04 │ http://libev.schmorp.de/ │ @@ -17,13 +16,18 @@ │ yajl │ 1.0.8 │ 2.0.1 │ http://lloyd.github.com/yajl/ │ │ asciidoc │ 8.3.0 │ 8.6.4 │ http://www.methods.co.nz/asciidoc/ │ │ xmlto │ 0.0.23 │ 0.0.23 │ http://www.methods.co.nz/asciidoc/ │ +│ Pod::Simple²│ 3.22 │ 3.22 │ http://search.cpan.org/~dwheeler/Pod-Simple-3.23/ │ docbook-xml │ 4.5 │ 4.5 │ http://www.methods.co.nz/asciidoc/ │ │ libxcursor │ 1.1.11 │ 1.1.11 │ http://ftp.x.org/pub/current/src/lib/ │ │ Xlib │ 1.3.3 │ 1.4.3 │ http://ftp.x.org/pub/current/src/lib/ │ │ PCRE │ 8.12 │ 8.12 │ http://www.pcre.org/ │ │ libsn¹ │ 0.10 │ 0.12 │ http://freedesktop.org/wiki/Software/startup-notification +│ pango │ 1.30.0 | 1.30.0 │ http://www.pango.org/ │ +│ cairo │ 1.12.2 │ 1.12.2 │ http://cairographics.org/ │ └─────────────┴────────┴────────┴────────────────────────────────────────┘ ¹ libsn = libstartup-notification + ² Pod::Simple is a Perl module required for converting the testsuite + documentation to HTML. See http://michael.stapelberg.de/cpan/#Pod::Simple i3bar, i3-msg, i3-input, i3-nagbar and i3-config-wizard do not introduce any new dependencies. diff --git a/Makefile b/Makefile index 065cdbcb..3b675034 100644 --- a/Makefile +++ b/Makefile @@ -2,151 +2,58 @@ TOPDIR=$(shell pwd) include $(TOPDIR)/common.mk -# Depend on the object files of all source-files in src/*.c and on all header files -AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c -FILES:=$(filter-out $(AUTOGENERATED),$(wildcard src/*.c)) -FILES:=$(FILES:.c=.o) -HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) -CMDPARSE_HEADERS:=include/GENERATED_call.h include/GENERATED_enums.h include/GENERATED_tokens.h +SUBDIRS:= -# Recursively generate loglevels.h by explicitly calling make -# We need this step because we need to ensure that loglevels.h will be -# updated if necessary, but we also want to save rebuilds of the object -# files, so we cannot let the object files depend on loglevels.h. -ifeq ($(MAKECMDGOALS),loglevels.h) -#UNUSED:=$(warning Generating loglevels.h) -else -UNUSED:=$(shell $(MAKE) loglevels.h) -endif +ALL_TARGETS = +INSTALL_TARGETS = +CLEAN_TARGETS = +DISTCLEAN_TARGETS = -SUBDIRS:=i3-msg i3-input i3-nagbar i3-config-wizard i3bar i3-dump-log +all: real-all -# Depend on the specific file (.c for each .o) and on all headers -src/%.o: src/%.c ${HEADERS} - echo "[i3] CC $<" - $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR; exit 0; }' loglevels.tmp))" -c -o $@ $< +include libi3/libi3.mk +include src/i3.mk +include i3-config-wizard/i3-config-wizard.mk +include i3-msg/i3-msg.mk +include i3-input/i3-input.mk +include i3-nagbar/i3-nagbar.mk +include i3bar/i3bar.mk +include i3-dump-log/i3-dump-log.mk +include docs/docs.mk +include man/man.mk -all: i3 subdirs +real-all: $(ALL_TARGETS) -i3: libi3/libi3.a src/cfgparse.y.o src/cfgparse.yy.o ${FILES} - echo "[i3] LINK i3" - $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS) - -libi3/%.a: libi3/*.c - $(MAKE) -C libi3 - -subdirs: - for dir in $(SUBDIRS); do \ - echo ""; \ - echo "MAKE $$dir"; \ - $(MAKE) -C $$dir; \ - done - -loglevels.h: - echo "[i3] LOGLEVELS" - for file in $$(ls src/*.c src/*.y src/*.l | grep -v 'cfgparse.\(tab\|yy\).c'); \ - do \ - echo $$(basename $$file .c); \ - done > loglevels.tmp - (echo "char *loglevels[] = {"; for file in $$(cat loglevels.tmp); \ - do \ - echo "\"$$file\", "; \ - done; \ - echo "};") > include/loglevels.h; - -# The GENERATED_* files are actually all created from a single pass, so all -# files just depend on the first one. -include/GENERATED_call.h: generate-command-parser.pl parser-specs/commands.spec - echo "[i3] Generating command parser" - (cd include; ../generate-command-parser.pl) -include/GENERATED_enums.h: include/GENERATED_call.h -include/GENERATED_tokens.h: include/GENERATED_call.h - -# This target compiles the command parser twice: -# Once with -DTEST_PARSER, creating a stand-alone executable used for tests, -# and once as an object file for i3. -src/commands_parser.o: src/commands_parser.c ${HEADERS} ${CMDPARSE_HEADERS} - echo "[i3] CC $<" - $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -DTEST_PARSER -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR; exit 0; }' loglevels.tmp))" -o test.commands_parser $< $(LIBS) - $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR; exit 0; }' loglevels.tmp))" -c -o $@ $< - -src/cfgparse.yy.o: src/cfgparse.l src/cfgparse.y.o ${HEADERS} - echo "[i3] LEX $<" - $(FLEX) -i -o$(@:.o=.c) $< - $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c) - - -src/cfgparse.y.o: src/cfgparse.y ${HEADERS} - echo "[i3] YACC $<" - $(BISON) --debug --verbose -b $(basename $< .y) -d $< - $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c) - - -install: all - echo "[i3] INSTALL" - $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin - $(INSTALL) -d -m 0755 $(DESTDIR)$(SYSCONFDIR)/i3 - $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/include/i3 - $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/xsessions - $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/applications - $(INSTALL) -m 0755 i3 $(DESTDIR)$(PREFIX)/bin/ - $(INSTALL) -m 0755 i3-migrate-config-to-v4 $(DESTDIR)$(PREFIX)/bin/ - $(INSTALL) -m 0755 i3-sensible-editor $(DESTDIR)$(PREFIX)/bin/ - $(INSTALL) -m 0755 i3-sensible-pager $(DESTDIR)$(PREFIX)/bin/ - $(INSTALL) -m 0755 i3-sensible-terminal $(DESTDIR)$(PREFIX)/bin/ - test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config - test -e $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes || $(INSTALL) -m 0644 i3.config.keycodes $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes - $(INSTALL) -m 0644 i3.xsession.desktop $(DESTDIR)$(PREFIX)/share/xsessions/i3.desktop - $(INSTALL) -m 0644 i3.applications.desktop $(DESTDIR)$(PREFIX)/share/applications/i3.desktop - $(INSTALL) -m 0644 include/i3/ipc.h $(DESTDIR)$(PREFIX)/include/i3/ - for dir in $(SUBDIRS); do \ - $(MAKE) -C $$dir install; \ - done +install: $(INSTALL_TARGETS) dist: distclean [ ! -d i3-${VERSION} ] || rm -rf i3-${VERSION} [ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2 mkdir i3-${VERSION} - cp i3-migrate-config-to-v4 generate-command-parser.pl i3-sensible-* i3.config.keycodes DEPENDS LICENSE PACKAGE-MAINTAINER RELEASE-NOTES-${VERSION} i3.config i3.xsession.desktop i3.applications.desktop pseudo-doc.doxygen Makefile i3-${VERSION} - cp -r src libi3 i3-msg i3-nagbar i3-config-wizard i3bar i3-dump-log yajl-fallback include man parser-specs i3-${VERSION} + cp i3-migrate-config-to-v4 generate-command-parser.pl i3-sensible-* i3.config.keycodes DEPENDS LICENSE PACKAGE-MAINTAINER RELEASE-NOTES-${VERSION} i3.config i3.xsession.desktop i3.applications.desktop pseudo-doc.doxygen common.mk Makefile i3-${VERSION} + cp -r src libi3 i3-msg i3-nagbar i3-config-wizard i3bar i3-dump-log yajl-fallback include man parser-specs testcases i3-${VERSION} # Only copy toplevel documentation (important stuff) mkdir i3-${VERSION}/docs # Pre-generate documentation - $(MAKE) -C docs - $(MAKE) -C i3bar/doc + $(MAKE) docs # Cleanup τεχ output files find docs -regex ".*\.\(aux\|out\|log\|toc\|bm\|dvi\|log\)" -exec rm '{}' \; find docs -maxdepth 1 -type f ! \( -name "*.xcf" -or -name "*.svg" \) -exec cp '{}' i3-${VERSION}/docs \; # Only copy source code from i3-input mkdir i3-${VERSION}/i3-input - find i3-input -maxdepth 1 -type f \( -name "*.c" -or -name "*.h" -or -name "Makefile" \) -exec cp '{}' i3-${VERSION}/i3-input \; - sed -e 's/^GIT_VERSION:=\(.*\)/GIT_VERSION:=$(shell /bin/echo '${GIT_VERSION}' | sed 's/\\/\\\\/g')/g;s/^VERSION:=\(.*\)/VERSION:=${VERSION}/g' common.mk > i3-${VERSION}/common.mk + find i3-input -maxdepth 1 -type f \( -name "*.c" -or -name "*.mk" -or -name "*.h" -or -name "Makefile" \) -exec cp '{}' i3-${VERSION}/i3-input \; + echo -n ${I3_VERSION} > i3-${VERSION}/I3_VERSION + echo -n ${VERSION} > i3-${VERSION}/VERSION # Pre-generate a manpage to allow distributors to skip this step and save some dependencies - $(MAKE) -C man + $(MAKE) mans cp man/*.1 i3-${VERSION}/man/ - cp i3bar/doc/*.1 i3-${VERSION}/i3bar/doc/ tar cfj i3-${VERSION}.tar.bz2 i3-${VERSION} rm -rf i3-${VERSION} -clean: - rm -f src/*.o src/*.gcno src/cmdparse.* src/cfgparse.tab.{c,h} src/cfgparse.yy.c src/cfgparse.{output,dot} loglevels.tmp include/loglevels.h include/GENERATED_* +clean: $(CLEAN_TARGETS) (which lcov >/dev/null 2>&1 && lcov -d . --zerocounters) || true - $(MAKE) -C libi3 clean - $(MAKE) -C docs clean - $(MAKE) -C man clean - for dir in $(SUBDIRS); do \ - echo ""; \ - echo "CLEAN $$dir"; \ - $(MAKE) TOPDIR=$(TOPDIR) -C $$dir distclean; \ - done -distclean: clean - rm -f i3 - for dir in $(SUBDIRS); do \ - echo ""; \ - echo "DISTCLEAN $$dir"; \ - $(MAKE) TOPDIR=$(TOPDIR) -C $$dir distclean; \ - done +distclean: clean $(DISTCLEAN_TARGETS) coverage: rm -f /tmp/i3-coverage.info diff --git a/RELEASE-NOTES-4.3 b/RELEASE-NOTES-4.3 new file mode 100644 index 00000000..d828a0dc --- /dev/null +++ b/RELEASE-NOTES-4.3 @@ -0,0 +1,165 @@ + + ┌──────────────────────────────┐ + │ Release notes for i3 v4.3 │ + └──────────────────────────────┘ + +This is the third release of the new major version of i3, v4.3. It is +considered stable. All users of i3 are strongly encouraged to upgrade. + +One rather visible change is that commands which could not be parsed properly + will now spawn i3-nagbar. In case you used "bindsym $mod+x firefox" (and + forgot the "exec" keyword) or you made a typo in your config, you will now + notice that :). + +We also made the orientation (horizontal/vertical) part of the layout + mechanism: Before, we got the default layout and you could change + orientation. Now, there are two new layouts "splitv" and "splith", which + replace the default layout. The "split h" and "split v" commands continue to + work as before, they split the current container and you will end up in a + split container with layout splith (after "split h") or splitv (after "split + v"). + + To change a splith container into a splitv container, use either "layout + splitv" or "layout toggle split". The latter command is used in the + default config as mod+l (previously "layout default"). In case you have + "layout default" in your config file, it is recommended to just replace + it by "layout toggle split", which will work as "layout default" did + before when pressing it once, but toggle between horizontal/vertical + when pressing it repeatedly. + + The rationale behind this commit is that it’s cleaner to have all + parameters that influence how windows are rendered in the layout itself + rather than having a special parameter in combination with only one + layout. This enables us to change existing split containers in all cases + without breaking existing features (see ticket #464). Also, users should + feel more confident about whether they are actually splitting or just + changing an existing split container now. + + As a nice side-effect, this commit brings back the "layout toggle" + feature we once had in i3 version 3 (see the userguide). + + +Another very important change is that we now support pango for rendering text. + The default is still to use misc-fixed (X core fonts), but you can use a font + specification starting with "xft:" now, such as "xft:DejaVu Sans Mono 10" and + i3 will use pango. The sole motivation for this is NOT to have fancier window + decorations, but to support fonts which have more glyphs (think Japanese for + example) and to support right-to-left rendering (open http://www.ftpal.net/ + for an example). Supporting users from all over the planet is important, and + as such I would strongly advise distribution packagers to leave pango support + in. In case you are working on a very low-spec embedded device, it is easy + enough to disable pango support, see common.mk. + + +Also, the 'layout' command now always works on the parent split container. This + allows you to do things like this: + + for_window [class="XTerm"] layout tabbed + + When you now open XTerm on an empty workspace, the whole workspace will be + set to tabbed. In case you want to open XTerm in its own tabbed split + container, you need to split before: + + for_window [class="XTerm"] split v, layout tabbed + + ┌────────────────────────────┐ + │ Changes in v4.3 │ + └────────────────────────────┘ + + • docs/refcard: update for v4 + • docs/userguide: clarify the default for focus_follows_mouse and new_window + • docs/userguide: add section about implicit containers + • docs/ipc: document the 'window' field in the GET_TREE reply + • docs/ipc: update links to ipc libraries + • docs/ipc: make the reply sections consistent + • docs/i3bar-protocol: add example (illustration-only!) shell script + • man/i3bar.man: reference i3bar-protocol + • IPC: Commands now lead to proper error messages in general. If we forgot + about a specific one, please open a ticket. + • IPC: implement GET_VERSION to find out the i3 version + • i3-dump-log now comes with a massively more helpful error message that + should cover all the use cases. + • 'workspace number ' now opens a new workspace + • 'workspace number ' now works with the back_and_forth option + • Allow focus with target (criteria) when in fullscreen mode in some cases + • Allow focus child/parent when in fullscreen mode + • Restrict directional focus when in fullscreen mode + • Prevent moving out of fullscreen containers + • Add 'move to workspace current' (useful when used with criteria) + • replace loglevels by a global debug logging + • make: new makefile layout + • make: canonicalize path when compiling. This leads to sth like + ../i3-4.2/src/main.c in backtraces, clearly identifying i3 code. + • automatically hide i3bar when it’s unneeded (after urgency hints) + • i3-config-wizard: use the level 0 keysym whenever it’s unambiguous + • i3-nagbar: use custom scripts to work around different terminal emulators + using different ways of interpreting the arguments to -e + • i3-sensible-terminal: add xfce4-terminal + • default config: require confirmation when exiting i3 + • Display i3-nagbar when a command leads to an error. + • testcases: complete-run now supports --xtrace + • testcases: handle EAGAIN (fixes hangs) + • testcases: handle test bailouts + • Introduce splith/splitv layouts, remove orientation + • Implement hide_edge_borders option + • Support _NET_ACTIVE_WINDOW ClientMessages + • Set I3_PID atom on the X11 root window + • Implement i3 --moreversion, handy for figuring out whether you run the + latest binary which is installed. + • i3bar: be less strict about the {"version":1} JSON header + • shm-logging: implement i3-dump-log -f (follow) + • Implement pango support + • 'move workspace number n' will now create the workspace if it doesn’t exist + • Accept slashes in RandR output names + • Keep startup-notification sequences around for 30s after completion + + ┌────────────────────────────┐ + │ Bugfixes │ + └────────────────────────────┘ + + • Fix floating precision bug when floating windows are moved between outputs. + • i3bar won’t crash when full_text is missing or null in the JSON input + • When having "workspace number 1" in your config, there will no longer be a + stray workspace "number 1". + • i3.config.keycodes used bindsym instead of bindcode for the arrow key + resizing bindings by mistake + • Fix 'move to workspace' when used with criteria + • Handle clicks to the very left edge of i3bar + • When using i3 -C, don’t send remaining arguments as an IPC command + • Fix reload crashes in rare cases + • i3bar: inform all clients of new tray selection owner (fixes tray problems + with X-Chat and possibly others) + • resizing: traverse containers up properly (fixes non-working resizing when + having a h-split within a h-split for example) + • Fix floating coordinates when moving assigned workspaces + • Properly fix floating coordinates when disabling outputs + • floating_fix_coordinates: properly deal with negative positions + • floating windows: add deco_height only when in normal border mode (fixes + initial floating window position/size when using a different default border + setting). + • Fix moving scratchpad window + • Cleanup zero-byte logfile on immediate exit (they are created by i3 + --get-socketpath for example). + • Fix resizing floating windows by height + • Fix back_and_forth in 'workspace number' for named workspaces + • Grab server and process pending events before managing existing windows + (fixes problems with GIMP windows not being managed after an in-place + restart) + • Don’t allow ConfigureRequests while in fullscreen (fixes a compatibility + issue with gnome-terminal and xfce’s terminal) + • Fix flickering with 1pixel border tabbed layouts + • Use _exit() instead of exit() when i3 utility programs cannot be executed + + ┌────────────────────────────┐ + │ Thanks! │ + └────────────────────────────┘ + +Thanks for testing, bugfixes, discussions and everything I forgot go out to: + + aksr, Axel Wagner, darkraven, David Coppa, eeemsi, Felicitus, Fernando Tarlá + Cardoso Lemos, Iakov Davydov, jh, Joel Stemmer, Julius Plenz, Marcel Hellwig, + Marcus, mloskot, Moritz Bandemer, oblique, Ondrej Grover, Pavel Löbl, Philipp + Middendorf, prg, Quentin Glidic, Sebastian Ullrich, somelauw, stfn, tucos, + TunnelWicht, Valentin Haenel + +-- Michael Stapelberg, 2012-09-06 diff --git a/common.mk b/common.mk index 43949059..bb5cf793 100644 --- a/common.mk +++ b/common.mk @@ -14,9 +14,52 @@ ifndef SYSCONFDIR SYSCONFDIR=$(PREFIX)/etc endif endif -# The escaping is absurd, but we need to escape for shell, sed, make, define -GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch $(shell [ -f $(TOPDIR)/.git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' $(TOPDIR)/.git/HEAD || echo 'unknown'))" -VERSION:=$(shell git describe --tags --abbrev=0) + +# In dist tarballs, the version is stored in the I3_VERSION and VERSION files. +I3_VERSION := '$(shell [ -f $(TOPDIR)/I3_VERSION ] && cat $(TOPDIR)/I3_VERSION)' +VERSION := '$(shell [ -f $(TOPDIR)/VERSION ] && cat $(TOPDIR)/VERSION)' +ifeq ('',$(I3_VERSION)) +VERSION := $(shell git describe --tags --abbrev=0) +I3_VERSION := '$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch \"$(shell git describe --tags --always --all | sed s:heads/::)\")' +endif + +MAJOR_VERSION := $(shell echo ${VERSION} | cut -d '.' -f 1) +MINOR_VERSION := $(shell echo ${VERSION} | cut -d '.' -f 2) +PATCH_VERSION := $(shell echo ${VERSION} | cut -d '.' -f 3) +ifeq (${PATCH_VERSION},) +PATCH_VERSION := 0 +endif + +## Generic flags + +# Default CFLAGS that users should be able to override +ifeq ($(DEBUG),1) +# Extended debugging flags, macros shall be available in gcc +CFLAGS ?= -pipe -gdwarf-2 -g3 +else +CFLAGS ?= -pipe -O2 -freorder-blocks-and-partition +endif + +# Default LDFLAGS that users should be able to override +LDFLAGS ?= $(as_needed_LDFLAG) + +# Common CFLAGS for all i3 related binaries +I3_CFLAGS = -std=c99 +I3_CFLAGS += -Wall +# unused-function, unused-label, unused-variable are turned on by -Wall +# We don’t want unused-parameter because of the use of many callbacks +I3_CFLAGS += -Wunused-value +I3_CFLAGS += -Iinclude + +I3_CPPFLAGS = -DI3_VERSION=\"${I3_VERSION}\" +I3_CPPFLAGS += -DMAJOR_VERSION=${MAJOR_VERSION} +I3_CPPFLAGS += -DMINOR_VERSION=${MINOR_VERSION} +I3_CPPFLAGS += -DPATCH_VERSION=${PATCH_VERSION} +I3_CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\" +I3_CPPFLAGS += -DI3__FILE__=__FILE__ + + +## Libraries flags ifeq ($(shell which pkg-config 2>/dev/null 1>/dev/null || echo 1),1) $(error "pkg-config was not found") @@ -35,79 +78,92 @@ endif cflags_for_lib = $(shell pkg-config --silence-errors --cflags $(1) 2>/dev/null) ldflags_for_lib = $(shell pkg-config --exists 2>/dev/null $(1) && pkg-config --libs $(1) 2>/dev/null || echo -l$(2)) -CFLAGS += -std=c99 -CFLAGS += -pipe -CFLAGS += -Wall -# unused-function, unused-label, unused-variable are turned on by -Wall -# We don’t want unused-parameter because of the use of many callbacks -CFLAGS += -Wunused-value -CFLAGS += -Iinclude -CFLAGS += $(call cflags_for_lib, xcb-keysyms) +# XCB common stuff +XCB_CFLAGS := $(call cflags_for_lib, xcb) +XCB_CFLAGS += $(call cflags_for_lib, xcb-event) +XCB_LIBS := $(call ldflags_for_lib, xcb,xcb) +XCB_LIBS += $(call ldflags_for_lib, xcb-event,xcb-event) ifeq ($(shell pkg-config --exists xcb-util 2>/dev/null || echo 1),1) -CPPFLAGS += -DXCB_COMPAT -CFLAGS += $(call cflags_for_lib, xcb-atom) -CFLAGS += $(call cflags_for_lib, xcb-aux) +XCB_CFLAGS += $(call cflags_for_lib, xcb-atom) +XCB_CFLAGS += $(call cflags_for_lib, xcb-aux) +XCB_LIBS += $(call ldflags_for_lib, xcb-atom,xcb-atom) +XCB_LIBS += $(call ldflags_for_lib, xcb-aux,xcb-aux) +XCB_CPPFLAGS+= -DXCB_COMPAT else -CFLAGS += $(call cflags_for_lib, xcb-util) -endif -CFLAGS += $(call cflags_for_lib, xcb-icccm) -CFLAGS += $(call cflags_for_lib, xcb-xinerama) -CFLAGS += $(call cflags_for_lib, xcb-randr) -CFLAGS += $(call cflags_for_lib, xcb) -CFLAGS += $(call cflags_for_lib, xcursor) -CFLAGS += $(call cflags_for_lib, x11) -CFLAGS += $(call cflags_for_lib, yajl) -CFLAGS += $(call cflags_for_lib, libev) -CFLAGS += $(call cflags_for_lib, libpcre) -CFLAGS += $(call cflags_for_lib, libstartup-notification-1.0) -CPPFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" -CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\" +XCB_CFLAGS += $(call cflags_for_lib, xcb-util) +XCB_LIBS += $(call ldflags_for_lib, xcb-util) +endif + +# XCB keyboard stuff +XCB_KBD_CFLAGS := $(call cflags_for_lib, xcb-keysyms) +XCB_KBD_LIBS := $(call ldflags_for_lib, xcb-keysyms,xcb-keysyms) + +# XCB WM stuff +XCB_WM_CFLAGS := $(call cflags_for_lib, xcb-icccm) +XCB_WM_CFLAGS += $(call cflags_for_lib, xcb-xinerama) +XCB_WM_CFLAGS += $(call cflags_for_lib, xcb-randr) +XCB_WM_LIBS := $(call ldflags_for_lib, xcb-icccm,xcb-icccm) +XCB_WM_LIBS += $(call ldflags_for_lib, xcb-xinerama,xcb-xinerama) +XCB_WM_LIBS += $(call ldflags_for_lib, xcb-randr,xcb-randr) + +# Xlib +X11_CFLAGS := $(call cflags_for_lib, x11) +X11_LIBS := $(call ldflags_for_lib, x11,X11) + +# Xcursor +XCURSOR_CFLAGS := $(call cflags_for_lib, xcursor) +XCURSOR_LIBS := $(call ldflags_for_lib, xcursor,Xcursor) +# yajl +YAJL_CFLAGS := $(call cflags_for_lib, yajl) +# Fallback for libyajl 1 which did not include yajl_version.h. We need +# YAJL_MAJOR from that file to decide which code path should be used. +YAJL_CFLAGS += -idirafter $(TOPDIR)/yajl-fallback +YAJL_LIBS := $(call ldflags_for_lib, yajl,yajl) + +#libev +LIBEV_CFLAGS := $(call cflags_for_lib, libev) +LIBEV_LIBS := $(call ldflags_for_lib, libev,ev) + +# libpcre +PCRE_CFLAGS := $(call cflags_for_lib, libpcre) ifeq ($(shell pkg-config --atleast-version=8.10 libpcre 2>/dev/null && echo 1),1) -CPPFLAGS += -DPCRE_HAS_UCP=1 +I3_CPPFLAGS += -DPCRE_HAS_UCP=1 endif +PCRE_LIBS := $(call ldflags_for_lib, libpcre,pcre) -LIBS += -lm -# Darwin (Mac OS X) doesn’t have librt -ifneq ($(UNAME),Darwin) -LIBS += -lrt -endif -LIBS += -L $(TOPDIR)/libi3 -li3 -LIBS += $(call ldflags_for_lib, xcb-event,xcb-event) -LIBS += $(call ldflags_for_lib, xcb-keysyms,xcb-keysyms) -ifeq ($(shell pkg-config --exists xcb-util 2>/dev/null || echo 1),1) -LIBS += $(call ldflags_for_lib, xcb-atom,xcb-atom) -LIBS += $(call ldflags_for_lib, xcb-aux,xcb-aux) -else -LIBS += $(call ldflags_for_lib, xcb-util) -endif -LIBS += $(call ldflags_for_lib, xcb-icccm,xcb-icccm) -LIBS += $(call ldflags_for_lib, xcb-xinerama,xcb-xinerama) -LIBS += $(call ldflags_for_lib, xcb-randr,xcb-randr) -LIBS += $(call ldflags_for_lib, xcb,xcb) -LIBS += $(call ldflags_for_lib, xcursor,Xcursor) -LIBS += $(call ldflags_for_lib, x11,X11) -LIBS += $(call ldflags_for_lib, yajl,yajl) -LIBS += $(call ldflags_for_lib, libev,ev) -LIBS += $(call ldflags_for_lib, libpcre,pcre) -LIBS += $(call ldflags_for_lib, libstartup-notification-1.0,startup-notification-1) +# startup-notification +LIBSN_CFLAGS := $(call cflags_for_lib, libstartup-notification-1.0) +LIBSN_LIBS := $(call ldflags_for_lib, libstartup-notification-1.0,startup-notification-1) + +# Pango +PANGO_CFLAGS := $(call cflags_for_lib, cairo) +PANGO_CFLAGS += $(call cflags_for_lib, pangocairo) +I3_CPPFLAGS += -DPANGO_SUPPORT=1 +PANGO_LIBS := $(call ldflags_for_lib, cairo) +PANGO_LIBS += $(call ldflags_for_lib, pangocairo) + +# libi3 +LIBS = -L$(TOPDIR) -li3 + +## Platform-specific flags # Please test if -Wl,--as-needed works on your platform and send me a patch. # it is known not to work on Darwin (Mac OS X) ifneq (,$(filter Linux GNU GNU/%, $(UNAME))) -LDFLAGS += -Wl,--as-needed +as_needed_LDFLAG = -Wl,--as-needed endif ifeq ($(UNAME),NetBSD) # We need -idirafter instead of -I to prefer the system’s iconv over GNU libiconv -CFLAGS += -idirafter /usr/pkg/include -LDFLAGS += -Wl,-rpath,/usr/local/lib -Wl,-rpath,/usr/pkg/lib +I3_CFLAGS += -idirafter /usr/pkg/include +I3_LDFLAGS += -Wl,-rpath,/usr/local/lib -Wl,-rpath,/usr/pkg/lib endif ifeq ($(UNAME),OpenBSD) -CFLAGS += -I${X11BASE}/include +I3_CFLAGS += -I${X11BASE}/include LIBS += -liconv -LDFLAGS += -L${X11BASE}/lib +I3_LDFLAGS += -L${X11BASE}/lib endif ifeq ($(UNAME),FreeBSD) @@ -116,33 +172,32 @@ endif ifeq ($(UNAME),Darwin) LIBS += -liconv +else +# Darwin (Mac OS X) doesn’t have librt +LIBS += -lrt endif -# Fallback for libyajl 1 which did not include yajl_version.h. We need -# YAJL_MAJOR from that file to decide which code path should be used. -CFLAGS += -idirafter $(TOPDIR)/yajl-fallback - ifneq (,$(filter Linux GNU GNU/%, $(UNAME))) -CPPFLAGS += -D_GNU_SOURCE +I3_CPPFLAGS += -D_GNU_SOURCE endif -ifeq ($(DEBUG),1) -# Extended debugging flags, macros shall be available in gcc -CFLAGS += -gdwarf-2 -CFLAGS += -g3 -else -CFLAGS += -O2 -CFLAGS += -freorder-blocks-and-partition -endif ifeq ($(COVERAGE),1) -CFLAGS += -fprofile-arcs -ftest-coverage +I3_CFLAGS += -fprofile-arcs -ftest-coverage LIBS += -lgcov endif +V ?= 0 +ifeq ($(V),0) # Don’t print command lines which are run .SILENT: +# echo-ing vars +V_ASCIIDOC = echo ASCIIDOC $@; +V_POD2HTML = echo POD2HTML $@; +V_A2X = echo A2X $@; +endif + # Always remake the following targets .PHONY: install clean dist distclean diff --git a/contrib/trivial-bar-script.sh b/contrib/trivial-bar-script.sh new file mode 100755 index 00000000..15bc7dee --- /dev/null +++ b/contrib/trivial-bar-script.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# vim:ts=4:sw=4:expandtab +# © 2012 Michael Stapelberg, Public Domain + +# This script is a trivial shell script to send your own output to i3bar while +# using the JSON protocol. +# +# It is ugly and that is inherent to using JSON with shell scripts. You +# _really_ should not do that. See i3status or i3status’s contrib/ directory +# for examples of how to handle the output in higher-level languages. +# +# This example is purely for illustration of the protocol. DO NOT USE IT IN THE +# REAL WORLD. + +# Send the header so that i3bar knows we want to use JSON: +echo '{ "version": 1 }' + +# Begin the endless array. +echo '[' + +# We send an empty first array of blocks to make the loop simpler: +echo '[]' + +# Now send blocks with information forever: +while :; +do + echo ",[{\"name\":\"time\",\"full_text\":\"$(date)\"}]" + sleep 1 +done diff --git a/debian/control b/debian/control index 3a4adc68..02f00de2 100644 --- a/debian/control +++ b/debian/control @@ -2,14 +2,34 @@ Source: i3-wm Section: x11 Priority: extra Maintainer: Michael Stapelberg -Build-Depends: debhelper (>= 7.0.50~), libx11-dev, libxcb-util0-dev (>= 0.3.8), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-icccm4-dev, libxcursor-dev, asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, libpcre3-dev, libstartup-notification0-dev (>= 0.10) +Build-Depends: debhelper (>= 7.0.50~), + libx11-dev, + libxcb-util0-dev (>= 0.3.8), + libxcb-keysyms1-dev, + libxcb-xinerama0-dev (>= 1.1), + libxcb-randr0-dev, + libxcb-icccm4-dev, + libxcursor-dev, + asciidoc (>= 8.4.4), + xmlto, + docbook-xml, + pkg-config, + libev-dev, + flex, + bison, + libyajl-dev, + libpcre3-dev, + libstartup-notification0-dev (>= 0.10), + libcairo2-dev, + libpango1.0-dev, + libpod-simple-perl Standards-Version: 3.9.3 Homepage: http://i3wm.org/ Package: i3 Architecture: any Depends: i3-wm (=${binary:Version}), ${misc:Depends} -Recommends: i3lock (>= 2.2), suckless-tools, i3status (>= 2.3) +Recommends: i3lock (>= 2.2), suckless-tools, i3status (>= 2.3), dunst Description: metapackage (i3 window manager, screen locker, menu, statusbar) This metapackage installs the i3 window manager (i3-wm), the i3lock screen locker, i3status (for system information) and suckless-tools (for dmenu). diff --git a/debian/i3-wm.docs b/debian/i3-wm.docs index a14b8152..6bd9c5ba 100644 --- a/debian/i3-wm.docs +++ b/debian/i3-wm.docs @@ -28,3 +28,5 @@ docs/tree-shot4.png docs/refcard.html docs/refcard_style.css docs/logo-30.png +docs/lib-i3test.html +docs/lib-i3test-test.html diff --git a/debian/i3-wm.manpages b/debian/i3-wm.manpages index 4df7233c..a1b05bd3 100644 --- a/debian/i3-wm.manpages +++ b/debian/i3-wm.manpages @@ -8,4 +8,4 @@ man/i3-migrate-config-to-v4.1 man/i3-sensible-pager.1 man/i3-sensible-editor.1 man/i3-sensible-terminal.1 -i3bar/doc/i3bar.1 +man/i3bar.1 diff --git a/docs/Makefile b/docs/Makefile index fc41236f..d6e670c9 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,36 +1,7 @@ -# To pass additional parameters for asciidoc -ASCIIDOC=asciidoc - -ASCIIDOC_TARGETS:=hacking-howto.html debugging.html debugging-release-version.html userguide.html ipc.html multi-monitor.html wsbar.html testsuite.html i3bar-protocol.html - -all: ${ASCIIDOC_TARGETS} - -hacking-howto.html: hacking-howto - $(ASCIIDOC) -a toc -n $< - -i3bar-protocol.html: i3bar-protocol - $(ASCIIDOC) -a toc -n $< - -debugging.html: debugging - $(ASCIIDOC) -n $< - -debugging-release-version.html: debugging-release-version - $(ASCIIDOC) -n $< - -userguide.html: userguide - $(ASCIIDOC) -a toc -n $< - -testsuite.html: testsuite - $(ASCIIDOC) -a toc -n $< - -ipc.html: ipc - $(ASCIIDOC) -a toc -n $< - -multi-monitor.html: multi-monitor - $(ASCIIDOC) -a toc -n $< - -wsbar.html: wsbar - $(ASCIIDOC) -a toc -n $< +all: + $(MAKE) -C .. docs clean: - rm -f ${ASCIIDOC_TARGETS} + $(MAKE) -C .. clean-docs + +.PHONY: all clean diff --git a/docs/asciidoc-git.conf b/docs/asciidoc-git.conf index 24dcb596..cc135ae9 100644 --- a/docs/asciidoc-git.conf +++ b/docs/asciidoc-git.conf @@ -647,7 +647,7 @@ endif::doctype-manpage[] {disable-javascript%

} diff --git a/docs/docs.mk b/docs/docs.mk new file mode 100644 index 00000000..c0daed64 --- /dev/null +++ b/docs/docs.mk @@ -0,0 +1,46 @@ +DISTCLEAN_TARGETS += clean-docs + +# To pass additional parameters for asciidoc +ASCIIDOC = asciidoc +I3POD2HTML = ./docs/i3-pod2html + +ASCIIDOC_NOTOC_TARGETS = \ + docs/debugging.html \ + docs/debugging-release-version.html + +ASCIIDOC_TOC_TARGETS = \ + docs/hacking-howto.html \ + docs/userguide.html \ + docs/ipc.html \ + docs/multi-monitor.html \ + docs/wsbar.html \ + docs/testsuite.html \ + docs/i3bar-protocol.html + +ASCIIDOC_TARGETS = \ + $(ASCIIDOC_TOC_TARGETS) \ + $(ASCIIDOC_NOTOC_TARGETS) + +ASCIIDOC_CALL = $(V_ASCIIDOC)$(ASCIIDOC) -n $(ASCIIDOC_FLAGS) -o $@ $< +ASCIIDOC_TOC_CALL = $(V_ASCIIDOC)$(ASCIIDOC) -a toc -n $(ASCIIDOC_FLAGS) -o $@ $< + +POD2HTML_TARGETS = \ + docs/lib-i3test.html \ + docs/lib-i3test-test.html + +docs/lib-i3test.html: testcases/lib/i3test.pm + $(V_POD2HTML)$(I3POD2HTML) $< $@ + +docs/lib-i3test-test.html: testcases/lib/i3test/Test.pm + $(V_POD2HTML)$(I3POD2HTML) $< $@ + +docs: $(ASCIIDOC_TARGETS) $(POD2HTML_TARGETS) + +$(ASCIIDOC_TOC_TARGETS): docs/%.html: docs/% + $(ASCIIDOC_TOC_CALL) + +$(ASCIIDOC_NOTOC_TARGETS): docs/%.html: docs/% + $(ASCIIDOC_CALL) + +clean-docs: + rm -f $(ASCIIDOC_TARGETS) $(POD2HTML_TARGETS) diff --git a/docs/hacking-howto b/docs/hacking-howto index 73ae9633..7f2c35e6 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -141,7 +141,7 @@ src/load_layout.c:: Contains code for loading layouts from JSON files. src/log.c:: -Handles the setting of loglevels, contains the logging functions. +Contains the logging functions. src/main.c:: Initializes the window manager. diff --git a/docs/i3-pod2html b/docs/i3-pod2html new file mode 100755 index 00000000..56a769f8 --- /dev/null +++ b/docs/i3-pod2html @@ -0,0 +1,90 @@ +#!/usr/bin/env perl +# vim:ts=4:sw=4:expandtab + +use strict; +use warnings; +use Pod::Simple::HTML; +use v5.10; + +$Pod::Simple::HTML::Tagmap{'Verbatim'} = '
';
+$Pod::Simple::HTML::Tagmap{'VerbatimFormatted'} = '
';
+$Pod::Simple::HTML::Tagmap{'/Verbatim'} = '
'; +$Pod::Simple::HTML::Tagmap{'/VerbatimFormatted'} = '
'; + +if (@ARGV < 2) { + say STDERR "Syntax: i3-pod2html "; + exit 1; +} + +open(my $in, '<', $ARGV[0]) or die "Couldn’t open $ARGV[0] for reading: $!\n"; +open(my $out, '>', $ARGV[1]) or die "Couldn’t open $ARGV[1] for writing: $!\n"; + +my $parser = Pod::Simple::HTML->new(); + +$parser->index(1); +$parser->html_header_before_title( +<<'EOF' + + + + + + + + + + +EOF +); +$parser->html_header_after_title( +<<'EOF' + + + + +
+

i3 - improved tiling WM

+ +
+
+

i3 Perl documentation (testsuite)

+ +EOF +); + +$parser->output_fh($out); +$parser->parse_file($in); diff --git a/docs/i3bar-protocol b/docs/i3bar-protocol index f66c7a9a..21ba9aa0 100644 --- a/docs/i3bar-protocol +++ b/docs/i3bar-protocol @@ -1,7 +1,7 @@ i3bar input protocol ==================== Michael Stapelberg -February 2012 +August 2012 This document explains the protocol in which i3bar expects its input. It provides support for colors, urgency, shortening and easy manipulation. @@ -49,6 +49,9 @@ consists of a single JSON hash: { "version": 1 } ---------------- +(Note that before i3 v4.3 the precise format had to be +{"version":1}+, +byte-for-byte.) + What follows is an infinite array (so it should be parsed by a streaming JSON parser, but as described above you can go for a simpler solution), whose elements are one array per status line. A status line is one unit of @@ -86,6 +89,10 @@ Please note that this example was pretty printed for human consumption. i3status and others will output single statuslines in one line, separated by \n. +You can find an example of a shell script which can be used as your ++status_command+ in the bar configuration at +http://code.stapelberg.de/git/i3/tree/contrib/trivial-bar-script.sh?h=next + === Blocks in detail full_text:: diff --git a/docs/ipc b/docs/ipc index 83ab7218..f8dfa78e 100644 --- a/docs/ipc +++ b/docs/ipc @@ -1,7 +1,7 @@ IPC interface (interprocess communication) ========================================== Michael Stapelberg -February 2012 +August 2012 This document describes how to interface with i3 from a separate process. This is useful for example to remote-control i3 (to write test cases for example) or @@ -70,6 +70,9 @@ GET_BAR_CONFIG (6):: Gets the configuration (as JSON map) of the workspace bar with the given ID. If no ID is provided, an array with all configured bar IDs is returned instead. +GET_VERSION (7):: + Gets the version of i3. The reply will be a JSON-encoded dictionary + with the major, minor, patch and human-readable version. So, a typical message could look like this: -------------------------------------------------- @@ -125,6 +128,8 @@ MARKS (5):: Reply to the GET_MARKS message. BAR_CONFIG (6):: Reply to the GET_BAR_CONFIG message. +VERSION (7):: + Reply to the GET_VERSION message. === COMMAND reply @@ -206,7 +211,7 @@ default) or whether a JSON parse error occurred. { "success": true } ------------------- -=== GET_OUTPUTS reply +=== OUTPUTS reply The reply consists of a serialized list of outputs. Each output has the following properties: @@ -270,12 +275,15 @@ border (string):: Can be either "normal", "none" or "1pixel", dependending on the container’s border style. layout (string):: - Can be either "default", "stacked", "tabbed", "dockarea" or "output". + Can be either "splith", "splitv", "stacked", "tabbed", "dockarea" or + "output". Other values might be possible in the future, should we add new layouts. orientation (string):: Can be either "none" (for non-split containers), "horizontal" or "vertical". + THIS FIELD IS OBSOLETE. It is still present, but your code should not + use it. Instead, rely on the layout field. percent (float):: The percentage which this container takes in its parent. A value of +null+ means that the percent property does not make sense for this @@ -296,6 +304,11 @@ window_rect (map):: geometry (map):: The original geometry the window specified when i3 mapped it. Used when switching a window to floating mode, for example. +window (integer):: + The X11 window ID of the *actual client window* inside this container. + This field is set to null for split containers or otherwise empty + containers. This ID corresponds to what xwininfo(1) and other + X11-related tools display (usually in hex). urgent (bool):: Whether this container (window or workspace) has the urgency hint set. focused (bool):: @@ -526,6 +539,35 @@ urgent_workspace_text/urgent_workspace_bar:: } -------------- +=== VERSION reply + +The reply consists of a single JSON dictionary with the following keys: + +major (integer):: + The major version of i3, such as +4+. +minor (integer):: + The minor version of i3, such as +2+. Changes in the IPC interface (new + features) will only occur with new minor (or major) releases. However, + bugfixes might be introduced in patch releases, too. +patch (integer):: + The patch version of i3, such as +1+ (when the complete version is + +4.2.1+). For versions such as +4.2+, patch will be set to +0+. +human_readable (string):: + A human-readable version of i3 containing the precise git version, + build date and branch name. When you need to display the i3 version to + your users, use the human-readable version whenever possible (since + this is what +i3 --version+ displays, too). + +*Example:* +------------------- +{ + "human_readable" : "4.2-169-gf80b877 (2012-08-05, branch \"next\")", + "minor" : 2, + "patch" : 0, + "major" : 4 +} +------------------- + == Events [[events]] @@ -621,7 +663,7 @@ C:: Ruby:: http://github.com/badboy/i3-ipc Perl:: - http://search.cpan.org/search?query=AnyEvent::I3 + https://metacpan.org/module/AnyEvent::I3 Python:: - https://github.com/whitelynx/i3ipc - https://github.com/ziberna/i3-py (includes higher-level features) + * https://github.com/whitelynx/i3ipc + * https://github.com/ziberna/i3-py (includes higher-level features) diff --git a/docs/refcard.html b/docs/refcard.html index a4427f4f..7156da36 100644 --- a/docs/refcard.html +++ b/docs/refcard.html @@ -42,95 +42,111 @@

+

Basics

- - - - - +
+ + +  open new terminal -
+j + + j focus left
+k + + k focus down
+l + + l focus up
+; + + ; focus right +
+  + toggle focus mode
-
-

Changing the container layout

+

Moving windows

- - - +
+e - default - + +  + j + move window left
+s - stacking - + +  + k + move window down
+w - tabbed + +  + l + move window up +
+  + ; + move window right
-

Fullscreen mode

+

Modifying windows

- + + +
+f + + f toggle fullscreen +
+ v + split a window vertically +
+ h + split a window horizontally +
+ r + resize mode
+

Look at the “Resizing containers / windows” section of the user guide.

-
-

Opening other applications

+

Changing the container layout

-
+d - open application (with dmenu) -
+ + e + default + + + s + stacking -
-

Closing windows

- -
+ + q - kill a window + + w + tabbed
-
-

Using workspaces

+

Floating

- +
+1–9 - switch to another workspace + +  +  + toggle floating +
+  + drag floating
-

Moving windows to workspaces

+

Using workspaces

- +
+ + 1–9 + + 0-9 + switch to another workspace +
+  + 0-9 move a window to another workspace
@@ -138,37 +154,33 @@
-

Resizing

-

Look at “Resizing containers / windows” section of the user guide.

-
- - -
-

Restart / Exit

+

Opening applications / Closing windows

- -
+ + r - restart i3 inplace - + + d + open application launcher (dmenu)
+ + e - - exit i3 + +  + q + kill a window
- +
-

Floating

+

Restart / Exit

- + -
+ +  - toggle floating + +  + c + reload the configuration file +
+  + r + restart i3 inplace
+ - drag floating -
+ +  + e + exit i3
+ +

Permission is granted to copy, distribute and/or modify this document provided diff --git a/docs/testsuite b/docs/testsuite index 720ff394..4dcf1670 100644 --- a/docs/testsuite +++ b/docs/testsuite @@ -1,7 +1,7 @@ i3 testsuite ============ -Michael Stapelberg -September 2011 +Michael Stapelberg +September 2012 This document explains how the i3 testsuite works, how to use it and extend it. It is targeted at developers who not necessarily have been doing testing before @@ -33,6 +33,19 @@ able to easily test if the feature is working correctly. Many developers will test manually if everything works. Having a testcase not only helps you with that, but it will also be useful for every future change. +== Relevant documentation + +Apart from this document, you should also have a look at: + +1. The "Modern Perl" book, which can be found at + http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +2. The latest Perl documentation of the "i3test" (general testcase setup) and + "i3test::Test" (additional test instructions) modules: + http://build.i3wm.org/docs/lib-i3test.html respectively + http://build.i3wm.org/docs/lib-i3test-test.html +3. The latest documentation on i3’s IPC interface: + http://build.i3wm.org/docs/ipc.html + == Implementation For several reasons, the i3 testsuite has been implemented in Perl: @@ -45,6 +58,8 @@ For several reasons, the i3 testsuite has been implemented in Perl: 2. Perl is widely available and has a well-working package infrastructure. 3. The author is familiar with Perl :). +4. It is a good idea to use a different language for the tests than the + implementation itself. Please do not start programming language flamewars at this point. diff --git a/docs/userguide b/docs/userguide index 853fc5e6..e42a6acb 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1,7 +1,7 @@ i3 User’s Guide =============== -Michael Stapelberg -April 2012 +Michael Stapelberg +August 2012 This document contains all the information you need to configure and use the i3 window manager. If it does not, please contact us on IRC (preferred) or post your @@ -61,16 +61,18 @@ windows. TODO: picture of the tree -To split a window vertically, press +mod+v+. To split it horizontally, press -+mod+h+. +To split a window vertically, press +mod+v+ before you create the new window. +To split it horizontally, press +mod+h+. === Changing the container layout A split container can have one of the following layouts: -default:: +splith/splitv:: Windows are sized so that every window gets an equal amount of space in the -container. +container. splith distributes the windows horizontally (windows are right next +to each other), splitv distributes them vertically (windows are on top of each +other). stacking:: Only the focused window in the container is displayed. You get a list of windows at the top of the container. @@ -78,8 +80,8 @@ tabbed:: The same principle as +stacking+, but the list of windows at the top is only a single line which is vertically split. -To switch modes, press +mod+e+ for default, +mod+s+ for stacking and -+mod+w+ for tabbed. +To switch modes, press +mod+e+ for splith/splitv (it toggles), +mod+s+ for +stacking and +mod+w+ for tabbed. image:modes.png[Container modes] @@ -196,20 +198,21 @@ image::tree-shot4.png["shot4",title="Two terminals on standard workspace"] It is only natural to use so-called +Split Containers+ in order to build a layout when using a tree as data structure. In i3, every +Container+ has an -orientation (horizontal, vertical or unspecified). So, in our example with the -workspace, the default orientation of the workspace +Container+ is horizontal -(most monitors are widescreen nowadays). If you change the orientation to -vertical (+mod+v+ in the default config) and *then* open two terminals, i3 will -configure your windows like this: +orientation (horizontal, vertical or unspecified) and the orientation depends +on the layout the container is in (vertical for splitv and stacking, horizontal +for splith and tabbed). So, in our example with the workspace, the default +layout of the workspace +Container+ is splith (most monitors are widescreen +nowadays). If you change the layout to splitv (+mod+l+ in the default config) +and *then* open two terminals, i3 will configure your windows like this: image::tree-shot2.png["shot2",title="Vertical Workspace Orientation"] -An interesting new feature of the tree branch is the ability to split anything: -Let’s assume you have two terminals on a workspace (with horizontal -orientation), focus is on the right terminal. Now you want to open another -terminal window below the current one. If you would just open a new terminal -window, it would show up to the right due to the horizontal workspace -orientation. Instead, press +mod+v+ to create a +Vertical Split Container+ (to +An interesting new feature of i3 since version 4 is the ability to split anything: +Let’s assume you have two terminals on a workspace (with splith layout, that is +horizontal orientation), focus is on the right terminal. Now you want to open +another terminal window below the current one. If you would just open a new +terminal window, it would show up to the right due to the splith layout. +Instead, press +mod+v+ to split the container with the splitv layout (to open a +Horizontal Split Container+, use +mod+h+). Now you can open a new terminal and it will open below the current one: @@ -291,11 +294,21 @@ a # and can only be used at the beginning of a line: # This is a comment ------------------- +[[fonts]] + === Fonts -i3 uses X core fonts (not Xft) for rendering window titles. You can use -+xfontsel(1)+ to generate such a font description. To see special characters -(Unicode), you need to use a font which supports the ISO-10646 encoding. +i3 has support for both X core fonts and FreeType fonts (through Pango) to +render window titles. + +To generate an X core font description, you can use +xfontsel(1)+. To see +special characters (Unicode), you need to use a font which supports the +ISO-10646 encoding. + +A FreeType font description is composed by a font family, a style, a weight, +a variant, a stretch and a size. +FreeType fonts support right-to-left rendering and contain often more +Unicode glyphs than X core fonts. If i3 cannot open the configured font, it will output an error in the logfile and fall back to a working font. @@ -303,11 +316,13 @@ and fall back to a working font. *Syntax*: ------------------------------ font +font xft: ------------------------------ *Examples*: -------------------------------------------------------------- font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 +font xft:DejaVu Sans Mono 10 -------------------------------------------------------------- [[keybindings]] @@ -333,10 +348,15 @@ your bindings in the same physical location on the keyboard, use keycodes. If you don’t switch layouts, and want a clean and simple config file, use keysyms. +Some tools (such as +import+ or +xdotool+) might be unable to run upon a +KeyPress event, because the keyboard/pointer is still grabbed. For these +situations, the +--release+ flag can be used, which will execute the command +after the keys have been released. + *Syntax*: ---------------------------------- -bindsym [Modifiers+]keysym command -bindcode [Modifiers+]keycode command +bindsym [--release] [Modifiers+]keysym command +bindcode [--release] [Modifiers+]keycode command ---------------------------------- *Examples*: @@ -348,7 +368,13 @@ bindsym mod+f fullscreen bindsym mod+Shift+r restart # Notebook-specific hotkeys -bindcode 214 exec /home/michael/toggle_beamer.sh +bindcode 214 exec --no-startup-id /home/michael/toggle_beamer.sh + +# Simulate ctrl+v upon pressing $mod+x +bindsym --release $mod+x exec --no-startup-id xdotool key --clearmodifiers ctrl+v + +# Take a screenshot upon pressing $mod+x (select an area) +bindsym --release $mod+x exec --no-startup-id import /tmp/latest-screenshot.png -------------------------------- Available Modifiers: @@ -465,6 +491,22 @@ new_window new_window 1pixel --------------------- +=== Hiding vertical borders + +You can hide vertical borders adjacent to the screen edges using ++hide_edge_borders+. This is useful if you are using scrollbars, or do not want +to waste even two pixels in displayspace. Default is none. + +*Syntax*: +---------------------------- +hide_edge_borders +---------------------------- + +*Example*: +---------------------- +hide_edge_borders vertical +---------------------- + === Arbitrary commands for specific windows (for_window) With the +for_window+ command, you can let i3 execute any command when it @@ -573,6 +615,22 @@ logfile first (see http://i3wm.org/docs/debugging.html). It includes more details about the matching process and the window’s actual class, instance and title when starting up. +Note that if you want to start an application just once on a specific +workspace, but you don’t want to assign all instances of it permanently, you +can make use of i3’s startup-notification support (see <>) in your config +file in the following way: + +*Start iceweasel on workspace 3 (once)*: +------------------------------------------------------------------------------- +# Start iceweasel on workspace 3, then switch back to workspace 1 +# (Being a command-line utility, i3-msg does not support startup notifications, +# hence the exec --no-startup-id.) +# (Starting iceweasel with i3’s exec command is important in order to make i3 +# create a startup notification context, without which the iceweasel window(s) +# cannot be matched onto the workspace on which the command was started.) +exec --no-startup-id i3-msg 'workspace 3; exec iceweasel; workspace 1' +------------------------------------------------------------------------------- + === Automatically starting applications on i3 startup By using the +exec+ keyword outside a keybinding, you can configure @@ -1010,8 +1068,7 @@ xrandr --output --primary === Font -Specifies the font (again, X core font, not Xft, just like in i3) to be used in -the bar. +Specifies the font to be used in the bar. See <>. *Syntax*: --------------------- @@ -1022,6 +1079,7 @@ font -------------------------------------------------------------- bar { font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 + font xft:DejaVu Sans Mono 10 } -------------------------------------------------------------- @@ -1190,13 +1248,15 @@ cursor for 60 seconds. === Splitting containers The split command makes the current window a split container. Split containers -can contain multiple windows. Every split container has an orientation, it is -either split horizontally (a new window gets placed to the right of the current -one) or vertically (a new window gets placed below the current one). +can contain multiple windows. Depending on the layout of the split container, +new windows get placed to the right of the current one (splith) or new windows +get placed below the current one (splitv). If you apply this command to a split container with the same orientation, nothing will happen. If you use a different orientation, the split container’s -orientation will be changed (if it does not have more than one window). +orientation will be changed (if it does not have more than one window). Use ++layout toggle split+ to change the layout of any split container from splitv +to splith or vice-versa. *Syntax*: --------------------------- @@ -1211,19 +1271,32 @@ bindsym mod+h split horizontal === Manipulating layout -Use +layout default+, +layout stacking+ or +layout tabbed+ to change the -current container layout to default, stacking or tabbed layout, respectively. +Use +layout toggle split+, +layout stacking+ or +layout tabbed+ to change the +current container layout to splith/splitv, stacking or tabbed layout, +respectively. To make the current window (!) fullscreen, use +fullscreen+, to make it floating (or tiling again) use +floating enable+ respectively +floating disable+ (or +floating toggle+): +*Syntax*: +-------------- +layout +layout toggle [split|all] +-------------- + *Examples*: -------------- bindsym mod+s layout stacking -bindsym mod+l layout default +bindsym mod+l layout toggle split bindsym mod+w layout tabbed +# Toggle between stacking/tabbed/split: +bindsym mod+x layout toggle + +# Toggle between stacking/tabbed/splith/splitv: +bindsym mod+x layout toggle all + # Toggle fullscreen bindsym mod+f fullscreen @@ -1313,8 +1386,9 @@ You can also switch to the next and previous workspace with the commands workspace 1, 3, 4 and 9 and you want to cycle through them with a single key combination. To restrict those to the current output, use +workspace next_on_output+ and +workspace prev_on_output+. Similarly, you can use +move -container to workspace next+ and +move container to workspace prev+ to move a -container to the next/previous workspace. +container to workspace next+, +move container to workspace prev+ to move a +container to the next/previous workspace and +move container to workspace current+ +(the last one makes sense only when used with criteria). [[back_and_forth]] To switch back to the previously focused workspace, use +workspace @@ -1330,6 +1404,18 @@ you can use the +move workspace to output+ command followed by the name of the target output. You may also use +left+, +right+, +up+, +down+ instead of the xrandr output name to move to the next output in the specified direction. +*Syntax*: +----------------------------------- +workspace +workspace back_and_forth +workspace +workspace number + +move [window|container] [to] workspace +move [window|container] [to] workspace number +move [window|container] [to] workspace +----------------------------------- + *Examples*: ------------------------- bindsym mod+1 workspace 1 @@ -1345,6 +1431,9 @@ bindsym mod+b workspace back_and_forth # move the whole workspace to the next output bindsym mod+x move workspace to output right + +# move firefox to current workspace +bindsym mod+F1 [class="Firefox"] move workspace current ------------------------- ==== Named workspaces diff --git a/generate-command-parser.pl b/generate-command-parser.pl index 993c64fc..01cbe462 100755 --- a/generate-command-parser.pl +++ b/generate-command-parser.pl @@ -152,7 +152,7 @@ for my $state (@keys) { $cmd =~ s/[^(]+\(//; $cmd =~ s/\)$//; $cmd = ", $cmd" if length($cmd) > 0; - say $callfh qq| printf("$fmt\\n"$cmd);|; + say $callfh qq| fprintf(stderr, "$fmt\\n"$cmd);|; say $callfh '#endif'; say $callfh " state = $next_state;"; say $callfh " break;"; diff --git a/i3-config-wizard/Makefile b/i3-config-wizard/Makefile index 75d4684f..d5ac18c6 100644 --- a/i3-config-wizard/Makefile +++ b/i3-config-wizard/Makefile @@ -1,47 +1,10 @@ -# Default value so one can compile i3-input standalone -TOPDIR=.. +all: + $(MAKE) -C .. i3-config-wizard/i3-config-wizard -include $(TOPDIR)/common.mk - -# Depend on the object files of all source-files in src/*.c and on all header files -AUTOGENERATED:=cfgparse.tab.c cfgparse.yy.c -FILES:=$(patsubst %.c,%.o,$(filter-out $(AUTOGENERATED),$(wildcard *.c))) -HEADERS:=$(wildcard *.h) - -CPPFLAGS += -I$(TOPDIR)/include - -# Depend on the specific file (.c for each .o) and on all headers -%.o: %.c ${HEADERS} - echo "[i3-config-wizard] CC $<" - $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< - -all: i3-config-wizard - -i3-config-wizard: $(TOPDIR)/libi3/libi3.a cfgparse.y.o cfgparse.yy.o ${FILES} - echo "[i3-config-wizard] LINK i3-config-wizard" - $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS) - -$(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c - $(MAKE) -C $(TOPDIR)/libi3 - -cfgparse.yy.o: cfgparse.l cfgparse.y.o ${HEADERS} - echo "[i3-config-wizard] LEX $<" - $(FLEX) -i -o$(@:.o=.c) $< - $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(@:.o=.c) - -cfgparse.y.o: cfgparse.y ${HEADERS} - echo "[i3-config-wizard] YACC $<" - $(BISON) --debug --verbose -b $(basename $< .y) -d $< - $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(<:.y=.tab.c) - - -install: all - echo "[i3-config-wizard] INSTALL" - $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin - $(INSTALL) -m 0755 i3-config-wizard $(DESTDIR)$(PREFIX)/bin/ +install: + $(MAKE) -C .. install-i3-config-wizard clean: - rm -f *.o cfgparse.tab.{c,h} cfgparse.output cfgparse.yy.c + $(MAKE) -C .. clean-i3-config-wizard -distclean: clean - rm -f i3-config-wizard +.PHONY: all install clean diff --git a/i3-config-wizard/cfgparse.y b/i3-config-wizard/cfgparse.y index e5a86868..17c3953c 100644 --- a/i3-config-wizard/cfgparse.y +++ b/i3-config-wizard/cfgparse.y @@ -39,6 +39,8 @@ extern FILE *yyin; YY_BUFFER_STATE yy_scan_string(const char *); static struct context *context; +static xcb_connection_t *conn; +static xcb_key_symbols_t *keysyms; /* We don’t need yydebug for now, as we got decent error messages using * yyerror(). Should you ever want to extend the parser, it might be handy @@ -67,6 +69,13 @@ int yywrap() { char *rewrite_binding(const char *bindingline) { char *result = NULL; + conn = xcb_connect(NULL, NULL); + if (conn == NULL || xcb_connection_has_error(conn)) { + fprintf(stderr, "Cannot open display\n"); + exit(1); + } + keysyms = xcb_key_symbols_alloc(conn); + context = calloc(sizeof(struct context), 1); yy_scan_string(bindingline); @@ -81,6 +90,8 @@ char *rewrite_binding(const char *bindingline) { if (context->line_copy) free(context->line_copy); free(context); + xcb_key_symbols_free(keysyms); + xcb_disconnect(conn); return result; } @@ -101,6 +112,28 @@ static char *modifier_to_string(int modifiers) { else return strdup(""); } +/* + * Returns true if sym is bound to any key except for 'except_keycode' on the + * first four layers (normal, shift, mode_switch, mode_switch + shift). + * + */ +static bool keysym_used_on_other_key(KeySym sym, xcb_keycode_t except_keycode) { + xcb_keycode_t i, + min_keycode = xcb_get_setup(conn)->min_keycode, + max_keycode = xcb_get_setup(conn)->max_keycode; + + for (i = min_keycode; i && i <= max_keycode; i++) { + if (i == except_keycode) + continue; + for (int level = 0; level < 4; level++) { + if (xcb_key_symbols_get_keysym(keysyms, i, level) != sym) + continue; + return true; + } + } + return false; +} + %} %error-verbose @@ -139,11 +172,22 @@ bindcode: * different key than the upper-case one (unlikely for letters, but * more likely for special characters). */ level = 1; + + /* Try to use the keysym on the first level (lower-case). In case + * this doesn’t make it ambiguous (think of a keyboard layout + * having '1' on two different keys, but '!' only on keycode 10), + * we’ll stick with the keysym of the first level. + * + * This reduces a lot of confusion for users who switch keyboard + * layouts from qwerty to qwertz or other slight variations of + * qwerty (yes, that happens quite often). */ + KeySym sym = XkbKeycodeToKeysym(dpy, $4, 0, 0); + if (!keysym_used_on_other_key(sym, $4)) + level = 0; } KeySym sym = XkbKeycodeToKeysym(dpy, $4, 0, level); char *str = XKeysymToString(sym); char *modifiers = modifier_to_string($3); - // TODO: modifier to string sasprintf(&(context->result), "bindsym %s%s %s\n", modifiers, str, $6); free(modifiers); } diff --git a/i3-config-wizard/i3-config-wizard.mk b/i3-config-wizard/i3-config-wizard.mk new file mode 100644 index 00000000..1598cfed --- /dev/null +++ b/i3-config-wizard/i3-config-wizard.mk @@ -0,0 +1,37 @@ +ALL_TARGETS += i3-config-wizard/i3-config-wizard +INSTALL_TARGETS += install-i3-config-wizard +CLEAN_TARGETS += clean-i3-config-wizard + +i3_config_wizard_SOURCES_GENERATED = i3-config-wizard/cfgparse.tab.c i3-config-wizard/cfgparse.yy.c +i3_config_wizard_SOURCES := $(filter-out $(i3_config_wizard_SOURCES_GENERATED),$(wildcard i3-config-wizard/*.c)) +i3_config_wizard_HEADERS := $(wildcard i3-config-wizard/*.h) +i3_config_wizard_CFLAGS = $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(X11_CFLAGS) $(PANGO_CFLAGS) +i3_config_wizard_LIBS = $(XCB_LIBS) $(XCB_KBD_LIBS) $(X11_LIBS) $(PANGO_LIBS) + +i3_config_wizard_OBJECTS := $(i3_config_wizard_SOURCES_GENERATED:.c=.o) $(i3_config_wizard_SOURCES:.c=.o) + + +i3-config-wizard/%.o: i3-config-wizard/%.c $(i3_config_wizard_HEADERS) + echo "[i3-config-wizard] CC $<" + $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_config_wizard_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $< + +i3-config-wizard/cfgparse.yy.c: i3-config-wizard/cfgparse.l i3-config-wizard/cfgparse.tab.o $(i3_config_wizard_HEADERS) + echo "[i3-config-wizard] LEX $<" + $(FLEX) -i -o $@ $< + +i3-config-wizard/cfgparse.tab.c: i3-config-wizard/cfgparse.y $(i3_config_wizard_HEADERS) + echo "[i3-config-wizard] YACC $<" + $(BISON) --debug --verbose -b $(basename $< .y) -d $< + +i3-config-wizard/i3-config-wizard: libi3.a $(i3_config_wizard_OBJECTS) + echo "[i3-config-wizard] Link i3-config-wizard" + $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(LIBS) $(i3_config_wizard_LIBS) + +install-i3-config-wizard: i3-config-wizard/i3-config-wizard + echo "[i3-config-wizard] Install" + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m 0755 i3-config-wizard/i3-config-wizard $(DESTDIR)$(PREFIX)/bin/ + +clean-i3-config-wizard: + echo "[i3-config-wizard] Clean" + rm -f $(i3_config_wizard_OBJECTS) $(i3_config_wizard_SOURCES_GENERATED) i3-config-wizard/i3-config-wizard i3-config-wizard/cfgparse.{output,dot} diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index be042673..679c5e6d 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -69,6 +69,7 @@ enum { MOD_Mod1, MOD_Mod4 } modifier = MOD_Mod4; static char *config_path; static uint32_t xcb_numlock_mask; xcb_connection_t *conn; +xcb_screen_t *root_screen; static xcb_get_modifier_mapping_reply_t *modmap_reply; static i3Font font; static i3Font bold_font; @@ -83,6 +84,26 @@ Display *dpy; char *rewrite_binding(const char *bindingline); static void finish(); +/* + * Having verboselog() and errorlog() is necessary when using libi3. + * + */ +void verboselog(char *fmt, ...) { + va_list args; + + va_start(args, fmt); + vfprintf(stdout, fmt, args); + va_end(args); +} + +void errorlog(char *fmt, ...) { + va_list args; + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} + /* * This function resolves ~ in pathnames. * It may resolve wildcards in the first part of the path, but if no match @@ -128,7 +149,7 @@ static int handle_expose() { set_font(&font); #define txt(x, row, text) \ - draw_text(text, strlen(text), false, pixmap, pixmap_gc,\ + draw_text_ascii(text, pixmap, pixmap_gc,\ x, (row - 1) * font.height + 4, 300 - x * 2) if (current_step == STEP_WELCOME) { @@ -456,7 +477,7 @@ int main(int argc, char *argv[]) { #include "atoms.xmacro" #undef xmacro - xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); + root_screen = xcb_aux_get_screen(conn, screens); root = root_screen->root; if (!(modmap_reply = xcb_get_modifier_mapping_reply(conn, modmap_cookie, NULL))) diff --git a/i3-dump-log/Makefile b/i3-dump-log/Makefile index 18076e51..2b9e4fe9 100644 --- a/i3-dump-log/Makefile +++ b/i3-dump-log/Makefile @@ -1,32 +1,10 @@ -# Default value so one can compile i3-dump-log standalone -TOPDIR=.. +all: + $(MAKE) -C .. i3-dump-log/i3-dump-log -include $(TOPDIR)/common.mk - -CFLAGS += -I$(TOPDIR)/include - -# Depend on the object files of all source-files in src/*.c and on all header files -FILES=$(patsubst %.c,%.o,$(wildcard *.c)) -HEADERS=$(wildcard *.h) - -# Depend on the specific file (.c for each .o) and on all headers -%.o: %.c ${HEADERS} - echo "[i3-dump-log] CC $<" - $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< - -all: i3-dump-log - -i3-dump-log: ${FILES} - echo "[i3-dump-log] LINK i3-dump-log" - $(CC) $(LDFLAGS) -o i3-dump-log ${FILES} $(LIBS) - -install: all - echo "[i3-dump-log] INSTALL" - $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin - $(INSTALL) -m 0755 i3-dump-log $(DESTDIR)$(PREFIX)/bin/ +install: + $(MAKE) -C .. install-i3-dump-log clean: - rm -f *.o + $(MAKE) -C .. clean-i3-dump-log -distclean: clean - rm -f i3-dump-log +.PHONY: all install clean diff --git a/i3-dump-log/i3-dump-log.mk b/i3-dump-log/i3-dump-log.mk new file mode 100644 index 00000000..bbce356f --- /dev/null +++ b/i3-dump-log/i3-dump-log.mk @@ -0,0 +1,28 @@ +ALL_TARGETS += i3-dump-log/i3-dump-log +INSTALL_TARGETS += install-i3-dump-log +CLEAN_TARGETS += clean-i3-dump-log + +i3_dump_log_SOURCES := $(wildcard i3-dump-log/*.c) +i3_dump_log_HEADERS := $(wildcard i3-dump-log/*.h) +i3_dump_log_CFLAGS = $(XCB_CFLAGS) $(PANGO_CFLAGS) +i3_dump_log_LIBS = $(XCB_LIBS) + +i3_dump_log_OBJECTS := $(i3_dump_log_SOURCES:.c=.o) + + +i3-dump-log/%.o: i3-dump-log/%.c $(i3_dump_log_HEADERS) + echo "[i3-dump-log] CC $<" + $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_dump_log_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $< + +i3-dump-log/i3-dump-log: libi3.a $(i3_dump_log_OBJECTS) + echo "[i3-dump-log] Link i3-dump-log" + $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(LIBS) $(i3_dump_log_LIBS) + +install-i3-dump-log: i3-dump-log/i3-dump-log + echo "[i3-dump-log] Install" + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m 0755 i3-dump-log/i3-dump-log $(DESTDIR)$(PREFIX)/bin/ + +clean-i3-dump-log: + echo "[i3-dump-log] Clean" + rm -f $(i3_dump_log_OBJECTS) i3-dump-log/i3-dump-log diff --git a/i3-dump-log/main.c b/i3-dump-log/main.c index 6b500ea3..48465c75 100644 --- a/i3-dump-log/main.c +++ b/i3-dump-log/main.c @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) * * i3-dump-log/main.c: Dumps the i3 SHM log to stdout. * @@ -28,18 +28,47 @@ #include "shmlog.h" #include +static uint32_t offset_next_write, + wrap_count; + +static i3_shmlog_header *header; +static char *logbuffer, + *walk; + +static int check_for_wrap(void) { + if (wrap_count == header->wrap_count) + return 0; + + /* The log wrapped. Print the remaining content and reset walk to the top + * of the log. */ + wrap_count = header->wrap_count; + write(STDOUT_FILENO, walk, ((logbuffer + header->offset_last_wrap) - walk)); + walk = logbuffer + sizeof(i3_shmlog_header); + return 1; +} + +static void print_till_end(void) { + check_for_wrap(); + int n = write(STDOUT_FILENO, walk, ((logbuffer + header->offset_next_write) - walk)); + if (n > 0) { + walk += n; + } +} + int main(int argc, char *argv[]) { int o, option_index = 0; - bool verbose = false; + bool verbose = false, + follow = false; static struct option long_options[] = { {"version", no_argument, 0, 'v'}, {"verbose", no_argument, 0, 'V'}, + {"follow", no_argument, 0, 'f'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; - char *options_string = "s:vVh"; + char *options_string = "s:vfVh"; while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { if (o == 'v') { @@ -47,9 +76,11 @@ int main(int argc, char *argv[]) { return 0; } else if (o == 'V') { verbose = true; + } else if (o == 'f') { + follow = true; } else if (o == 'h') { printf("i3-dump-log " I3_VERSION "\n"); - printf("i3-dump-log [-s ]\n"); + printf("i3-dump-log [-f] [-s ]\n"); return 0; } } @@ -90,45 +121,61 @@ int main(int argc, char *argv[]) { struct stat statbuf; - int logbuffer_shm = shm_open(shmname, O_RDONLY, 0); + /* NB: While we must never read, we need O_RDWR for the pthread condvar. */ + int logbuffer_shm = shm_open(shmname, O_RDWR, 0); if (logbuffer_shm == -1) err(EXIT_FAILURE, "Could not shm_open SHM segment for the i3 log (%s)", shmname); if (fstat(logbuffer_shm, &statbuf) != 0) err(EXIT_FAILURE, "stat(%s)", shmname); - char *logbuffer = mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, logbuffer_shm, 0); + /* NB: While we must never read, we need O_RDWR for the pthread condvar. */ + logbuffer = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, logbuffer_shm, 0); if (logbuffer == MAP_FAILED) err(EXIT_FAILURE, "Could not mmap SHM segment for the i3 log"); - i3_shmlog_header *header = (i3_shmlog_header*)logbuffer; + header = (i3_shmlog_header*)logbuffer; if (verbose) printf("next_write = %d, last_wrap = %d, logbuffer_size = %d, shmname = %s\n", header->offset_next_write, header->offset_last_wrap, header->size, shmname); - int chars; - char *walk = logbuffer + header->offset_next_write; - /* Skip the first line, it very likely is mangled. Not a problem, though, - * the log is chatty enough to have plenty lines left. */ - while (*walk != '\0') - walk++; + walk = logbuffer + header->offset_next_write; - /* Print the oldest log lines. We use printf("%s") to stop on \0. */ - while (walk < (logbuffer + header->offset_last_wrap)) { - chars = printf("%s", walk); - /* Shortcut: If there are two consecutive \0 bytes, this part of the - * buffer was never touched. To not call printf() for every byte of the - * buffer, we directly exit the loop. */ - if (*walk == '\0' && *(walk+1) == '\0') - break; - walk += (chars > 0 ? chars : 1); + /* We first need to print old content in case there was at least one + * wrapping already. */ + + if (*walk != '\0') { + /* In case there was a write to the buffer already, skip the first + * old line, it very likely is mangled. Not a problem, though, the log + * is chatty enough to have plenty lines left. */ + while (*walk != '\n') + walk++; + walk++; } + /* In case there was no wrapping, this is a no-op, otherwise it prints the + * old lines. */ + wrap_count = 0; + check_for_wrap(); + /* Then start from the beginning and print the newer lines */ walk = logbuffer + sizeof(i3_shmlog_header); - while (walk < (logbuffer + header->offset_next_write)) { - chars = printf("%s", walk); - walk += (chars > 0 ? chars : 1); + print_till_end(); + + if (follow) { + /* Since pthread_cond_wait() expects a mutex, we need to provide one. + * To not lock i3 (that’s bad, mhkay?) we just define one outside of + * the shared memory. */ + pthread_mutex_t dummy_mutex = PTHREAD_MUTEX_INITIALIZER; + pthread_mutex_lock(&dummy_mutex); + while (1) { + pthread_cond_wait(&(header->condvar), &dummy_mutex); + /* If this was not a spurious wakeup, print the new lines. */ + if (header->offset_next_write != offset_next_write) { + offset_next_write = header->offset_next_write; + print_till_end(); + } + } } return 0; diff --git a/i3-input/Makefile b/i3-input/Makefile index 493df784..e1c8eae5 100644 --- a/i3-input/Makefile +++ b/i3-input/Makefile @@ -1,35 +1,10 @@ -# Default value so one can compile i3-input standalone -TOPDIR=.. +all: + $(MAKE) -C .. i3-input/i3-input -include $(TOPDIR)/common.mk - -CPPFLAGS += -I$(TOPDIR)/include - -# Depend on the object files of all source-files in src/*.c and on all header files -FILES=$(patsubst %.c,%.o,$(wildcard *.c)) -HEADERS=$(wildcard *.h) - -# Depend on the specific file (.c for each .o) and on all headers -%.o: %.c ${HEADERS} - echo "[i3-input] CC $<" - $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< - -all: i3-input - -i3-input: $(TOPDIR)/libi3/libi3.a ${FILES} - echo "[i3-input] LINK i3-input" - $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS) - -$(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c - $(MAKE) -C $(TOPDIR)/libi3 - -install: all - echo "[i3-input] INSTALL" - $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin - $(INSTALL) -m 0755 i3-input $(DESTDIR)$(PREFIX)/bin/ +install: + $(MAKE) -C .. install-i3-input clean: - rm -f *.o + $(MAKE) -C .. clean-i3-input -distclean: clean - rm -f i3-input +.PHONY: all install clean diff --git a/i3-input/i3-input.mk b/i3-input/i3-input.mk new file mode 100644 index 00000000..03f4e0a6 --- /dev/null +++ b/i3-input/i3-input.mk @@ -0,0 +1,28 @@ +ALL_TARGETS += i3-input/i3-input +INSTALL_TARGETS += install-i3-input +CLEAN_TARGETS += clean-i3-input + +i3_input_SOURCES := $(wildcard i3-input/*.c) +i3_input_HEADERS := $(wildcard i3-input/*.h) +i3_input_CFLAGS = $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(PANGO_CFLAGS) +i3_input_LIBS = $(XCB_LIBS) $(XCB_KBD_LIBS) $(PANGO_LIBS) + +i3_input_OBJECTS := $(i3_input_SOURCES:.c=.o) + + +i3-input/%.o: i3-input/%.c $(i3_input_HEADERS) + echo "[i3-input] CC $<" + $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_input_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $< + +i3-input/i3-input: libi3.a $(i3_input_OBJECTS) + echo "[i3-input] Link i3-input" + $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(LIBS) $(i3_input_LIBS) + +install-i3-input: i3-input/i3-input + echo "[i3-input] Install" + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m 0755 i3-input/i3-input $(DESTDIR)$(PREFIX)/bin/ + +clean-i3-input: + echo "[i3-input] Clean" + rm -f $(i3_input_OBJECTS) i3-input/i3-input diff --git a/i3-input/main.c b/i3-input/main.c index b5709523..b3e626e6 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -45,15 +45,36 @@ static bool modeswitch_active = false; static xcb_window_t win; static xcb_pixmap_t pixmap; static xcb_gcontext_t pixmap_gc; -static char *glyphs_ucs[512]; +static xcb_char2b_t glyphs_ucs[512]; static char *glyphs_utf8[512]; static int input_position; static i3Font font; -static char *prompt; -static size_t prompt_len; +static i3String *prompt; +static int prompt_offset = 0; static int limit; xcb_window_t root; xcb_connection_t *conn; +xcb_screen_t *root_screen; + +/* + * Having verboselog() and errorlog() is necessary when using libi3. + * + */ +void verboselog(char *fmt, ...) { + va_list args; + + va_start(args, fmt); + vfprintf(stdout, fmt, args); + va_end(args); +} + +void errorlog(char *fmt, ...) { + va_list args; + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} /* * Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for @@ -96,25 +117,21 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t /* restore font color */ set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000")); - /* draw the text */ - uint8_t *con = concat_strings(glyphs_ucs, input_position); - char *full_text = (char*)con; + /* draw the prompt … */ if (prompt != NULL) { - full_text = malloc((prompt_len + input_position) * 2 + 1); - if (full_text == NULL) - err(EXIT_FAILURE, "malloc() failed\n"); - memcpy(full_text, prompt, prompt_len * 2); - memcpy(full_text + (prompt_len * 2), con, input_position * 2); + draw_text(prompt, pixmap, pixmap_gc, 4, 4, 492); + } + /* … and the text */ + if (input_position > 0) + { + i3String *input = i3string_from_ucs2(glyphs_ucs, input_position); + draw_text(input, pixmap, pixmap_gc, prompt_offset + 4, 4, 492); + i3string_free(input); } - if (input_position + prompt_len != 0) - draw_text(full_text, input_position + prompt_len, true, pixmap, pixmap_gc, 4, 4, 492); /* Copy the contents of the pixmap to the real window */ xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, font.height + 8); xcb_flush(conn); - free(con); - if (prompt != NULL) - free(full_text); return 1; } @@ -126,16 +143,6 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event) { printf("releasing %d, state raw = %d\n", event->detail, event->state); - /* See the documentation of xcb_key_symbols_get_keysym for this one. - * Basically: We get either col 0 or col 1, depending on whether shift is - * pressed. */ - int col = (event->state & XCB_MOD_MASK_SHIFT); - - /* If modeswitch is currently active, we need to look in group 2 or 3, - * respectively. */ - if (modeswitch_active) - col += 2; - xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state); if (sym == XK_Mode_switch) { printf("Mode switch disabled\n"); @@ -226,7 +233,6 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press return 1; input_position--; - free(glyphs_ucs[input_position]); free(glyphs_utf8[input_position]); handle_expose(NULL, conn, NULL); @@ -257,18 +263,16 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press return 1; } - /* store the UCS into a string */ - uint8_t inp[3] = {(ucs & 0xFF00) >> 8, (ucs & 0xFF), 0}; + xcb_char2b_t inp; + inp.byte1 = ( ucs & 0xff00 ) >> 2; + inp.byte2 = ( ucs & 0x00ff ) >> 0; - printf("inp[0] = %02x, inp[1] = %02x, inp[2] = %02x\n", inp[0], inp[1], inp[2]); + printf("inp.byte1 = %02x, inp.byte2 = %02x\n", inp.byte1, inp.byte2); /* convert it to UTF-8 */ - char *out = convert_ucs2_to_utf8((xcb_char2b_t*)inp, 1); + char *out = convert_ucs2_to_utf8(&inp, 1); printf("converted to %s\n", out); - glyphs_ucs[input_position] = malloc(3 * sizeof(uint8_t)); - if (glyphs_ucs[input_position] == NULL) - err(EXIT_FAILURE, "malloc() failed\n"); - memcpy(glyphs_ucs[input_position], inp, 3); + glyphs_ucs[input_position] = inp; glyphs_utf8[input_position] = out; input_position++; @@ -318,8 +322,8 @@ int main(int argc, char *argv[]) { limit = atoi(optarg); break; case 'P': - FREE(prompt); - prompt = strdup(optarg); + i3string_free(prompt); + prompt = i3string_from_utf8(optarg); break; case 'f': FREE(pattern); @@ -349,15 +353,12 @@ int main(int argc, char *argv[]) { sockfd = ipc_connect(socket_path); - if (prompt != NULL) - prompt = (char*)convert_utf8_to_ucs2(prompt, &prompt_len); - int screens; conn = xcb_connect(NULL, &screens); if (!conn || xcb_connection_has_error(conn)) die("Cannot open display\n"); - xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); + root_screen = xcb_aux_get_screen(conn, screens); root = root_screen->root; symbols = xcb_key_symbols_alloc(conn); @@ -365,6 +366,9 @@ int main(int argc, char *argv[]) { font = load_font(pattern, true); set_font(&font); + if (prompt != NULL) + prompt_offset = predict_text_width(prompt); + /* Open an input window */ win = xcb_generate_id(conn); xcb_create_window( diff --git a/i3-migrate-config-to-v4 b/i3-migrate-config-to-v4 index c8ff41c9..ae5bf4de 100755 --- a/i3-migrate-config-to-v4 +++ b/i3-migrate-config-to-v4 @@ -202,7 +202,7 @@ sub convert_command { # simple replacements my @replace = ( qr/^s/ => 'layout stacking', - qr/^d/ => 'layout default', + qr/^d/ => 'layout toggle split', qr/^T/ => 'layout tabbed', qr/^f($|[^go])/ => 'fullscreen', qr/^fg/ => 'fullscreen global', diff --git a/i3-msg/Makefile b/i3-msg/Makefile index 617df932..fedb31e7 100644 --- a/i3-msg/Makefile +++ b/i3-msg/Makefile @@ -1,32 +1,10 @@ -# Default value so one can compile i3-msg standalone -TOPDIR=.. +all: + $(MAKE) -C .. i3-msg/i3-msg -include $(TOPDIR)/common.mk - -CFLAGS += -I$(TOPDIR)/include - -# Depend on the object files of all source-files in src/*.c and on all header files -FILES=$(patsubst %.c,%.o,$(wildcard *.c)) -HEADERS=$(wildcard *.h) - -# Depend on the specific file (.c for each .o) and on all headers -%.o: %.c ${HEADERS} - echo "[i3-msg] CC $<" - $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< - -all: i3-msg - -i3-msg: ${FILES} - echo "[i3-msg] LINK i3-msg" - $(CC) $(LDFLAGS) -o i3-msg ${FILES} $(LIBS) - -install: all - echo "[i3-msg] INSTALL" - $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin - $(INSTALL) -m 0755 i3-msg $(DESTDIR)$(PREFIX)/bin/ +install: + $(MAKE) -C .. install-i3-msg clean: - rm -f *.o + $(MAKE) -C .. clean-i3-msg -distclean: clean - rm -f i3-msg +.PHONY: all install clean diff --git a/i3-msg/i3-msg.mk b/i3-msg/i3-msg.mk new file mode 100644 index 00000000..fef9581d --- /dev/null +++ b/i3-msg/i3-msg.mk @@ -0,0 +1,28 @@ +ALL_TARGETS += i3-msg/i3-msg +INSTALL_TARGETS += install-i3-msg +CLEAN_TARGETS += clean-i3-msg + +i3_msg_SOURCES := $(wildcard i3-msg/*.c) +i3_msg_HEADERS := $(wildcard i3-msg/*.h) +i3_msg_CFLAGS = $(XCB_CFLAGS) $(PANGO_CFLAGS) +i3_msg_LIBS = $(XCB_LIBS) + +i3_msg_OBJECTS := $(i3_msg_SOURCES:.c=.o) + + +i3-msg/%.o: i3-msg/%.c $(i3_msg_HEADERS) + echo "[i3-msg] CC $<" + $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_msg_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $< + +i3-msg/i3-msg: libi3.a $(i3_msg_OBJECTS) + echo "[i3-msg] Link i3-msg" + $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(LIBS) $(i3_msg_LIBS) + +install-i3-msg: i3-msg/i3-msg + echo "[i3-msg] Install" + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m 0755 i3-msg/i3-msg $(DESTDIR)$(PREFIX)/bin/ + +clean-i3-msg: + echo "[i3-msg] Clean" + rm -f $(i3_msg_OBJECTS) i3-msg/i3-msg diff --git a/i3-msg/main.c b/i3-msg/main.c index ccf6e10f..a04e6690 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) * * i3-msg/main.c: Utility which sends messages to a running i3-instance using * IPC via UNIX domain sockets. @@ -73,9 +73,11 @@ int main(int argc, char *argv[]) { message_type = I3_IPC_MESSAGE_TYPE_GET_MARKS; else if (strcasecmp(optarg, "get_bar_config") == 0) message_type = I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG; + else if (strcasecmp(optarg, "get_version") == 0) + message_type = I3_IPC_MESSAGE_TYPE_GET_VERSION; else { printf("Unknown message type\n"); - printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config\n"); + printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_version\n"); exit(EXIT_FAILURE); } } else if (o == 'q') { diff --git a/i3-nagbar/Makefile b/i3-nagbar/Makefile index 05a5b911..12748e21 100644 --- a/i3-nagbar/Makefile +++ b/i3-nagbar/Makefile @@ -1,35 +1,10 @@ -# Default value so one can compile i3-nagbar standalone -TOPDIR=.. +all: + $(MAKE) -C .. i3-nagbar/i3-nagbar -include $(TOPDIR)/common.mk - -CPPFLAGS += -I$(TOPDIR)/include - -# Depend on the object files of all source-files in src/*.c and on all header files -FILES=$(patsubst %.c,%.o,$(wildcard *.c)) -HEADERS=$(wildcard *.h) - -# Depend on the specific file (.c for each .o) and on all headers -%.o: %.c ${HEADERS} - echo "[i3-nagbar] CC $<" - $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< - -all: i3-nagbar - -i3-nagbar: $(TOPDIR)/libi3/libi3.a ${FILES} - echo "[i3-nagbar] LINK i3-nagbar" - $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS) - -$(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c - $(MAKE) -C $(TOPDIR)/libi3 - -install: all - echo "[i3-nagbar] INSTALL" - $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin - $(INSTALL) -m 0755 i3-nagbar $(DESTDIR)$(PREFIX)/bin/ +install: + $(MAKE) -C .. install-i3-nagbar clean: - rm -f *.o + $(MAKE) -C .. clean-i3-nagbar -distclean: clean - rm -f i3-nagbar +.PHONY: all install clean diff --git a/i3-nagbar/i3-nagbar.mk b/i3-nagbar/i3-nagbar.mk new file mode 100644 index 00000000..e54aa654 --- /dev/null +++ b/i3-nagbar/i3-nagbar.mk @@ -0,0 +1,28 @@ +ALL_TARGETS += i3-nagbar/i3-nagbar +INSTALL_TARGETS += install-i3-nagbar +CLEAN_TARGETS += clean-i3-nagbar + +i3_nagbar_SOURCES := $(wildcard i3-nagbar/*.c) +i3_nagbar_HEADERS := $(wildcard i3-nagbar/*.h) +i3_nagbar_CFLAGS = $(XCB_CFLAGS) $(PANGO_CFLAGS) +i3_nagbar_LIBS = $(XCB_LIBS) $(PANGO_LIBS) + +i3_nagbar_OBJECTS := $(i3_nagbar_SOURCES:.c=.o) + + +i3-nagbar/%.o: i3-nagbar/%.c $(i3_nagbar_HEADERS) + echo "[i3-nagbar] CC $<" + $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_nagbar_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $< + +i3-nagbar/i3-nagbar: libi3.a $(i3_nagbar_OBJECTS) + echo "[i3-nagbar] Link i3-nagbar" + $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(LIBS) $(i3_nagbar_LIBS) + +install-i3-nagbar: i3-nagbar/i3-nagbar + echo "[i3-nagbar] Install" + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m 0755 i3-nagbar/i3-nagbar $(DESTDIR)$(PREFIX)/bin/ + +clean-i3-nagbar: + echo "[i3-nagbar] Clean" + rm -f $(i3_nagbar_OBJECTS) i3-nagbar/i3-nagbar diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index 1dbd7736..7aee191c 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -30,7 +30,7 @@ #include "i3-nagbar.h" typedef struct { - char *label; + i3String *label; char *action; int16_t x; uint16_t width; @@ -41,7 +41,7 @@ static xcb_pixmap_t pixmap; static xcb_gcontext_t pixmap_gc; static xcb_rectangle_t rect = { 0, 0, 600, 20 }; static i3Font font; -static char *prompt; +static i3String *prompt; static button_t *buttons; static int buttoncnt; @@ -54,6 +54,27 @@ static uint32_t color_text; /* color of the text */ xcb_window_t root; xcb_connection_t *conn; +xcb_screen_t *root_screen; + +/* + * Having verboselog() and errorlog() is necessary when using libi3. + * + */ +void verboselog(char *fmt, ...) { + va_list args; + + va_start(args, fmt); + vfprintf(stdout, fmt, args); + va_end(args); +} + +void errorlog(char *fmt, ...) { + va_list args; + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} /* * Starts the given application by passing it through a shell. We use double fork @@ -132,7 +153,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { /* restore font color */ set_font_colors(pixmap_gc, color_text, color_background); - draw_text(prompt, strlen(prompt), false, pixmap, pixmap_gc, + draw_text(prompt, pixmap, pixmap_gc, 4 + 4, 4 + 4, rect.width - 4 - 4); /* render close button */ @@ -159,7 +180,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { values[0] = 1; set_font_colors(pixmap_gc, color_text, color_button_background); - draw_text("X", 1, false, pixmap, pixmap_gc, y - w - line_width + w / 2 - 4, + draw_text_ascii("X", pixmap, pixmap_gc, y - w - line_width + w / 2 - 4, 4 + 4 - 1, rect.width - y + w + line_width - w / 2 + 4); y -= w; @@ -190,7 +211,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { values[0] = color_text; values[1] = color_button_background; set_font_colors(pixmap_gc, color_text, color_button_background); - draw_text(buttons[c].label, strlen(buttons[c].label), false, pixmap, pixmap_gc, + draw_text(buttons[c].label, pixmap, pixmap_gc, y - w - line_width + 6, 4 + 3, rect.width - y + w + line_width - 6); y -= w; @@ -216,7 +237,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { } int main(int argc, char *argv[]) { - char *pattern = strdup("-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"); + char *pattern = sstrdup("-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"); int o, option_index = 0; enum { TYPE_ERROR = 0, TYPE_WARNING = 1 } bar_type = TYPE_ERROR; @@ -232,7 +253,7 @@ int main(int argc, char *argv[]) { char *options_string = "b:f:m:t:vh"; - prompt = strdup("Please do not run this program."); + prompt = i3string_from_utf8("Please do not run this program."); while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { switch (o) { @@ -241,11 +262,11 @@ int main(int argc, char *argv[]) { return 0; case 'f': FREE(pattern); - pattern = strdup(optarg); + pattern = sstrdup(optarg); break; case 'm': - FREE(prompt); - prompt = strdup(optarg); + i3string_free(prompt); + prompt = i3string_from_utf8(optarg); break; case 't': bar_type = (strcasecmp(optarg, "warning") == 0 ? TYPE_WARNING : TYPE_ERROR); @@ -256,10 +277,10 @@ int main(int argc, char *argv[]) { return 0; case 'b': buttons = realloc(buttons, sizeof(button_t) * (buttoncnt + 1)); - buttons[buttoncnt].label = optarg; + buttons[buttoncnt].label = i3string_from_utf8(optarg); buttons[buttoncnt].action = argv[optind]; printf("button with label *%s* and action *%s*\n", - buttons[buttoncnt].label, + i3string_as_utf8(buttons[buttoncnt].label), buttons[buttoncnt].action); buttoncnt++; printf("now %d buttons\n", buttoncnt); @@ -280,7 +301,7 @@ int main(int argc, char *argv[]) { #include "atoms.xmacro" #undef xmacro - xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); + root_screen = xcb_aux_get_screen(conn, screens); root = root_screen->root; if (bar_type == TYPE_ERROR) { @@ -432,5 +453,7 @@ int main(int argc, char *argv[]) { free(event); } + FREE(pattern); + return 0; } diff --git a/i3-sensible-terminal b/i3-sensible-terminal index a9975740..fddefae1 100755 --- a/i3-sensible-terminal +++ b/i3-sensible-terminal @@ -8,7 +8,7 @@ # Distributions/packagers should enhance this script with a # distribution-specific mechanism to find the preferred terminal emulator. On # Debian, there is the x-terminal-emulator symlink for example. -for terminal in $TERMINAL urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm; do +for terminal in $TERMINAL urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal; do if which $terminal > /dev/null 2>&1; then exec $terminal "$@" fi diff --git a/i3.config b/i3.config index 1a457fca..e45b31ba 100644 --- a/i3.config +++ b/i3.config @@ -9,8 +9,14 @@ # layout, use the i3-config-wizard # -# font for window titles. ISO 10646 = Unicode +# Font for window titles. Will also be used by the bar unless a different font +# is used in the bar {} block below. ISO 10646 = Unicode font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 +# The font above is very space-efficient, that is, it looks good, sharp and +# clear in small sizes. However, if you need a lot of unicode glyphs or +# right-to-left text rendering, you should instead use pango for rendering and +# chose an xft font, such as: +# font xft:DejaVu Sans Mono 10 # use Mouse+Mod1 to drag floating windows to their wanted position floating_modifier Mod1 @@ -57,10 +63,10 @@ bindsym Mod1+v split v # enter fullscreen mode for the focused container bindsym Mod1+f fullscreen -# change container layout (stacked, tabbed, default) +# change container layout (stacked, tabbed, toggle split) bindsym Mod1+s layout stacking bindsym Mod1+w layout tabbed -bindsym Mod1+e layout default +bindsym Mod1+e layout toggle split # toggle tiling / floating bindsym Mod1+Shift+space floating toggle @@ -103,7 +109,7 @@ bindsym Mod1+Shift+c reload # restart i3 inplace (preserves your layout/session, can be used to upgrade i3) bindsym Mod1+Shift+r restart # exit i3 (logs you out of your X session) -bindsym Mod1+Shift+e exit +bindsym Mod1+Shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'" # resize window (you can also use the mouse for that) mode "resize" { diff --git a/i3.config.keycodes b/i3.config.keycodes index e360fff9..162660d3 100644 --- a/i3.config.keycodes +++ b/i3.config.keycodes @@ -10,8 +10,14 @@ set $mod Mod1 -# font for window titles. ISO 10646 = Unicode +# Font for window titles. Will also be used by the bar unless a different font +# is used in the bar {} block below. ISO 10646 = Unicode font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 +# The font above is very space-efficient, that is, it looks good, sharp and +# clear in small sizes. However, if you need a lot of unicode glyphs or +# right-to-left text rendering, you should instead use pango for rendering and +# chose an xft font, such as: +# font xft:DejaVu Sans Mono 10 # Use Mouse+$mod to drag floating windows to their wanted position floating_modifier $mod @@ -58,10 +64,10 @@ bindcode $mod+55 split v # enter fullscreen mode for the focused container bindcode $mod+41 fullscreen -# change container layout (stacked, tabbed, default) +# change container layout (stacked, tabbed, toggle split) bindcode $mod+39 layout stacking bindcode $mod+25 layout tabbed -bindcode $mod+26 layout default +bindcode $mod+26 layout toggle split # toggle tiling / floating bindcode $mod+Shift+65 floating toggle @@ -104,7 +110,7 @@ bindcode $mod+Shift+54 reload # restart i3 inplace (preserves your layout/session, can be used to upgrade i3) bindcode $mod+Shift+27 restart # exit i3 (logs you out of your X session) -bindcode $mod+Shift+26 exit +bindcode $mod+Shift+26 exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'" # resize window (you can also use the mouse for that) mode "resize" { diff --git a/i3bar/Makefile b/i3bar/Makefile index 79d0e7cd..cd23cd81 100644 --- a/i3bar/Makefile +++ b/i3bar/Makefile @@ -1,42 +1,10 @@ -TOPDIR=.. +all: + $(MAKE) -C .. i3bar/i3bar -include $(TOPDIR)/common.mk - -FILES:=$(wildcard src/*.c) -FILES:=$(FILES:.c=.o) -HEADERS:=$(wildcard include/*.h) - -CPPFLAGS += -I$(TOPDIR)/include - -all: i3bar doc - -i3bar: $(TOPDIR)/libi3/libi3.a ${FILES} - echo "[i3bar] LINK" - $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS) - -$(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c - $(MAKE) -C $(TOPDIR)/libi3 - -doc: - echo "" - echo "[i3bar] SUBDIR doc" - $(MAKE) -C doc - -src/%.o: src/%.c ${HEADERS} - echo "[i3bar] CC $<" - $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< - -install: all - echo "[i3bar] INSTALL" - $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin - $(INSTALL) -m 0755 i3bar $(DESTDIR)$(PREFIX)/bin +install: + $(MAKE) -C .. install-i3bar clean: - rm -f src/*.o - $(MAKE) -C doc clean - -distclean: clean - rm -f i3bar - $(MAKE) -C doc distclean + $(MAKE) -C .. clean-i3bar -.PHONY: install clean distclean doc +.PHONY: all install clean diff --git a/i3bar/doc/Makefile b/i3bar/doc/Makefile deleted file mode 100644 index 69566750..00000000 --- a/i3bar/doc/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -all: i3bar.1 - -i3bar.1: i3bar.man - echo "A2X i3bar" - a2x --no-xmllint -f manpage i3bar.man -clean: - rm -f i3bar.xml i3bar.1 i3bar.html - -distclean: clean diff --git a/i3bar/doc/i3bar.man b/i3bar/doc/i3bar.man deleted file mode 100644 index dcf3022b..00000000 --- a/i3bar/doc/i3bar.man +++ /dev/null @@ -1,68 +0,0 @@ -i3bar(1) -======== -Axel Wagner -v4.1, October 2011 - -== NAME - -i3bar - xcb-based status- and workspace-bar - -== SYNOPSIS - -*i3bar* [*-s* 'sock_path'] [*-b* 'bar_id'] [*-v*] [*-h*] - -== WARNING - -i3bar will automatically be invoked by i3 for every 'bar' configuration block. - -Starting it manually is usually not what you want to do. - -You have been warned! - -== OPTIONS - -*-s, --socket* 'sock_path':: -Overwrites the path to the i3 IPC socket. - -*-b, --bar_id* 'bar_id':: -Specifies the bar ID for which to get the configuration from i3. - -*-v, --version*:: -Display version number and exit. - -*-h, --help*:: -Display a short help-message and exit - -== DESCRIPTION - -*i3bar* displays a bar at the bottom (or top) of your monitor(s) containing -workspace switching buttons and a statusline generated by i3status(1) or -similar. It is automatically invoked (and configured through) i3. - -i3bar does not support any color or other markups, so stdin should be plain -utf8, one line at a time. If you use *i3status*(1), you therefore should -specify 'output_format = none' in the general section of its config file. - -== ENVIRONMENT - -=== I3SOCK - -Used as a fallback for the i3 IPC socket path if neither the commandline -contains an argument nor the I3_SOCKET_PATH property is set on the X11 root -window. - -== EXAMPLES - -Nothing to see here, move along. As stated above, you should not run i3bar manually. - -Instead, see the i3 documentation, especially the User’s Guide. - -== SEE ALSO - -+i3status(1)+ or +conky(1)+ for programs generating a statusline. - -+dzen2(1)+ or +xmobar(1)+ for similar programs to i3bar. - -== AUTHORS - -Axel Wagner and contributors diff --git a/i3bar/i3bar.mk b/i3bar/i3bar.mk new file mode 100644 index 00000000..06780250 --- /dev/null +++ b/i3bar/i3bar.mk @@ -0,0 +1,28 @@ +ALL_TARGETS += i3bar/i3bar +INSTALL_TARGETS += install-i3bar +CLEAN_TARGETS += clean-i3bar + +i3bar_SOURCES := $(wildcard i3bar/src/*.c) +i3bar_HEADERS := $(wildcard i3bar/include/*.h) +i3bar_CFLAGS = $(XCB_CFLAGS) $(X11_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) +i3bar_LIBS = $(XCB_LIBS) $(X11_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) + +i3bar_OBJECTS := $(i3bar_SOURCES:.c=.o) + + +i3bar/src/%.o: i3bar/src/%.c $(i3bar_HEADERS) + echo "[i3bar] CC $<" + $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3bar_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -Ii3bar/include -c -o $@ $< + +i3bar/i3bar: libi3.a $(i3bar_OBJECTS) + echo "[i3bar] Link i3bar" + $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(LIBS) $(i3bar_LIBS) + +install-i3bar: i3bar/i3bar + echo "[i3bar] Install" + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m 0755 i3bar/i3bar $(DESTDIR)$(PREFIX)/bin/ + +clean-i3bar: + echo "[i3bar] Clean" + rm -f $(i3bar_OBJECTS) i3bar/i3bar diff --git a/i3bar/include/child.h b/i3bar/include/child.h index 24c7e460..c0b56a01 100644 --- a/i3bar/include/child.h +++ b/i3bar/include/child.h @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3bar - an xcb-based status- and ws-bar for i3 - * © 2010-2011 Axel Wagner and contributors (see also: LICENSE) + * © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * * child.c: Getting Input for the statusline * @@ -24,25 +24,25 @@ void start_child(char *command); * kill()s the child-process (if any). Called when exit()ing. * */ -void kill_child_at_exit(); +void kill_child_at_exit(void); /* * kill()s the child-process (if any) and closes and * free()s the stdin- and sigchild-watchers * */ -void kill_child(); +void kill_child(void); /* * Sends a SIGSTOP to the child-process (if existent) * */ -void stop_child(); +void stop_child(void); /* * Sends a SIGCONT to the child-process (if existent) * */ -void cont_child(); +void cont_child(void); #endif diff --git a/i3bar/include/common.h b/i3bar/include/common.h index 212b9dd1..6f8a7b2d 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -11,6 +11,7 @@ #include #include #include +#include "libi3.h" #include "queue.h" typedef struct rect_t rect; @@ -29,15 +30,10 @@ struct rect_t { /* This data structure represents one JSON dictionary, multiple of these make * up one status line. */ struct status_block { - char *full_text; + i3String *full_text; char *color; - /* full_text, but converted to UCS-2. This variable is only temporarily - * used in refresh_statusline(). */ - xcb_char2b_t *ucs2_full_text; - size_t glyph_count_full_text; - /* The amount of pixels necessary to render this block. This variable is * only temporarily used in refresh_statusline(). */ uint32_t width; @@ -56,5 +52,6 @@ TAILQ_HEAD(statusline_head, status_block) statusline_head; #include "xcb.h" #include "config.h" #include "libi3.h" +#include "determine_json_version.h" #endif diff --git a/i3bar/include/determine_json_version.h b/i3bar/include/determine_json_version.h new file mode 100644 index 00000000..52c6f5d2 --- /dev/null +++ b/i3bar/include/determine_json_version.h @@ -0,0 +1,29 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3bar - an xcb-based status- and ws-bar for i3 + * © 2010-2012 Axel Wagner and contributors (see also: LICENSE) + * + * determine_json_version.c: Determines the JSON protocol version based on the + * first line of input from a child program. + * + */ +#ifndef DETERMINE_JSON_VERSION_H_ +#define DETERMINE_JSON_VERSION_H_ + +#include + +/* + * Determines the JSON i3bar protocol version from the given buffer. In case + * the buffer does not contain valid JSON, or no version field is found, this + * function returns -1. The amount of bytes consumed by parsing the header is + * returned in *consumed (if non-NULL). + * + * The return type is an int32_t to avoid machines with different sizes of + * 'int' to allow different values here. It’s highly unlikely we ever exceed + * even an int8_t, but still… + * + */ +int32_t determine_json_version(const unsigned char *buffer, int length, unsigned int *consumed); + +#endif diff --git a/i3bar/include/ipc.h b/i3bar/include/ipc.h index a0c49704..f20d45f0 100644 --- a/i3bar/include/ipc.h +++ b/i3bar/include/ipc.h @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3bar - an xcb-based status- and ws-bar for i3 - * © 2010-2011 Axel Wagner and contributors (see also: LICENSE) + * © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * * ipc.c: Communicating with i3 * @@ -23,7 +23,7 @@ int init_connection(const char *socket_path); * Destroy the connection to i3. * */ -void destroy_connection(); +void destroy_connection(void); /* * Sends a Message to i3. @@ -36,6 +36,6 @@ int i3_send_msg(uint32_t type, const char* payload); * Subscribe to all the i3-events, we need * */ -void subscribe_events(); +void subscribe_events(void); #endif diff --git a/i3bar/include/outputs.h b/i3bar/include/outputs.h index f9ddd54b..ad249786 100644 --- a/i3bar/include/outputs.h +++ b/i3bar/include/outputs.h @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3bar - an xcb-based status- and ws-bar for i3 - * © 2010-2011 Axel Wagner and contributors (see also: LICENSE) + * © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * * outputs.c: Maintaining the output-list * @@ -29,7 +29,7 @@ void parse_outputs_json(char* json); * Initiate the output-list * */ -void init_outputs(); +void init_outputs(void); /* * Returns the output with the given name diff --git a/i3bar/include/util.h b/i3bar/include/util.h index eb05ed08..43c56c58 100644 --- a/i3bar/include/util.h +++ b/i3bar/include/util.h @@ -11,7 +11,9 @@ #include "queue.h" /* Get the maximum/minimum of x and y */ +#undef MAX #define MAX(x,y) ((x) > (y) ? (x) : (y)) +#undef MIN #define MIN(x,y) ((x) < (y) ? (x) : (y)) /* Securely free p */ @@ -51,6 +53,11 @@ } \ } while(0) +/* We will include libi3.h which define its own version of ELOG. + * We want *our* version, so we undef the libi3 one. */ +#if defined(ELOG) +#undef ELOG +#endif #define ELOG(fmt, ...) do { \ fprintf(stderr, "[%s:%d] ERROR: " fmt, __FILE__, __LINE__, ##__VA_ARGS__); \ } while(0) diff --git a/i3bar/include/workspaces.h b/i3bar/include/workspaces.h index dfd93d9b..5fe1ba1e 100644 --- a/i3bar/include/workspaces.h +++ b/i3bar/include/workspaces.h @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3bar - an xcb-based status- and ws-bar for i3 - * © 2010-2011 Axel Wagner and contributors (see also: LICENSE) + * © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * * workspaces.c: Maintaining the workspace-lists * @@ -28,13 +28,11 @@ void parse_workspaces_json(char *json); * free() all workspace data-structures * */ -void free_workspaces(); +void free_workspaces(void); struct i3_ws { int num; /* The internal number of the ws */ - char *name; /* The name (in utf8) of the ws */ - xcb_char2b_t *ucs2_name; /* The name (in ucs2) of the ws */ - int name_glyphs; /* The length (in glyphs) of the name */ + i3String *name; /* The name of the ws */ int name_width; /* The rendered width of the name */ bool visible; /* If the ws is currently visible on an output */ bool focused; /* If the ws is currently focused */ diff --git a/i3bar/include/xcb.h b/i3bar/include/xcb.h index 9ed21496..6c7bc567 100644 --- a/i3bar/include/xcb.h +++ b/i3bar/include/xcb.h @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3bar - an xcb-based status- and ws-bar for i3 - * © 2010-2011 Axel Wagner and contributors (see also: LICENSE) + * © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * * xcb.c: Communicating with X * @@ -69,13 +69,13 @@ void init_colors(const struct xcb_color_strings_t *colors); * Called once, before the program terminates. * */ -void clean_xcb(); +void clean_xcb(void); /* * Get the earlier requested atoms and save them in the prepared data-structure * */ -void get_atoms(); +void get_atoms(void); /* * Reparents all tray clients of the specified output to the root window. This @@ -98,24 +98,24 @@ void destroy_window(i3_output *output); * Reallocate the statusline-buffer * */ -void realloc_sl_buffer(); +void realloc_sl_buffer(void); /* * Reconfigure all bars and create new for newly activated outputs * */ -void reconfig_windows(); +void reconfig_windows(void); /* * Render the bars, with buttons and statusline * */ -void draw_bars(); +void draw_bars(void); /* * Redraw the bars, i.e. simply copy the buffer to the barwindow * */ -void redraw_bars(); +void redraw_bars(void); #endif diff --git a/i3bar/src/child.c b/i3bar/src/child.c index 12782caf..058ddb7a 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3bar - an xcb-based status- and ws-bar for i3 - * © 2010-2011 Axel Wagner and contributors (see also: LICENSE) + * © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * * child.c: Getting Input for the statusline * @@ -56,7 +56,7 @@ char *statusline_buffer = NULL; * Stop and free() the stdin- and sigchild-watchers * */ -void cleanup() { +void cleanup(void) { if (stdin_io != NULL) { ev_io_stop(main_loop, stdin_io); FREE(stdin_io); @@ -80,7 +80,7 @@ static int stdin_start_array(void *context) { struct status_block *first; while (!TAILQ_EMPTY(&statusline_head)) { first = TAILQ_FIRST(&statusline_head); - FREE(first->full_text); + I3STRING_FREE(first->full_text); FREE(first->color); TAILQ_REMOVE(&statusline_head, first, blocks); free(first); @@ -116,7 +116,7 @@ static int stdin_string(void *context, const unsigned char *val, unsigned int le #endif parser_ctx *ctx = context; if (strcasecmp(ctx->last_map_key, "full_text") == 0) { - sasprintf(&(ctx->block.full_text), "%.*s", len, val); + ctx->block.full_text = i3string_from_utf8_with_length((const char *)val, len); } if (strcasecmp(ctx->last_map_key, "color") == 0) { sasprintf(&(ctx->block.color), "%.*s", len, val); @@ -131,7 +131,7 @@ static int stdin_end_map(void *context) { /* Ensure we have a full_text set, so that when it is missing (or null), * i3bar doesn’t crash and the user gets an annoying message. */ if (!new_block->full_text) - new_block->full_text = sstrdup("SPEC VIOLATION (null)"); + new_block->full_text = i3string_from_utf8("SPEC VIOLATION (null)"); TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks); return 1; } @@ -140,7 +140,7 @@ static int stdin_end_array(void *context) { DLOG("dumping statusline:\n"); struct status_block *current; TAILQ_FOREACH(current, &statusline_head, blocks) { - DLOG("full_text = %s\n", current->full_text); + DLOG("full_text = %s\n", i3string_as_utf8(current->full_text)); DLOG("color = %s\n", current->color); } DLOG("end of dump\n"); @@ -192,19 +192,18 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { if (first_line) { DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer); /* Detect whether this is JSON or plain text. */ - plaintext = (strncasecmp((char*)buffer, "{\"version\":", strlen("{\"version\":")) != 0); + unsigned int consumed = 0; + /* At the moment, we don’t care for the version. This might change + * in the future, but for now, we just discard it. */ + plaintext = (determine_json_version(buffer, buffer_len, &consumed) == -1); if (plaintext) { /* In case of plaintext, we just add a single block and change its * full_text pointer later. */ struct status_block *new_block = scalloc(sizeof(struct status_block)); TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks); } else { - /* At the moment, we don’t care for the version. This might change - * in the future, but for now, we just discard it. */ - while (*json_input != '\n' && *json_input != '\0') { - json_input++; - rec--; - } + json_input += consumed; + rec -= consumed; } first_line = false; } @@ -218,18 +217,18 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { fprintf(stderr, "[i3bar] Could not parse JSON input (code %d): %.*s\n", status, rec, json_input); } - free(buffer); } else { struct status_block *first = TAILQ_FIRST(&statusline_head); /* Clear the old buffer if any. */ - FREE(first->full_text); + I3STRING_FREE(first->full_text); /* Remove the trailing newline and terminate the string at the same * time. */ if (buffer[rec-1] == '\n' || buffer[rec-1] == '\r') buffer[rec-1] = '\0'; else buffer[rec] = '\0'; - first->full_text = (char*)buffer; + first->full_text = i3string_from_utf8((const char *)buffer); } + free(buffer); draw_bars(); } @@ -328,7 +327,7 @@ void start_child(char *command) { * kill()s the child-process (if any). Called when exit()ing. * */ -void kill_child_at_exit() { +void kill_child_at_exit(void) { if (child_pid != 0) { kill(child_pid, SIGCONT); kill(child_pid, SIGTERM); @@ -340,7 +339,7 @@ void kill_child_at_exit() { * free()s the stdin- and sigchild-watchers * */ -void kill_child() { +void kill_child(void) { if (child_pid != 0) { kill(child_pid, SIGCONT); kill(child_pid, SIGTERM); @@ -355,7 +354,7 @@ void kill_child() { * Sends a SIGSTOP to the child-process (if existent) * */ -void stop_child() { +void stop_child(void) { if (child_pid != 0) { kill(child_pid, SIGSTOP); } @@ -365,7 +364,7 @@ void stop_child() { * Sends a SIGCONT to the child-process (if existent) * */ -void cont_child() { +void cont_child(void) { if (child_pid != 0) { kill(child_pid, SIGCONT); } diff --git a/i3bar/src/determine_json_version.c b/i3bar/src/determine_json_version.c new file mode 100644 index 00000000..abd43038 --- /dev/null +++ b/i3bar/src/determine_json_version.c @@ -0,0 +1,104 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3bar - an xcb-based status- and ws-bar for i3 + * © 2010-2012 Axel Wagner and contributors (see also: LICENSE) + * + * determine_json_version.c: Determines the JSON protocol version based on the + * first line of input from a child program. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool version_key; +static int32_t version_number; + +#if YAJL_MAJOR >= 2 +static int version_integer(void *ctx, long long val) { +#else +static int version_integer(void *ctx, long val) { +#endif + if (version_key) + version_number = (uint32_t)val; + return 1; +} + +#if YAJL_MAJOR >= 2 +static int version_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) { +#else +static int version_map_key(void *ctx, const unsigned char *stringval, unsigned int stringlen) { +#endif + version_key = (stringlen == strlen("version") && + strncmp((const char*)stringval, "version", strlen("version")) == 0); + return 1; +} + +static yajl_callbacks version_callbacks = { + NULL, /* null */ + NULL, /* boolean */ + &version_integer, + NULL, /* double */ + NULL, /* number */ + NULL, /* string */ + NULL, /* start_map */ + &version_map_key, + NULL, /* end_map */ + NULL, /* start_array */ + NULL /* end_array */ +}; + +/* + * Determines the JSON i3bar protocol version from the given buffer. In case + * the buffer does not contain valid JSON, or no version field is found, this + * function returns -1. The amount of bytes consumed by parsing the header is + * returned in *consumed (if non-NULL). + * + * The return type is an int32_t to avoid machines with different sizes of + * 'int' to allow different values here. It’s highly unlikely we ever exceed + * even an int8_t, but still… + * + */ +int32_t determine_json_version(const unsigned char *buffer, int length, unsigned int *consumed) { +#if YAJL_MAJOR >= 2 + yajl_handle handle = yajl_alloc(&version_callbacks, NULL, NULL); + /* Allow trailing garbage. yajl 1 always behaves that way anyways, but for + * yajl 2, we need to be explicit. */ + yajl_config(handle, yajl_allow_trailing_garbage, 1); +#else + yajl_parser_config parse_conf = { 0, 0 }; + + yajl_handle handle = yajl_alloc(&version_callbacks, &parse_conf, NULL, NULL); +#endif + + version_key = false; + version_number = -1; + + yajl_status state = yajl_parse(handle, buffer, length); + if (state != yajl_status_ok) { + version_number = -1; + if (consumed != NULL) + *consumed = 0; + } else { + if (consumed != NULL) + *consumed = yajl_get_bytes_consumed(handle); + } + + yajl_free(handle); + + return version_number; +} diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index 41b8e151..2cc80cf7 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3bar - an xcb-based status- and ws-bar for i3 - * © 2010-2011 Axel Wagner and contributors (see also: LICENSE) + * © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * * ipc.c: Communicating with i3 * @@ -286,7 +286,7 @@ int init_connection(const char *socket_path) { /* * Destroy the connection to i3. */ -void destroy_connection() { +void destroy_connection(void) { close(i3_connection->fd); ev_io_stop(main_loop, i3_connection); } @@ -295,7 +295,7 @@ void destroy_connection() { * Subscribe to all the i3-events, we need * */ -void subscribe_events() { +void subscribe_events(void) { if (config.disable_ws) { i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\" ]"); } else { diff --git a/i3bar/src/main.c b/i3bar/src/main.c index e648e00e..ea605647 100644 --- a/i3bar/src/main.c +++ b/i3bar/src/main.c @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3bar - an xcb-based status- and ws-bar for i3 - * © 2010-2011 Axel Wagner and contributors (see also: LICENSE) + * © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * */ #include @@ -17,6 +17,26 @@ #include "common.h" +/* + * Having verboselog() and errorlog() is necessary when using libi3. + * + */ +void verboselog(char *fmt, ...) { + va_list args; + + va_start(args, fmt); + vfprintf(stdout, fmt, args); + va_end(args); +} + +void errorlog(char *fmt, ...) { + va_list args; + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} + /* * Glob path, i.e. expand ~ * diff --git a/i3bar/src/outputs.c b/i3bar/src/outputs.c index eabf4d7b..db986702 100644 --- a/i3bar/src/outputs.c +++ b/i3bar/src/outputs.c @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3bar - an xcb-based status- and ws-bar for i3 - * © 2010-2011 Axel Wagner and contributors (see also: LICENSE) + * © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * * outputs.c: Maintaining the output-list * @@ -266,7 +266,7 @@ yajl_callbacks outputs_callbacks = { * Initiate the output-list * */ -void init_outputs() { +void init_outputs(void) { outputs = smalloc(sizeof(struct outputs_head)); SLIST_INIT(outputs); } diff --git a/i3bar/src/workspaces.c b/i3bar/src/workspaces.c index 5df1899f..5e01b98d 100644 --- a/i3bar/src/workspaces.c +++ b/i3bar/src/workspaces.c @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3bar - an xcb-based status- and ws-bar for i3 - * © 2010-2011 Axel Wagner and contributors (see also: LICENSE) + * © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * * workspaces.c: Maintaining the workspace-lists * @@ -114,23 +114,16 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, unsigne if (!strcmp(params->cur_key, "name")) { /* Save the name */ - params->workspaces_walk->name = smalloc(sizeof(const unsigned char) * (len + 1)); - strncpy(params->workspaces_walk->name, (const char*) val, len); - params->workspaces_walk->name[len] = '\0'; - - /* Convert the name to ucs2, save its length in glyphs and calculate its rendered width */ - size_t ucs2_len; - xcb_char2b_t *ucs2_name = (xcb_char2b_t*) convert_utf8_to_ucs2(params->workspaces_walk->name, &ucs2_len); - params->workspaces_walk->ucs2_name = ucs2_name; - params->workspaces_walk->name_glyphs = ucs2_len; + params->workspaces_walk->name = i3string_from_utf8_with_length((const char *)val, len); + + /* Save its rendered width */ params->workspaces_walk->name_width = - predict_text_width((char *)params->workspaces_walk->ucs2_name, - params->workspaces_walk->name_glyphs, true); + predict_text_width(params->workspaces_walk->name); - DLOG("Got Workspace %s, name_width: %d, glyphs: %d\n", - params->workspaces_walk->name, + DLOG("Got Workspace %s, name_width: %d, glyphs: %zu\n", + i3string_as_utf8(params->workspaces_walk->name), params->workspaces_walk->name_width, - params->workspaces_walk->name_glyphs); + i3string_get_num_glyphs(params->workspaces_walk->name)); FREE(params->cur_key); return 1; @@ -269,7 +262,7 @@ void parse_workspaces_json(char *json) { * free() all workspace data-structures. Does not free() the heads of the tailqueues. * */ -void free_workspaces() { +void free_workspaces(void) { i3_output *outputs_walk; if (outputs == NULL) { return; @@ -279,8 +272,7 @@ void free_workspaces() { SLIST_FOREACH(outputs_walk, outputs, slist) { if (outputs_walk->workspaces != NULL && !TAILQ_EMPTY(outputs_walk->workspaces)) { TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) { - FREE(ws_walk->name); - FREE(ws_walk->ucs2_name); + I3STRING_FREE(ws_walk->name); } FREE_TAILQ(outputs_walk->workspaces, i3_ws); } diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 289d7d9e..861925b9 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3bar - an xcb-based status- and ws-bar for i3 - * © 2010-2011 Axel Wagner and contributors (see also: LICENSE) + * © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * * xcb.c: Communicating with X * @@ -47,7 +47,7 @@ xcb_atom_t atoms[NUM_ATOMS]; /* Variables, that are the same for all functions at all times */ xcb_connection_t *xcb_connection; int screen; -xcb_screen_t *xcb_screen; +xcb_screen_t *root_screen; xcb_window_t xcb_root; /* This is needed for integration with libi3 */ @@ -108,20 +108,18 @@ int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) { * Redraws the statusline to the buffer * */ -void refresh_statusline() { +void refresh_statusline(void) { struct status_block *block; uint32_t old_statusline_width = statusline_width; statusline_width = 0; - /* Convert all blocks from UTF-8 to UCS-2 and predict the text width (in - * pixels). */ + /* Predict the text width of all blocks (in pixels). */ TAILQ_FOREACH(block, &statusline_head, blocks) { - if (strlen(block->full_text) == 0) + if (i3string_get_num_bytes(block->full_text) == 0) continue; - block->ucs2_full_text = (xcb_char2b_t*)convert_utf8_to_ucs2(block->full_text, &(block->glyph_count_full_text)); - block->width = predict_text_width((char*)block->ucs2_full_text, block->glyph_count_full_text, true); + block->width = predict_text_width(block->full_text); /* If this is not the last block, add some pixels for a separator. */ if (TAILQ_NEXT(block, blocks) != NULL) block->width += 9; @@ -130,24 +128,23 @@ void refresh_statusline() { /* If the statusline is bigger than our screen we need to make sure that * the pixmap provides enough space, so re-allocate if the width grew */ - if (statusline_width > xcb_screen->width_in_pixels && + if (statusline_width > root_screen->width_in_pixels && statusline_width > old_statusline_width) realloc_sl_buffer(); /* Clear the statusline pixmap. */ - xcb_rectangle_t rect = { 0, 0, xcb_screen->width_in_pixels, font.height }; + xcb_rectangle_t rect = { 0, 0, root_screen->width_in_pixels, font.height }; xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_clear, 1, &rect); /* Draw the text of each block. */ uint32_t x = 0; TAILQ_FOREACH(block, &statusline_head, blocks) { - if (strlen(block->full_text) == 0) + if (i3string_get_num_bytes(block->full_text) == 0) continue; uint32_t colorpixel = (block->color ? get_colorpixel(block->color) : colors.bar_fg); set_font_colors(statusline_ctx, colorpixel, colors.bar_bg); - draw_text((char*)block->ucs2_full_text, block->glyph_count_full_text, - true, statusline_pm, statusline_ctx, x, 0, block->width); + draw_text(block->full_text, statusline_pm, statusline_ctx, x, 0, block->width); x += block->width; if (TAILQ_NEXT(block, blocks) != NULL) { @@ -157,8 +154,6 @@ void refresh_statusline() { statusline_ctx, 2, (xcb_point_t[]){ { x - 5, 2 }, { x - 5, font.height - 2 } }); } - - FREE(block->ucs2_full_text); } } @@ -166,7 +161,7 @@ void refresh_statusline() { * Hides all bars (unmaps them) * */ -void hide_bars() { +void hide_bars(void) { if (!config.hide_on_modifier) { return; } @@ -185,7 +180,7 @@ void hide_bars() { * Unhides all bars (maps them) * */ -void unhide_bars() { +void unhide_bars(void) { if (!config.hide_on_modifier) { return; } @@ -326,7 +321,8 @@ void handle_button(xcb_button_press_event_t *event) { * buffer, then we copy character by character. */ int num_quotes = 0; size_t namelen = 0; - for (char *walk = cur_ws->name; *walk != '\0'; walk++) { + const char *utf8_name = i3string_as_utf8(cur_ws->name); + for (const char *walk = utf8_name; *walk != '\0'; walk++) { if (*walk == '"') num_quotes++; /* While we’re looping through the name anyway, we can save one @@ -341,11 +337,11 @@ void handle_button(xcb_button_press_event_t *event) { for (inpos = 0, outpos = strlen("workspace \""); inpos < namelen; inpos++, outpos++) { - if (cur_ws->name[inpos] == '"') { + if (utf8_name[inpos] == '"') { buffer[outpos] = '\\'; outpos++; } - buffer[outpos] = cur_ws->name[inpos]; + buffer[outpos] = utf8_name[inpos]; } buffer[outpos] = '"'; i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, buffer); @@ -357,7 +353,7 @@ void handle_button(xcb_button_press_event_t *event) { * new tray client or removing an old one. * */ -static void configure_trayclients() { +static void configure_trayclients(void) { trayclient *trayclient; i3_output *output; SLIST_FOREACH(output, outputs, slist) { @@ -828,8 +824,8 @@ char *init_xcb_early() { #define ATOM_DO(name) atom_cookies[name] = xcb_intern_atom(xcb_connection, 0, strlen(#name), #name); #include "xcb_atoms.def" - xcb_screen = xcb_aux_get_screen(xcb_connection, screen); - xcb_root = xcb_screen->root; + root_screen = xcb_aux_get_screen(xcb_connection, screen); + xcb_root = root_screen->root; /* We draw the statusline to a seperate pixmap, because it looks the same on all bars and * this way, we can choose to crop it */ @@ -852,11 +848,11 @@ char *init_xcb_early() { statusline_pm = xcb_generate_id(xcb_connection); xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection, - xcb_screen->root_depth, + root_screen->root_depth, statusline_pm, xcb_root, - xcb_screen->width_in_pixels, - xcb_screen->height_in_pixels); + root_screen->width_in_pixels, + root_screen->height_in_pixels); /* The various Watchers to communicate with xcb */ @@ -970,7 +966,7 @@ void init_xcb_late(char *fontname) { * atom. Afterwards, tray clients will send ClientMessages to our window. * */ -void init_tray() { +void init_tray(void) { DLOG("Initializing system tray functionality\n"); /* request the tray manager atom for the X11 display we are running on */ char atomname[strlen("_NET_SYSTEM_TRAY_S") + 11]; @@ -984,14 +980,14 @@ void init_tray() { uint32_t selmask = XCB_CW_OVERRIDE_REDIRECT; uint32_t selval[] = { 1 }; xcb_create_window(xcb_connection, - xcb_screen->root_depth, + root_screen->root_depth, selwin, xcb_root, -1, -1, 1, 1, 1, XCB_WINDOW_CLASS_INPUT_OUTPUT, - xcb_screen->root_visual, + root_screen->root_visual, selmask, selval); @@ -1059,7 +1055,7 @@ void init_tray() { * Called once, before the program terminates. * */ -void clean_xcb() { +void clean_xcb(void) { i3_output *o_walk; free_workspaces(); SLIST_FOREACH(o_walk, outputs, slist) { @@ -1087,7 +1083,7 @@ void clean_xcb() { * Get the earlier requested atoms and save them in the prepared data structure * */ -void get_atoms() { +void get_atoms(void) { xcb_intern_atom_reply_t *reply; #define ATOM_DO(name) reply = xcb_intern_atom_reply(xcb_connection, atom_cookies[name], NULL); \ if (reply == NULL) { \ @@ -1149,17 +1145,17 @@ void destroy_window(i3_output *output) { * Reallocate the statusline-buffer * */ -void realloc_sl_buffer() { - DLOG("Re-allocating statusline-buffer, statusline_width = %d, xcb_screen->width_in_pixels = %d\n", - statusline_width, xcb_screen->width_in_pixels); +void realloc_sl_buffer(void) { + DLOG("Re-allocating statusline-buffer, statusline_width = %d, root_screen->width_in_pixels = %d\n", + statusline_width, root_screen->width_in_pixels); xcb_free_pixmap(xcb_connection, statusline_pm); statusline_pm = xcb_generate_id(xcb_connection); xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection, - xcb_screen->root_depth, + root_screen->root_depth, statusline_pm, xcb_root, - MAX(xcb_screen->width_in_pixels, statusline_width), - xcb_screen->height_in_pixels); + MAX(root_screen->width_in_pixels, statusline_width), + root_screen->height_in_pixels); uint32_t mask = XCB_GC_FOREGROUND; uint32_t vals[2] = { colors.bar_bg, colors.bar_bg }; @@ -1193,7 +1189,7 @@ void realloc_sl_buffer() { * Reconfigure all bars and create new bars for recently activated outputs * */ -void reconfig_windows() { +void reconfig_windows(void) { uint32_t mask; uint32_t values[5]; static bool tray_configured = false; @@ -1229,20 +1225,20 @@ void reconfig_windows() { values[2] |= XCB_EVENT_MASK_BUTTON_PRESS; } xcb_void_cookie_t win_cookie = xcb_create_window_checked(xcb_connection, - xcb_screen->root_depth, + root_screen->root_depth, walk->bar, xcb_root, walk->rect.x, walk->rect.y + walk->rect.h - font.height - 6, walk->rect.w, font.height + 6, 1, XCB_WINDOW_CLASS_INPUT_OUTPUT, - xcb_screen->root_visual, + root_screen->root_visual, mask, values); /* The double-buffer we use to render stuff off-screen */ xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection, - xcb_screen->root_depth, + root_screen->root_depth, walk->buffer, walk->bar, walk->rect.w, @@ -1382,7 +1378,7 @@ void reconfig_windows() { DLOG("Recreating buffer for output %s", walk->name); xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection, - xcb_screen->root_depth, + root_screen->root_depth, walk->buffer, walk->bar, walk->rect.w, @@ -1402,7 +1398,7 @@ void reconfig_windows() { * Render the bars, with buttons and statusline * */ -void draw_bars() { +void draw_bars(void) { DLOG("Drawing Bars...\n"); int i = 0; @@ -1464,8 +1460,11 @@ void draw_bars() { } i3_ws *ws_walk; + static char *last_urgent_ws = NULL; + bool has_urgent = false, walks_away = true; + TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) { - DLOG("Drawing Button for WS %s at x = %d, len = %d\n", ws_walk->name, i, ws_walk->name_width); + DLOG("Drawing Button for WS %s at x = %d, len = %d\n", i3string_as_utf8(ws_walk->name), i, ws_walk->name_width); uint32_t fg_color = colors.inactive_ws_fg; uint32_t bg_color = colors.inactive_ws_bg; uint32_t border_color = colors.inactive_ws_border; @@ -1478,13 +1477,20 @@ void draw_bars() { fg_color = colors.focus_ws_fg; bg_color = colors.focus_ws_bg; border_color = colors.focus_ws_border; + if (last_urgent_ws && strcmp(i3string_as_utf8(ws_walk->name), last_urgent_ws) == 0) + walks_away = false; } } if (ws_walk->urgent) { - DLOG("WS %s is urgent!\n", ws_walk->name); + DLOG("WS %s is urgent!\n", i3string_as_utf8(ws_walk->name)); fg_color = colors.urgent_ws_fg; bg_color = colors.urgent_ws_bg; border_color = colors.urgent_ws_border; + has_urgent = true; + if (!ws_walk->focused) { + FREE(last_urgent_ws); + last_urgent_ws = sstrdup(i3string_as_utf8(ws_walk->name)); + } /* The urgent-hint should get noticed, so we unhide the bars shortly */ unhide_bars(); } @@ -1512,11 +1518,15 @@ void draw_bars() { 1, &rect); set_font_colors(outputs_walk->bargc, fg_color, bg_color); - draw_text((char*)ws_walk->ucs2_name, ws_walk->name_glyphs, true, - outputs_walk->buffer, outputs_walk->bargc, i + 5, 2, ws_walk->name_width); + draw_text(ws_walk->name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 2, ws_walk->name_width); i += 10 + ws_walk->name_width + 1; } + if (!has_urgent && !mod_pressed && walks_away) { + FREE(last_urgent_ws); + hide_bars(); + } + i = 0; } @@ -1527,7 +1537,7 @@ void draw_bars() { * Redraw the bars, i.e. simply copy the buffer to the barwindow * */ -void redraw_bars() { +void redraw_bars(void) { i3_output *outputs_walk; SLIST_FOREACH(outputs_walk, outputs, slist) { if (!outputs_walk->active) { diff --git a/include/all.h b/include/all.h index 11eaaba4..b83b9f4e 100644 --- a/include/all.h +++ b/include/all.h @@ -54,6 +54,7 @@ #include "i3.h" #include "x.h" #include "click.h" +#include "key_press.h" #include "floating.h" #include "config.h" #include "handlers.h" @@ -79,5 +80,6 @@ #include "commands.h" #include "commands_parser.h" #include "fake_outputs.h" +#include "display_version.h" #endif diff --git a/include/atoms.xmacro b/include/atoms.xmacro index b907f41e..af60b966 100644 --- a/include/atoms.xmacro +++ b/include/atoms.xmacro @@ -27,3 +27,4 @@ xmacro(I3_SOCKET_PATH) xmacro(I3_CONFIG_PATH) xmacro(I3_SYNC) xmacro(I3_SHMLOG_PATH) +xmacro(I3_PID) diff --git a/include/commands.h b/include/commands.h index 85057d19..37ee98d9 100644 --- a/include/commands.h +++ b/include/commands.h @@ -200,11 +200,17 @@ void cmd_fullscreen(I3_CMD, char *fullscreen_mode); void cmd_move_direction(I3_CMD, char *direction, char *move_px); /** - * Implementation of 'layout default|stacked|stacking|tabbed'. + * Implementation of 'layout default|stacked|stacking|tabbed|splitv|splith'. * */ void cmd_layout(I3_CMD, char *layout_str); +/** + * Implementation of 'layout toggle [all|split]'. + * + */ +void cmd_layout_toggle(I3_CMD, char *toggle_mode); + /** * Implementaiton of 'exit'. * diff --git a/include/con.h b/include/con.h index b14c477e..20e83df9 100644 --- a/include/con.h +++ b/include/con.h @@ -221,6 +221,12 @@ Con *con_descend_direction(Con *con, direction_t direction); */ Rect con_border_style_rect(Con *con); +/** + * Returns adjacent borders of the window. We need this if hide_edge_borders is + * enabled. + */ +adjacent_t con_adjacent_borders(Con *con); + /** * Use this function to get a container’s border style. This is important * because when inside a stack, the border style is always BS_NORMAL. @@ -248,6 +254,15 @@ void con_set_border_style(Con *con, int border_style); */ void con_set_layout(Con *con, int layout); +/** + * This function toggles the layout of a given container. toggle_mode can be + * either 'default' (toggle only between stacked/tabbed/last_split_layout), + * 'split' (toggle only between splitv/splith) or 'all' (toggle between all + * layouts). + * + */ +void con_toggle_layout(Con *con, const char *toggle_mode); + /** * Determines the minimum size of the given con by looking at its children (for * split/stacked/tabbed cons). Will be called when resizing floating cons @@ -255,4 +270,27 @@ void con_set_layout(Con *con, int layout); */ Rect con_minimum_size(Con *con); +/** + * Returns true if changing the focus to con would be allowed considering + * the fullscreen focus constraints. Specifically, if a fullscreen container or + * any of its descendants is focused, this function returns true if and only if + * focusing con would mean that focus would still be visible on screen, i.e., + * the newly focused container would not be obscured by a fullscreen container. + * + * In the simplest case, if a fullscreen container or any of its descendants is + * fullscreen, this functions returns true if con is the fullscreen container + * itself or any of its descendants, as this means focus wouldn't escape the + * boundaries of the fullscreen container. + * + * In case the fullscreen container is of type CF_OUTPUT, this function returns + * true if con is on a different workspace, as focus wouldn't be obscured by + * the fullscreen container that is constrained to a different workspace. + * + * Note that this same logic can be applied to moving containers. If a + * container can be focused under the fullscreen focus constraints, it can also + * become a parent or sibling to the currently focused container. + * + */ +bool con_fullscreen_permits_focusing(Con *con); + #endif diff --git a/include/config.h b/include/config.h index 310f8b02..1a48016a 100644 --- a/include/config.h +++ b/include/config.h @@ -108,6 +108,12 @@ struct Config { * It is not planned to add any different focus models. */ bool disable_focus_follows_mouse; + /** Remove borders if they are adjacent to the screen edge. + * This is useful if you are reaching scrollbar on the edge of the + * screen or do not want to waste a single pixel of displayspace. + * By default, this is disabled. */ + adjacent_t hide_edge_borders; + /** By default, a workspace bar is drawn at the bottom of the screen. * If you want to have a more fancy bar, it is recommended to replace * the whole bar by dzen2, for example using the i3-wsbar script which @@ -306,7 +312,7 @@ void switch_mode(const char *new_mode); * or NULL if no such binding exists. * */ -Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode); +Binding *get_binding(uint16_t modifiers, bool key_release, xcb_keycode_t keycode); /** * Kills the configerror i3-nagbar process, if any. diff --git a/include/data.h b/include/data.h index f4ed9a3e..e8df78c7 100644 --- a/include/data.h +++ b/include/data.h @@ -19,6 +19,7 @@ #include #include +#include "libi3.h" #include "queue.h" /* @@ -60,6 +61,13 @@ typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_1PIXEL = 2 } border_style_t; * only this specific window or the whole X11 client */ typedef enum { DONT_KILL_WINDOW = 0, KILL_WINDOW = 1, KILL_CLIENT = 2 } kill_window_t; +/** describes if the window is adjacent to the output (physical screen) edges. */ +typedef enum { ADJ_NONE = 0, + ADJ_LEFT_SCREEN_EDGE = (1 << 0), + ADJ_RIGHT_SCREEN_EDGE = (1 << 1), + ADJ_UPPER_SCREEN_EDGE = (1 << 2), + ADJ_LOWER_SCREEN_EDGE = (1 << 4)} adjacent_t; + enum { BIND_NONE = 0, BIND_SHIFT = XCB_MOD_MASK_SHIFT, /* (1 << 0) */ @@ -160,6 +168,9 @@ struct Startup_Sequence { char *workspace; /** libstartup-notification context for this launch */ SnLauncherContext *context; + /** time at which this sequence should be deleted (after it was marked as + * completed) */ + time_t delete_at; TAILQ_ENTRY(Startup_Sequence) sequences; }; @@ -189,6 +200,20 @@ struct regex { * */ struct Binding { + /** If true, the binding should be executed upon a KeyRelease event, not a + * KeyPress (the default). */ + enum { + /* This binding will only be executed upon KeyPress events */ + B_UPON_KEYPRESS = 0, + /* This binding will be executed either upon a KeyRelease event, or… */ + B_UPON_KEYRELEASE = 1, + /* …upon a KeyRelease event, even if the modifiers don’t match. This + * state is triggered from get_binding() when the corresponding + * KeyPress (!) happens, so that users can release the modifier keys + * before releasing the actual key. */ + B_UPON_KEYRELEASE_IGNORE_MODS = 2, + } release; + /** Symbol the user specified in configfile, if any. This needs to be * stored with the binding to be able to re-convert it into a keycode * if the keyboard mapping changes (using Xmodmap for example) */ @@ -280,9 +305,8 @@ struct Window { char *class_class; char *class_instance; - /** The name of the window as it will be passed to X11 (in UCS2 if the - * application supports _NET_WM_NAME, in COMPOUND_TEXT otherwise). */ - char *name_x; + /** The name of the window. */ + i3String *name; /** The WM_WINDOW_ROLE of this window (for example, the pidgin buddy window * sets "buddy list"). Useful to match specific windows in assignments or @@ -292,13 +316,6 @@ struct Window { /** Flag to force re-rendering the decoration upon changes */ bool name_x_changed; - /** The name of the window as used in JSON (in UTF-8 if the application - * supports _NET_WM_NAME, in COMPOUND_TEXT otherwise) */ - char *name_json; - - /** The length of the name in glyphs (not bytes) */ - size_t name_len; - /** Whether the application used _NET_WM_NAME */ bool uses_net_wm_name; @@ -423,6 +440,8 @@ struct Assignment { */ struct Con { bool mapped; + /** whether this is a split container or not */ + bool split; enum { CT_ROOT = 0, CT_OUTPUT = 1, @@ -431,7 +450,6 @@ struct Con { CT_WORKSPACE = 4, CT_DOCKAREA = 5 } type; - orientation_t orientation; struct Con *parent; struct Rect rect; @@ -496,7 +514,15 @@ struct Con { TAILQ_HEAD(swallow_head, Match) swallow_head; enum { CF_NONE = 0, CF_OUTPUT = 1, CF_GLOBAL = 2 } fullscreen_mode; - enum { L_DEFAULT = 0, L_STACKED = 1, L_TABBED = 2, L_DOCKAREA = 3, L_OUTPUT = 4 } layout; + enum { + L_DEFAULT = 0, + L_STACKED = 1, + L_TABBED = 2, + L_DOCKAREA = 3, + L_OUTPUT = 4, + L_SPLITV = 5, + L_SPLITH = 6 + } layout, last_split_layout; border_style_t border_style; /** floating? (= not in tiling layout) This cannot be simply a bool * because we want to keep track of whether the status was set by the diff --git a/include/display_version.h b/include/display_version.h new file mode 100644 index 00000000..97b3902c --- /dev/null +++ b/include/display_version.h @@ -0,0 +1,27 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) + * + * display_version.c: displays the running i3 version, runs as part of + * i3 --moreversion. + */ +#ifndef _DISPLAY_VERSION_H +#define _DISPLAY_VERSION_H + +/** + * Connects to i3 to find out the currently running version. Useful since it + * might be different from the version compiled into this binary (maybe the + * user didn’t correctly install i3 or forgot te restart it). + * + * The output looks like this: + * Running i3 version: 4.2-202-gb8e782c (2012-08-12, branch "next") (pid 14804) + * + * The i3 binary you just called: /home/michael/i3/i3 + * The i3 binary you are running: /home/michael/i3/i3 + * + */ +void display_running_version(void); + +#endif diff --git a/include/i3/ipc.h b/include/i3/ipc.h index bfadf4cf..0906b7f9 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) * * This public header defines the different constants and message types to use * for the IPC interface to i3 (see docs/ipc for more information). @@ -40,6 +40,9 @@ /** Request the configuration for a specific 'bar' */ #define I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG 6 +/** Request the i3 version */ +#define I3_IPC_MESSAGE_TYPE_GET_VERSION 7 + /* * Messages from i3 to clients * @@ -66,6 +69,9 @@ /** Bar config reply type */ #define I3_IPC_REPLY_TYPE_BAR_CONFIG 6 +/** i3 version reply type */ +#define I3_IPC_REPLY_TYPE_VERSION 7 + /* * Events from i3 to clients. Events have the first bit set high. * diff --git a/include/key_press.h b/include/key_press.h new file mode 100644 index 00000000..4d469bab --- /dev/null +++ b/include/key_press.h @@ -0,0 +1,32 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) + * + * key_press.c: key press handler + * + */ +#ifndef _KEY_PRESS_H +#define _KEY_PRESS_H + +/** + * There was a key press. We compare this key code with our bindings table and pass + * the bound action to parse_command(). + * + */ +void handle_key_press(xcb_key_press_event_t *event); + +/** + * Kills the commanderror i3-nagbar process, if any. + * + * Called when reloading/restarting, since the user probably fixed his wrong + * keybindings. + * + * If wait_for_it is set (restarting), this function will waitpid(), otherwise, + * ev is assumed to handle it (reloading). + * + */ +void kill_commanderror_nagbar(bool wait_for_it); + +#endif diff --git a/include/libi3.h b/include/libi3.h index 2126e100..d4df901f 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -18,6 +18,16 @@ #include #include +#if PANGO_SUPPORT +#include +#endif + +/** + * Opaque data structure for storing strings. + * + */ +typedef struct _i3String i3String; + typedef struct Font i3Font; /** @@ -27,23 +37,44 @@ typedef struct Font i3Font; * */ struct Font { - /** The xcb-id for the font */ - xcb_font_t id; - - /** Font information gathered from the server */ - xcb_query_font_reply_t *info; - - /** Font table for this font (may be NULL) */ - xcb_charinfo_t *table; + /** The type of font */ + enum { + FONT_TYPE_NONE = 0, + FONT_TYPE_XCB, + FONT_TYPE_PANGO + } type; /** The height of the font, built from font_ascent + font_descent */ int height; + + union { + struct { + /** The xcb-id for the font */ + xcb_font_t id; + + /** Font information gathered from the server */ + xcb_query_font_reply_t *info; + + /** Font table for this font (may be NULL) */ + xcb_charinfo_t *table; + } xcb; + +#if PANGO_SUPPORT + /** The pango font description */ + PangoFontDescription *pango_desc; +#endif + } specific; }; /* Since this file also gets included by utilities which don’t use the i3 log * infrastructure, we define a fallback. */ +#if !defined(LOG) +void verboselog(char *fmt, ...); +#define LOG(fmt, ...) verboselog("[libi3] " __FILE__ " " fmt, ##__VA_ARGS__) +#endif #if !defined(ELOG) -#define ELOG(fmt, ...) fprintf(stderr, "ERROR: " fmt, ##__VA_ARGS__) +void errorlog(char *fmt, ...); +#define ELOG(fmt, ...) errorlog("[libi3] ERROR: " fmt, ##__VA_ARGS__) #endif /** @@ -91,6 +122,71 @@ char *sstrdup(const char *str); */ int sasprintf(char **strp, const char *fmt, ...); +/** + * Build an i3String from an UTF-8 encoded string. + * Returns the newly-allocated i3String. + * + */ +i3String *i3string_from_utf8(const char *from_utf8); + +/** + * Build an i3String from an UTF-8 encoded string with fixed length. + * To be used when no proper NUL-terminaison is available. + * Returns the newly-allocated i3String. + * + */ +i3String *i3string_from_utf8_with_length(const char *from_utf8, size_t num_bytes); + +/** + * Build an i3String from an UCS-2 encoded string. + * Returns the newly-allocated i3String. + * + */ +i3String *i3string_from_ucs2(const xcb_char2b_t *from_ucs2, size_t num_glyphs); + +/** + * Free an i3String. + * + */ +void i3string_free(i3String *str); + +/** + * Securely i3string_free by setting the pointer to NULL + * to prevent accidentally using freed memory. + * + */ +#define I3STRING_FREE(str) \ +do { \ + if (str != NULL) { \ + i3string_free(str); \ + str = NULL; \ + } \ +} while (0) + +/** + * Returns the UTF-8 encoded version of the i3String. + * + */ +const char *i3string_as_utf8(i3String *str); + +/** + * Returns the UCS-2 encoded version of the i3String. + * + */ +const xcb_char2b_t *i3string_as_ucs2(i3String *str); + +/** + * Returns the number of bytes (UTF-8 encoded) in an i3String. + * + */ +size_t i3string_get_num_bytes(i3String *str); + +/** + * Returns the number of glyphs in an i3String. + * + */ +size_t i3string_get_num_glyphs(i3String *str); + /** * Connects to the i3 IPC socket and returns the file descriptor for the * socket. die()s if anything goes wrong. @@ -226,21 +322,31 @@ void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background * specified coordinates (from the top left corner of the leftmost, uppermost * glyph) and using the provided gc. * - * Text can be specified as UCS-2 or UTF-8. If it's specified as UCS-2, then - * text_len must be the number of glyphs in the string. If it's specified as - * UTF-8, then text_len must be the number of bytes in the string (not counting - * the null terminator). + * Text must be specified as an i3String. + * + */ +void draw_text(i3String *text, xcb_drawable_t drawable, + xcb_gcontext_t gc, int x, int y, int max_width); + +/** + * ASCII version of draw_text to print static strings. * */ -void draw_text(char *text, size_t text_len, bool is_ucs2, xcb_drawable_t drawable, +void draw_text_ascii(const char *text, xcb_drawable_t drawable, xcb_gcontext_t gc, int x, int y, int max_width); /** - * Predict the text width in pixels for the given text. Text can be specified - * as UCS-2 or UTF-8. + * Predict the text width in pixels for the given text. Text must be + * specified as an i3String. + * + */ +int predict_text_width(i3String *text); + +/** + * Returns the visual type associated with the given screen. * */ -int predict_text_width(char *text, size_t text_len, bool is_ucs2); +xcb_visualtype_t *get_visualtype(xcb_screen_t *screen); /** * Returns true if this version of i3 is a debug build (anything which is not a diff --git a/include/log.h b/include/log.h index e5e20dc1..7822fba5 100644 --- a/include/log.h +++ b/include/log.h @@ -4,7 +4,7 @@ * i3 - an improved dynamic tiling window manager * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) * - * log.c: Setting of loglevels, logging functions. + * log.c: Logging functions. * */ #ifndef _LOG_H @@ -13,13 +13,20 @@ #include #include +/* We will include libi3.h which define its own version of LOG, ELOG. + * We want *our* version, so we undef the libi3 one. */ +#if defined(LOG) +#undef LOG +#endif +#if defined(ELOG) +#undef ELOG +#endif /** ##__VA_ARGS__ means: leave out __VA_ARGS__ completely if it is empty, that is, delete the preceding comma */ #define LOG(fmt, ...) verboselog(fmt, ##__VA_ARGS__) #define ELOG(fmt, ...) errorlog("ERROR: " fmt, ##__VA_ARGS__) -#define DLOG(fmt, ...) debuglog(LOGLEVEL, "%s:%s:%d - " fmt, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__) +#define DLOG(fmt, ...) debuglog("%s:%s:%d - " fmt, I3__FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__) -extern char *loglevels[]; extern char *errorfilename; extern char *shmlogname; extern int shmlog_size; @@ -32,10 +39,10 @@ extern int shmlog_size; void init_logging(void); /** - * Enables the given loglevel. + * Set debug logging. * */ -void add_loglevel(const char *level); +void set_debug_logging(const bool _debug_logging); /** * Set verbosity of i3. If verbose is set to true, informative messages will @@ -47,29 +54,32 @@ void set_verbosity(bool _verbose); /** * Logs the given message to stdout while prefixing the current time to it, - * but only if the corresponding debug loglevel was activated. + * but only if debug logging was activated. * */ -void debuglog(uint64_t lev, char *fmt, ...); +void debuglog(char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); /** * Logs the given message to stdout while prefixing the current time to it. * */ -void errorlog(char *fmt, ...); +void errorlog(char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); /** * Logs the given message to stdout while prefixing the current time to it, * but only if verbose mode is activated. * */ -void verboselog(char *fmt, ...); +void verboselog(char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); /** - * Logs the given message to stdout while prefixing the current time to it. - * This is to be called by LOG() which includes filename/linenumber - * + * Deletes the unused log files. Useful if i3 exits immediately, eg. + * because --get-socketpath was called. We don't care for syscall + * failures. This function is invoked automatically when exiting. */ -void slog(char *fmt, va_list args); +void purge_zerobyte_logfile(void); #endif diff --git a/include/regex.h b/include/regex.h index d55bb6cb..fe1e9f95 100644 --- a/include/regex.h +++ b/include/regex.h @@ -31,7 +31,7 @@ void regex_free(struct regex *regex); /** * Checks if the given regular expression matches the given input and returns * true if it does. In either case, it logs the outcome using LOG(), so it will - * be visible without any debug loglevel. + * be visible without debug logging. * */ bool regex_matches(struct regex *regex, const char *input); diff --git a/include/scratchpad.h b/include/scratchpad.h index 4fb7523a..4d553327 100644 --- a/include/scratchpad.h +++ b/include/scratchpad.h @@ -30,4 +30,14 @@ void scratchpad_move(Con *con); */ void scratchpad_show(Con *con); +/** + * When starting i3 initially (and after each change to the connected outputs), + * this function fixes the resolution of the __i3 pseudo-output. When that + * resolution is not set to a function which shares a common divisor with every + * active output’s resolution, floating point calculation errors will lead to + * the scratchpad window moving when shown repeatedly. + * + */ +void scratchpad_fix_resolution(void); + #endif diff --git a/include/shmlog.h b/include/shmlog.h index c513babf..e755d2f1 100644 --- a/include/shmlog.h +++ b/include/shmlog.h @@ -12,11 +12,33 @@ #define _I3_SHMLOG_H #include +#include +/* + * Header of the shmlog file. Used by i3/src/log.c and i3/i3-dump-log/main.c. + * + */ typedef struct i3_shmlog_header { + /* Byte offset where the next line will be written to. */ uint32_t offset_next_write; + + /* Byte offset where the last wrap occured. */ uint32_t offset_last_wrap; + + /* The size of the logfile in bytes. Since the size is limited to 25 MiB + * an uint32_t is sufficient. */ uint32_t size; + + /* wrap counter. We need it to reliably signal to clients that we just + * wrapped (clients cannot use offset_last_wrap because that might + * coincidentally be exactly the same as previously). Overflows can happen + * and don’t matter — clients use an equality check (==). */ + uint32_t wrap_count; + + /* pthread condvar which will be broadcasted whenever there is a new + * message in the log. i3-dump-log uses this to implement -f (follow, like + * tail -f) in an efficient way. */ + pthread_cond_t condvar; } i3_shmlog_header; #endif diff --git a/include/tree.h b/include/tree.h index b9159e3b..8816b19a 100644 --- a/include/tree.h +++ b/include/tree.h @@ -39,16 +39,16 @@ Con *tree_open_con(Con *con, i3Window *window); void tree_split(Con *con, orientation_t orientation); /** - * Moves focus one level up. + * Moves focus one level up. Returns true if focus changed. * */ -void level_up(void); +bool level_up(void); /** - * Moves focus one level down. + * Moves focus one level down. Returns true if focus changed. * */ -void level_down(void); +bool level_down(void); /** * Renders the tree, that is rendering all outputs using render_con() and diff --git a/libi3/Makefile b/libi3/Makefile index e9efcf7b..2c2f68ad 100644 --- a/libi3/Makefile +++ b/libi3/Makefile @@ -1,26 +1,7 @@ -# Default value so one can compile i3-msg standalone -TOPDIR=.. - -include $(TOPDIR)/common.mk - -CFLAGS += -I$(TOPDIR)/include - -# Depend on the object files of all source-files in src/*.c and on all header files -FILES=$(patsubst %.c,%.o,$(wildcard *.c)) -HEADERS=$(wildcard *.h) - -# Depend on the specific file (.c for each .o) and on all headers -%.o: %.c ${HEADERS} - echo "[libi3] CC $<" - $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< - -all: libi3.a - -libi3.a: ${FILES} - echo "[libi3] AR libi3.a" - ar rcs libi3.a ${FILES} +all: + $(MAKE) -C .. libi3.a clean: - rm -f *.o libi3.a + $(MAKE) -C .. clean-libi3 -distclean: clean +.PHONY: all clean diff --git a/libi3/font.c b/libi3/font.c index 0b276b0b..23d7420d 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -12,11 +12,125 @@ #include #include +#if PANGO_SUPPORT +#include +#include +#endif + #include "libi3.h" extern xcb_connection_t *conn; +extern xcb_screen_t *root_screen; + static const i3Font *savedFont = NULL; +#if PANGO_SUPPORT +static xcb_visualtype_t *root_visual_type; +static double pango_font_red; +static double pango_font_green; +static double pango_font_blue; + +/* + * Loads a Pango font description into an i3Font structure. Returns true + * on success, false otherwise. + * + */ +static bool load_pango_font(i3Font *font, const char *desc) { + /* Load the font description */ + font->specific.pango_desc = pango_font_description_from_string(desc); + if (!font->specific.pango_desc) { + ELOG("Could not open font %s with Pango, fallback to X font.\n", desc); + return false; + } + + LOG("Using Pango font %s, size %d\n", + pango_font_description_get_family(font->specific.pango_desc), + pango_font_description_get_size(font->specific.pango_desc) / PANGO_SCALE + ); + + /* We cache root_visual_type here, since you must call + * load_pango_font before any other pango function + * that would need root_visual_type */ + root_visual_type = get_visualtype(root_screen); + + /* Create a dummy Pango layout to compute the font height */ + cairo_surface_t *surface = cairo_xcb_surface_create(conn, root_screen->root, root_visual_type, 1, 1); + cairo_t *cr = cairo_create(surface); + PangoLayout *layout = pango_cairo_create_layout(cr); + pango_layout_set_font_description(layout, font->specific.pango_desc); + + /* Get the font height */ + gint height; + pango_layout_get_pixel_size(layout, NULL, &height); + font->height = height; + + /* Free resources */ + g_object_unref(layout); + cairo_destroy(cr); + cairo_surface_destroy(surface); + + /* Set the font type and return successfully */ + font->type = FONT_TYPE_PANGO; + return true; +} + +/* + * Draws text using Pango rendering. + * + */ +static void draw_text_pango(const char *text, size_t text_len, + xcb_drawable_t drawable, int x, int y, int max_width) { + /* Create the Pango layout */ + /* root_visual_type is cached in load_pango_font */ + cairo_surface_t *surface = cairo_xcb_surface_create(conn, drawable, + root_visual_type, x + max_width, y + savedFont->height); + cairo_t *cr = cairo_create(surface); + PangoLayout *layout = pango_cairo_create_layout(cr); + pango_layout_set_font_description(layout, savedFont->specific.pango_desc); + pango_layout_set_width(layout, max_width * PANGO_SCALE); + pango_layout_set_wrap(layout, PANGO_WRAP_CHAR); + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); + + /* Do the drawing */ + cairo_set_source_rgb(cr, pango_font_red, pango_font_green, pango_font_blue); + cairo_move_to(cr, x, y); + pango_layout_set_text(layout, text, text_len); + pango_cairo_update_layout(cr, layout); + pango_cairo_show_layout(cr, layout); + + /* Free resources */ + g_object_unref(layout); + cairo_destroy(cr); + cairo_surface_destroy(surface); +} + +/* + * Calculate the text width using Pango rendering. + * + */ +static int predict_text_width_pango(const char *text, size_t text_len) { + /* Create a dummy Pango layout */ + /* root_visual_type is cached in load_pango_font */ + cairo_surface_t *surface = cairo_xcb_surface_create(conn, root_screen->root, root_visual_type, 1, 1); + cairo_t *cr = cairo_create(surface); + PangoLayout *layout = pango_cairo_create_layout(cr); + + /* Get the font width */ + gint width; + pango_layout_set_font_description(layout, savedFont->specific.pango_desc); + pango_layout_set_text(layout, text, text_len); + pango_cairo_update_layout(cr, layout); + pango_layout_get_pixel_size(layout, &width, NULL); + + /* Free resources */ + g_object_unref(layout); + cairo_destroy(cr); + cairo_surface_destroy(surface); + + return width; +} +#endif + /* * Loads a font for usage, also getting its metrics. If fallback is true, * the fonts 'fixed' or '-misc-*' will be loaded instead of exiting. @@ -24,12 +138,22 @@ static const i3Font *savedFont = NULL; */ i3Font load_font(const char *pattern, const bool fallback) { i3Font font; + font.type = FONT_TYPE_NONE; + +#if PANGO_SUPPORT + /* Try to load a pango font if specified */ + if (strlen(pattern) > strlen("xft:") && !strncmp(pattern, "xft:", strlen("xft:"))) { + pattern += strlen("xft:"); + if (load_pango_font(&font, pattern)) + return font; + } +#endif /* Send all our requests first */ - font.id = xcb_generate_id(conn); - xcb_void_cookie_t font_cookie = xcb_open_font_checked(conn, font.id, + font.specific.xcb.id = xcb_generate_id(conn); + xcb_void_cookie_t font_cookie = xcb_open_font_checked(conn, font.specific.xcb.id, strlen(pattern), pattern); - xcb_query_font_cookie_t info_cookie = xcb_query_font(conn, font.id); + xcb_query_font_cookie_t info_cookie = xcb_query_font(conn, font.specific.xcb.id); /* Check for errors. If errors, fall back to default font. */ xcb_generic_error_t *error; @@ -40,8 +164,9 @@ i3Font load_font(const char *pattern, const bool fallback) { ELOG("Could not open font %s (X error %d). Trying fallback to 'fixed'.\n", pattern, error->error_code); pattern = "fixed"; - font_cookie = xcb_open_font_checked(conn, font.id, strlen(pattern), pattern); - info_cookie = xcb_query_font(conn, font.id); + font_cookie = xcb_open_font_checked(conn, font.specific.xcb.id, + strlen(pattern), pattern); + info_cookie = xcb_query_font(conn, font.specific.xcb.id); /* Check if we managed to open 'fixed' */ error = xcb_request_check(conn, font_cookie); @@ -50,8 +175,9 @@ i3Font load_font(const char *pattern, const bool fallback) { if (error != NULL) { ELOG("Could not open fallback font 'fixed', trying with '-misc-*'.\n"); pattern = "-misc-*"; - font_cookie = xcb_open_font_checked(conn, font.id, strlen(pattern), pattern); - info_cookie = xcb_query_font(conn, font.id); + font_cookie = xcb_open_font_checked(conn, font.specific.xcb.id, + strlen(pattern), pattern); + info_cookie = xcb_query_font(conn, font.specific.xcb.id); if ((error = xcb_request_check(conn, font_cookie)) != NULL) errx(EXIT_FAILURE, "Could open neither requested font nor fallbacks " @@ -59,19 +185,23 @@ i3Font load_font(const char *pattern, const bool fallback) { } } + LOG("Using X font %s\n", pattern); + /* Get information (height/name) for this font */ - if (!(font.info = xcb_query_font_reply(conn, info_cookie, NULL))) + if (!(font.specific.xcb.info = xcb_query_font_reply(conn, info_cookie, NULL))) errx(EXIT_FAILURE, "Could not load font \"%s\"", pattern); /* Get the font table, if possible */ - if (xcb_query_font_char_infos_length(font.info) == 0) - font.table = NULL; + if (xcb_query_font_char_infos_length(font.specific.xcb.info) == 0) + font.specific.xcb.table = NULL; else - font.table = xcb_query_font_char_infos(font.info); + font.specific.xcb.table = xcb_query_font_char_infos(font.specific.xcb.info); /* Calculate the font height */ - font.height = font.info->font_ascent + font.info->font_descent; + font.height = font.specific.xcb.info->font_ascent + font.specific.xcb.info->font_descent; + /* Set the font type and return successfully */ + font.type = FONT_TYPE_XCB; return font; } @@ -88,10 +218,27 @@ void set_font(i3Font *font) { * */ void free_font(void) { - /* Close the font and free the info */ - xcb_close_font(conn, savedFont->id); - if (savedFont->info) - free(savedFont->info); + switch (savedFont->type) { + case FONT_TYPE_NONE: + /* Nothing to do */ + break; + case FONT_TYPE_XCB: { + /* Close the font and free the info */ + xcb_close_font(conn, savedFont->specific.xcb.id); + if (savedFont->specific.xcb.info) + free(savedFont->specific.xcb.info); + break; + } +#if PANGO_SUPPORT + case FONT_TYPE_PANGO: + /* Free the font description */ + pango_font_description_free(savedFont->specific.pango_desc); + break; +#endif + default: + assert(false); + break; + } } /* @@ -100,50 +247,49 @@ void free_font(void) { */ void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background) { assert(savedFont != NULL); - uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT; - uint32_t values[] = { foreground, background, savedFont->id }; - xcb_change_gc(conn, gc, mask, values); + + switch (savedFont->type) { + case FONT_TYPE_NONE: + /* Nothing to do */ + break; + case FONT_TYPE_XCB: { + /* Change the font and colors in the GC */ + uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT; + uint32_t values[] = { foreground, background, savedFont->specific.xcb.id }; + xcb_change_gc(conn, gc, mask, values); + break; + } +#if PANGO_SUPPORT + case FONT_TYPE_PANGO: + /* Save the foreground font */ + pango_font_red = ((foreground >> 16) & 0xff) / 255.0; + pango_font_green = ((foreground >> 8) & 0xff) / 255.0; + pango_font_blue = (foreground & 0xff) / 255.0; + break; +#endif + default: + assert(false); + break; + } } -/* - * Draws text onto the specified X drawable (normally a pixmap) at the - * specified coordinates (from the top left corner of the leftmost, uppermost - * glyph) and using the provided gc. - * - * Text can be specified as UCS-2 or UTF-8. If it's specified as UCS-2, then - * text_len must be the number of glyphs in the string. If it's specified as - * UTF-8, then text_len must be the number of bytes in the string (not counting - * the null terminator). - * - */ -void draw_text(char *text, size_t text_len, bool is_ucs2, xcb_drawable_t drawable, - xcb_gcontext_t gc, int x, int y, int max_width) { - assert(savedFont != NULL); - assert(text_len != 0); +static int predict_text_width_xcb(const xcb_char2b_t *text, size_t text_len); +static void draw_text_xcb(const xcb_char2b_t *text, size_t text_len, xcb_drawable_t drawable, + xcb_gcontext_t gc, int x, int y, int max_width) { /* X11 coordinates for fonts start at the baseline */ - int pos_y = y + savedFont->info->font_ascent; - - /* As an optimization, check if we can bypass conversion */ - if (!is_ucs2 && text_len <= 255) { - xcb_image_text_8(conn, text_len, drawable, gc, x, pos_y, text); - return; - } - - /* Convert the text into UCS-2 so we can do basic pointer math */ - char *input = (is_ucs2 ? text : (char*)convert_utf8_to_ucs2(text, &text_len)); + int pos_y = y + savedFont->specific.xcb.info->font_ascent; /* The X11 protocol limits text drawing to 255 chars, so we may need * multiple calls */ - int pos_x = x; int offset = 0; for (;;) { /* Calculate the size of this chunk */ int chunk_size = (text_len > 255 ? 255 : text_len); - xcb_char2b_t *chunk = (xcb_char2b_t*)input + offset; + const xcb_char2b_t *chunk = text + offset; /* Draw it */ - xcb_image_text_16(conn, chunk_size, drawable, gc, pos_x, pos_y, chunk); + xcb_image_text_16(conn, chunk_size, drawable, gc, x, pos_y, chunk); /* Advance the offset and length of the text to draw */ offset += chunk_size; @@ -154,15 +300,83 @@ void draw_text(char *text, size_t text_len, bool is_ucs2, xcb_drawable_t drawabl break; /* Advance pos_x based on the predicted text width */ - pos_x += predict_text_width((char*)chunk, chunk_size, true); + x += predict_text_width_xcb(chunk, chunk_size); + } +} + +/* + * Draws text onto the specified X drawable (normally a pixmap) at the + * specified coordinates (from the top left corner of the leftmost, uppermost + * glyph) and using the provided gc. + * + * Text must be specified as an i3String. + * + */ +void draw_text(i3String *text, xcb_drawable_t drawable, + xcb_gcontext_t gc, int x, int y, int max_width) { + assert(savedFont != NULL); + + switch (savedFont->type) { + case FONT_TYPE_NONE: + /* Nothing to do */ + return; + case FONT_TYPE_XCB: + draw_text_xcb(i3string_as_ucs2(text), i3string_get_num_glyphs(text), + drawable, gc, x, y, max_width); + break; +#if PANGO_SUPPORT + case FONT_TYPE_PANGO: + /* Render the text using Pango */ + draw_text_pango(i3string_as_utf8(text), i3string_get_num_bytes(text), + drawable, x, y, max_width); + return; +#endif + default: + assert(false); } +} + +/* + * ASCII version of draw_text to print static strings. + * + */ +void draw_text_ascii(const char *text, xcb_drawable_t drawable, + xcb_gcontext_t gc, int x, int y, int max_width) { + assert(savedFont != NULL); - /* If we had to convert, free the converted string */ - if (!is_ucs2) - free(input); + switch (savedFont->type) { + case FONT_TYPE_NONE: + /* Nothing to do */ + return; + case FONT_TYPE_XCB: + { + size_t text_len = strlen(text); + if (text_len > 255) { + /* The text is too long to draw it directly to X */ + i3String *str = i3string_from_utf8(text); + draw_text(str, drawable, gc, x, y, max_width); + i3string_free(str); + } else { + /* X11 coordinates for fonts start at the baseline */ + int pos_y = y + savedFont->specific.xcb.info->font_ascent; + + xcb_image_text_8(conn, text_len, drawable, gc, x, pos_y, text); + } + break; + } +#if PANGO_SUPPORT + case FONT_TYPE_PANGO: + /* Render the text using Pango */ + draw_text_pango(text, strlen(text), + drawable, x, y, max_width); + return; +#endif + default: + assert(false); + } } -static int xcb_query_text_width(xcb_char2b_t *text, size_t text_len) { +static int xcb_query_text_width(const xcb_char2b_t *text, size_t text_len) { /* Make the user know we’re using the slow path, but only once. */ static bool first_invocation = true; if (first_invocation) { @@ -173,7 +387,7 @@ static int xcb_query_text_width(xcb_char2b_t *text, size_t text_len) { /* Query the text width */ xcb_generic_error_t *error; xcb_query_text_extents_cookie_t cookie = xcb_query_text_extents(conn, - savedFont->id, text_len, (xcb_char2b_t*)text); + savedFont->specific.xcb.id, text_len, (xcb_char2b_t*)text); xcb_query_text_extents_reply_t *reply = xcb_query_text_extents_reply(conn, cookie, &error); if (reply == NULL) { @@ -181,7 +395,7 @@ static int xcb_query_text_width(xcb_char2b_t *text, size_t text_len) { * a crash. Plus, the user will see the error in his log. */ fprintf(stderr, "Could not get text extents (X error code %d)\n", error->error_code); - return savedFont->info->max_bounds.character_width * text_len; + return savedFont->specific.xcb.info->max_bounds.character_width * text_len; } int width = reply->overall_width; @@ -189,27 +403,18 @@ static int xcb_query_text_width(xcb_char2b_t *text, size_t text_len) { return width; } -/* - * Predict the text width in pixels for the given text. Text can be specified - * as UCS-2 or UTF-8. - * - */ -int predict_text_width(char *text, size_t text_len, bool is_ucs2) { - /* Convert the text into UTF-16 so we can do basic pointer math */ - xcb_char2b_t *input; - if (is_ucs2) - input = (xcb_char2b_t*)text; - else - input = convert_utf8_to_ucs2(text, &text_len); +static int predict_text_width_xcb(const xcb_char2b_t *input, size_t text_len) { + if (text_len == 0) + return 0; int width; - if (savedFont->table == NULL) { + if (savedFont->specific.xcb.table == NULL) { /* If we don't have a font table, fall back to querying the server */ width = xcb_query_text_width(input, text_len); } else { /* Save some pointers for convenience */ - xcb_query_font_reply_t *font_info = savedFont->info; - xcb_charinfo_t *font_table = savedFont->table; + xcb_query_font_reply_t *font_info = savedFont->specific.xcb.info; + xcb_charinfo_t *font_table = savedFont->specific.xcb.table; /* Calculate the width using the font table */ width = 0; @@ -239,9 +444,30 @@ int predict_text_width(char *text, size_t text_len, bool is_ucs2) { } } - /* If we had to convert, free the converted string */ - if (!is_ucs2) - free(input); - return width; } + +/* + * Predict the text width in pixels for the given text. Text must be + * specified as an i3String. + * + */ +int predict_text_width(i3String *text) { + assert(savedFont != NULL); + + switch (savedFont->type) { + case FONT_TYPE_NONE: + /* Nothing to do */ + return 0; + case FONT_TYPE_XCB: + return predict_text_width_xcb(i3string_as_ucs2(text), i3string_get_num_glyphs(text)); +#if PANGO_SUPPORT + case FONT_TYPE_PANGO: + /* Calculate extents using Pango */ + return predict_text_width_pango(i3string_as_utf8(text), i3string_get_num_bytes(text)); +#endif + default: + assert(false); + return 0; + } +} diff --git a/libi3/get_visualtype.c b/libi3/get_visualtype.c new file mode 100644 index 00000000..d11722f0 --- /dev/null +++ b/libi3/get_visualtype.c @@ -0,0 +1,28 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) + * + */ +#include "libi3.h" + +/* + * Returns the visual type associated with the given screen. + * + */ +xcb_visualtype_t *get_visualtype(xcb_screen_t *screen) { + xcb_depth_iterator_t depth_iter; + for (depth_iter = xcb_screen_allowed_depths_iterator(screen); + depth_iter.rem; + xcb_depth_next(&depth_iter)) { + xcb_visualtype_iterator_t visual_iter; + for (visual_iter = xcb_depth_visuals_iterator(depth_iter.data); + visual_iter.rem; + xcb_visualtype_next(&visual_iter)) { + if (screen->root_visual == visual_iter.data->visual_id) + return visual_iter.data; + } + } + return NULL; +} diff --git a/libi3/libi3.mk b/libi3/libi3.mk new file mode 100644 index 00000000..d99bacf4 --- /dev/null +++ b/libi3/libi3.mk @@ -0,0 +1,21 @@ +CLEAN_TARGETS += clean-libi3 + +libi3_SOURCES := $(wildcard libi3/*.c) +libi3_HEADERS := $(wildcard libi3/*.h) +libi3_CFLAGS = $(PANGO_CFLAGS) +libi3_LIBS = + +libi3_OBJECTS := $(libi3_SOURCES:.c=.o) + + +libi3/%.o: libi3/%.c $(libi3_HEADERS) + echo "[libi3] CC $<" + $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(libi3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $< + +libi3.a: $(libi3_OBJECTS) + echo "[libi3] AR libi3.a" + ar rcs $@ $^ $(libi3_LIBS) + +clean-libi3: + echo "[libi3] Clean" + rm -f $(libi3_OBJECTS) libi3.a diff --git a/libi3/root_atom_contents.c b/libi3/root_atom_contents.c index 927cc5f8..cabaaf2c 100644 --- a/libi3/root_atom_contents.c +++ b/libi3/root_atom_contents.c @@ -28,7 +28,7 @@ char *root_atom_contents(const char *atomname) { xcb_intern_atom_cookie_t atom_cookie; xcb_intern_atom_reply_t *atom_reply; int screen; - char *socket_path; + char *content; if ((conn = xcb_connect(NULL, &screen)) == NULL || xcb_connection_has_error(conn)) @@ -50,10 +50,17 @@ char *root_atom_contents(const char *atomname) { prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0) return NULL; - if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply), - (char*)xcb_get_property_value(prop_reply)) == -1) - return NULL; + if (prop_reply->type == XCB_ATOM_CARDINAL) { + /* We treat a CARDINAL as a >= 32-bit unsigned int. The only CARDINAL + * we query is I3_PID, which is 32-bit. */ + if (asprintf(&content, "%u", *((unsigned int*)xcb_get_property_value(prop_reply))) == -1) + return NULL; + } else { + if (asprintf(&content, "%.*s", xcb_get_property_value_length(prop_reply), + (char*)xcb_get_property_value(prop_reply)) == -1) + return NULL; + } xcb_disconnect(conn); - return socket_path; + return content; } diff --git a/libi3/string.c b/libi3/string.c new file mode 100644 index 00000000..009312d6 --- /dev/null +++ b/libi3/string.c @@ -0,0 +1,143 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) + * + * string.c: Define an i3String type to automagically handle UTF-8/UCS-2 + * conversions. Some font backends need UCS-2 (X core fonts), + * others want UTF-8 (Pango). + * + */ + +#include +#include + +#include "libi3.h" + +struct _i3String { + char *utf8; + xcb_char2b_t *ucs2; + size_t num_glyphs; + size_t num_bytes; +}; + +/* + * Build an i3String from an UTF-8 encoded string. + * Returns the newly-allocated i3String. + * + */ +i3String *i3string_from_utf8(const char *from_utf8) { + i3String *str = scalloc(sizeof(i3String)); + + /* Get the text */ + str->utf8 = sstrdup(from_utf8); + + /* Compute and store the length */ + str->num_bytes = strlen(str->utf8); + + return str; +} + +/* + * Build an i3String from an UTF-8 encoded string with fixed length. + * To be used when no proper NUL-terminaison is available. + * Returns the newly-allocated i3String. + * + */ +i3String *i3string_from_utf8_with_length(const char *from_utf8, size_t num_bytes) { + i3String *str = scalloc(sizeof(i3String)); + + /* Copy the actual text to our i3String */ + str->utf8 = scalloc(sizeof(char) * (num_bytes + 1)); + strncpy(str->utf8, from_utf8, num_bytes); + str->utf8[num_bytes] = '\0'; + + /* Store the length */ + str->num_bytes = num_bytes; + + return str; +} + +/* + * Build an i3String from an UCS-2 encoded string. + * Returns the newly-allocated i3String. + * + */ +i3String *i3string_from_ucs2(const xcb_char2b_t *from_ucs2, size_t num_glyphs) { + i3String *str = scalloc(sizeof(i3String)); + + /* Copy the actual text to our i3String */ + size_t num_bytes = num_glyphs * sizeof(xcb_char2b_t); + str->ucs2 = scalloc(num_bytes); + memcpy(str->ucs2, from_ucs2, num_bytes); + + /* Store the length */ + str->num_glyphs = num_glyphs; + + str->utf8 = NULL; + str->num_bytes = 0; + + return str; +} + +/* + * Free an i3String. + * + */ +void i3string_free(i3String *str) { + if (str == NULL) + return; + free(str->utf8); + free(str->ucs2); + free(str); +} + +static void i3string_ensure_utf8(i3String *str) { + if (str->utf8 != NULL) + return; + if ((str->utf8 = convert_ucs2_to_utf8(str->ucs2, str->num_glyphs)) != NULL) + str->num_bytes = strlen(str->utf8); +} + +static void i3string_ensure_ucs2(i3String *str) { + if (str->ucs2 != NULL) + return; + str->ucs2 = convert_utf8_to_ucs2(str->utf8, &str->num_glyphs); +} + +/* + * Returns the UTF-8 encoded version of the i3String. + * + */ +const char *i3string_as_utf8(i3String *str) { + i3string_ensure_utf8(str); + return str->utf8; +} + +/* + * Returns the UCS-2 encoded version of the i3String. + * + */ +const xcb_char2b_t *i3string_as_ucs2(i3String *str) { + i3string_ensure_ucs2(str); + return str->ucs2; +} + +/* + * Returns the number of bytes (UTF-8 encoded) in an i3String. + * + */ +size_t i3string_get_num_bytes(i3String *str) { + i3string_ensure_utf8(str); + return str->num_bytes; +} + +/* + * Returns the number of glyphs in an i3String. + * + */ +size_t i3string_get_num_glyphs(i3String *str) { + i3string_ensure_ucs2(str); + return str->num_glyphs; +} diff --git a/man/Makefile b/man/Makefile index ff08dc57..e4cee0cc 100644 --- a/man/Makefile +++ b/man/Makefile @@ -1,15 +1,7 @@ -A2M:=a2x -f manpage --asciidoc-opts="-f asciidoc.conf" - -all: i3.1 i3-msg.1 i3-input.1 i3-nagbar.1 i3-config-wizard.1 i3-migrate-config-to-v4.1 i3-sensible-editor.1 i3-sensible-pager.1 i3-sensible-terminal.1 i3-dump-log.1 - -%.1: %.man asciidoc.conf - ${A2M} $< +all: + $(MAKE) -C .. mans clean: - for file in $$(echo i3 i3-msg i3-input i3-nagbar i3-wsbar i3-config-wizard i3-migrate-config-to-v4 i3-sensible-editor i3-sensible-pager i3-sensible-terminal i3-dump-log); \ - do \ - rm -f $${file}.1 $${file}.html $${file}.xml; \ - done + $(MAKE) -C .. clean-mans -distclean: clean - rm -f *.1 +.PHONY: all clean diff --git a/man/i3-dump-log.man b/man/i3-dump-log.man index 8e9094ff..eb8ba2f7 100644 --- a/man/i3-dump-log.man +++ b/man/i3-dump-log.man @@ -14,7 +14,7 @@ i3-dump-log [-s ] == 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. diff --git a/man/i3-msg.man b/man/i3-msg.man index 891c6c28..6b548d36 100644 --- a/man/i3-msg.man +++ b/man/i3-msg.man @@ -1,7 +1,7 @@ i3-msg(1) ========= -Michael Stapelberg -v4.2, January 2012 +Michael Stapelberg +v4.2, August 2012 == NAME @@ -38,6 +38,9 @@ get_bar_config:: Gets the configuration (as JSON map) of the workspace bar with the given ID. If no ID is provided, an array with all configured bar IDs is returned instead. +get_version:: +Gets the version of i3. The reply will be a JSON-encoded dictionary with the +major, minor, patch and human-readable version. == DESCRIPTION diff --git a/man/i3-sensible-terminal.man b/man/i3-sensible-terminal.man index 7e32aab4..1d9f9ff8 100644 --- a/man/i3-sensible-terminal.man +++ b/man/i3-sensible-terminal.man @@ -1,7 +1,7 @@ i3-sensible-terminal(1) ======================= -Michael Stapelberg -v4.1, November 2011 +Michael Stapelberg +v4.2, August 2012 == NAME @@ -30,6 +30,7 @@ It tries to start one of the following (in that order): * xterm * gnome-terminal * roxterm +* xfce4-terminal Please don’t complain about the order: If the user has any preference, he will have $TERMINAL set or modified his i3 configuration file. diff --git a/man/i3.man b/man/i3.man index 9d34c710..096a359b 100644 --- a/man/i3.man +++ b/man/i3.man @@ -1,7 +1,7 @@ i3(1) ===== -Michael Stapelberg -v4.0, July 2011 +Michael Stapelberg +v4.2, August 2012 == NAME @@ -9,7 +9,7 @@ i3 - an improved dynamic, tiling window manager == SYNOPSIS -i3 [-a] [-c configfile] [-C] [-d ] [-v] [-V] +i3 [-a] [-c configfile] [-C] [-d all] [-v] [-V] == OPTIONS @@ -22,8 +22,9 @@ Specifies an alternate configuration file path. -C:: Check the configuration file for validity and exit. --d:: -Specifies the debug loglevel. To see the most output, use -d all. +-d all:: +Enables debug logging. +The 'all' parameter is present for historical reasons. -v:: Display version number (and date of the last commit). @@ -47,8 +48,8 @@ Please be aware that i3 is primarily targeted at advanced users and developers. === IMPORTANT NOTE TO nVidia BINARY DRIVER USERS If you are using the nVidia binary graphics driver (also known as 'blob') -you need to use the +--force-xinerama+ flag (in your ~/.xsession) when starting -i3, like so: +before version 302.17, you need to use the +--force-xinerama+ flag (in your +~/.xsession) when starting i3, like so: ---------------------------------------------- exec i3 --force-xinerama -V >>~/.i3/i3log 2>&1 @@ -91,6 +92,12 @@ are connected to these outputs. Here is a short overview of the default keybindings: +Mod1+Enter:: +Open a new terminal emulator window. + +Mod1+d:: +Open dmenu for starting any application by typing (part of) its name. + j/k/l/;:: Direction keys (left, down, up, right). They are on your homerow (see the mark on your "j" key). Alternatively, you can use the cursor keys. @@ -261,19 +268,15 @@ xset -b # Enable zapping (C-A- kills X) setxkbmap -option terminate:ctrl_alt_bksp -# Enforce correct locales from the beginning -unset LC_COLLATE -export LC_CTYPE=de_DE.UTF-8 -export LC_TIME=de_DE.UTF-8 -export LC_NUMERIC=de_DE.UTF-8 -export LC_MONETARY=de_DE.UTF-8 +# Enforce correct locales from the beginning: +# LC_ALL is unset since it overwrites everything +# LANG=de_DE.UTF-8 is used, except for: +# LC_MESSAGES=C never translates program output +# LC_TIME=en_DK leads to yyyy-mm-dd hh:mm date/time output +unset LC_ALL +export LANG=de_DE.UTF-8 export LC_MESSAGES=C -export LC_PAPER=de_DE.UTF-8 -export LC_NAME=de_DE.UTF-8 -export LC_ADDRESS=de_DE.UTF-8 -export LC_TELEPHONE=de_DE.UTF-8 -export LC_MEASUREMENT=de_DE.UTF-8 -export LC_IDENTIFICATION=de_DE.UTF-8 +export LC_TIME=en_DK.UTF-8 # Use XToolkit in java applications export AWT_TOOLKIT=XToolkit diff --git a/man/i3bar.man b/man/i3bar.man new file mode 100644 index 00000000..fcefce7b --- /dev/null +++ b/man/i3bar.man @@ -0,0 +1,67 @@ +i3bar(1) +======== +Axel Wagner +v4.1, October 2011 + +== NAME + +i3bar - xcb-based status- and workspace-bar + +== SYNOPSIS + +*i3bar* [*-s* 'sock_path'] [*-b* 'bar_id'] [*-v*] [*-h*] + +== WARNING + +i3bar will automatically be invoked by i3 for every 'bar' configuration block. + +Starting it manually is usually not what you want to do. + +You have been warned! + +== OPTIONS + +*-s, --socket* 'sock_path':: +Overwrites the path to the i3 IPC socket. + +*-b, --bar_id* 'bar_id':: +Specifies the bar ID for which to get the configuration from i3. + +*-v, --version*:: +Display version number and exit. + +*-h, --help*:: +Display a short help-message and exit + +== DESCRIPTION + +*i3bar* displays a bar at the bottom (or top) of your monitor(s) containing +workspace switching buttons and a statusline generated by i3status(1) or +similar. It is automatically invoked (and configured through) i3. + +i3bar supports colors via a JSON protocol starting from v4.2, see +http://i3wm.org/docs/i3bar-protocol.html + +== ENVIRONMENT + +=== I3SOCK + +Used as a fallback for the i3 IPC socket path if neither the commandline +contains an argument nor the I3_SOCKET_PATH property is set on the X11 root +window. + +== EXAMPLES + +Nothing to see here, move along. As stated above, you should not run i3bar manually. + +Instead, see the i3 documentation, especially the User’s Guide. + +== SEE ALSO + ++i3status(1)+, +j4status(1)+ or +conky(1)+ for programs generating a statusline. + ++dzen2(1)+ or +xmobar(1)+ for similar programs to i3bar. + +== AUTHORS + +Axel Wagner and contributors diff --git a/man/man.mk b/man/man.mk new file mode 100644 index 00000000..f999dc78 --- /dev/null +++ b/man/man.mk @@ -0,0 +1,32 @@ +DISTCLEAN_TARGETS += clean-mans + +A2X = a2x + +A2X_MAN_CALL = $(V_A2X)$(A2X) -f manpage --asciidoc-opts="-f man/asciidoc.conf" $(A2X_FLAGS) $< + +MANS_1 = \ + man/i3.1 \ + man/i3bar.1 \ + man/i3-msg.1 \ + man/i3-input.1 \ + man/i3-nagbar.1 \ + man/i3-config-wizard.1 \ + man/i3-migrate-config-to-v4.1 \ + man/i3-sensible-editor.1 \ + man/i3-sensible-pager.1 \ + man/i3-sensible-terminal.1 \ + man/i3-dump-log.1 + +MANS = \ + $(MANS_1) + +mans: $(MANS) + +$(MANS_1): %.1: %.man man/asciidoc.conf + $(A2X_MAN_CALL) + +clean-mans: + for file in $(notdir $(MANS)); \ + do \ + rm -f man/$${file} man/$${file%.*}.html man/$${file%.*}.xml; \ + done diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index b0fb9e01..b4c9e005 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -66,10 +66,20 @@ state BORDER: border_style = 'normal', 'none', '1pixel', 'toggle' -> call cmd_border($border_style) -# layout default|stacked|stacking|tabbed +# layout default|stacked|stacking|tabbed|splitv|splith +# layout toggle [split|all] state LAYOUT: - layout_mode = 'default', 'stacked', 'stacking', 'tabbed' + layout_mode = 'default', 'stacked', 'stacking', 'tabbed', 'splitv', 'splith' -> call cmd_layout($layout_mode) + 'toggle' + -> LAYOUT_TOGGLE + +# layout toggle [split|all] +state LAYOUT_TOGGLE: + end + -> call cmd_layout_toggle($toggle_mode) + toggle_mode = 'split', 'all' + -> call cmd_layout_toggle($toggle_mode) # append_layout state APPEND_LAYOUT: @@ -190,7 +200,7 @@ state RENAME_WORKSPACE_TO: -> call cmd_rename_workspace($old_name, $new_name) # move [ [px]] -# move [window|container] [to] workspace +# move [window|container] [to] workspace [|next|prev|current] # move [window|container] [to] output # move [window|container] [to] scratchpad # move workspace to [output] @@ -231,7 +241,7 @@ state MOVE_DIRECTION_PX: state MOVE_WORKSPACE: 'to' -> MOVE_WORKSPACE_TO_OUTPUT - workspace = 'next', 'prev', 'next_on_output', 'prev_on_output' + workspace = 'next', 'prev', 'next_on_output', 'prev_on_output', 'current' -> call cmd_move_con_to_workspace($workspace) 'number' -> MOVE_WORKSPACE_NUMBER diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 00000000..6a37f56c --- /dev/null +++ b/src/Makefile @@ -0,0 +1,10 @@ +all: + $(MAKE) -C .. i3 + +install: + $(MAKE) -C .. install-i3 + +clean: + $(MAKE) -C .. clean-i3 + +.PHONY: all install clean diff --git a/src/assignments.c b/src/assignments.c index ae4affaa..655816a3 100644 --- a/src/assignments.c +++ b/src/assignments.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "assignments.c" /* * vim:ts=4:sw=4:expandtab * diff --git a/src/cfgparse.l b/src/cfgparse.l index cdf110d3..8ee2a1da 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -66,6 +66,7 @@ EOL (\r?\n) %x BAR_COLOR %x EXEC +%x OPTRELEASE %% @@ -159,7 +160,7 @@ EOL (\r?\n) return STR; } [^\n]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return STR; } -[a-zA-Z0-9_-]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return OUTPUT; } +[a-zA-Z0-9\/_-]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return OUTPUT; } ^[ \t]*#[^\n]* { return TOKCOMMENT; } #[0-9a-fA-F]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return HEXCOLOR; } {EOL} { @@ -172,12 +173,14 @@ EOL (\r?\n) [ \t]+ { BEGIN(WANT_STRING); } --no-startup-id { printf("no startup id\n"); yy_pop_state(); return TOK_NO_STARTUP_ID; } . { printf("anything else: *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); } +--release { printf("--release\n"); yy_pop_state(); return TOK_RELEASE; } +. { printf("anything else (optrelease): *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); } [0-9-]+ { yylval.number = atoi(yytext); return NUMBER; } bar { yy_push_state(BAR); return TOK_BAR; } mode { return TOKMODE; } bind { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; } -bindcode { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; } -bindsym { yy_push_state(BINDSYM_COND); yy_push_state(EAT_WHITESPACE); return TOKBINDSYM; } +bindcode { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); yy_push_state(OPTRELEASE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; } +bindsym { yy_push_state(BINDSYM_COND); yy_push_state(EAT_WHITESPACE); yy_push_state(OPTRELEASE); yy_push_state(EAT_WHITESPACE); return TOKBINDSYM; } floating_maximum_size { return TOKFLOATING_MAXIMUM_SIZE; } floating_minimum_size { return TOKFLOATING_MINIMUM_SIZE; } floating_modifier { return TOKFLOATING_MODIFIER; } @@ -200,6 +203,8 @@ new_float { return TOKNEWFLOAT; } normal { return TOK_NORMAL; } none { return TOK_NONE; } 1pixel { return TOK_1PIXEL; } +hide_edge_borders { return TOK_HIDE_EDGE_BORDERS; } +both { return TOK_BOTH; } focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; } force_focus_wrapping { return TOK_FORCE_FOCUS_WRAPPING; } force_xinerama { return TOK_FORCE_XINERAMA; } diff --git a/src/cfgparse.y b/src/cfgparse.y index ab8be57c..29c519f0 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -3,6 +3,8 @@ * vim:ts=4:sw=4:expandtab * */ +#undef I3__FILE__ +#define I3__FILE__ "cfgparse.y" #include #include #include @@ -19,6 +21,9 @@ static Barconfig current_bar; * store this in a separate variable because in the i3 config struct we just * store the i3Font. */ static char *font_pattern; +/* The path to the temporary script files used by i3-nagbar. We need to keep + * them around to delete the files in the i3-nagbar SIGCHLD handler. */ +static char *edit_script_path, *pager_script_path; typedef struct yy_buffer_state *YY_BUFFER_STATE; extern int yylex(struct context *context); @@ -233,6 +238,12 @@ static char *migrate_config(char *input, off_t size) { */ static void nagbar_exited(EV_P_ ev_child *watcher, int revents) { ev_child_stop(EV_A_ watcher); + + if (unlink(edit_script_path) != 0) + warn("Could not delete temporary i3-nagbar script %s", edit_script_path); + if (unlink(pager_script_path) != 0) + warn("Could not delete temporary i3-nagbar script %s", pager_script_path); + if (!WIFEXITED(watcher->rstatus)) { fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n"); return; @@ -264,6 +275,23 @@ static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) { } #endif +/* + * Writes the given command as a shell script to path. + * Returns true unless something went wrong. + * + */ +static bool write_nagbar_script(const char *path, const char *command) { + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IXUSR); + if (fd == -1) { + warn("Could not create temporary script to store the nagbar command"); + return false; + } + write(fd, "#!/bin/sh\n", strlen("#!/bin/sh\n")); + write(fd, command, strlen(command)); + close(fd); + return true; +} + /* * Starts an i3-nagbar process which alerts the user that his configuration * file contains one or more errors. Also offers two buttons: One to launch an @@ -276,6 +304,18 @@ static void start_configerror_nagbar(const char *config_path) { return; fprintf(stderr, "Starting i3-nagbar due to configuration errors\n"); + + /* We need to create a custom script containing our actual command + * since not every terminal emulator which is contained in + * i3-sensible-terminal supports -e with multiple arguments (and not + * all of them support -e with one quoted argument either). + * + * NB: The paths need to be unique, that is, don’t assume users close + * their nagbars at any point in time (and they still need to work). + * */ + edit_script_path = get_process_filename("nagbar-cfgerror-edit"); + pager_script_path = get_process_filename("nagbar-cfgerror-pager"); + configerror_pid = fork(); if (configerror_pid == -1) { warn("Could not fork()"); @@ -284,10 +324,17 @@ static void start_configerror_nagbar(const char *config_path) { /* child */ if (configerror_pid == 0) { + char *edit_command, *pager_command; + sasprintf(&edit_command, "i3-sensible-editor \"%s\" && i3-msg reload\n", config_path); + sasprintf(&pager_command, "i3-sensible-pager \"%s\"\n", errorfilename); + if (!write_nagbar_script(edit_script_path, edit_command) || + !write_nagbar_script(pager_script_path, pager_command)) + return; + char *editaction, *pageraction; - sasprintf(&editaction, "i3-sensible-terminal -e sh -c \"i3-sensible-editor \\\"%s\\\" && i3-msg reload\"", config_path); - sasprintf(&pageraction, "i3-sensible-terminal -e i3-sensible-pager \"%s\"", errorfilename); + sasprintf(&editaction, "i3-sensible-terminal -e \"%s\"", edit_script_path); + sasprintf(&pageraction, "i3-sensible-terminal -e \"%s\"", pager_script_path); char *argv[] = { NULL, /* will be replaced by the executable path */ "-t", @@ -384,8 +431,10 @@ static void check_for_duplicate_bindings(struct context *context) { /* Check if the keycodes or modifiers are different. If so, they * can't be duplicate */ if (bind->keycode != current->keycode || - bind->mods != current->mods) + bind->mods != current->mods || + bind->release != current->release) continue; + context->has_errors = true; if (current->keycode != 0) { ELOG("Duplicate keybinding in config file:\n modmask %d with keycode %d, command \"%s\"\n", @@ -690,6 +739,8 @@ void parse_file(const char *f) { %token TOK_NORMAL "normal" %token TOK_NONE "none" %token TOK_1PIXEL "1pixel" +%token TOK_HIDE_EDGE_BORDERS "hide_edge_borders" +%token TOK_BOTH "both" %token TOKFOCUSFOLLOWSMOUSE "focus_follows_mouse" %token TOK_FORCE_FOCUS_WRAPPING "force_focus_wrapping" %token TOK_FORCE_XINERAMA "force_xinerama" @@ -735,6 +786,7 @@ void parse_file(const char *f) { %token TOK_BAR_COLOR_INACTIVE_WORKSPACE "inactive_workspace" %token TOK_BAR_COLOR_URGENT_WORKSPACE "urgent_workspace" %token TOK_NO_STARTUP_ID "--no-startup-id" +%token TOK_RELEASE "--release" %token TOK_MARK "mark" %token TOK_CLASS "class" @@ -754,6 +806,8 @@ void parse_file(const char *f) { %type layout_mode %type border_style %type new_window +%type hide_edge_borders +%type edge_hiding_mode %type new_float %type colorpixel %type bool @@ -762,6 +816,7 @@ void parse_file(const char *f) { %type bar_mode_mode %type bar_modifier_modifier %type optional_no_startup_id +%type optional_release %type command %type word_or_number %type qstring_or_number @@ -788,6 +843,7 @@ line: | workspace_layout | new_window | new_float + | hide_edge_borders | focus_follows_mouse | force_focus_wrapping | force_xinerama @@ -829,33 +885,40 @@ binding: ; bindcode: - binding_modifiers NUMBER command + optional_release binding_modifiers NUMBER command { - printf("\tFound keycode binding mod%d with key %d and command %s\n", $1, $2, $3); + DLOG("bindcode: release = %d, mod = %d, key = %d, command = %s\n", $1, $2, $3, $4); Binding *new = scalloc(sizeof(Binding)); - new->keycode = $2; - new->mods = $1; - new->command = $3; + new->release = $1; + new->keycode = $3; + new->mods = $2; + new->command = $4; $$ = new; } ; bindsym: - binding_modifiers word_or_number command + optional_release binding_modifiers word_or_number command { - printf("\tFound keysym binding mod%d with key %s and command %s\n", $1, $2, $3); + DLOG("bindsym: release = %d, mod = %d, key = %s, command = %s\n", $1, $2, $3, $4); Binding *new = scalloc(sizeof(Binding)); - new->symbol = $2; - new->mods = $1; - new->command = $3; + new->release = $1; + new->symbol = $3; + new->mods = $2; + new->command = $4; $$ = new; } ; +optional_release: + /* empty */ { $$ = B_UPON_KEYPRESS; } + | TOK_RELEASE { $$ = B_UPON_KEYRELEASE; } + ; + for_window: TOK_FOR_WINDOW match command { @@ -1429,6 +1492,22 @@ bool: } ; +hide_edge_borders: + TOK_HIDE_EDGE_BORDERS edge_hiding_mode + { + DLOG("hide edge borders = %d\n", $2); + config.hide_edge_borders = $2; + } + ; + +edge_hiding_mode: + TOK_NONE { $$ = ADJ_NONE; } + | TOK_VERT { $$ = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE; } + | TOK_HORIZ { $$ = ADJ_UPPER_SCREEN_EDGE | ADJ_LOWER_SCREEN_EDGE; } + | TOK_BOTH { $$ = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE | ADJ_UPPER_SCREEN_EDGE | ADJ_LOWER_SCREEN_EDGE; } + | bool { $$ = ($1 ? ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE : ADJ_NONE); } + ; + focus_follows_mouse: TOKFOCUSFOLLOWSMOUSE bool { diff --git a/src/click.c b/src/click.c index ca2a1037..23b6be4f 100644 --- a/src/click.c +++ b/src/click.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "click.c" /* * vim:ts=4:sw=4:expandtab * @@ -35,13 +37,13 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press Con *resize_con = con; while (resize_con->type != CT_WORKSPACE && resize_con->type != CT_FLOATING_CON && - resize_con->parent->orientation != orientation) + con_orientation(resize_con->parent) != orientation) resize_con = resize_con->parent; DLOG("resize_con = %p\n", resize_con); if (resize_con->type != CT_WORKSPACE && resize_con->type != CT_FLOATING_CON && - resize_con->parent->orientation == orientation) { + con_orientation(resize_con->parent) == orientation) { first = resize_con; second = (way == 'n') ? TAILQ_NEXT(first, nodes) : TAILQ_PREV(first, nodes_head, nodes); if (second == TAILQ_END(&(first->nodes_head))) { @@ -145,7 +147,7 @@ static bool tiling_resize(Con *con, xcb_button_press_event_t *event, const click if ((check_con->layout == L_STACKED || check_con->layout == L_TABBED || - check_con->orientation == HORIZ) && + con_orientation(check_con) == HORIZ) && con_num_children(check_con) > 1) { DLOG("Not handling this resize, this container has > 1 child.\n"); return false; diff --git a/src/commands.c b/src/commands.c index 0cd7852b..2d8fce3c 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "commands.c" /* * vim:ts=4:sw=4:expandtab * @@ -23,7 +25,7 @@ } while (0) /** When the command did not include match criteria (!), we use the currently - * focused command. Do not confuse this case with a command which included + * focused container. Do not confuse this case with a command which included * criteria but which did not match any windows. This macro has to be called in * every command. */ @@ -351,7 +353,7 @@ void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue) { /* * Implementation of 'move [window|container] [to] workspace - * next|prev|next_on_output|prev_on_output'. + * next|prev|next_on_output|prev_on_output|current'. * */ void cmd_move_con_to_workspace(I3_CMD, char *which) { @@ -359,6 +361,15 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) { DLOG("which=%s\n", which); + /* We have nothing to move: + * when criteria was specified but didn't match any window or + * when criteria wasn't specified and we don't have any window focused. */ + if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) || + (match_is_empty(current_match) && focused->type == CT_WORKSPACE)) { + ysuccess(false); + return; + } + HANDLE_EMPTY_MATCH; /* get the workspace */ @@ -371,6 +382,8 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) { ws = workspace_next_on_output(); else if (strcmp(which, "prev_on_output") == 0) ws = workspace_prev_on_output(); + else if (strcmp(which, "current") == 0) + ws = con_get_workspace(focused); else { ELOG("BUG: called with which=%s\n", which); ysuccess(false); @@ -400,9 +413,17 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) { owindow *current; - /* Error out early to not create a non-existing workspace (in - * workspace_get()) if we are not actually able to move anything. */ + /* We have nothing to move: + * when criteria was specified but didn't match any window or + * when criteria wasn't specified and we don't have any window focused. */ + if (!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) { + ELOG("No windows match your criteria, cannot move.\n"); + ysuccess(false); + return; + } + if (match_is_empty(current_match) && focused->type == CT_WORKSPACE) { + ELOG("No window to move, you have focused a workspace.\n"); ysuccess(false); return; } @@ -430,14 +451,16 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) { void cmd_move_con_to_workspace_number(I3_CMD, char *which) { owindow *current; - /* Error out early to not create a non-existing workspace (in - * workspace_get()) if we are not actually able to move anything. */ - if (match_is_empty(current_match) && focused->type == CT_WORKSPACE) { + /* We have nothing to move: + * when criteria was specified but didn't match any window or + * when criteria wasn't specified and we don't have any window focused. */ + if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) || + (match_is_empty(current_match) && focused->type == CT_WORKSPACE)) { ysuccess(false); return; } - LOG("should move window to workspace with number %d\n", which); + LOG("should move window to workspace %s\n", which); /* get the workspace */ Con *output, *workspace = NULL; @@ -463,14 +486,7 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { child->num == parsed_num); if (!workspace) { - y(map_open); - ystr("success"); - y(bool, false); - ystr("error"); - // TODO: better error message - ystr("No such workspace"); - y(map_close); - return; + workspace = workspace_get(which, NULL); } HANDLE_EMPTY_MATCH; @@ -504,6 +520,8 @@ static bool cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int LOG("tiling resize\n"); /* get the appropriate current container (skip stacked/tabbed cons) */ Con *current = focused; + Con *other = NULL; + double percentage = 0; while (current->parent->layout == L_STACKED || current->parent->layout == L_TABBED) current = current->parent; @@ -512,40 +530,50 @@ static bool cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int orientation_t search_orientation = (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0 ? HORIZ : VERT); - while (current->type != CT_WORKSPACE && - current->type != CT_FLOATING_CON && - current->parent->orientation != search_orientation) - current = current->parent; + do { + if (con_orientation(current->parent) != search_orientation) { + current = current->parent; + continue; + } - /* get the default percentage */ - int children = con_num_children(current->parent); - Con *other; - LOG("ins. %d children\n", children); - double percentage = 1.0 / children; - LOG("default percentage = %f\n", percentage); + /* get the default percentage */ + int children = con_num_children(current->parent); + LOG("ins. %d children\n", children); + percentage = 1.0 / children; + LOG("default percentage = %f\n", percentage); - orientation_t orientation = current->parent->orientation; + orientation_t orientation = con_orientation(current->parent); - if ((orientation == HORIZ && - (strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0)) || - (orientation == VERT && - (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0))) { - LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n", - (orientation == HORIZ ? "horizontal" : "vertical")); - ysuccess(false); - return false; - } + if ((orientation == HORIZ && + (strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0)) || + (orientation == VERT && + (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0))) { + LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n", + (orientation == HORIZ ? "horizontal" : "vertical")); + ysuccess(false); + return false; + } - if (strcmp(direction, "up") == 0 || strcmp(direction, "left") == 0) { - other = TAILQ_PREV(current, nodes_head, nodes); - } else { - other = TAILQ_NEXT(current, nodes); - } - if (other == TAILQ_END(workspaces)) { - LOG("No other container in this direction found, cannot resize.\n"); + if (strcmp(direction, "up") == 0 || strcmp(direction, "left") == 0) { + other = TAILQ_PREV(current, nodes_head, nodes); + } else { + other = TAILQ_NEXT(current, nodes); + } + if (other == TAILQ_END(workspaces)) { + LOG("No other container in this direction found, trying to look further up in the tree...\n"); + current = current->parent; + continue; + } + break; + } while (current->type != CT_WORKSPACE && + current->type != CT_FLOATING_CON); + + if (other == NULL) { + LOG("No other container in this direction found, trying to look further up in the tree...\n"); ysuccess(false); return false; } + LOG("other->percent = %f\n", other->percent); LOG("current->percent before = %f\n", current->percent); if (current->percent == 0.0) @@ -585,7 +613,7 @@ static bool cmd_resize_tiling_width_height(I3_CMD, char *way, char *direction, i while (current->type != CT_WORKSPACE && current->type != CT_FLOATING_CON && - current->parent->orientation != search_orientation) + con_orientation(current->parent) != search_orientation) current = current->parent; /* get the default percentage */ @@ -594,7 +622,7 @@ static bool cmd_resize_tiling_width_height(I3_CMD, char *way, char *direction, i double percentage = 1.0 / children; LOG("default percentage = %f\n", percentage); - orientation_t orientation = current->parent->orientation; + orientation_t orientation = con_orientation(current->parent); if ((orientation == HORIZ && strcmp(direction, "height") == 0) || @@ -808,17 +836,15 @@ void cmd_workspace_number(I3_CMD, char *which) { child->num == parsed_num); if (!workspace) { - LOG("There is no workspace with number %d, creating a new one.\n", parsed_num); + LOG("There is no workspace with number %ld, creating a new one.\n", parsed_num); ysuccess(true); /* terminate the which string after the endposition of the number */ *endptr = '\0'; - if (maybe_back_and_forth(cmd_output, which)) - return; workspace_show_by_name(which); cmd_output->needs_tree_render = true; return; } - if (maybe_back_and_forth(cmd_output, which)) + if (maybe_back_and_forth(cmd_output, workspace->name)) return; workspace_show(workspace); @@ -1088,9 +1114,17 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) { * */ void cmd_split(I3_CMD, char *direction) { + owindow *current; /* TODO: use matches */ LOG("splitting in direction %c\n", direction[0]); - tree_split(focused, (direction[0] == 'v' ? VERT : HORIZ)); + if (match_is_empty(current_match)) + tree_split(focused, (direction[0] == 'v' ? VERT : HORIZ)); + else { + TAILQ_FOREACH(current, &owindows, owindows) { + DLOG("matching: %p / %s\n", current->con, current->con->name); + tree_split(current->con, (direction[0] == 'v' ? VERT : HORIZ)); + } + } cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply @@ -1226,23 +1260,26 @@ void cmd_focus_window_mode(I3_CMD, char *window_mode) { * */ void cmd_focus_level(I3_CMD, char *level) { - if (focused && - focused->type != CT_WORKSPACE && - focused->fullscreen_mode != CF_NONE) { - LOG("Cannot change focus while in fullscreen mode.\n"); - ysuccess(false); - return; - } - DLOG("level = %s\n", level); + bool success = false; + + /* Focusing the parent can only be allowed if the newly + * focused container won't escape the fullscreen container. */ + if (strcmp(level, "parent") == 0) { + if (focused && focused->parent) { + if (con_fullscreen_permits_focusing(focused->parent)) + success = level_up(); + else + ELOG("'focus parent': Currently in fullscreen, not going up\n"); + } + } - if (strcmp(level, "parent") == 0) - level_up(); - else level_down(); + /* Focusing a child should always be allowed. */ + else success = level_down(); - cmd_output->needs_tree_render = true; + cmd_output->needs_tree_render = success; // XXX: default reply for now, make this a better reply - ysuccess(true); + ysuccess(success); } /* @@ -1275,13 +1312,9 @@ void cmd_focus(I3_CMD) { if (!ws) continue; - /* Don't allow the focus switch if the focused and current - * containers are in the same workspace. */ - if (focused && - focused->type != CT_WORKSPACE && - focused->fullscreen_mode != CF_NONE && - con_get_workspace(focused) == ws) { - LOG("Cannot change focus while in fullscreen mode (same workspace).\n"); + /* Check the fullscreen focus constraints. */ + if (!con_fullscreen_permits_focusing(current->con)) { + LOG("Cannot change focus while in fullscreen mode (fullscreen rules).\n"); ysuccess(false); return; } @@ -1376,21 +1409,35 @@ void cmd_move_direction(I3_CMD, char *direction, char *move_px) { } /* - * Implementation of 'layout default|stacked|stacking|tabbed'. + * Implementation of 'layout default|stacked|stacking|tabbed|splitv|splith'. * */ void cmd_layout(I3_CMD, char *layout_str) { if (strcmp(layout_str, "stacking") == 0) layout_str = "stacked"; - DLOG("changing layout to %s\n", layout_str); owindow *current; - int layout = (strcmp(layout_str, "default") == 0 ? L_DEFAULT : - (strcmp(layout_str, "stacked") == 0 ? L_STACKED : - L_TABBED)); + int layout; + /* default is a special case which will be handled in con_set_layout(). */ + if (strcmp(layout_str, "default") == 0) + layout = L_DEFAULT; + else if (strcmp(layout_str, "stacked") == 0) + layout = L_STACKED; + else if (strcmp(layout_str, "tabbed") == 0) + layout = L_TABBED; + else if (strcmp(layout_str, "splitv") == 0) + layout = L_SPLITV; + else if (strcmp(layout_str, "splith") == 0) + layout = L_SPLITH; + else { + ELOG("Unknown layout \"%s\", this is a mismatch between code and parser spec.\n", layout_str); + return; + } + + DLOG("changing layout to %s (%d)\n", layout_str, layout); /* check if the match is empty, not if the result is empty */ if (match_is_empty(current_match)) - con_set_layout(focused->parent, layout); + con_set_layout(focused, layout); else { TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); @@ -1403,12 +1450,40 @@ void cmd_layout(I3_CMD, char *layout_str) { ysuccess(true); } +/* + * Implementation of 'layout toggle [all|split]'. + * + */ +void cmd_layout_toggle(I3_CMD, char *toggle_mode) { + owindow *current; + + if (toggle_mode == NULL) + toggle_mode = "default"; + + DLOG("toggling layout (mode = %s)\n", toggle_mode); + + /* check if the match is empty, not if the result is empty */ + if (match_is_empty(current_match)) + con_toggle_layout(focused, toggle_mode); + else { + TAILQ_FOREACH(current, &owindows, owindows) { + DLOG("matching: %p / %s\n", current->con, current->con->name); + con_toggle_layout(current->con, toggle_mode); + } + } + + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + ysuccess(true); +} + /* * Implementaiton of 'exit'. * */ void cmd_exit(I3_CMD) { LOG("Exiting due to user command.\n"); + xcb_disconnect(conn); exit(0); /* unreached */ @@ -1421,6 +1496,7 @@ void cmd_exit(I3_CMD) { void cmd_reload(I3_CMD) { LOG("reloading\n"); kill_configerror_nagbar(false); + kill_commanderror_nagbar(false); load_configuration(conn, NULL, true); x_set_i3_atoms(); /* Send an IPC event just in case the ws names have changed */ @@ -1449,6 +1525,7 @@ void cmd_restart(I3_CMD) { void cmd_open(I3_CMD) { LOG("opening new container\n"); Con *con = tree_open_con(NULL, NULL); + con->layout = L_SPLITH; con_focus(con); y(map_open); diff --git a/src/commands_parser.c b/src/commands_parser.c index 73a14565..d739f4e1 100644 --- a/src/commands_parser.c +++ b/src/commands_parser.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "commands_parser.c" /* * vim:ts=4:sw=4:expandtab * @@ -104,7 +106,6 @@ static void push_string(const char *identifier, char *str) { // XXX: ideally, this would be const char. need to check if that works with all // called functions. static char *get_string(const char *identifier) { - DLOG("Getting string %s from stack...\n", identifier); for (int c = 0; c < 10; c++) { if (stack[c].identifier == NULL) break; @@ -115,7 +116,6 @@ static char *get_string(const char *identifier) { } static void clear_stack(void) { - DLOG("clearing stack.\n"); for (int c = 0; c < 10; c++) { if (stack[c].str != NULL) free(stack[c].str); @@ -187,8 +187,6 @@ static struct CommandResult command_output; static void next_state(const cmdp_token *token) { if (token->next_state == __CALL) { - DLOG("should call stuff, yay. call_id = %d\n", - token->extra.call_identifier); subcommand_output.json_gen = command_output.json_gen; subcommand_output.needs_tree_render = false; GENERATED_call(token->extra.call_identifier, &subcommand_output); @@ -206,9 +204,8 @@ static void next_state(const cmdp_token *token) { } } -/* TODO: Return parsing errors via JSON. */ struct CommandResult *parse_command(const char *input) { - DLOG("new parser handling: %s\n", input); + DLOG("COMMAND: *%s*\n", input); state = INITIAL; /* A YAJL JSON generator used for formatting replies. */ @@ -240,19 +237,14 @@ struct CommandResult *parse_command(const char *input) { *walk == '\r' || *walk == '\n') && *walk != '\0') walk++; - DLOG("remaining input = %s\n", walk); - cmdp_token_ptr *ptr = &(tokens[state]); token_handled = false; for (c = 0; c < ptr->n; c++) { token = &(ptr->array[c]); - DLOG("trying token %d = %s\n", c, token->name); /* A literal. */ if (token->name[0] == '\'') { - DLOG("literal\n"); if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) { - DLOG("found literal, moving to next state\n"); if (token->identifier != NULL) push_string(token->identifier, sstrdup(token->name + 1)); walk += strlen(token->name) - 1; @@ -265,7 +257,6 @@ struct CommandResult *parse_command(const char *input) { if (strcmp(token->name, "string") == 0 || strcmp(token->name, "word") == 0) { - DLOG("parsing this as a string\n"); const char *beginning = walk; /* Handle quoted strings (or words). */ if (*walk == '"') { @@ -310,7 +301,6 @@ struct CommandResult *parse_command(const char *input) { } if (token->identifier) push_string(token->identifier, str); - DLOG("str is \"%s\"\n", str); /* If we are at the end of a quoted string, skip the ending * double quote. */ if (*walk == '"') @@ -322,9 +312,7 @@ struct CommandResult *parse_command(const char *input) { } if (strcmp(token->name, "end") == 0) { - DLOG("checking for the end token.\n"); if (*walk == '\0' || *walk == ',' || *walk == ';') { - DLOG("yes, indeed. end\n"); next_state(token); token_handled = true; /* To make sure we start with an appropriate matching @@ -389,14 +377,19 @@ struct CommandResult *parse_command(const char *input) { position[(copywalk - input)] = (copywalk >= walk ? '^' : ' '); position[len] = '\0'; - printf("%s\n", errormessage); - printf("Your command: %s\n", input); - printf(" %s\n", position); + ELOG("%s\n", errormessage); + ELOG("Your command: %s\n", input); + ELOG(" %s\n", position); /* Format this error message as a JSON reply. */ y(map_open); ystr("success"); y(bool, false); + /* We set parse_error to true to distinguish this from other + * errors. i3-nagbar is spawned upon keypresses only for parser + * errors. */ + ystr("parse_error"); + y(bool, true); ystr("error"); ystr(errormessage); ystr("input"); @@ -414,7 +407,6 @@ struct CommandResult *parse_command(const char *input) { y(array_close); - DLOG("command_output.needs_tree_render = %d\n", command_output.needs_tree_render); return &command_output; } @@ -427,15 +419,23 @@ struct CommandResult *parse_command(const char *input) { /* * Logs the given message to stdout while prefixing the current time to it, - * but only if the corresponding debug loglevel was activated. + * but only if debug logging was activated. * This is to be called by DLOG() which includes filename/linenumber * */ -void debuglog(uint64_t lev, char *fmt, ...) { +void debuglog(char *fmt, ...) { + va_list args; + + va_start(args, fmt); + fprintf(stdout, "# "); + vfprintf(stdout, fmt, args); + va_end(args); +} + +void errorlog(char *fmt, ...) { va_list args; va_start(args, fmt); - fprintf(stderr, "# "); vfprintf(stderr, fmt, args); va_end(args); } diff --git a/src/con.c b/src/con.c index 23cf1568..1d86d73a 100644 --- a/src/con.c +++ b/src/con.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "con.c" /* * vim:ts=4:sw=4:expandtab * @@ -217,8 +219,8 @@ bool con_accepts_window(Con *con) { if (con->type == CT_WORKSPACE) return false; - if (con->orientation != NO_ORIENTATION) { - DLOG("container %p does not accepts windows, orientation != NO_ORIENTATION\n", con); + if (con->split) { + DLOG("container %p does not accept windows, it is a split container.\n", con); return false; } @@ -265,8 +267,11 @@ Con *con_parent_with_orientation(Con *con, orientation_t orientation) { while (con_orientation(parent) != orientation) { DLOG("Need to go one level further up\n"); parent = parent->parent; - /* Abort when we reach a floating con */ - if (parent && parent->type == CT_FLOATING_CON) + /* Abort when we reach a floating con, or an output con */ + if (parent && + (parent->type == CT_FLOATING_CON || + parent->type == CT_OUTPUT || + (parent->parent && parent->parent->type == CT_OUTPUT))) parent = NULL; if (parent == NULL) break; @@ -576,11 +581,27 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool return; } + /* Prevent moving if this would violate the fullscreen focus restrictions. */ + if (!con_fullscreen_permits_focusing(workspace)) { + LOG("Cannot move out of a fullscreen container"); + return; + } + if (con_is_floating(con)) { DLOG("Using FLOATINGCON instead\n"); con = con->parent; } + Con *source_ws = con_get_workspace(con); + if (workspace == source_ws) { + DLOG("Not moving, already there\n"); + return; + } + + /* Save the current workspace. So we can call workspace_show() by the end + * of this function. */ + Con *current_ws = con_get_workspace(focused); + Con *source_output = con_get_output(con), *dest_output = con_get_output(workspace); @@ -665,8 +686,12 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool /* Descend focus stack in case focus_next is a workspace which can * occur if we move to the same workspace. Also show current workspace * to ensure it is focused. */ - workspace_show(con_get_workspace(focus_next)); - con_focus(con_descend_focused(focus_next)); + workspace_show(current_ws); + + /* Set focus only if con was on current workspace before moving. + * Otherwise we would give focus to some window on different workspace. */ + if (source_ws == current_ws) + con_focus(con_descend_focused(focus_next)); } CALL(parent, on_remove_child); @@ -679,14 +704,32 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool * */ int con_orientation(Con *con) { - /* stacking containers behave like they are in vertical orientation */ - if (con->layout == L_STACKED) - return VERT; - - if (con->layout == L_TABBED) - return HORIZ; - - return con->orientation; + switch (con->layout) { + case L_SPLITV: + /* stacking containers behave like they are in vertical orientation */ + case L_STACKED: + return VERT; + + case L_SPLITH: + /* tabbed containers behave like they are in vertical orientation */ + case L_TABBED: + return HORIZ; + + case L_DEFAULT: + DLOG("Someone called con_orientation() on a con with L_DEFAULT, this is a bug in the code.\n"); + assert(false); + return HORIZ; + + case L_DOCKAREA: + case L_OUTPUT: + DLOG("con_orientation() called on dockarea/output (%d) container %p\n", con->layout, con); + assert(false); + return HORIZ; + + default: + DLOG("con_orientation() ran into default\n"); + assert(false); + } } /* @@ -895,12 +938,46 @@ Con *con_descend_direction(Con *con, direction_t direction) { * */ Rect con_border_style_rect(Con *con) { - switch (con_border_style(con)) { + adjacent_t borders_to_hide = ADJ_NONE; + Rect result; + /* Shortcut to avoid calling con_adjacent_borders() on dock containers. */ + int border_style = con_border_style(con); + if (border_style == BS_NONE) + return (Rect){ 0, 0, 0, 0 }; + borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders; + switch (border_style) { case BS_NORMAL: - return (Rect){2, 0, -(2 * 2), -2}; + result = (Rect){2, 0, -(2 * 2), -2}; + if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) { + result.x -= 2; + result.width += 2; + } + if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) { + result.width += 2; + } + /* With normal borders we never hide the upper border */ + if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) { + result.height += 2; + } + return result; case BS_1PIXEL: - return (Rect){1, 1, -2, -2}; + result = (Rect){1, 1, -2, -2}; + if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) { + result.x -= 1; + result.width += 1; + } + if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) { + result.width += 1; + } + if (borders_to_hide & ADJ_UPPER_SCREEN_EDGE) { + result.y -= 1; + result.height += 1; + } + if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) { + result.height += 1; + } + return result; case BS_NONE: return (Rect){0, 0, 0, 0}; @@ -910,6 +987,24 @@ Rect con_border_style_rect(Con *con) { } } +/* + * Returns adjacent borders of the window. We need this if hide_edge_borders is + * enabled. + */ +adjacent_t con_adjacent_borders(Con *con) { + adjacent_t result = ADJ_NONE; + Con *workspace = con_get_workspace(con); + if (con->rect.x == workspace->rect.x) + result |= ADJ_LEFT_SCREEN_EDGE; + if (con->rect.x + con->rect.width == workspace->rect.x + workspace->rect.width) + result |= ADJ_RIGHT_SCREEN_EDGE; + if (con->rect.y == workspace->rect.y) + result |= ADJ_UPPER_SCREEN_EDGE; + if (con->rect.y + con->rect.height == workspace->rect.y + workspace->rect.height) + result |= ADJ_LOWER_SCREEN_EDGE; + return result; +} + /* * Use this function to get a container’s border style. This is important * because when inside a stack, the border style is always BS_NORMAL. @@ -989,33 +1084,43 @@ void con_set_border_style(Con *con, int border_style) { * */ void con_set_layout(Con *con, int layout) { + DLOG("con_set_layout(%p, %d), con->type = %d\n", + con, layout, con->type); + + /* Users can focus workspaces, but not any higher in the hierarchy. + * Focus on the workspace is a special case, since in every other case, the + * user means "change the layout of the parent split container". */ + if (con->type != CT_WORKSPACE) + con = con->parent; + + /* We fill in last_split_layout when switching to a different layout + * since there are many places in the code that don’t use + * con_set_layout(). */ + if (con->layout == L_SPLITH || con->layout == L_SPLITV) + con->last_split_layout = con->layout; + /* When the container type is CT_WORKSPACE, the user wants to change the * whole workspace into stacked/tabbed mode. To do this and still allow * intuitive operations (like level-up and then opening a new window), we * need to create a new split container. */ - if (con->type == CT_WORKSPACE) { + if (con->type == CT_WORKSPACE && + (layout == L_STACKED || layout == L_TABBED)) { DLOG("Creating new split container\n"); /* 1: create a new split container */ Con *new = con_new(NULL, NULL); new->parent = con; - /* 2: set the requested layout on the split con */ + /* 2: Set the requested layout on the split container and mark it as + * split. */ new->layout = layout; - - /* 3: While the layout is irrelevant in stacked/tabbed mode, it needs - * to be set. Otherwise, this con will not be interpreted as a split - * container. */ - if (config.default_orientation == NO_ORIENTATION) { - new->orientation = (con->rect.height > con->rect.width) ? VERT : HORIZ; - } else { - new->orientation = config.default_orientation; - } + new->last_split_layout = con->last_split_layout; + new->split = true; Con *old_focused = TAILQ_FIRST(&(con->focus_head)); if (old_focused == TAILQ_END(&(con->focus_head))) old_focused = NULL; - /* 4: move the existing cons of this workspace below the new con */ + /* 3: move the existing cons of this workspace below the new con */ DLOG("Moving cons\n"); Con *child; while (!TAILQ_EMPTY(&(con->nodes_head))) { @@ -1036,7 +1141,72 @@ void con_set_layout(Con *con, int layout) { return; } - con->layout = layout; + if (layout == L_DEFAULT) { + /* Special case: the layout formerly known as "default" (in combination + * with an orientation). Since we switched to splith/splitv layouts, + * using the "default" layout (which "only" should happen when using + * legacy configs) is using the last split layout (either splith or + * splitv) in order to still do the same thing. + * + * Starting from v4.6 though, we will nag users about using "layout + * default", and in v4.9 we will remove it entirely (with an + * appropriate i3-migrate-config mechanism). */ + con->layout = con->last_split_layout; + /* In case last_split_layout was not initialized… */ + if (con->layout == L_DEFAULT) + con->layout = L_SPLITH; + } else { + con->layout = layout; + } +} + +/* + * This function toggles the layout of a given container. toggle_mode can be + * either 'default' (toggle only between stacked/tabbed/last_split_layout), + * 'split' (toggle only between splitv/splith) or 'all' (toggle between all + * layouts). + * + */ +void con_toggle_layout(Con *con, const char *toggle_mode) { + Con *parent = con; + /* Users can focus workspaces, but not any higher in the hierarchy. + * Focus on the workspace is a special case, since in every other case, the + * user means "change the layout of the parent split container". */ + if (con->type != CT_WORKSPACE) + parent = con->parent; + DLOG("con_toggle_layout(%p, %s), parent = %p\n", con, toggle_mode, parent); + + if (strcmp(toggle_mode, "split") == 0) { + /* Toggle between splits. When the current layout is not a split + * layout, we just switch back to last_split_layout. Otherwise, we + * change to the opposite split layout. */ + if (parent->layout != L_SPLITH && parent->layout != L_SPLITV) + con_set_layout(con, parent->last_split_layout); + else { + if (parent->layout == L_SPLITH) + con_set_layout(con, L_SPLITV); + else con_set_layout(con, L_SPLITH); + } + } else { + if (parent->layout == L_STACKED) + con_set_layout(con, L_TABBED); + else if (parent->layout == L_TABBED) { + if (strcmp(toggle_mode, "all") == 0) + con_set_layout(con, L_SPLITH); + else con_set_layout(con, parent->last_split_layout); + } else if (parent->layout == L_SPLITH || parent->layout == L_SPLITV) { + if (strcmp(toggle_mode, "all") == 0) { + /* When toggling through all modes, we toggle between + * splith/splitv, whereas normally we just directly jump to + * stacked. */ + if (parent->layout == L_SPLITH) + con_set_layout(con, L_SPLITV); + else con_set_layout(con, L_STACKED); + } else { + con_set_layout(con, L_STACKED); + } + } + } } /* @@ -1113,12 +1283,12 @@ Rect con_minimum_size(Con *con) { /* For horizontal/vertical split containers we sum up the width (h-split) * or height (v-split) and use the maximum of the height (h-split) or width * (v-split) as minimum size. */ - if (con->orientation == HORIZ || con->orientation == VERT) { + if (con->split) { uint32_t width = 0, height = 0; Con *child; TAILQ_FOREACH(child, &(con->nodes_head), nodes) { Rect min = con_minimum_size(child); - if (con->orientation == HORIZ) { + if (con->layout == L_SPLITH) { width += min.width; height = max(height, min.height); } else { @@ -1130,7 +1300,70 @@ Rect con_minimum_size(Con *con) { return (Rect){ 0, 0, width, height }; } - ELOG("Unhandled case, type = %d, layout = %d, orientation = %d\n", - con->type, con->layout, con->orientation); + ELOG("Unhandled case, type = %d, layout = %d, split = %d\n", + con->type, con->layout, con->split); assert(false); } + +/* + * Returns true if changing the focus to con would be allowed considering + * the fullscreen focus constraints. Specifically, if a fullscreen container or + * any of its descendants is focused, this function returns true if and only if + * focusing con would mean that focus would still be visible on screen, i.e., + * the newly focused container would not be obscured by a fullscreen container. + * + * In the simplest case, if a fullscreen container or any of its descendants is + * fullscreen, this functions returns true if con is the fullscreen container + * itself or any of its descendants, as this means focus wouldn't escape the + * boundaries of the fullscreen container. + * + * In case the fullscreen container is of type CF_OUTPUT, this function returns + * true if con is on a different workspace, as focus wouldn't be obscured by + * the fullscreen container that is constrained to a different workspace. + * + * Note that this same logic can be applied to moving containers. If a + * container can be focused under the fullscreen focus constraints, it can also + * become a parent or sibling to the currently focused container. + * + */ +bool con_fullscreen_permits_focusing(Con *con) { + /* No focus, no problem. */ + if (!focused) + return true; + + /* Find the first fullscreen ascendent. */ + Con *fs = focused; + while (fs && fs->fullscreen_mode == CF_NONE) + fs = fs->parent; + + /* fs must be non-NULL since the workspace con doesn’t have CF_NONE and + * there always has to be a workspace con in the hierarchy. */ + assert(fs != NULL); + /* The most common case is we hit the workspace level. In this + * situation, changing focus is also harmless. */ + assert(fs->fullscreen_mode != CF_NONE); + if (fs->type == CT_WORKSPACE) + return true; + + /* Allow it if the container itself is the fullscreen container. */ + if (con == fs) + return true; + + /* If fullscreen is per-output, the focus being in a different workspace is + * sufficient to guarantee that change won't leave fullscreen in bad shape. */ + if (fs->fullscreen_mode == CF_OUTPUT && + con_get_workspace(con) != con_get_workspace(fs)) { + return true; + } + + /* Allow it only if the container to be focused is contained within the + * current fullscreen container. */ + do { + if (con->parent == fs) + return true; + con = con->parent; + } while (con); + + /* Focusing con would hide it behind a fullscreen window, disallow it. */ + return false; +} diff --git a/src/config.c b/src/config.c index 50ec2823..fcf3841e 100644 --- a/src/config.c +++ b/src/config.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "config.c" /* * vim:ts=4:sw=4:expandtab * @@ -24,27 +26,27 @@ struct barconfig_head barconfigs = TAILQ_HEAD_INITIALIZER(barconfigs); * */ void ungrab_all_keys(xcb_connection_t *conn) { - DLOG("Ungrabbing all keys\n"); - xcb_ungrab_key(conn, XCB_GRAB_ANY, root, XCB_BUTTON_MASK_ANY); + DLOG("Ungrabbing all keys\n"); + xcb_ungrab_key(conn, XCB_GRAB_ANY, root, XCB_BUTTON_MASK_ANY); } static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint32_t keycode) { - DLOG("Grabbing %d\n", keycode); - /* Grab the key in all combinations */ - #define GRAB_KEY(modifier) \ - do { \ - xcb_grab_key(conn, 0, root, modifier, keycode, \ - XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); \ - } while (0) - int mods = bind->mods; - if ((bind->mods & BIND_MODE_SWITCH) != 0) { - mods &= ~BIND_MODE_SWITCH; - if (mods == 0) - mods = XCB_MOD_MASK_ANY; - } - GRAB_KEY(mods); - GRAB_KEY(mods | xcb_numlock_mask); - GRAB_KEY(mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK); + DLOG("Grabbing %d\n", keycode); + /* Grab the key in all combinations */ + #define GRAB_KEY(modifier) \ + do { \ + xcb_grab_key(conn, 0, root, modifier, keycode, \ + XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); \ + } while (0) + int mods = bind->mods; + if ((bind->mods & BIND_MODE_SWITCH) != 0) { + mods &= ~BIND_MODE_SWITCH; + if (mods == 0) + mods = XCB_MOD_MASK_ANY; + } + GRAB_KEY(mods); + GRAB_KEY(mods | xcb_numlock_mask); + GRAB_KEY(mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK); } /* @@ -52,29 +54,55 @@ static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint * or NULL if no such binding exists. * */ -Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode) { - Binding *bind; +Binding *get_binding(uint16_t modifiers, bool key_release, xcb_keycode_t keycode) { + Binding *bind; + if (!key_release) { + /* On a KeyPress event, we first reset all + * B_UPON_KEYRELEASE_IGNORE_MODS bindings back to B_UPON_KEYRELEASE */ TAILQ_FOREACH(bind, bindings, bindings) { - /* First compare the modifiers */ - if (bind->mods != modifiers) - continue; - - /* If a symbol was specified by the user, we need to look in - * the array of translated keycodes for the event’s keycode */ - if (bind->symbol != NULL) { - if (memmem(bind->translated_to, - bind->number_keycodes * sizeof(xcb_keycode_t), - &keycode, sizeof(xcb_keycode_t)) != NULL) - break; - } else { - /* This case is easier: The user specified a keycode */ - if (bind->keycode == keycode) - break; - } + if (bind->release == B_UPON_KEYRELEASE_IGNORE_MODS) + bind->release = B_UPON_KEYRELEASE; + } + + /* Then we transition the KeyRelease bindings into a state where the + * modifiers no longer matter for the KeyRelease event so that users + * can release the modifier key before releasing the actual key. */ + TAILQ_FOREACH(bind, bindings, bindings) { + if (bind->release == B_UPON_KEYRELEASE && !key_release) + bind->release = B_UPON_KEYRELEASE_IGNORE_MODS; + } + } + + TAILQ_FOREACH(bind, bindings, bindings) { + /* First compare the modifiers (unless this is a + * B_UPON_KEYRELEASE_IGNORE_MODS binding and this is a KeyRelease + * event) */ + if (bind->mods != modifiers && + (bind->release != B_UPON_KEYRELEASE_IGNORE_MODS || + !key_release)) + continue; + + /* Check if the binding is for a KeyPress or a KeyRelease event */ + if ((bind->release == B_UPON_KEYPRESS && key_release) || + (bind->release >= B_UPON_KEYRELEASE && !key_release)) + continue; + + /* If a symbol was specified by the user, we need to look in + * the array of translated keycodes for the event’s keycode */ + if (bind->symbol != NULL) { + if (memmem(bind->translated_to, + bind->number_keycodes * sizeof(xcb_keycode_t), + &keycode, sizeof(xcb_keycode_t)) != NULL) + break; + } else { + /* This case is easier: The user specified a keycode */ + if (bind->keycode == keycode) + break; } + } - return (bind == TAILQ_END(bindings) ? NULL : bind); + return (bind == TAILQ_END(bindings) ? NULL : bind); } /* @@ -131,22 +159,22 @@ void translate_keysyms(void) { * */ void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) { - Binding *bind; - TAILQ_FOREACH(bind, bindings, bindings) { - if ((bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) == 0) || - (!bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) != 0)) - continue; - - /* The easy case: the user specified a keycode directly. */ - if (bind->keycode > 0) { - grab_keycode_for_binding(conn, bind, bind->keycode); - continue; - } - - xcb_keycode_t *walk = bind->translated_to; - for (int i = 0; i < bind->number_keycodes; i++) - grab_keycode_for_binding(conn, bind, *walk++); + Binding *bind; + TAILQ_FOREACH(bind, bindings, bindings) { + if ((bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) == 0) || + (!bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) != 0)) + continue; + + /* The easy case: the user specified a keycode directly. */ + if (bind->keycode > 0) { + grab_keycode_for_binding(conn, bind, bind->keycode); + continue; } + + xcb_keycode_t *walk = bind->translated_to; + for (int i = 0; i < bind->number_keycodes; i++) + grab_keycode_for_binding(conn, bind, *walk++); + } } /* @@ -154,22 +182,22 @@ void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) { * */ void switch_mode(const char *new_mode) { - struct Mode *mode; + struct Mode *mode; - LOG("Switching to mode %s\n", new_mode); + LOG("Switching to mode %s\n", new_mode); - SLIST_FOREACH(mode, &modes, modes) { - if (strcasecmp(mode->name, new_mode) != 0) - continue; + SLIST_FOREACH(mode, &modes, modes) { + if (strcasecmp(mode->name, new_mode) != 0) + continue; - ungrab_all_keys(conn); - bindings = mode->bindings; - translate_keysyms(); - grab_all_keys(conn, false); - return; - } + ungrab_all_keys(conn); + bindings = mode->bindings; + translate_keysyms(); + grab_all_keys(conn, false); + return; + } - ELOG("ERROR: Mode not found\n"); + ELOG("ERROR: Mode not found\n"); } /* @@ -392,7 +420,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, grab_all_keys(conn, false); } - if (config.font.id == 0) { + if (config.font.type == FONT_TYPE_NONE) { ELOG("You did not specify required configuration option \"font\"\n"); config.font = load_font("fixed", true); set_font(&config.font); diff --git a/src/debug.c b/src/debug.c index 30822353..2dcdb56a 100644 --- a/src/debug.c +++ b/src/debug.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "debug.c" /* * vim:ts=4:sw=4:expandtab * diff --git a/src/display_version.c b/src/display_version.c new file mode 100644 index 00000000..ac1a622c --- /dev/null +++ b/src/display_version.c @@ -0,0 +1,172 @@ +#undef I3__FILE__ +#define I3__FILE__ "key_press.c" +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) + * + * display_version.c: displays the running i3 version, runs as part of + * i3 --moreversion. + * + */ +#include +#include +#include +#include +#include +#include +#include "all.h" + +static bool human_readable_key; +static char *human_readable_version; + +#if YAJL_MAJOR >= 2 +static int version_string(void *ctx, const unsigned char *val, size_t len) { +#else +static int version_string(void *ctx, const unsigned char *val, unsigned int len) { +#endif + if (human_readable_key) + sasprintf(&human_readable_version, "%.*s", (int)len, val); + return 1; +} + +#if YAJL_MAJOR >= 2 +static int version_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) { +#else +static int version_map_key(void *ctx, const unsigned char *stringval, unsigned int stringlen) { +#endif + human_readable_key = (stringlen == strlen("human_readable") && + strncmp((const char*)stringval, "human_readable", strlen("human_readable")) == 0); + return 1; +} + +static yajl_callbacks version_callbacks = { + NULL, /* null */ + NULL, /* boolean */ + NULL, /* integer */ + NULL, /* double */ + NULL, /* number */ + &version_string, + NULL, /* start_map */ + &version_map_key, + NULL, /* end_map */ + NULL, /* start_array */ + NULL /* end_array */ +}; + +/* + * Connects to i3 to find out the currently running version. Useful since it + * might be different from the version compiled into this binary (maybe the + * user didn’t correctly install i3 or forgot te restart it). + * + * The output looks like this: + * Running i3 version: 4.2-202-gb8e782c (2012-08-12, branch "next") (pid 14804) + * + * The i3 binary you just called: /home/michael/i3/i3 + * The i3 binary you are running: /home/michael/i3/i3 + * + */ +void display_running_version(void) { + char *socket_path = root_atom_contents("I3_SOCKET_PATH"); + if (socket_path == NULL) + exit(EXIT_SUCCESS); + + char *pid_from_atom = root_atom_contents("I3_PID"); + if (pid_from_atom == NULL) { + /* If I3_PID is not set, the running version is older than 4.2-200. */ + printf("\nRunning version: < 4.2-200\n"); + exit(EXIT_SUCCESS); + } + + /* Inform the user of what we are doing. While a single IPC request is + * really fast normally, in case i3 hangs, this will not terminate. */ + printf("(Getting version from running i3, press ctrl-c to abort…)"); + fflush(stdout); + + /* TODO: refactor this with the code for sending commands */ + int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (sockfd == -1) + err(EXIT_FAILURE, "Could not create socket"); + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_LOCAL; + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); + if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) + err(EXIT_FAILURE, "Could not connect to i3"); + + if (ipc_send_message(sockfd, 0, I3_IPC_MESSAGE_TYPE_GET_VERSION, + (uint8_t*)"") == -1) + err(EXIT_FAILURE, "IPC: write()"); + + uint32_t reply_length; + uint8_t *reply; + int ret; + if ((ret = ipc_recv_message(sockfd, I3_IPC_MESSAGE_TYPE_GET_VERSION, + &reply_length, &reply)) != 0) { + if (ret == -1) + err(EXIT_FAILURE, "IPC: read()"); + exit(EXIT_FAILURE); + } + +#if YAJL_MAJOR >= 2 + yajl_handle handle = yajl_alloc(&version_callbacks, NULL, NULL); +#else + yajl_parser_config parse_conf = { 0, 0 }; + + yajl_handle handle = yajl_alloc(&version_callbacks, &parse_conf, NULL, NULL); +#endif + + yajl_status state = yajl_parse(handle, (const unsigned char*)reply, (int)reply_length); + if (state != yajl_status_ok) + errx(EXIT_FAILURE, "Could not parse my own reply. That's weird. reply is %.*s", (int)reply_length, reply); + + printf("\rRunning i3 version: %s (pid %s)\n", human_readable_version, pid_from_atom); + +#ifdef __linux__ + char exepath[PATH_MAX], + destpath[PATH_MAX]; + ssize_t linksize; + + snprintf(exepath, sizeof(exepath), "/proc/%d/exe", getpid()); + + if ((linksize = readlink(exepath, destpath, sizeof(destpath))) == -1) + err(EXIT_FAILURE, "readlink(%s)", exepath); + + /* readlink() does not NULL-terminate strings, so we have to. */ + destpath[linksize] = '\0'; + + printf("\n"); + printf("The i3 binary you just called: %s\n", destpath); + + snprintf(exepath, sizeof(exepath), "/proc/%s/exe", pid_from_atom); + + if ((linksize = readlink(exepath, destpath, sizeof(destpath))) == -1) + err(EXIT_FAILURE, "readlink(%s)", exepath); + + /* readlink() does not NULL-terminate strings, so we have to. */ + destpath[linksize] = '\0'; + + /* Check if "(deleted)" is the readlink result. If so, the running version + * does not match the file on disk. */ + if (strstr(destpath, "(deleted)") != NULL) + printf("RUNNING BINARY DIFFERENT FROM BINARY ON DISK!\n"); + + /* Since readlink() might put a "(deleted)" somewhere in the buffer and + * stripping that out seems hackish and ugly, we read the process’s argv[0] + * instead. */ + snprintf(exepath, sizeof(exepath), "/proc/%s/cmdline", pid_from_atom); + + int fd; + if ((fd = open(exepath, O_RDONLY)) == -1) + err(EXIT_FAILURE, "open(%s)", exepath); + if (read(fd, destpath, sizeof(destpath)) == -1) + err(EXIT_FAILURE, "read(%s)", exepath); + close(fd); + + printf("The i3 binary you are running: %s\n", destpath); +#endif + + yajl_free(handle); +} diff --git a/src/ewmh.c b/src/ewmh.c index 1c6d918b..45d4e5fe 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "ewmh.c" /* * vim:ts=4:sw=4:expandtab * diff --git a/src/fake_outputs.c b/src/fake_outputs.c index 512a808f..e1153299 100644 --- a/src/fake_outputs.c +++ b/src/fake_outputs.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "fake_outputs.c" /* * vim:ts=4:sw=4:expandtab * diff --git a/src/floating.c b/src/floating.c index b90eac3e..3d2c1d31 100644 --- a/src/floating.c +++ b/src/floating.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "floating.c" /* * vim:ts=4:sw=4:expandtab * @@ -40,7 +42,7 @@ void floating_enable(Con *con, bool automatic) { } /* 1: If the container is a workspace container, we need to create a new - * split-container with the same orientation and make that one floating. We + * split-container with the same layout and make that one floating. We * cannot touch the workspace container itself because floating containers * are children of the workspace. */ if (con->type == CT_WORKSPACE) { @@ -52,7 +54,7 @@ void floating_enable(Con *con, bool automatic) { /* TODO: refactor this with src/con.c:con_set_layout */ Con *new = con_new(NULL, NULL); new->parent = con; - new->orientation = con->orientation; + new->layout = con->layout; /* since the new container will be set into floating mode directly * afterwards, we need to copy the workspace rect. */ @@ -97,8 +99,9 @@ void floating_enable(Con *con, bool automatic) { * otherwise. */ Con *ws = con_get_workspace(con); nc->parent = ws; - nc->orientation = NO_ORIENTATION; + nc->split = true; nc->type = CT_FLOATING_CON; + nc->layout = L_SPLITH; /* We insert nc already, even though its rect is not yet calculated. This * is necessary because otherwise the workspace might be empty (and get * closed in tree_close()) even though it’s not. */ @@ -174,11 +177,25 @@ void floating_enable(Con *con, bool automatic) { nc->rect.width = max(nc->rect.width, config.floating_minimum_width); } - /* add pixels for the decoration */ - /* TODO: don’t add them when the user automatically puts new windows into - * 1pixel/borderless mode */ - nc->rect.height += deco_height + 2; - nc->rect.width += 4; + /* 3: attach the child to the new parent container. We need to do this + * because con_border_style_rect() needs to access con->parent. */ + con->parent = nc; + con->percent = 1.0; + con->floating = FLOATING_USER_ON; + + /* 4: set the border style as specified with new_float */ + if (automatic) + con->border_style = config.default_floating_border; + + /* Add pixels for the decoration. */ + Rect border_style_rect = con_border_style_rect(con); + + nc->rect.height -= border_style_rect.height; + nc->rect.width -= border_style_rect.width; + + /* Add some more pixels for the title bar */ + if(con_border_style(con) == BS_NORMAL) + nc->rect.height += deco_height; /* Honor the X11 border */ nc->rect.height += con->border_width * 2; @@ -218,15 +235,6 @@ void floating_enable(Con *con, bool automatic) { DLOG("Floating rect: (%d, %d) with %d x %d\n", nc->rect.x, nc->rect.y, nc->rect.width, nc->rect.height); - /* 3: attach the child to the new parent container */ - con->parent = nc; - con->percent = 1.0; - con->floating = FLOATING_USER_ON; - - /* 4: set the border style as specified with new_float */ - if (automatic) - con->border_style = config.default_floating_border; - /* 5: Subtract the deco_height in order to make the floating window appear * at precisely the position it specified in its original geometry (which * is what applications might remember). */ @@ -492,7 +500,7 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t confine_to, border_t border, callback_t callback, const void *extra) { uint32_t new_x, new_y; - Rect old_rect; + Rect old_rect = { 0, 0, 0, 0 }; if (con != NULL) memcpy(&old_rect, &(con->rect), sizeof(Rect)); @@ -619,12 +627,12 @@ void floating_fix_coordinates(Con *con, Rect *old_rect, Rect *new_rect) { int32_t rel_y = (con->rect.y - old_rect->y); /* Then we calculate a fraction, for example 0.63 for a window * which is at y = 1212 of a 1920 px high output */ - double fraction_x = ((double)rel_x / (int32_t)old_rect->width); - double fraction_y = ((double)rel_y / (int32_t)old_rect->height); DLOG("rel_x = %d, rel_y = %d, fraction_x = %f, fraction_y = %f, output->w = %d, output->h = %d\n", - rel_x, rel_y, fraction_x, fraction_y, old_rect->width, old_rect->height); - con->rect.x = (int32_t)new_rect->x + (fraction_x * (int32_t)new_rect->width); - con->rect.y = (int32_t)new_rect->y + (fraction_y * (int32_t)new_rect->height); + rel_x, rel_y, (double)rel_x / old_rect->width, (double)rel_y / old_rect->height, + old_rect->width, old_rect->height); + /* Here we have to multiply at first. Or we will lose precision when not compiled with -msse2 */ + con->rect.x = (int32_t)new_rect->x + (double)(rel_x * (int32_t)new_rect->width) / (int32_t)old_rect->width; + con->rect.y = (int32_t)new_rect->y + (double)(rel_y * (int32_t)new_rect->height) / (int32_t)old_rect->height; DLOG("Resulting coordinates: x = %d, y = %d\n", con->rect.x, con->rect.y); } diff --git a/src/handlers.c b/src/handlers.c index d63a4e5c..21a87342 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "handlers.c" /* * vim:ts=4:sw=4:expandtab * @@ -77,58 +79,6 @@ bool event_is_ignored(const int sequence, const int response_type) { return false; } - -/* - * There was a key press. We compare this key code with our bindings table and pass - * the bound action to parse_command(). - * - */ -static void handle_key_press(xcb_key_press_event_t *event) { - - last_timestamp = event->time; - - DLOG("Keypress %d, state raw = %d\n", event->detail, event->state); - - /* Remove the numlock bit, all other bits are modifiers we can bind to */ - uint16_t state_filtered = event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK); - DLOG("(removed numlock, state = %d)\n", state_filtered); - /* Only use the lower 8 bits of the state (modifier masks) so that mouse - * button masks are filtered out */ - state_filtered &= 0xFF; - DLOG("(removed upper 8 bits, state = %d)\n", state_filtered); - - if (xkb_current_group == XkbGroup2Index) - state_filtered |= BIND_MODE_SWITCH; - - DLOG("(checked mode_switch, state %d)\n", state_filtered); - - /* Find the binding */ - Binding *bind = get_binding(state_filtered, event->detail); - - /* No match? Then the user has Mode_switch enabled but does not have a - * specific keybinding. Fall back to the default keybindings (without - * Mode_switch). Makes it much more convenient for users of a hybrid - * layout (like us, ru). */ - if (bind == NULL) { - state_filtered &= ~(BIND_MODE_SWITCH); - DLOG("no match, new state_filtered = %d\n", state_filtered); - if ((bind = get_binding(state_filtered, event->detail)) == NULL) { - ELOG("Could not lookup key binding (modifiers %d, keycode %d)\n", - state_filtered, event->detail); - return; - } - } - - char *command_copy = sstrdup(bind->command); - struct CommandResult *command_output = parse_command(command_copy); - free(command_copy); - - if (command_output->needs_tree_render) - tree_render(); - - yajl_gen_free(command_output->json_gen); -} - /* * Called with coordinates of an enter_notify event or motion_notify event * to check if the user crossed virtual screen boundaries and adjust the @@ -263,6 +213,7 @@ static void handle_motion_notify(xcb_motion_notify_event_t *event) { Con *con; if ((con = con_by_frame_id(event->event)) == NULL) { + DLOG("MotionNotify for an unknown container, checking if it crosses screen boundaries.\n"); check_crossing_screen_boundary(event->root_x, event->root_y); return; } @@ -468,6 +419,8 @@ static void handle_screen_change(xcb_generic_event_t *e) { randr_query_outputs(); + scratchpad_fix_resolution(); + ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}"); return; @@ -686,12 +639,29 @@ static void handle_client_message(xcb_client_message_event_t *event) { } tree_render(); - x_push_changes(croot); + } else if (event->type == A__NET_ACTIVE_WINDOW) { + DLOG("_NET_ACTIVE_WINDOW: Window 0x%08x should be activated\n", event->window); + Con *con = con_by_window_id(event->window); + if (con == NULL) { + DLOG("Could not get window for client message\n"); + return; + } + + Con *ws = con_get_workspace(con); + if (!workspace_is_visible(ws)) { + DLOG("Workspace not visible, ignoring _NET_ACTIVE_WINDOW\n"); + return; + } + + if (ws != con_get_workspace(focused)) + workspace_show(ws); + + con_focus(con); + tree_render(); } else if (event->type == A_I3_SYNC) { - DLOG("i3 sync, yay\n"); xcb_window_t window = event->data.data32[0]; uint32_t rnd = event->data.data32[1]; - DLOG("Sending random value %d back to X11 window 0x%08x\n", rnd, window); + DLOG("[i3 sync protocol] Sending random value %d back to X11 window 0x%08x\n", rnd, window); void *reply = scalloc(32); xcb_client_message_event_t *ev = reply; @@ -1075,6 +1045,7 @@ void handle_event(int type, xcb_generic_event_t *event) { switch (type) { case XCB_KEY_PRESS: + case XCB_KEY_RELEASE: handle_key_press((xcb_key_press_event_t*)event); break; diff --git a/src/i3.mk b/src/i3.mk new file mode 100644 index 00000000..a93cc4c4 --- /dev/null +++ b/src/i3.mk @@ -0,0 +1,85 @@ +ALL_TARGETS += i3 +INSTALL_TARGETS += install-i3 +CLEAN_TARGETS += clean-i3 + +i3_SOURCES_GENERATED = src/cfgparse.tab.c src/cfgparse.yy.c +i3_SOURCES := $(filter-out $(i3_SOURCES_GENERATED),$(wildcard src/*.c)) +i3_HEADERS_CMDPARSER := $(wildcard include/GENERATED_*.h) +i3_HEADERS := $(filter-out $(i3_HEADERS_CMDPARSER),$(wildcard include/*.h)) +i3_CFLAGS = $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(XCB_WM_CFLAGS) $(X11_CFLAGS) $(XCURSOR_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) $(PCRE_CFLAGS) $(LIBSN_CFLAGS) +i3_LIBS = $(XCB_LIBS) $(XCB_KBD_LIBS) $(XCB_WM_LIBS) $(X11_LIBS) $(XCURSOR_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) $(PCRE_LIBS) $(LIBSN_LIBS) -lm -lpthread + +# When using clang, we use pre-compiled headers to speed up the build. With +# gcc, this actually makes the build slower. +ifeq ($(CC),clang) +i3_HEADERS_DEP := $(i3_HEADERS) include/all.h.pch +PCH_FLAGS := -include include/all.h +else +i3_HEADERS_DEP := $(i3_HEADERS) +PCH_FLAGS := +endif + +i3_OBJECTS := $(i3_SOURCES_GENERATED:.c=.o) $(i3_SOURCES:.c=.o) + +# The basename/pwd calls are for canonicalizing the path: Instead +# of src/main.c, we will see something like ../i3-4.2/src/main.c in +# debugger backtraces, making it clearer which code belongs to i3 and +# which code doesn’t. +# We only do this for src/ since all the other subdirectories contain i3 in +# their name already. +canonical_path := ../$(shell basename $(shell pwd -P)) + +include/all.h.pch: $(i3_HEADERS) + echo "[i3] PCH all.h" + $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -x c-header include/all.h -o include/all.h.pch + +src/%.o: src/%.c $(i3_HEADERS_DEP) + echo "[i3] CC $<" + $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(PCH_FLAGS) -c -o $@ ${canonical_path}/$< + +src/cfgparse.yy.c: src/cfgparse.l src/cfgparse.tab.o $(i3_HEADERS_DEP) + echo "[i3] LEX $<" + $(FLEX) -i -o $@ ${canonical_path}/$< + +src/cfgparse.tab.c: src/cfgparse.y $(i3_HEADERS_DEP) + echo "[i3] YACC $<" + $(BISON) --debug --verbose -b $(basename $< .y) -d ${canonical_path}/$< + +# This target compiles the command parser twice: +# Once with -DTEST_PARSER, creating a stand-alone executable used for tests, +# and once as an object file for i3. +src/commands_parser.o: src/commands_parser.c $(i3_HEADERS_DEP) i3-command-parser.stamp + echo "[i3] CC $<" + $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(I3_LDFLAGS) $(LDFLAGS) -DTEST_PARSER -o test.commands_parser $< $(LIBS) $(i3_LIBS) + $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ ${canonical_path}/$< + +i3-command-parser.stamp: generate-command-parser.pl parser-specs/commands.spec + echo "[i3] Generating command parser" + (cd include; ../generate-command-parser.pl) + touch $@ + +i3: libi3.a $(i3_OBJECTS) + echo "[i3] Link i3" + $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(LIBS) $(i3_LIBS) + +install-i3: i3 + echo "[i3] Install" + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -d -m 0755 $(DESTDIR)$(SYSCONFDIR)/i3 + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/include/i3 + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/xsessions + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/applications + $(INSTALL) -m 0755 i3 $(DESTDIR)$(PREFIX)/bin/ + $(INSTALL) -m 0755 i3-migrate-config-to-v4 $(DESTDIR)$(PREFIX)/bin/ + $(INSTALL) -m 0755 i3-sensible-editor $(DESTDIR)$(PREFIX)/bin/ + $(INSTALL) -m 0755 i3-sensible-pager $(DESTDIR)$(PREFIX)/bin/ + $(INSTALL) -m 0755 i3-sensible-terminal $(DESTDIR)$(PREFIX)/bin/ + test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config + test -e $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes || $(INSTALL) -m 0644 i3.config.keycodes $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes + $(INSTALL) -m 0644 i3.xsession.desktop $(DESTDIR)$(PREFIX)/share/xsessions/i3.desktop + $(INSTALL) -m 0644 i3.applications.desktop $(DESTDIR)$(PREFIX)/share/applications/i3.desktop + $(INSTALL) -m 0644 include/i3/ipc.h $(DESTDIR)$(PREFIX)/include/i3/ + +clean-i3: + echo "[i3] Clean" + rm -f $(i3_OBJECTS) $(i3_SOURCES_GENERATED) $(i3_HEADERS_CMDPARSER) include/loglevels.h loglevels.tmp include/all.h.pch i3-command-parser.stamp i3 src/*.gcno src/cfgparse.{output,dot} src/cmdparse.* diff --git a/src/ipc.c b/src/ipc.c index 60ce8145..1c6de798 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "ipc.c" /* * vim:ts=4:sw=4:expandtab * @@ -161,17 +163,14 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { ystr("type"); y(integer, con->type); + /* provided for backwards compatibility only. */ ystr("orientation"); - switch (con->orientation) { - case NO_ORIENTATION: - ystr("none"); - break; - case HORIZ: + if (!con->split) + ystr("none"); + else { + if (con_orientation(con) == HORIZ) ystr("horizontal"); - break; - case VERT: - ystr("vertical"); - break; + else ystr("vertical"); } ystr("scratchpad_state"); @@ -203,10 +202,20 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { ystr("focused"); y(bool, (con == focused)); + ystr("split"); + y(bool, con->split); + ystr("layout"); switch (con->layout) { case L_DEFAULT: - ystr("default"); + DLOG("About to dump layout=default, this is a bug in the code.\n"); + assert(false); + break; + case L_SPLITV: + ystr("splitv"); + break; + case L_SPLITH: + ystr("splith"); break; case L_STACKED: ystr("stacked"); @@ -222,6 +231,16 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { break; } + ystr("last_split_layout"); + switch (con->layout) { + case L_SPLITV: + ystr("splitv"); + break; + default: + ystr("splith"); + break; + } + ystr("border"); switch (con->border_style) { case BS_NORMAL: @@ -240,8 +259,8 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { dump_rect(gen, "geometry", con->geometry); ystr("name"); - if (con->window && con->window->name_json) - ystr(con->window->name_json); + if (con->window && con->window->name) + ystr(i3string_as_utf8(con->window->name)); else ystr(con->name); @@ -519,6 +538,44 @@ IPC_HANDLER(get_marks) { y(free); } +/* + * Returns the version of i3 + * + */ +IPC_HANDLER(get_version) { +#if YAJL_MAJOR >= 2 + yajl_gen gen = yajl_gen_alloc(NULL); +#else + yajl_gen gen = yajl_gen_alloc(NULL, NULL); +#endif + y(map_open); + + ystr("major"); + y(integer, MAJOR_VERSION); + + ystr("minor"); + y(integer, MINOR_VERSION); + + ystr("patch"); + y(integer, PATCH_VERSION); + + ystr("human_readable"); + ystr(I3_VERSION); + + y(map_close); + + const unsigned char *payload; +#if YAJL_MAJOR >= 2 + size_t length; +#else + unsigned int length; +#endif + y(get_buf, &payload, &length); + + ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_VERSION, payload); + y(free); +} + /* * Formats the reply message for a GET_BAR_CONFIG request and sends it to the * client. @@ -703,7 +760,7 @@ static int add_subscription(void *extra, const unsigned char *s, #endif ipc_client *client = extra; - DLOG("should add subscription to extra %p, sub %.*s\n", client, len, s); + DLOG("should add subscription to extra %p, sub %.*s\n", client, (int)len, s); int event = client->num_events; client->num_events++; @@ -775,7 +832,7 @@ IPC_HANDLER(subscribe) { /* The index of each callback function corresponds to the numeric * value of the message type (see include/i3/ipc.h) */ -handler_t handlers[7] = { +handler_t handlers[8] = { handle_command, handle_get_workspaces, handle_subscribe, @@ -783,6 +840,7 @@ handler_t handlers[7] = { handle_tree, handle_get_marks, handle_get_bar_config, + handle_get_version, }; /* diff --git a/src/key_press.c b/src/key_press.c new file mode 100644 index 00000000..5919e64c --- /dev/null +++ b/src/key_press.c @@ -0,0 +1,312 @@ +#undef I3__FILE__ +#define I3__FILE__ "key_press.c" +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) + * + * key_press.c: key press handler + * + */ +#include +#include +#include +#include +#include "all.h" + +static int current_nesting_level; +static bool parse_error_key; +static bool command_failed; + +/* XXX: I don’t want to touch too much of the nagbar code at once, but we + * should refactor this with src/cfgparse.y into a clean generic nagbar + * interface. It might come in handy in other situations within i3, too. */ +static char *pager_script_path; +static pid_t nagbar_pid = -1; + +/* + * Handler which will be called when we get a SIGCHLD for the nagbar, meaning + * it exited (or could not be started, depending on the exit code). + * + */ +static void nagbar_exited(EV_P_ ev_child *watcher, int revents) { + ev_child_stop(EV_A_ watcher); + + if (unlink(pager_script_path) != 0) + warn("Could not delete temporary i3-nagbar script %s", pager_script_path); + + if (!WIFEXITED(watcher->rstatus)) { + fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n"); + return; + } + + int exitcode = WEXITSTATUS(watcher->rstatus); + printf("i3-nagbar process exited with status %d\n", exitcode); + if (exitcode == 2) { + fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n"); + } + + nagbar_pid = -1; +} + +/* We need ev >= 4 for the following code. Since it is not *that* important (it + * only makes sure that there are no i3-nagbar instances left behind) we still + * support old systems with libev 3. */ +#if EV_VERSION_MAJOR >= 4 +/* + * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal + * SIGKILL (9) to make sure there are no left-over i3-nagbar processes. + * + */ +static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) { + if (nagbar_pid != -1) { + LOG("Sending SIGKILL (%d) to i3-nagbar with PID %d\n", SIGKILL, nagbar_pid); + kill(nagbar_pid, SIGKILL); + } +} +#endif + +/* + * Writes the given command as a shell script to path. + * Returns true unless something went wrong. + * + */ +static bool write_nagbar_script(const char *path, const char *command) { + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IXUSR); + if (fd == -1) { + warn("Could not create temporary script to store the nagbar command"); + return false; + } + write(fd, "#!/bin/sh\n", strlen("#!/bin/sh\n")); + write(fd, command, strlen(command)); + close(fd); + return true; +} + +/* + * Starts an i3-nagbar process which alerts the user that his configuration + * file contains one or more errors. Also offers two buttons: One to launch an + * $EDITOR on the config file and another one to launch a $PAGER on the error + * logfile. + * + */ +static void start_commanderror_nagbar(void) { + if (nagbar_pid != -1) { + DLOG("i3-nagbar for command error already running, not starting again.\n"); + return; + } + + DLOG("Starting i3-nagbar due to command error\n"); + + /* We need to create a custom script containing our actual command + * since not every terminal emulator which is contained in + * i3-sensible-terminal supports -e with multiple arguments (and not + * all of them support -e with one quoted argument either). + * + * NB: The paths need to be unique, that is, don’t assume users close + * their nagbars at any point in time (and they still need to work). + * */ + pager_script_path = get_process_filename("nagbar-cfgerror-pager"); + + nagbar_pid = fork(); + if (nagbar_pid == -1) { + warn("Could not fork()"); + return; + } + + /* child */ + if (nagbar_pid == 0) { + char *pager_command; + sasprintf(&pager_command, "i3-sensible-pager \"%s\"\n", errorfilename); + if (!write_nagbar_script(pager_script_path, pager_command)) + return; + + char *pageraction; + sasprintf(&pageraction, "i3-sensible-terminal -e \"%s\"", pager_script_path); + char *argv[] = { + NULL, /* will be replaced by the executable path */ + "-t", + "error", + "-m", + "The configured command for this shortcut could not be run successfully.", + "-b", + "show errors", + pageraction, + NULL + }; + exec_i3_utility("i3-nagbar", argv); + } + + /* parent */ + /* install a child watcher */ + ev_child *child = smalloc(sizeof(ev_child)); + ev_child_init(child, &nagbar_exited, nagbar_pid, 0); + ev_child_start(main_loop, child); + +/* We need ev >= 4 for the following code. Since it is not *that* important (it + * only makes sure that there are no i3-nagbar instances left behind) we still + * support old systems with libev 3. */ +#if EV_VERSION_MAJOR >= 4 + /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is + * still running) */ + ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup)); + ev_cleanup_init(cleanup, nagbar_cleanup); + ev_cleanup_start(main_loop, cleanup); +#endif +} + +/* + * Kills the commanderror i3-nagbar process, if any. + * + * Called when reloading/restarting, since the user probably fixed his wrong + * keybindings. + * + * If wait_for_it is set (restarting), this function will waitpid(), otherwise, + * ev is assumed to handle it (reloading). + * + */ +void kill_commanderror_nagbar(bool wait_for_it) { + if (nagbar_pid == -1) + return; + + if (kill(nagbar_pid, SIGTERM) == -1) + warn("kill(configerror_nagbar) failed"); + + if (!wait_for_it) + return; + + /* When restarting, we don’t enter the ev main loop anymore and after the + * exec(), our old pid is no longer watched. So, ev won’t handle SIGCHLD + * for us and we would end up with a process. Therefore we + * waitpid() here. */ + waitpid(nagbar_pid, NULL, 0); +} + +static int json_boolean(void *ctx, int boolval) { + DLOG("Got bool: %d, parse_error_key %d, nesting_level %d\n", boolval, parse_error_key, current_nesting_level); + + if (parse_error_key && current_nesting_level == 1 && boolval) + command_failed = true; + + return 1; +} + +#if YAJL_MAJOR >= 2 +static int json_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) { +#else +static int json_map_key(void *ctx, const unsigned char *stringval, unsigned int stringlen) { +#endif + parse_error_key = (stringlen >= strlen("parse_error") && + strncmp((const char*)stringval, "parse_error", strlen("parse_error")) == 0); + return 1; +} + +static int json_start_map(void *ctx) { + current_nesting_level++; + return 1; +} + +static int json_end_map(void *ctx) { + current_nesting_level--; + return 1; +} + +static yajl_callbacks command_error_callbacks = { + NULL, + &json_boolean, + NULL, + NULL, + NULL, + NULL, + &json_start_map, + &json_map_key, + &json_end_map, + NULL, + NULL +}; + +/* + * There was a KeyPress or KeyRelease (both events have the same fields). We + * compare this key code with our bindings table and pass the bound action to + * parse_command(). + * + */ +void handle_key_press(xcb_key_press_event_t *event) { + bool key_release = (event->response_type == XCB_KEY_RELEASE); + + last_timestamp = event->time; + + DLOG("%s %d, state raw = %d\n", (key_release ? "KeyRelease" : "KeyPress"), event->detail, event->state); + + /* Remove the numlock bit, all other bits are modifiers we can bind to */ + uint16_t state_filtered = event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK); + DLOG("(removed numlock, state = %d)\n", state_filtered); + /* Only use the lower 8 bits of the state (modifier masks) so that mouse + * button masks are filtered out */ + state_filtered &= 0xFF; + DLOG("(removed upper 8 bits, state = %d)\n", state_filtered); + + if (xkb_current_group == XkbGroup2Index) + state_filtered |= BIND_MODE_SWITCH; + + DLOG("(checked mode_switch, state %d)\n", state_filtered); + + /* Find the binding */ + Binding *bind = get_binding(state_filtered, key_release, event->detail); + + /* No match? Then the user has Mode_switch enabled but does not have a + * specific keybinding. Fall back to the default keybindings (without + * Mode_switch). Makes it much more convenient for users of a hybrid + * layout (like us, ru). */ + if (bind == NULL) { + state_filtered &= ~(BIND_MODE_SWITCH); + DLOG("no match, new state_filtered = %d\n", state_filtered); + if ((bind = get_binding(state_filtered, key_release, event->detail)) == NULL) { + /* This is not a real error since we can have release and + * non-release keybindings. On a KeyPress event for which there is + * only a !release-binding, but no release-binding, the + * corresponding KeyRelease event will trigger this. No problem, + * though. */ + DLOG("Could not lookup key binding (modifiers %d, keycode %d)\n", + state_filtered, event->detail); + return; + } + } + + char *command_copy = sstrdup(bind->command); + struct CommandResult *command_output = parse_command(command_copy); + free(command_copy); + + if (command_output->needs_tree_render) + tree_render(); + + /* We parse the JSON reply to figure out whether there was an error + * ("success" being false in on of the returned dictionaries). */ + const unsigned char *reply; +#if YAJL_MAJOR >= 2 + size_t length; + yajl_handle handle = yajl_alloc(&command_error_callbacks, NULL, NULL); +#else + unsigned int length; + yajl_parser_config parse_conf = { 0, 0 }; + + yajl_handle handle = yajl_alloc(&command_error_callbacks, &parse_conf, NULL, NULL); +#endif + yajl_gen_get_buf(command_output->json_gen, &reply, &length); + + current_nesting_level = 0; + parse_error_key = false; + command_failed = false; + yajl_status state = yajl_parse(handle, reply, length); + if (state != yajl_status_ok) { + ELOG("Could not parse my own reply. That's weird. reply is %.*s\n", (int)length, reply); + } else { + if (command_failed) + start_commanderror_nagbar(); + } + + yajl_free(handle); + + yajl_gen_free(command_output->json_gen); +} diff --git a/src/load_layout.c b/src/load_layout.c index a8063dca..795fb6d8 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "load_layout.c" /* * vim:ts=4:sw=4:expandtab * @@ -139,7 +141,7 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) { #else static int json_string(void *ctx, const unsigned char *val, unsigned int len) { #endif - LOG("string: %.*s for key %s\n", len, val, last_key); + LOG("string: %.*s for key %s\n", (int)len, val, last_key); if (parsing_swallows) { /* TODO: the other swallowing keys */ if (strcasecmp(last_key, "class") == 0) { @@ -156,15 +158,25 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) { memcpy(json_node->sticky_group, val, len); LOG("sticky_group of this container is %s\n", json_node->sticky_group); } else if (strcasecmp(last_key, "orientation") == 0) { + /* Upgrade path from older versions of i3 (doing an inplace restart + * to a newer version): + * "orientation" is dumped before "layout". Therefore, we store + * whether the orientation was horizontal or vertical in the + * last_split_layout. When we then encounter layout == "default", + * we will use the last_split_layout as layout instead. */ char *buf = NULL; sasprintf(&buf, "%.*s", (int)len, val); - if (strcasecmp(buf, "none") == 0) - json_node->orientation = NO_ORIENTATION; - else if (strcasecmp(buf, "horizontal") == 0) - json_node->orientation = HORIZ; + if (strcasecmp(buf, "none") == 0 || + strcasecmp(buf, "horizontal") == 0) + json_node->last_split_layout = L_SPLITH; else if (strcasecmp(buf, "vertical") == 0) - json_node->orientation = VERT; + json_node->last_split_layout = L_SPLITV; else LOG("Unhandled orientation: %s\n", buf); + + /* What used to be an implicit check whether orientation != + * NO_ORIENTATION is now a proper separate flag. */ + if (strcasecmp(buf, "none") != 0) + json_node->split = true; free(buf); } else if (strcasecmp(last_key, "border") == 0) { char *buf = NULL; @@ -181,17 +193,33 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) { char *buf = NULL; sasprintf(&buf, "%.*s", (int)len, val); if (strcasecmp(buf, "default") == 0) - json_node->layout = L_DEFAULT; + /* This set above when we read "orientation". */ + json_node->layout = json_node->last_split_layout; else if (strcasecmp(buf, "stacked") == 0) json_node->layout = L_STACKED; else if (strcasecmp(buf, "tabbed") == 0) json_node->layout = L_TABBED; - else if (strcasecmp(buf, "dockarea") == 0) + else if (strcasecmp(buf, "dockarea") == 0) { json_node->layout = L_DOCKAREA; - else if (strcasecmp(buf, "output") == 0) + /* Necessary for migrating from older versions of i3. */ + json_node->split = false; + } else if (strcasecmp(buf, "output") == 0) json_node->layout = L_OUTPUT; + else if (strcasecmp(buf, "splith") == 0) + json_node->layout = L_SPLITH; + else if (strcasecmp(buf, "splitv") == 0) + json_node->layout = L_SPLITV; else LOG("Unhandled \"layout\": %s\n", buf); free(buf); + } else if (strcasecmp(last_key, "last_split_layout") == 0) { + char *buf = NULL; + sasprintf(&buf, "%.*s", (int)len, val); + if (strcasecmp(buf, "splith") == 0) + json_node->last_split_layout = L_SPLITH; + else if (strcasecmp(buf, "splitv") == 0) + json_node->last_split_layout = L_SPLITV; + else LOG("Unhandled \"last_splitlayout\": %s\n", buf); + free(buf); } else if (strcasecmp(last_key, "mark") == 0) { char *buf = NULL; sasprintf(&buf, "%.*s", (int)len, val); @@ -288,6 +316,9 @@ static int json_bool(void *ctx, int val) { to_focus = json_node; } + if (strcasecmp(last_key, "split") == 0) + json_node->split = val; + if (parsing_swallows) { if (strcasecmp(last_key, "restart_mode") == 0) current_swallow->restart_mode = val; diff --git a/src/log.c b/src/log.c index 92e8f57c..16fa0bed 100644 --- a/src/log.c +++ b/src/log.c @@ -1,10 +1,12 @@ +#undef I3__FILE__ +#define I3__FILE__ "log.c" /* * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) * - * log.c: Setting of loglevels, logging functions. + * log.c: Logging functions. * */ #include @@ -18,6 +20,7 @@ #include #include #include +#include #if defined(__APPLE__) #include #include @@ -29,10 +32,7 @@ #include "libi3.h" #include "shmlog.h" -/* loglevels.h is autogenerated at make time */ -#include "loglevels.h" - -static uint64_t loglevel = 0; +static bool debug_logging = false; static bool verbose = false; static FILE *errorfile; char *errorfilename; @@ -49,6 +49,8 @@ int shmlog_size = 0; static char *logbuffer; /* A pointer (within logbuffer) where data will be written to next. */ static char *logwalk; +/* A pointer to the shmlog header */ +static i3_shmlog_header *header; /* A pointer to the byte where we last wrapped. Necessary to not print the * left-overs at the end of the ringbuffer. */ static char *loglastwrap; @@ -64,8 +66,6 @@ static int logbuffer_shm; * */ static void store_log_markers(void) { - i3_shmlog_header *header = (i3_shmlog_header*)logbuffer; - header->offset_next_write = (logwalk - logbuffer); header->offset_last_wrap = (loglastwrap - logbuffer); header->size = logbuffer_size; @@ -129,10 +129,23 @@ void init_logging(void) { logbuffer = NULL; return; } + + /* Initialize with 0-bytes, just to be sure… */ + memset(logbuffer, '\0', logbuffer_size); + + header = (i3_shmlog_header*)logbuffer; + + pthread_condattr_t cond_attr; + pthread_condattr_init(&cond_attr); + if (pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED) != 0) + ELOG("pthread_condattr_setpshared() failed, i3-dump-log -f will not work!\n"); + pthread_cond_init(&(header->condvar), &cond_attr); + logwalk = logbuffer + sizeof(i3_shmlog_header); loglastwrap = logbuffer + logbuffer_size; store_log_markers(); } + atexit(purge_zerobyte_logfile); } /* @@ -146,26 +159,11 @@ void set_verbosity(bool _verbose) { } /* - * Enables the given loglevel. + * Set debug logging. * */ -void add_loglevel(const char *level) { - /* Handle the special loglevel "all" */ - if (strcasecmp(level, "all") == 0) { - loglevel = UINT64_MAX; - return; - } - - for (int i = 0; i < sizeof(loglevels) / sizeof(char*); i++) { - if (strcasecmp(loglevels[i], level) != 0) - continue; - - /* The position in the array (plus one) is the amount of times - * which we need to shift 1 to the left to get our bitmask for - * the specific loglevel. */ - loglevel |= (1 << (i+1)); - break; - } +void set_debug_logging(const bool _debug_logging) { + debug_logging = _debug_logging; } /* @@ -214,22 +212,25 @@ static void vlog(const bool print, const char *fmt, va_list args) { fprintf(stderr, "BUG: single log message > 4k\n"); } - /* If there is no space for the current message (plus trailing - * nullbyte) in the ringbuffer, we need to wrap and write to the - * beginning again. */ - if ((len+1) >= (logbuffer_size - (logwalk - logbuffer))) { + /* If there is no space for the current message in the ringbuffer, we + * need to wrap and write to the beginning again. */ + if (len >= (logbuffer_size - (logwalk - logbuffer))) { loglastwrap = logwalk; logwalk = logbuffer + sizeof(i3_shmlog_header); + store_log_markers(); + header->wrap_count++; } - /* Copy the buffer, terminate it, move the write pointer to the byte after - * our current message. */ + /* Copy the buffer, move the write pointer to the byte after our + * current message. */ strncpy(logwalk, message, len); - logwalk[len] = '\0'; - logwalk += len + 1; + logwalk += len; store_log_markers(); + /* Wake up all (i3-dump-log) processes waiting for condvar. */ + pthread_cond_broadcast(&(header->condvar)); + if (print) fwrite(message, len, 1, stdout); } @@ -271,17 +272,44 @@ void errorlog(char *fmt, ...) { /* * Logs the given message to stdout while prefixing the current time to it, - * but only if the corresponding debug loglevel was activated. + * but only if debug logging was activated. * This is to be called by DLOG() which includes filename/linenumber * */ -void debuglog(uint64_t lev, char *fmt, ...) { +void debuglog(char *fmt, ...) { va_list args; - if (!logbuffer && !(loglevel & lev)) + if (!logbuffer && !(debug_logging)) return; va_start(args, fmt); - vlog((loglevel & lev), fmt, args); + vlog(debug_logging, fmt, args); va_end(args); } + +/* + * Deletes the unused log files. Useful if i3 exits immediately, eg. + * because --get-socketpath was called. We don't care for syscall + * failures. This function is invoked automatically when exiting. + */ +void purge_zerobyte_logfile(void) { + struct stat st; + char *slash; + + if (!errorfilename) + return; + + /* don't delete the log file if it contains something */ + if ((stat(errorfilename, &st)) == -1 || st.st_size > 0) + return; + + if (unlink(errorfilename) == -1) + return; + + if ((slash = strrchr(errorfilename, '/')) != NULL) { + *slash = '\0'; + /* possibly fails with ENOTEMPTY if there are files (or + * sockets) left. */ + rmdir(errorfilename); + } +} diff --git a/src/main.c b/src/main.c index e332f5b4..7936e758 100644 --- a/src/main.c +++ b/src/main.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "main.c" /* * vim:ts=4:sw=4:expandtab * @@ -121,7 +123,7 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) { DLOG("Expected X11 Error received for sequence %x\n", event->sequence); else { xcb_generic_error_t *error = (xcb_generic_error_t*)event; - ELOG("X11 Error received! sequence 0x%x, error_code = %d\n", + DLOG("X11 Error received (probably harmless)! sequence 0x%x, error_code = %d\n", error->sequence, error->error_code); } free(event); @@ -255,6 +257,9 @@ int main(int argc, char *argv[]) { {"no-autostart", no_argument, 0, 'a'}, {"config", required_argument, 0, 'c'}, {"version", no_argument, 0, 'v'}, + {"moreversion", no_argument, 0, 'm'}, + {"more-version", no_argument, 0, 'm'}, + {"more_version", no_argument, 0, 'm'}, {"help", no_argument, 0, 'h'}, {"layout", required_argument, 0, 'L'}, {"restart", required_argument, 0, 0}, @@ -292,7 +297,7 @@ int main(int argc, char *argv[]) { start_argv = argv; - while ((opt = getopt_long(argc, argv, "c:CvaL:hld:V", long_options, &option_index)) != -1) { + while ((opt = getopt_long(argc, argv, "c:CvmaL:hld:V", long_options, &option_index)) != -1) { switch (opt) { case 'a': LOG("Autostart disabled using -a\n"); @@ -314,12 +319,18 @@ int main(int argc, char *argv[]) { case 'v': printf("i3 version " I3_VERSION " © 2009-2012 Michael Stapelberg and contributors\n"); exit(EXIT_SUCCESS); + break; + case 'm': + printf("Binary i3 version: " I3_VERSION " © 2009-2012 Michael Stapelberg and contributors\n"); + display_running_version(); + exit(EXIT_SUCCESS); + break; case 'V': set_verbosity(true); break; case 'd': - LOG("Enabling debug loglevel %s\n", optarg); - add_loglevel(optarg); + LOG("Enabling debug logging\n"); + set_debug_logging(true); break; case 'l': /* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */ @@ -342,10 +353,10 @@ int main(int argc, char *argv[]) { char *socket_path = root_atom_contents("I3_SOCKET_PATH"); if (socket_path) { printf("%s\n", socket_path); - return 0; + exit(EXIT_SUCCESS); } - return 1; + exit(EXIT_FAILURE); } else if (strcmp(long_options[option_index].name, "shmlog-size") == 0 || strcmp(long_options[option_index].name, "shmlog_size") == 0) { shmlog_size = atoi(optarg); @@ -367,12 +378,12 @@ int main(int argc, char *argv[]) { } /* fall-through */ default: - fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V] [-C]\n", argv[0]); + fprintf(stderr, "Usage: %s [-c configfile] [-d all] [-a] [-v] [-V] [-C]\n", argv[0]); fprintf(stderr, "\n"); fprintf(stderr, "\t-a disable autostart ('exec' lines in config)\n"); fprintf(stderr, "\t-c use the provided configfile instead\n"); fprintf(stderr, "\t-C validate configuration file and exit\n"); - fprintf(stderr, "\t-d enable debug output with the specified loglevel\n"); + fprintf(stderr, "\t-d all enable debug output\n"); fprintf(stderr, "\t-L path to the serialized layout during restarts\n"); fprintf(stderr, "\t-v display version and exit\n"); fprintf(stderr, "\t-V enable verbose mode\n"); @@ -424,7 +435,7 @@ int main(int argc, char *argv[]) { } optind++; } - LOG("Command is: %s (%d bytes)\n", payload, strlen(payload)); + DLOG("Command is: %s (%zd bytes)\n", payload, strlen(payload)); char *socket_path = root_atom_contents("I3_SOCKET_PATH"); if (!socket_path) { ELOG("Could not get i3 IPC socket path\n"); @@ -483,7 +494,7 @@ int main(int argc, char *argv[]) { } } - LOG("i3 (tree) version " I3_VERSION " starting\n"); + LOG("i3 " I3_VERSION " starting\n"); conn = xcb_connect(NULL, &conn_screen); if (xcb_connection_has_error(conn)) @@ -656,11 +667,12 @@ int main(int argc, char *argv[]) { randr_init(&randr_base); } + scratchpad_fix_resolution(); + xcb_query_pointer_reply_t *pointerreply; Output *output = NULL; if (!(pointerreply = xcb_query_pointer_reply(conn, pointercookie, NULL))) { ELOG("Could not query pointer position, using first screen\n"); - output = get_first_output(); } else { DLOG("Pointer at %d, %d\n", pointerreply->root_x, pointerreply->root_y); output = get_output_containing(pointerreply->root_x, pointerreply->root_y); @@ -743,7 +755,29 @@ int main(int argc, char *argv[]) { xcb_flush(conn); - manage_existing_windows(root); + /* What follows is a fugly consequence of X11 protocol race conditions like + * the following: In an i3 in-place restart, i3 will reparent all windows + * to the root window, then exec() itself. In the new process, it calls + * manage_existing_windows. However, in case any application sent a + * generated UnmapNotify message to the WM (as GIMP does), this message + * will be handled by i3 *after* managing the window, thus i3 thinks the + * window just closed itself. In reality, the message was sent in the time + * period where i3 wasn’t running yet. + * + * To prevent this, we grab the server (disables processing of any other + * connections), then discard all pending events (since we didn’t do + * anything, there cannot be any meaningful responses), then ungrab the + * server. */ + xcb_grab_server(conn); + { + xcb_aux_sync(conn); + xcb_generic_event_t *event; + while ((event = xcb_poll_for_event(conn)) != NULL) { + free(event); + } + manage_existing_windows(root); + } + xcb_ungrab_server(conn); struct sigaction action; diff --git a/src/manage.c b/src/manage.c index ea060d97..1dc39b9e 100644 --- a/src/manage.c +++ b/src/manage.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "manage.c" /* * vim:ts=4:sw=4:expandtab * @@ -323,7 +325,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki (cwindow->leader != XCB_NONE && cwindow->leader != cwindow->id && con_by_window_id(cwindow->leader) != NULL)) { - LOG("This window is transiert for another window, setting floating\n"); + LOG("This window is transient for another window, setting floating\n"); want_floating = true; if (config.popup_during_fullscreen == PDF_LEAVE_FULLSCREEN && diff --git a/src/match.c b/src/match.c index e92a95d2..350a2c11 100644 --- a/src/match.c +++ b/src/match.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "match.c" /* * vim:ts=4:sw=4:expandtab * @@ -111,9 +113,9 @@ bool match_matches_window(Match *match, i3Window *window) { } if (match->title != NULL) { - if (window->name_json != NULL && - regex_matches(match->title, window->name_json)) { - LOG("title matches (%s)\n", window->name_json); + if (window->name != NULL && + regex_matches(match->title, i3string_as_utf8(window->name))) { + LOG("title matches (%s)\n", i3string_as_utf8(window->name)); } else { return false; } diff --git a/src/move.c b/src/move.c index d3065c24..46b90177 100644 --- a/src/move.c +++ b/src/move.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "move.c" /* * vim:ts=4:sw=4:expandtab * @@ -169,6 +171,12 @@ void tree_move(int direction) { while (above->parent != same_orientation) above = above->parent; + /* Enforce the fullscreen focus restrictions. */ + if (!con_fullscreen_permits_focusing(above->parent)) { + LOG("Cannot move out of fullscreen container\n"); + return; + } + DLOG("above = %p\n", above); Con *next; position_t position; diff --git a/src/output.c b/src/output.c index a54cb6f3..fe8d4983 100644 --- a/src/output.c +++ b/src/output.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "output.c" /* * vim:ts=4:sw=4:expandtab * diff --git a/src/randr.c b/src/randr.c index d29ce128..8b6ba1d9 100644 --- a/src/randr.c +++ b/src/randr.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "randr.c" /* * vim:ts=4:sw=4:expandtab * @@ -256,7 +258,6 @@ void output_init_con(Output *output) { Con *topdock = con_new(NULL, NULL); topdock->type = CT_DOCKAREA; topdock->layout = L_DOCKAREA; - topdock->orientation = VERT; /* this container swallows dock clients */ Match *match = scalloc(sizeof(Match)); match_init(match); @@ -278,6 +279,7 @@ void output_init_con(Output *output) { DLOG("adding main content container\n"); Con *content = con_new(NULL, NULL); content->type = CT_CON; + content->layout = L_SPLITH; FREE(content->name); content->name = sstrdup("content"); @@ -290,7 +292,6 @@ void output_init_con(Output *output) { Con *bottomdock = con_new(NULL, NULL); bottomdock->type = CT_DOCKAREA; bottomdock->layout = L_DOCKAREA; - bottomdock->orientation = VERT; /* this container swallows dock clients */ match = scalloc(sizeof(Match)); match_init(match); @@ -460,11 +461,12 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) { if (con_num_children(workspace) > 1) continue; - workspace->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ; - DLOG("Setting workspace [%d,%s]'s orientation to %d.\n", workspace->num, workspace->name, workspace->orientation); + workspace->layout = (output->rect.height > output->rect.width) ? L_SPLITV : L_SPLITH; + DLOG("Setting workspace [%d,%s]'s layout to %d.\n", workspace->num, workspace->name, workspace->layout); if ((child = TAILQ_FIRST(&(workspace->nodes_head)))) { - child->orientation = workspace->orientation; - DLOG("Setting child [%d,%s]'s orientation to %d.\n", child->num, child->name, child->orientation); + if (child->layout == L_SPLITV || child->layout == L_SPLITH) + child->layout = workspace->layout; + DLOG("Setting child [%d,%s]'s layout to %d.\n", child->num, child->name, child->layout); } } } diff --git a/src/regex.c b/src/regex.c index a0b51f66..60dee5cc 100644 --- a/src/regex.c +++ b/src/regex.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "regex.c" /* * vim:ts=4:sw=4:expandtab * @@ -67,7 +69,7 @@ void regex_free(struct regex *regex) { /* * Checks if the given regular expression matches the given input and returns * true if it does. In either case, it logs the outcome using LOG(), so it will - * be visible without any debug loglevel. + * be visible without debug logging. * */ bool regex_matches(struct regex *regex, const char *input) { diff --git a/src/render.c b/src/render.c index afc4b761..da993a57 100644 --- a/src/render.c +++ b/src/render.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "render.c" /* * vim:ts=4:sw=4:expandtab * @@ -106,9 +108,9 @@ static void render_l_output(Con *con) { */ void render_con(Con *con, bool render_fullscreen) { int children = con_num_children(con); - DLOG("Rendering %snode %p / %s / layout %d / children %d / orient %d\n", + DLOG("Rendering %snode %p / %s / layout %d / children %d\n", (render_fullscreen ? "fullscreen " : ""), con, con->name, con->layout, - children, con->orientation); + children); /* Copy container rect, subtract container border */ /* This is the actually usable space inside this container for clients */ @@ -172,19 +174,11 @@ void render_con(Con *con, bool render_fullscreen) { inset->width = new_width; } - if (con->height_increment > 1) { - int old_height = inset->height; - inset->height -= (inset->height - con->base_height) % con->height_increment; - DLOG("Lost %d pixel due to client's height_increment (%d px, base_height = %d)\n", - old_height - inset->height, con->height_increment, con->base_height); - } - - if (con->width_increment > 1) { - int old_width = inset->width; - inset->width -= (inset->width - con->base_width) % con->width_increment; - DLOG("Lost %d pixel due to client's width_increment (%d px, base_width = %d)\n", - old_width - inset->width, con->width_increment, con->base_width); - } + /* NB: We used to respect resize increment size hints for tiling + * windows up until commit 0db93d9 here. However, since all terminal + * emulators cope with ignoring the size hints in a better way than we + * can (by providing their fake-transparency or background color), this + * code was removed. See also http://bugs.i3wm.org/540 */ DLOG("child will be at %dx%d with size %dx%d\n", inset->x, inset->y, inset->width, inset->height); } @@ -208,11 +202,11 @@ void render_con(Con *con, bool render_fullscreen) { /* precalculate the sizes to be able to correct rounding errors */ int sizes[children]; - if (con->layout == L_DEFAULT && children > 0) { + if ((con->layout == L_SPLITH || con->layout == L_SPLITV) && children > 0) { assert(!TAILQ_EMPTY(&con->nodes_head)); Con *child; int i = 0, assigned = 0; - int total = con->orientation == HORIZ ? rect.width : rect.height; + int total = con_orientation(con) == HORIZ ? rect.width : rect.height; TAILQ_FOREACH(child, &(con->nodes_head), nodes) { double percentage = child->percent > 0.0 ? child->percent : 1.0 / children; assigned += sizes[i++] = percentage * total; @@ -289,8 +283,8 @@ void render_con(Con *con, bool render_fullscreen) { assert(children > 0); /* default layout */ - if (con->layout == L_DEFAULT) { - if (con->orientation == HORIZ) { + if (con->layout == L_SPLITH || con->layout == L_SPLITV) { + if (con->layout == L_SPLITH) { child->rect.x = x; child->rect.y = y; child->rect.width = sizes[i]; @@ -343,7 +337,7 @@ void render_con(Con *con, bool render_fullscreen) { child->rect.width = rect.width; child->rect.height = rect.height; - child->deco_rect.width = child->rect.width / children; + child->deco_rect.width = ceil((float)child->rect.width / children); child->deco_rect.x = x - con->rect.x + i * child->deco_rect.width; child->deco_rect.y = y - con->rect.y; diff --git a/src/resize.c b/src/resize.c index 4b3289cd..b65344a2 100644 --- a/src/resize.c +++ b/src/resize.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "resize.c" /* * vim:ts=4:sw=4:expandtab * diff --git a/src/scratchpad.c b/src/scratchpad.c index 508d4a82..16e26cee 100644 --- a/src/scratchpad.c +++ b/src/scratchpad.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "scratchpad.c" /* * vim:ts=4:sw=4:expandtab * @@ -151,3 +153,67 @@ void scratchpad_show(Con *con) { con_focus(con_descend_focused(con)); } + +/* + * Greatest common divisor, implemented only for the least common multiple + * below. + * + */ +static int _gcd(const int m, const int n) { + if (n == 0) + return m; + return _gcd(n, (m % n)); +} + +/* + * Least common multiple. We use it to determine the (ideally not too large) + * resolution for the __i3 pseudo-output on which the scratchpad is on (see + * below). We could just multiply the resolutions, but for some pathetic cases + * (many outputs), using the LCM will achieve better results. + * + * Man, when you were learning about these two algorithms for the first time, + * did you think you’d ever need them in a real-world software project of + * yours? I certainly didn’t until now. :-D + * + */ +static int _lcm(const int m, const int n) { + const int o = _gcd(m, n); + return ((m * n) / o); +} + +/* + * When starting i3 initially (and after each change to the connected outputs), + * this function fixes the resolution of the __i3 pseudo-output. When that + * resolution is not set to a function which shares a common divisor with every + * active output’s resolution, floating point calculation errors will lead to + * the scratchpad window moving when shown repeatedly. + * + */ +void scratchpad_fix_resolution(void) { + Con *__i3_scratch = workspace_get("__i3_scratch", NULL); + Con *__i3_output = con_get_output(__i3_scratch); + DLOG("Current resolution: (%d, %d) %d x %d\n", + __i3_output->rect.x, __i3_output->rect.y, + __i3_output->rect.width, __i3_output->rect.height); + Con *output; + int new_width = -1, + new_height = -1; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + if (output == __i3_output) + continue; + DLOG("output %s's resolution: (%d, %d) %d x %d\n", + output->name, output->rect.x, output->rect.y, + output->rect.width, output->rect.height); + if (new_width == -1) { + new_width = output->rect.width; + new_height = output->rect.height; + } else { + new_width = _lcm(new_width, output->rect.width); + new_height = _lcm(new_height, output->rect.height); + } + } + DLOG("new width = %d, new height = %d\n", + new_width, new_height); + __i3_output->rect.width = new_width; + __i3_output->rect.height = new_height; +} diff --git a/src/sighandler.c b/src/sighandler.c index fa608ed8..988927f0 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "sighandler.c" /* * vim:ts=4:sw=4:expandtab * @@ -14,11 +16,14 @@ #include #include #include +#include #include #include +static void open_popups(void); + static xcb_gcontext_t pixmap_gc; static xcb_pixmap_t pixmap; static int raised_signal; @@ -27,17 +32,102 @@ static char *crash_text[] = { "i3 just crashed.", "To debug this problem, either attach gdb now", "or press", - "- 'e' to exit and get a core-dump,", + "- 'b' to save a backtrace (needs GDB),", "- 'r' to restart i3 in-place or", "- 'f' to forget the current layout and restart" }; static int crash_text_longest = 5; +static int backtrace_string_index = 3; +static int backtrace_done = 0; + +/* + * Attach gdb to pid_parent and dump a backtrace to i3-backtrace.$pid in the + * tmpdir + */ +static int backtrace(void) { + char *tmpdir = getenv("TMPDIR"); + if (tmpdir == NULL) + tmpdir = "/tmp"; + + pid_t pid_parent = getpid(); + + char *filename = NULL; + int suffix = 0; + struct stat bt; + /* Find a unique filename for the backtrace (since the PID of i3 stays the + * same), so that we don’t overwrite earlier backtraces. */ + do { + FREE(filename); + sasprintf(&filename, "%s/i3-backtrace.%d.%d.txt", tmpdir, pid_parent, suffix); + suffix++; + } while (stat(filename, &bt) == 0); + + pid_t pid_gdb = fork(); + if (pid_gdb < 0) { + DLOG("Failed to fork for GDB\n"); + return -1; + } else if (pid_gdb == 0) { + /* child */ + int stdin_pipe[2], + stdout_pipe[2]; + + pipe(stdin_pipe); + pipe(stdout_pipe); + + /* close standard streams in case i3 is started from a terminal; gdb + * needs to run without controlling terminal for it to work properly in + * this situation */ + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + /* We provide pipe file descriptors for stdin/stdout because gdb < 7.5 + * crashes otherwise, see + * http://sourceware.org/bugzilla/show_bug.cgi?id=14114 */ + dup2(stdin_pipe[0], STDIN_FILENO); + dup2(stdout_pipe[1], STDOUT_FILENO); + + char *pid_s, *gdb_log_cmd; + sasprintf(&pid_s, "%d", pid_parent); + sasprintf(&gdb_log_cmd, "set logging file %s", filename); + + char *args[] = { + "gdb", + start_argv[0], + "-p", + pid_s, + "-batch", + "-nx", + "-ex", gdb_log_cmd, + "-ex", "set logging on", + "-ex", "bt full", + "-ex", "quit", + NULL + }; + execvp(args[0], args); + DLOG("Failed to exec GDB\n"); + exit(1); + } + int status = 0; + + waitpid(pid_gdb, &status, 0); + + /* see if the backtrace was succesful or not */ + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + DLOG("GDB did not run properly\n"); + return -1; + } else if (stat(filename, &bt) == -1) { + DLOG("GDB executed succesfully, but no backtrace was generated\n"); + return -1; + } + return 1; +} /* * Draw the window containing the info text * */ -static int sig_draw_window(xcb_window_t win, int width, int height, int font_height) { +static int sig_draw_window(xcb_window_t win, int width, int height, int font_height, i3String **crash_text_i3strings) { /* re-draw the background */ xcb_rectangle_t border = { 0, 0, width, height}, inner = { 2, 2, width - 4, height - 4}; @@ -49,9 +139,23 @@ static int sig_draw_window(xcb_window_t win, int width, int height, int font_hei /* restore font color */ set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000")); - for (int i = 0; i < sizeof(crash_text) / sizeof(char*); i++) { - draw_text(crash_text[i], strlen(crash_text[i]), false, pixmap, pixmap_gc, + char *bt_colour = "#FFFFFF"; + if (backtrace_done < 0) + bt_colour = "#AA0000"; + else if (backtrace_done > 0) + bt_colour = "#00AA00"; + + for (int i = 0; crash_text_i3strings[i] != NULL; ++i) { + /* fix the colour for the backtrace line when it finished */ + if (i == backtrace_string_index) + set_font_colors(pixmap_gc, get_colorpixel(bt_colour), get_colorpixel("#000000")); + + draw_text(crash_text_i3strings[i], pixmap, pixmap_gc, 8, 5 + i * font_height, width - 16); + + /* and reset the colour again for other lines */ + if (i == backtrace_string_index) + set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000")); } /* Copy the contents of the pixmap to the real window */ @@ -62,7 +166,7 @@ static int sig_draw_window(xcb_window_t win, int width, int height, int font_hei } /* - * Handles keypresses of 'e' or 'r' to exit or restart i3 + * Handles keypresses of 'b', 'r' and 'f' to get a backtrace or restart i3 * */ static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) { @@ -75,10 +179,15 @@ static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_p xcb_keysym_t sym = xcb_key_press_lookup_keysym(keysyms, event, state); - if (sym == 'e') { - DLOG("User issued exit-command, raising error again.\n"); - raise(raised_signal); - exit(1); + if (sym == 'b') { + DLOG("User issued core-dump command.\n"); + + /* fork and exec/attach GDB to the parent to get a backtrace in the + * tmpdir */ + backtrace_done = backtrace(); + + /* re-open the windows to indicate that it's finished */ + open_popups(); } if (sym == 'r') @@ -127,28 +236,20 @@ static xcb_window_t open_input_window(xcb_connection_t *conn, Rect screen_rect, return win; } -/* - * Handle signals - * It creates a window asking the user to restart in-place - * or exit to generate a core dump - * - */ -void handle_signal(int sig, siginfo_t *info, void *data) { - DLOG("i3 crashed. SIG: %d\n", sig); - - struct sigaction action; - action.sa_handler = SIG_DFL; - sigaction(sig, &action, NULL); - raised_signal = sig; - +static void open_popups() { /* width and height of the popup window, so that the text fits in */ int crash_text_num = sizeof(crash_text) / sizeof(char*); int height = 13 + (crash_text_num * config.font.height); + int crash_text_length = sizeof(crash_text) / sizeof(char*); + i3String **crash_text_i3strings = smalloc(sizeof(i3String *) * (crash_text_length + 1)); + /* Pre-compute i3Strings for our text */ + for (int i = 0; i < crash_text_length; ++i) { + crash_text_i3strings[i] = i3string_from_utf8(crash_text[i]); + } + crash_text_i3strings[crash_text_length] = NULL; /* calculate width for longest text */ - size_t text_len = strlen(crash_text[crash_text_longest]); - xcb_char2b_t *longest_text = convert_utf8_to_ucs2(crash_text[crash_text_longest], &text_len); - int font_width = predict_text_width((char *)longest_text, text_len, true); + int font_width = predict_text_width(crash_text_i3strings[crash_text_longest]); int width = font_width + 20; /* Open a popup window on each virtual screen */ @@ -172,9 +273,26 @@ void handle_signal(int sig, siginfo_t *info, void *data) { xcb_grab_pointer(conn, false, win, XCB_NONE, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, win, XCB_NONE, XCB_CURRENT_TIME); - sig_draw_window(win, width, height, config.font.height); + sig_draw_window(win, width, height, config.font.height, crash_text_i3strings); xcb_flush(conn); } +} + +/* + * Handle signals + * It creates a window asking the user to restart in-place + * or exit to generate a core dump + * + */ +void handle_signal(int sig, siginfo_t *info, void *data) { + DLOG("i3 crashed. SIG: %d\n", sig); + + struct sigaction action; + action.sa_handler = SIG_DFL; + sigaction(sig, &action, NULL); + raised_signal = sig; + + open_popups(); xcb_generic_event_t *event; /* Yay, more own eventhandlers… */ diff --git a/src/startup.c b/src/startup.c index bcc2415a..89324dbd 100644 --- a/src/startup.c +++ b/src/startup.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "startup.c" /* * vim:ts=4:sw=4:expandtab * @@ -55,6 +57,57 @@ static void startup_timeout(EV_P_ ev_timer *w, int revents) { free(w); } +/* + * Some applications (such as Firefox) mark a startup sequence as completede + * *before* they even map a window. Therefore, we cannot entirely delete the + * startup sequence once it’s marked as complete. Instead, we’ll mark it for + * deletion in 30 seconds and use that chance to delete old sequences. + * + * This function returns the number of active (!) startup notifications, that + * is, those which are not marked for deletion yet. This is used for changing + * the root window cursor. + * + */ +static int _delete_startup_sequence(struct Startup_Sequence *sequence) { + time_t current_time = time(NULL); + int active_sequences = 0; + + /* Mark the given sequence for deletion in 30 seconds. */ + sequence->delete_at = current_time + 30; + DLOG("Will delete startup sequence %s at timestamp %ld\n", + sequence->id, sequence->delete_at); + + /* Traverse the list and delete everything which was marked for deletion 30 + * seconds ago or earlier. */ + struct Startup_Sequence *current, *next; + for (next = TAILQ_FIRST(&startup_sequences); + next != TAILQ_END(&startup_sequences); + ) { + current = next; + next = TAILQ_NEXT(next, sequences); + + if (current->delete_at == 0) { + active_sequences++; + continue; + } + + if (current_time <= current->delete_at) + continue; + + DLOG("Deleting startup sequence %s, delete_at = %ld, current_time = %ld\n", + current->id, current->delete_at, current_time); + + /* Unref the context, will be free()d */ + sn_launcher_context_unref(current->context); + + /* Delete our internal sequence */ + TAILQ_REMOVE(&startup_sequences, current, sequences); + } + + return active_sequences; + +} + /* * Starts the given application by passing it through a shell. We use double fork * to avoid zombie processes. As the started application’s parent exits (immediately), @@ -180,13 +233,7 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata) { case SN_MONITOR_EVENT_COMPLETED: DLOG("startup sequence %s completed\n", sn_startup_sequence_get_id(snsequence)); - /* Unref the context, will be free()d */ - sn_launcher_context_unref(sequence->context); - - /* Delete our internal sequence */ - TAILQ_REMOVE(&startup_sequences, sequence, sequences); - - if (TAILQ_EMPTY(&startup_sequences)) { + if (_delete_startup_sequence(sequence) == 0) { DLOG("No more startup sequences running, changing root window cursor to default pointer.\n"); /* Change the pointer of the root window to indicate progress */ if (xcursor_supported) @@ -248,13 +295,14 @@ char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t * break; } - free(startup_id); - free(startup_id_reply); - if (!sequence) { DLOG("WARNING: This sequence (ID %s) was not found\n", startup_id); + free(startup_id); + free(startup_id_reply); return NULL; } + free(startup_id); + free(startup_id_reply); return sequence->workspace; } diff --git a/src/tree.c b/src/tree.c index f29369c6..321bc78a 100644 --- a/src/tree.c +++ b/src/tree.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "tree.c" /* * vim:ts=4:sw=4:expandtab * @@ -29,7 +31,9 @@ static Con *_create___i3(void) { x_set_name(__i3, "[i3 con] pseudo-output __i3"); /* For retaining the correct position/size of a scratchpad window, the * dimensions of the real outputs should be multiples of the __i3 - * pseudo-output. */ + * pseudo-output. Ensuring that is the job of scratchpad_fix_resolution() + * which gets called after this function and after detecting all the + * outputs (or whenever an output changes). */ __i3->rect.width = 1280; __i3->rect.height = 1024; @@ -39,6 +43,7 @@ static Con *_create___i3(void) { content->type = CT_CON; FREE(content->name); content->name = sstrdup("content"); + content->layout = L_SPLITH; x_set_name(content, "[i3 con] content __i3"); con_attach(content, __i3, false); @@ -48,6 +53,7 @@ static Con *_create___i3(void) { ws->type = CT_WORKSPACE; ws->num = -1; ws->name = sstrdup("__i3_scratch"); + ws->layout = L_SPLITH; con_attach(ws, content, false); x_set_name(ws, "[i3 con] workspace __i3_scratch"); ws->fullscreen_mode = CF_OUTPUT; @@ -112,6 +118,7 @@ void tree_init(xcb_get_geometry_reply_t *geometry) { FREE(croot->name); croot->name = "root"; croot->type = CT_ROOT; + croot->layout = L_SPLITH; croot->rect = (Rect){ geometry->x, geometry->y, @@ -151,6 +158,7 @@ Con *tree_open_con(Con *con, i3Window *window) { /* 3. create the container and attach it to its parent */ Con *new = con_new(con, window); + new->layout = L_SPLITH; /* 4: re-calculate child->percent for each child */ con_fix_percent(con); @@ -239,8 +247,7 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool } FREE(con->window->class_class); FREE(con->window->class_instance); - FREE(con->window->name_x); - FREE(con->window->name_json); + i3string_free(con->window->name); free(con->window); } @@ -296,7 +303,7 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool } } else { - DLOG("not focusing because we're not killing anybody"); + DLOG("not focusing because we're not killing anybody\n"); } } else { DLOG("not focusing, was not mapped\n"); @@ -337,7 +344,7 @@ void tree_split(Con *con, orientation_t orientation) { /* for a workspace, we just need to change orientation */ if (con->type == CT_WORKSPACE) { DLOG("Workspace, simply changing orientation to %d\n", orientation); - con->orientation = orientation; + con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV; return; } @@ -351,8 +358,9 @@ void tree_split(Con *con, orientation_t orientation) { * child (its split functionality is unused so far), we just change the * orientation (more intuitive than splitting again) */ if (con_num_children(parent) == 1 && - parent->layout == L_DEFAULT) { - parent->orientation = orientation; + (parent->layout == L_SPLITH || + parent->layout == L_SPLITV)) { + parent->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV; DLOG("Just changing orientation of existing container\n"); return; } @@ -364,7 +372,8 @@ void tree_split(Con *con, orientation_t orientation) { TAILQ_REPLACE(&(parent->nodes_head), con, new, nodes); TAILQ_REPLACE(&(parent->focus_head), con, new, focused); new->parent = parent; - new->orientation = orientation; + new->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV; + new->split = true; /* 3: swap 'percent' (resize factor) */ new->percent = con->percent; @@ -375,38 +384,34 @@ void tree_split(Con *con, orientation_t orientation) { } /* - * Moves focus one level up. + * Moves focus one level up. Returns true if focus changed. * */ -void level_up(void) { - /* We cannot go up when we are in fullscreen mode at the moment, that would - * be totally not intuitive */ - if (focused->fullscreen_mode != CF_NONE) { - LOG("Currently in fullscreen, not going up\n"); - return; - } +bool level_up(void) { /* We can focus up to the workspace, but not any higher in the tree */ if ((focused->parent->type != CT_CON && focused->parent->type != CT_WORKSPACE) || focused->type == CT_WORKSPACE) { - LOG("Cannot go up any further\n"); - return; + ELOG("'focus parent': Focus is already on the workspace, cannot go higher than that.\n"); + return false; } con_focus(focused->parent); + return true; } /* - * Moves focus one level down. + * Moves focus one level down. Returns true if focus changed. * */ -void level_down(void) { +bool level_down(void) { /* Go down the focus stack of the current node */ Con *next = TAILQ_FIRST(&(focused->focus_head)); if (next == TAILQ_END(&(focused->focus_head))) { printf("cannot go down\n"); - return; + return false; } con_focus(next); + return true; } static void mark_unmapped(Con *con) { @@ -560,6 +565,10 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) else next = TAILQ_LAST(&(parent->nodes_head), nodes_head); } + /* Don't violate fullscreen focus restrictions. */ + if (!con_fullscreen_permits_focusing(next)) + return false; + /* 3: focus choice comes in here. at the moment we will go down * until we find a window */ /* TODO: check for window, atm we only go down as far as possible */ @@ -594,7 +603,9 @@ void tree_flatten(Con *con) { DLOG("Checking if I can flatten con = %p / %s\n", con, con->name); /* We only consider normal containers without windows */ - if (con->type != CT_CON || con->window != NULL) + if (con->type != CT_CON || + parent->layout == L_OUTPUT || /* con == "content" */ + con->window != NULL) goto recurse; /* Ensure it got only one child */ @@ -602,12 +613,14 @@ void tree_flatten(Con *con) { if (child == NULL || TAILQ_NEXT(child, nodes) != NULL) goto recurse; + DLOG("child = %p, con = %p, parent = %p\n", child, con, parent); + /* The child must have a different orientation than the con but the same as * the con’s parent to be redundant */ - if (con->orientation == NO_ORIENTATION || - child->orientation == NO_ORIENTATION || - con->orientation == child->orientation || - child->orientation != parent->orientation) + if (!con->split || + !child->split || + con_orientation(con) == con_orientation(child) || + con_orientation(child) != con_orientation(parent)) goto recurse; DLOG("Alright, I have to flatten this situation now. Stay calm.\n"); diff --git a/src/util.c b/src/util.c index 8273e0cf..e623ce81 100644 --- a/src/util.c +++ b/src/util.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "util.c" /* * vim:ts=4:sw=4:expandtab * @@ -303,6 +305,7 @@ void i3_restart(bool forget_layout) { char *restart_filename = forget_layout ? NULL : store_restart_layout(); kill_configerror_nagbar(true); + kill_commanderror_nagbar(true); restore_geometry(); diff --git a/src/window.c b/src/window.c index e9e61f16..b886c380 100644 --- a/src/window.c +++ b/src/window.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "window.c" /* * vim:ts=4:sw=4:expandtab * @@ -58,31 +60,11 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo return; } - /* Save the old pointer to make the update atomic */ - char *new_name; - if (asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop), - (char*)xcb_get_property_value(prop)) == -1) { - perror("asprintf()"); - DLOG("Could not get window name\n"); - free(prop); - return; - } - /* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */ - size_t len; - xcb_char2b_t *ucs2_name = convert_utf8_to_ucs2(new_name, &len); - if (ucs2_name == NULL) { - LOG("Could not convert _NET_WM_NAME to UCS-2, ignoring new hint\n"); - FREE(new_name); - free(prop); - return; - } - FREE(win->name_x); - FREE(win->name_json); - win->name_json = new_name; - win->name_x = (char*)ucs2_name; - win->name_len = len; + i3string_free(win->name); + win->name = i3string_from_utf8_with_length(xcb_get_property_value(prop), + xcb_get_property_value_length(prop)); win->name_x_changed = true; - LOG("_NET_WM_NAME changed to \"%s\"\n", win->name_json); + LOG("_NET_WM_NAME changed to \"%s\"\n", i3string_as_utf8(win->name)); win->uses_net_wm_name = true; @@ -116,24 +98,14 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bo return; } - char *new_name; - if (asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop), - (char*)xcb_get_property_value(prop)) == -1) { - perror("asprintf()"); - DLOG("Could not get legacy window name\n"); - free(prop); - return; - } + i3string_free(win->name); + win->name = i3string_from_utf8_with_length(xcb_get_property_value(prop), + xcb_get_property_value_length(prop)); - LOG("WM_NAME changed to \"%s\"\n", new_name); + LOG("WM_NAME changed to \"%s\"\n", i3string_as_utf8(win->name)); LOG("Using legacy window title. Note that in order to get Unicode window " "titles in i3, the application has to set _NET_WM_NAME (UTF-8)\n"); - FREE(win->name_x); - FREE(win->name_json); - win->name_x = new_name; - win->name_json = sstrdup(new_name); - win->name_len = strlen(new_name); win->name_x_changed = true; if (before_mgmt) { diff --git a/src/workspace.c b/src/workspace.c index 928f0bd6..1749959a 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "workspace.c" /* * vim:ts=4:sw=4:expandtab * @@ -14,6 +16,25 @@ * back-and-forth switching. */ static char *previous_workspace_name = NULL; +/* + * Sets ws->layout to splith/splitv if default_orientation was specified in the + * configfile. Otherwise, it uses splith/splitv depending on whether the output + * is higher than wide. + * + */ +static void _workspace_apply_default_orientation(Con *ws) { + /* If default_orientation is set to NO_ORIENTATION we determine + * orientation depending on output resolution. */ + if (config.default_orientation == NO_ORIENTATION) { + Con *output = con_get_output(ws); + ws->layout = (output->rect.height > output->rect.width) ? L_SPLITV : L_SPLITH; + DLOG("Auto orientation. Workspace size set to (%d,%d), setting layout to %d.\n", + output->rect.width, output->rect.height, ws->layout); + } else { + ws->layout = (config.default_orientation == HORIZ) ? L_SPLITH : L_SPLITV; + } +} + /* * Returns a pointer to the workspace with the given number (starting at 0), * creating the workspace if necessary (by allocating the necessary amount of @@ -64,16 +85,8 @@ Con *workspace_get(const char *num, bool *created) { else workspace->num = parsed_num; LOG("num = %d\n", workspace->num); - /* If default_orientation is set to NO_ORIENTATION we - * determine workspace orientation from workspace size. - * Otherwise we just set the orientation to default_orientation. */ - if (config.default_orientation == NO_ORIENTATION) { - workspace->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ; - DLOG("Auto orientation. Output resolution set to (%d,%d), setting orientation to %d.\n", - workspace->rect.width, workspace->rect.height, workspace->orientation); - } else { - workspace->orientation = config.default_orientation; - } + workspace->parent = content; + _workspace_apply_default_orientation(workspace); con_attach(workspace, content, false); @@ -114,14 +127,15 @@ Con *create_workspace_on_output(Output *output, Con *content) { /* We check if this is the workspace * next/prev/next_on_output/prev_on_output/back_and_forth/number command. * Beware: The workspace names "next", "prev", "next_on_output", - * "prev_on_output", "number" and "back_and_forth" are OK, so we check - * before stripping the double quotes */ + * "prev_on_output", "number", "back_and_forth" and "current" are OK, + * so we check before stripping the double quotes */ if (strncasecmp(target, "next", strlen("next")) == 0 || strncasecmp(target, "prev", strlen("prev")) == 0 || strncasecmp(target, "next_on_output", strlen("next_on_output")) == 0 || strncasecmp(target, "prev_on_output", strlen("prev_on_output")) == 0 || strncasecmp(target, "number", strlen("number")) == 0 || - strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0) + strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0 || + strncasecmp(target, "current", strlen("current")) == 0) continue; if (*target == '"') target++; @@ -197,19 +211,12 @@ Con *create_workspace_on_output(Output *output, Con *content) { ws->fullscreen_mode = CF_OUTPUT; - /* If default_orientation is set to NO_ORIENTATION we determine - * orientation depending on output resolution. */ - if (config.default_orientation == NO_ORIENTATION) { - ws->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ; - DLOG("Auto orientation. Workspace size set to (%d,%d), setting orientation to %d.\n", - output->rect.width, output->rect.height, ws->orientation); - } else { - ws->orientation = config.default_orientation; - } + _workspace_apply_default_orientation(ws); return ws; } + /* * Returns true if the workspace is currently visible. Especially important for * multi-monitor environments, as they can have multiple currenlty active @@ -685,18 +692,17 @@ void workspace_update_urgent_flag(Con *ws) { /* * 'Forces' workspace orientation by moving all cons into a new split-con with - * the same orientation as the workspace and then changing the workspace - * orientation. + * the same layout as the workspace and then changing the workspace layout. * */ void ws_force_orientation(Con *ws, orientation_t orientation) { /* 1: create a new split container */ Con *split = con_new(NULL, NULL); split->parent = ws; + split->split = true; - /* 2: copy layout and orientation from workspace */ + /* 2: copy layout from workspace */ split->layout = ws->layout; - split->orientation = ws->orientation; Con *old_focused = TAILQ_FIRST(&(ws->focus_head)); @@ -708,11 +714,12 @@ void ws_force_orientation(Con *ws, orientation_t orientation) { con_attach(child, split, true); } - /* 4: switch workspace orientation */ - ws->orientation = orientation; + /* 4: switch workspace layout */ + ws->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV; + DLOG("split->layout = %d, ws->layout = %d\n", split->layout, ws->layout); /* 5: attach the new split container to the workspace */ - DLOG("Attaching new split to ws\n"); + DLOG("Attaching new split (%p) to ws (%p)\n", split, ws); con_attach(split, ws, false); /* 6: fix the percentages */ @@ -744,19 +751,11 @@ Con *workspace_attach_to(Con *ws) { /* 1: create a new split container */ Con *new = con_new(NULL, NULL); new->parent = ws; + new->split = true; /* 2: set the requested layout on the split con */ new->layout = config.default_layout; - /* 3: While the layout is irrelevant in stacked/tabbed mode, it needs - * to be set. Otherwise, this con will not be interpreted as a split - * container. */ - if (config.default_orientation == NO_ORIENTATION) { - new->orientation = (ws->rect.height > ws->rect.width) ? VERT : HORIZ; - } else { - new->orientation = config.default_orientation; - } - /* 4: attach the new split container to the workspace */ DLOG("Attaching new split %p to workspace %p\n", new, ws); con_attach(new, ws, false); diff --git a/src/x.c b/src/x.c index 08eb8fee..24fd0eac 100644 --- a/src/x.c +++ b/src/x.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "x.c" /* * vim:ts=4:sw=4:expandtab * @@ -299,16 +301,20 @@ void x_window_kill(xcb_window_t window, kill_window_t kill_window) { void x_draw_decoration(Con *con) { Con *parent = con->parent; bool leaf = con_is_leaf(con); + /* This code needs to run for: * • leaf containers * • non-leaf containers which are in a stacked/tabbed container * * It does not need to run for: + * • direct children of outputs or dockareas * • floating containers (they don’t have a decoration) */ if ((!leaf && parent->layout != L_STACKED && parent->layout != L_TABBED) || + parent->type == CT_OUTPUT || + parent->type == CT_DOCKAREA || con->type == CT_FLOATING_CON) return; @@ -396,6 +402,10 @@ void x_draw_decoration(Con *con) { /* 3: draw a rectangle in border color around the client */ if (p->border_style != BS_NONE && p->con_is_leaf) { + /* We might hide some borders adjacent to the screen-edge */ + adjacent_t borders_to_hide = ADJ_NONE; + borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders; + Rect br = con_border_style_rect(con); #if 0 DLOG("con->rect spans %d x %d\n", con->rect.width, con->rect.height); @@ -408,14 +418,20 @@ void x_draw_decoration(Con *con) { * rectangle because some childs are not freely resizable and we want * their background color to "shine through". */ xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->background }); - xcb_rectangle_t borders[] = { - { 0, 0, br.x, r->height }, - { 0, r->height + br.height + br.y, r->width, r->height }, - { r->width + br.width + br.x, 0, r->width, r->height } - }; - xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 3, borders); + if (!(borders_to_hide & ADJ_LEFT_SCREEN_EDGE)) { + xcb_rectangle_t leftline = { 0, 0, br.x, r->height }; + xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &leftline); + } + if (!(borders_to_hide & ADJ_RIGHT_SCREEN_EDGE)) { + xcb_rectangle_t rightline = { r->width + br.width + br.x, 0, r->width, r->height }; + xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &rightline); + } + if (!(borders_to_hide & ADJ_LOWER_SCREEN_EDGE)) { + xcb_rectangle_t bottomline = { 0, r->height + br.height + br.y, r->width, r->height }; + xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &bottomline); + } /* 1pixel border needs an additional line at the top */ - if (p->border_style == BS_1PIXEL) { + if (p->border_style == BS_1PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) { xcb_rectangle_t topline = { br.x, 0, con->rect.width + br.width + br.x, br.y }; xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &topline); } @@ -465,10 +481,10 @@ void x_draw_decoration(Con *con) { int text_offset_y = (con->deco_rect.height - config.font.height) / 2; struct Window *win = con->window; - if (win == NULL || win->name_x == NULL) { + if (win == NULL || win->name == NULL) { /* this is a non-leaf container, we need to make up a good description */ // TODO: use a good description instead of just "another container" - draw_text("another container", strlen("another container"), false, + draw_text_ascii("another container", parent->pixmap, parent->pm_gc, con->deco_rect.x + 2, con->deco_rect.y + text_offset_y, con->deco_rect.width - 2); @@ -492,7 +508,7 @@ void x_draw_decoration(Con *con) { //DLOG("indent_level = %d, indent_mult = %d\n", indent_level, indent_mult); int indent_px = (indent_level * 5) * indent_mult; - draw_text(win->name_x, win->name_len, win->uses_net_wm_name, + draw_text(win->name, parent->pixmap, parent->pm_gc, con->deco_rect.x + 2 + indent_px, con->deco_rect.y + text_offset_y, con->deco_rect.width - 2 - indent_px); @@ -1014,9 +1030,11 @@ void x_set_name(Con *con, const char *name) { * */ void x_set_i3_atoms(void) { + pid_t pid = getpid(); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_SOCKET_PATH, A_UTF8_STRING, 8, (current_socketpath == NULL ? 0 : strlen(current_socketpath)), current_socketpath); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_PID, XCB_ATOM_CARDINAL, 32, 1, &pid); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_CONFIG_PATH, A_UTF8_STRING, 8, strlen(current_configpath), current_configpath); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_SHMLOG_PATH, A_UTF8_STRING, 8, diff --git a/src/xcb.c b/src/xcb.c index 4d7a8c47..caa203f7 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "xcb.c" /* * vim:ts=4:sw=4:expandtab * @@ -48,8 +50,9 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, xcb_cursor_t cursor_id = xcb_generate_id(conn); i3Font cursor_font = load_font("cursor", false); int xcb_cursor = xcursor_get_xcb_cursor(cursor); - xcb_create_glyph_cursor(conn, cursor_id, cursor_font.id, cursor_font.id, - xcb_cursor, xcb_cursor + 1, 0, 0, 0, 65535, 65535, 65535); + xcb_create_glyph_cursor(conn, cursor_id, cursor_font.specific.xcb.id, + cursor_font.specific.xcb.id, xcb_cursor, xcb_cursor + 1, 0, 0, 0, + 65535, 65535, 65535); xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id); xcb_free_cursor(conn, cursor_id); } @@ -193,8 +196,9 @@ void xcb_set_root_cursor(int cursor) { xcb_cursor_t cursor_id = xcb_generate_id(conn); i3Font cursor_font = load_font("cursor", false); int xcb_cursor = xcursor_get_xcb_cursor(cursor); - xcb_create_glyph_cursor(conn, cursor_id, cursor_font.id, cursor_font.id, - xcb_cursor, xcb_cursor + 1, 0, 0, 0, 65535, 65535, 65535); + xcb_create_glyph_cursor(conn, cursor_id, cursor_font.specific.xcb.id, + cursor_font.specific.xcb.id, xcb_cursor, xcb_cursor + 1, 0, 0, 0, + 65535, 65535, 65535); xcb_change_window_attributes(conn, root, XCB_CW_CURSOR, &cursor_id); xcb_free_cursor(conn, cursor_id); xcb_flush(conn); diff --git a/src/xcursor.c b/src/xcursor.c index 058b7ae0..7683b0d3 100644 --- a/src/xcursor.c +++ b/src/xcursor.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "xcursor.c" /* * vim:ts=4:sw=4:expandtab * diff --git a/src/xinerama.c b/src/xinerama.c index f377840f..7e5b5aeb 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -1,3 +1,5 @@ +#undef I3__FILE__ +#define I3__FILE__ "xinerama.c" /* * vim:ts=4:sw=4:expandtab * diff --git a/testcases/Makefile.PL b/testcases/Makefile.PL index 1c987389..b1e698ae 100755 --- a/testcases/Makefile.PL +++ b/testcases/Makefile.PL @@ -11,6 +11,7 @@ WriteMakefile( 'AnyEvent::I3' => '0.09', 'X11::XCB' => '0.03', 'Inline' => 0, + 'ExtUtils::PkgConfig' => 0, 'Test::More' => '0.94', }, PM => {}, # do not install any files from this directory diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 020e2f90..5ea9d078 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -15,6 +15,7 @@ use TAP::Harness; use TAP::Parser; use TAP::Parser::Aggregator; use Time::HiRes qw(time); +use IO::Handle; # these are shipped with the testsuite use lib qw(lib); use StartXDummy; @@ -46,14 +47,18 @@ my @displays = (); my %options = ( valgrind => 0, strace => 0, + xtrace => 0, coverage => 0, restart => 0, ); +my $keep_xdummy_output = 0; my $result = GetOptions( "coverage-testing" => \$options{coverage}, + "keep-xdummy-output" => \$keep_xdummy_output, "valgrind" => \$options{valgrind}, "strace" => \$options{strace}, + "xtrace" => \$options{xtrace}, "display=s" => \@displays, "parallel=i" => \$parallel, "help|?" => \$help, @@ -74,7 +79,7 @@ my $numtests = scalar @testfiles; # No displays specified, let’s start some Xdummy instances. if (@displays == 0) { - @displays = start_xdummy($parallel, $numtests); + @displays = start_xdummy($parallel, $numtests, $keep_xdummy_output); } # 1: create an output directory for this test-run @@ -125,6 +130,7 @@ printf("\nRough time estimate for this run: %.2f seconds\n\n", $timings{GLOBAL}) my $logfile = "$outdir/complete-run.log"; open $log, '>', $logfile or die "Could not create '$logfile': $!"; +$log->autoflush(1); say "Writing logfile to '$logfile'..."; # 3: run all tests @@ -261,9 +267,16 @@ sub take_job { for (1 .. $lines) { my $result = $parser->next; - if (defined($result) and $result->is_test) { + next unless defined($result); + if ($result->is_test) { $tests_completed++; status($display, "$test: [$tests_completed/??] "); + } elsif ($result->is_bailout) { + Log status($display, "$test: BAILOUT"); + status_completed(scalar @done); + say ""; + say "test $test bailed out: " . $result->explanation; + exit 1; } } @@ -342,6 +355,11 @@ C. Runs i3 under strace to trace system calls. The output will be available in C. +=item B<--xtrace> + +Runs i3 under xtrace to trace X11 requests/replies. The output will be +available in C. + =item B<--coverage-testing> Exits i3 cleanly (instead of kill -9) to make coverage testing work properly. diff --git a/testcases/lib/SocketActivation.pm b/testcases/lib/SocketActivation.pm index 8f52bddc..0a062be4 100644 --- a/testcases/lib/SocketActivation.pm +++ b/testcases/lib/SocketActivation.pm @@ -124,6 +124,14 @@ sub activate_i3 { 'sh -c "export LISTEN_PID=\$\$; ' . $cmd . '"'; } + if ($args{xtrace}) { + my $out = "$outdir/xtrace-for-$test.log"; + + # See comment in $args{strace} branch. + $cmd = qq|xtrace -n -o "$out" -- | . + 'sh -c "export LISTEN_PID=\$\$; ' . $cmd . '"'; + } + # We need to use the shell due to using output redirections. exec '/bin/sh', '-c', $cmd; diff --git a/testcases/lib/StartXDummy.pm b/testcases/lib/StartXDummy.pm index 5c739fca..f2ebcadd 100644 --- a/testcases/lib/StartXDummy.pm +++ b/testcases/lib/StartXDummy.pm @@ -9,6 +9,7 @@ use v5.10; our @EXPORT = qw(start_xdummy); +my @pids; my $x_socketpath = '/tmp/.X11-unix/X'; # reads in a whole file @@ -20,13 +21,16 @@ sub slurp { # forks an Xdummy or Xdmx process sub fork_xserver { + my $keep_xdummy_output = shift; my $displaynum = shift; my $pid = fork(); die "Could not fork: $!" unless defined($pid); if ($pid == 0) { # Child, close stdout/stderr, then start Xdummy. - close STDOUT; - close STDERR; + if (!$keep_xdummy_output) { + close STDOUT; + close STDERR; + } exec @_; exit 1; @@ -37,6 +41,8 @@ sub fork_xserver { unlink($x_socketpath . $displaynum); }); + push @pids, $pid; + return $x_socketpath . $displaynum; } @@ -63,11 +69,23 @@ the Xdummy processes and a list of PIDs of the processes. =cut sub start_xdummy { - my ($parallel, $numtests) = @_; + my ($parallel, $numtests, $keep_xdummy_output) = @_; my @displays = (); my @childpids = (); + $SIG{CHLD} = sub { + my $child = waitpid -1, POSIX::WNOHANG; + @pids = grep { $_ != $child } @pids; + return unless @pids == 0; + print STDERR "All Xdummy processes died.\n"; + print STDERR "Use ./complete-run.pl --parallel 1 --keep-xdummy-output\n"; + print STDERR ""; + print STDERR "A frequent cause for this is missing the DUMMY Xorg module,\n"; + print STDERR "package xserver-xorg-video-dummy on Debian.\n"; + exit 1; + }; + # Yeah, I know it’s non-standard, but Perl’s POSIX module doesn’t have # _SC_NPROCESSORS_CONF. my $cpuinfo = slurp('/proc/cpuinfo'); @@ -93,8 +111,9 @@ sub start_xdummy { # We use -config /dev/null to prevent Xdummy from using the system # Xorg configuration. The tests should be independant from the # actual system X configuration. - my $socket = fork_xserver($displaynum, './Xdummy', ":$displaynum", - '-config', '/dev/null', '-nolisten', 'tcp'); + my $socket = fork_xserver($keep_xdummy_output, $displaynum, + './Xdummy', ":$displaynum", '-config', '/dev/null', + '-nolisten', 'tcp'); push(@displays, ":$displaynum"); push(@sockets_waiting, $socket); $displaynum++; diff --git a/testcases/lib/TestWorker.pm b/testcases/lib/TestWorker.pm index 66f22bc0..140537d4 100644 --- a/testcases/lib/TestWorker.pm +++ b/testcases/lib/TestWorker.pm @@ -8,6 +8,8 @@ use IO::Handle; # for ->autoflush use POSIX (); +use Errno qw(EAGAIN); + use Exporter 'import'; our @EXPORT = qw(worker worker_next); @@ -74,7 +76,12 @@ sub worker_wait { my $ipc = $self->{ipc}; my $ipc_fd = fileno($ipc); - while (defined(my $file = $ipc->getline)) { + while (1) { + my $file = $ipc->getline; + if (!defined($file)) { + next if $! == EAGAIN; + last; + } chomp $file; exit unless $file; @@ -105,12 +112,13 @@ sub worker_wait { $test->failure_output(\*STDERR); $test->todo_output(\*STDOUT); - @ENV{qw(DISPLAY TESTNAME OUTDIR VALGRIND STRACE COVERAGE RESTART)} + @ENV{qw(DISPLAY TESTNAME OUTDIR VALGRIND STRACE XTRACE COVERAGE RESTART)} = ($self->{display}, basename($file), $outdir, $options->{valgrind}, $options->{strace}, + $options->{xtrace}, $options->{coverage}, $options->{restart}); diff --git a/testcases/lib/i3test.pm b/testcases/lib/i3test.pm index 4c41a7f2..12f81ea1 100644 --- a/testcases/lib/i3test.pm +++ b/testcases/lib/i3test.pm @@ -34,7 +34,6 @@ our @EXPORT = qw( get_dock_clients cmd sync_with_i3 - does_i3_live exit_gracefully workspace_exists focused_ws @@ -46,6 +45,39 @@ our @EXPORT = qw( $x ); +=head1 NAME + +i3test - Testcase setup module + +=encoding utf-8 + +=head1 SYNOPSIS + + use i3test; + + my $ws = fresh_workspace; + is_num_children($ws, 0, 'no containers on this workspace yet'); + cmd 'open'; + is_num_children($ws, 1, 'one container after "open"'); + + done_testing; + +=head1 DESCRIPTION + +This module is used in every i3 testcase and takes care of automatically +starting i3 before any test instructions run. It also saves you typing of lots +of boilerplate in every test file. + + +i3test automatically "use"s C, C, C, +C’s C and C so that all of them are available +to you in your testcase. + +See also C (L) +which provides additional test instructions (like C or C). + +=cut + my $tester = Test::Builder->new(); my $_cached_socket_path = undef; my $_sync_window = undef; @@ -115,6 +147,7 @@ use Test::More $test_more_args; use Data::Dumper; use AnyEvent::I3; use Time::HiRes qw(sleep); +use i3test::Test; __ $tester->BAIL_OUT("$@") if $@; feature->import(":5.10"); @@ -128,15 +161,19 @@ __ goto \&Exporter::import; } -# -# Waits for the next event and calls the given callback for every event to -# determine if this is the event we are waiting for. -# -# Can be used to wait until a window is mapped, until a ClientMessage is -# received, etc. -# -# wait_for_event $x, 0.25, sub { $_[0]->{response_type} == MAP_NOTIFY }; -# +=head1 EXPORT + +=head2 wait_for_event($timeout, $callback) + +Waits for the next event and calls the given callback for every event to +determine if this is the event we are waiting for. + +Can be used to wait until a window is mapped, until a ClientMessage is +received, etc. + + wait_for_event 0.25, sub { $_[0]->{response_type} == MAP_NOTIFY }; + +=cut sub wait_for_event { my ($timeout, $cb) = @_; @@ -165,8 +202,24 @@ sub wait_for_event { return $result; } -# thin wrapper around wait_for_event which waits for MAP_NOTIFY -# make sure to include 'structure_notify' in the window’s event_mask attribute +=head2 wait_for_map($window) + +Thin wrapper around wait_for_event which waits for MAP_NOTIFY. +Make sure to include 'structure_notify' in the window’s event_mask attribute. + +This function is called by C, so in most cases, you don’t need to +call it on your own. If you need special setup of the window before mapping, +you might have to map it on your own and use this function: + + my $window = open_window(dont_map => 1); + # Do something special with the window first + # … + + # Now map it and wait until it’s been mapped + $window->map; + wait_for_map($window); + +=cut sub wait_for_map { my ($win) = @_; my $id = (blessed($win) && $win->isa('X11::XCB::Window')) ? $win->id : $win; @@ -175,9 +228,20 @@ sub wait_for_map { }; } -# Wrapper around wait_for_event which waits for UNMAP_NOTIFY. Also calls -# sync_with_i3 to make sure i3 also picked up and processed the UnmapNotify -# event. +=head2 wait_for_unmap($window) + +Wrapper around C which waits for UNMAP_NOTIFY. Also calls +C to make sure i3 also picked up and processed the UnmapNotify +event. + + my $ws = fresh_workspace; + my $window = open_window; + is_num_children($ws, 1, 'one window on workspace'); + $window->unmap; + wait_for_unmap; + is_num_children($ws, 0, 'no more windows on this workspace'); + +=cut sub wait_for_unmap { my ($win) = @_; # my $id = (blessed($win) && $win->isa('X11::XCB::Window')) ? $win->id : $win; @@ -187,25 +251,71 @@ sub wait_for_unmap { sync_with_i3(); } -# -# Opens a new window (see X11::XCB::Window), maps it, waits until it got mapped -# and synchronizes with i3. -# -# set dont_map to a true value to avoid mapping -# -# if you want to change aspects of your window before it would be mapped, -# set before_map to a coderef. $window gets passed as $_ and as first argument. -# -# if you set both dont_map and before_map, the coderef will be called nevertheless -# -# -# default values: -# class => WINDOW_CLASS_INPUT_OUTPUT -# rect => [ 0, 0, 30, 30 ] -# background_color => '#c0c0c0' -# event_mask => [ 'structure_notify' ] -# name => 'Window ' -# +=head2 open_window([ $args ]) + +Opens a new window (see C), maps it, waits until it got mapped +and synchronizes with i3. + +The following arguments can be passed: + +=over 4 + +=item class + +The X11 window class (e.g. WINDOW_CLASS_INPUT_OUTPUT), not to be confused with +the WM_CLASS! + +=item rect + +An arrayref with 4 members specifying the initial geometry (position and size) +of the window, e.g. C<< [ 0, 100, 70, 50 ] >> for a window appearing at x=0, y=100 +with width=70 and height=50. + +Note that this is entirely irrelevant for tiling windows. + +=item background_color + +The background pixel color of the window, formatted as "#rrggbb", like HTML +color codes (e.g. #c0c0c0). This is useful to tell windows apart when actually +watching the testcases. + +=item event_mask + +An arrayref containing strings which describe the X11 event mask we use for that +window. The default is C<< [ 'structure_notify' ] >>. + +=item name + +The window’s C<_NET_WM_NAME> (UTF-8 window title). By default, this is "Window +n" with n being replaced by a counter to keep windows apart. + +=item dont_map + +Set to a true value to avoid mapping the window (making it visible). + +=item before_map + +A coderef which is called before the window is mapped (unless C is +true). The freshly created C<$window> is passed as C<$_> and as the first +argument. + +=back + +The default values are equivalent to this call: + + open_window( + class => WINDOW_CLASS_INPUT_OUTPUT + rect => [ 0, 0, 30, 30 ] + background_color => '#c0c0c0' + event_mask => [ 'structure_notify' ] + name => 'Window ' + ); + +Usually, though, calls are simpler: + + my $top_window = open_window; + +=cut sub open_window { my %args = @_ == 1 ? %{$_[0]} : @_; @@ -233,8 +343,14 @@ sub open_window { return $window; } -# Thin wrapper around open_window which sets window_type to -# _NET_WM_WINDOW_TYPE_UTILITY to make the window floating. +=head2 open_floating_window([ $args ]) + +Thin wrapper around open_window which sets window_type to +C<_NET_WM_WINDOW_TYPE_UTILITY> to make the window floating. + +The arguments are the same as those of C. + +=cut sub open_floating_window { my %args = @_ == 1 ? %{$_[0]} : @_; @@ -250,6 +366,15 @@ sub open_empty_con { return $reply->[0]->{id}; } +=head2 get_workspace_names() + +Returns an arrayref containing the name of every workspace (regardless of its +output) which currently exists. + + my $workspace_names = get_workspace_names; + is(scalar @$workspace_names, 3, 'three workspaces exist currently'); + +=cut sub get_workspace_names { my $i3 = i3(get_socket_path()); my $tree = $i3->get_tree->recv; @@ -264,6 +389,15 @@ sub get_workspace_names { [ map { $_->{name} } @cons ] } +=head2 get_unused_workspace + +Returns a workspace name which has not yet been used. See also +C which directly switches to an unused workspace. + + my $ws = get_unused_workspace; + cmd "workspace $ws"; + +=cut sub get_unused_workspace { my @names = get_workspace_names(); my $tmp; @@ -271,7 +405,7 @@ sub get_unused_workspace { $tmp } -=head2 fresh_workspace(...) +=head2 fresh_workspace([ $args ]) Switches to an unused workspace and returns the name of that workspace. @@ -304,6 +438,30 @@ sub fresh_workspace { $unused } +=head2 get_ws($workspace) + +Returns the container (from the i3 layout tree) which represents C<$workspace>. + + my $ws = fresh_workspace; + my $ws_con = get_ws($ws); + ok(!$ws_con->{urgent}, 'fresh workspace not marked urgent'); + +Here is an example which counts the number of urgent containers recursively, +starting from the workspace container: + + sub count_urgent { + my ($con) = @_; + + my @children = (@{$con->{nodes}}, @{$con->{floating_nodes}}); + my $urgent = grep { $_->{urgent} } @children; + $urgent += count_urgent($_) for @children; + return $urgent; + } + my $urgent = count_urgent(get_ws($ws)); + is($urgent, 3, "three urgent windows on workspace $ws"); + + +=cut sub get_ws { my ($name) = @_; my $i3 = i3(get_socket_path()); @@ -322,17 +480,61 @@ sub get_ws { return first { $_->{name} eq $name } @workspaces; } -# -# returns the content (== tree, starting from the node of a workspace) -# of a workspace. If called in array context, also includes the focus -# stack of the workspace -# +=head2 get_ws_content($workspace) + +Returns the content (== tree, starting from the node of a workspace) +of a workspace. If called in array context, also includes the focus +stack of the workspace. + + my $nodes = get_ws_content($ws); + is(scalar @$nodes, 4, 'there are four containers at workspace-level'); + +Or, in array context: + + my $window = open_window; + my ($nodes, $focus) = get_ws_content($ws); + is($focus->[0], $window->id, 'newly opened window focused'); + +Note that this function does not do recursion for you! It only returns the +containers B. If you want to work with all containers (even +nested ones) on a workspace, you have to use recursion: + + # NB: This function does not count floating windows + sub count_urgent { + my ($nodes) = @_; + + my $urgent = 0; + for my $con (@$nodes) { + $urgent++ if $con->{urgent}; + $urgent += count_urgent($con->{nodes}); + } + + return $urgent; + } + my $nodes = get_ws_content($ws); + my $urgent = count_urgent($nodes); + is($urgent, 3, "three urgent windows on workspace $ws"); + +If you also want to deal with floating windows, you have to use C +instead and access C<< ->{nodes} >> and C<< ->{floating_nodes} >> on your own. + +=cut sub get_ws_content { my ($name) = @_; my $con = get_ws($name); return wantarray ? ($con->{nodes}, $con->{focus}) : $con->{nodes}; } +=head2 get_focused($workspace) + +Returns the container ID of the currently focused container on C<$workspace>. + + my $ws = fresh_workspace; + my $first_window = open_window; + my $second_window = open_window; + is(get_focused($ws), $second_window, 'second window focused'); + +=cut sub get_focused { my ($ws) = @_; my $con = get_ws($ws); @@ -350,6 +552,16 @@ sub get_focused { return $lf; } +=head2 get_dock_clients([ $dockarea ]) + +Returns an array of all dock containers in C<$dockarea> (one of "top" or +"bottom"). If C<$dockarea> is not specified, returns an array of all dock +containers in any dockarea. + + my @docked = get_dock_clients; + is(scalar @docked, 0, 'no dock clients yet'); + +=cut sub get_dock_clients { my $which = shift; @@ -374,10 +586,30 @@ sub get_dock_clients { return @docked; } +=head2 cmd($command) + +Sends the specified command to i3. + + my $ws = unused_workspace; + cmd "workspace $ws"; + cmd 'focus right'; + +=cut sub cmd { i3(get_socket_path())->command(@_)->recv } +=head2 workspace_exists($workspace) + +Returns true if C<$workspace> is the name of an existing workspace. + + my $old_ws = focused_ws; + # switch away from where we currently are + fresh_workspace; + + ok(workspace_exists($old_ws), 'old workspace still exists'); + +=cut sub workspace_exists { my ($name) = @_; ($name ~~ @{get_workspace_names()}) @@ -387,6 +619,9 @@ sub workspace_exists { Returns the name of the currently focused workspace. + my $ws = focused_ws; + is($ws, '1', 'i3 starts on workspace 1'); + =cut sub focused_ws { my $i3 = i3(get_socket_path()); @@ -398,16 +633,31 @@ sub focused_ws { return $first->{name} } -# -# Sends an I3_SYNC ClientMessage with a random value to the root window. -# i3 will reply with the same value, but, due to the order of events it -# processes, only after all other events are done. -# -# This can be used to ensure the results of a cmd 'focus left' are pushed to -# X11 and that $x->input_focus returns the correct value afterwards. -# -# See also docs/testsuite for a long explanation -# +=head2 sync_with_i3([ $args ]) + +Sends an I3_SYNC ClientMessage with a random value to the root window. +i3 will reply with the same value, but, due to the order of events it +processes, only after all other events are done. + +This can be used to ensure the results of a cmd 'focus left' are pushed to +X11 and that C<< $x->input_focus >> returns the correct value afterwards. + +See also L for a longer explanation. + + my $window = open_window; + $window->add_hint('urgency'); + # Ensure i3 picked up the change + sync_with_i3; + +The only time when you need to use the C argument is when you just +killed your own X11 connection: + + cmd 'kill client'; + # We need to re-establish the X11 connection which we just killed :). + $x = i3test::X11->new; + sync_with_i3(no_cache => 1); + +=cut sub sync_with_i3 { my %args = @_ == 1 ? %{$_[0]} : @_; @@ -458,15 +708,22 @@ sub sync_with_i3 { }; } -sub does_i3_live { - my $tree = i3(get_socket_path())->get_tree->recv; - my @nodes = @{$tree->{nodes}}; - my $ok = (@nodes > 0); - $tester->ok($ok, 'i3 still lives'); - return $ok; -} +=head2 exit_gracefully($pid, [ $socketpath ]) + +Tries to exit i3 gracefully (with the 'exit' cmd) or kills the PID if that fails. + +If C<$socketpath> is not specified, C will be called. -# Tries to exit i3 gracefully (with the 'exit' cmd) or kills the PID if that fails +You only need to use this function if you have launched i3 on your own with +C. Otherwise, it will be automatically called when the +testcase ends. + + use i3test i3_autostart => 0; + my $pid = launch_with_config($config); + # … + exit_gracefully($pid); + +=cut sub exit_gracefully { my ($pid, $socketpath) = @_; $socketpath ||= get_socket_path(); @@ -491,7 +748,20 @@ sub exit_gracefully { undef $i3_pid; } -# Gets the socket path from the I3_SOCKET_PATH atom stored on the X11 root window +=head2 get_socket_path([ $cache ]) + +Gets the socket path from the C atom stored on the X11 root +window. After the first call, this function will return a cached version of the +socket path unless you specify a false value for C<$cache>. + + my $i3 = i3(get_socket_path()); + $i3->command('nop test example')->recv; + +To avoid caching: + + my $i3 = i3(get_socket_path(0)); + +=cut sub get_socket_path { my ($cache) = @_; $cache ||= 1; @@ -511,9 +781,26 @@ sub get_socket_path { return $socketpath; } -# -# launches a new i3 process with the given string as configuration file. -# useful for tests which test specific config file directives. +=head2 launch_with_config($config, [ $args ]) + +Launches a new i3 process with C<$config> as configuration file. Useful for +tests which test specific config file directives. + + use i3test i3_autostart => 0; + + my $config = < $ENV{TESTNAME}, valgrind => $ENV{VALGRIND}, strace => $ENV{STRACE}, + xtrace => $ENV{XTRACE}, restart => $ENV{RESTART}, cv => $cv, dont_create_temp_dir => $args{dont_create_temp_dir}, @@ -563,6 +851,12 @@ sub launch_with_config { return $i3_pid; } +=head1 AUTHOR + +Michael Stapelberg + +=cut + package i3test::X11; use parent 'X11::XCB::Connection'; diff --git a/testcases/lib/i3test/Test.pm b/testcases/lib/i3test/Test.pm new file mode 100644 index 00000000..0253bc2d --- /dev/null +++ b/testcases/lib/i3test/Test.pm @@ -0,0 +1,109 @@ +package i3test::Test; +# vim:ts=4:sw=4:expandtab + +use base 'Test::Builder::Module'; + +our @EXPORT = qw( + is_num_children + cmp_float + does_i3_live +); + +my $CLASS = __PACKAGE__; + +=head1 NAME + +i3test::Test - Additional test instructions for use in i3 testcases + +=head1 SYNOPSIS + + use i3test; + + my $ws = fresh_workspace; + is_num_children($ws, 0, 'no containers on this workspace yet'); + cmd 'open'; + is_num_children($ws, 1, 'one container after "open"'); + + done_testing; + +=head1 DESCRIPTION + +This module provides convenience methods for i3 testcases. If you notice that a +certain pattern is present in 5 or more test cases, it should most likely be +moved into this module. + +=head1 EXPORT + +=head2 is_num_children($workspace, $expected, $test_name) + +Gets the number of children on the given workspace and verifies that they match +the expected amount of children. + + is_num_children('1', 0, 'no containers on workspace 1 at startup'); + +=cut + +sub is_num_children { + my ($workspace, $num_children, $name) = @_; + my $tb = $CLASS->builder; + + my $con = i3test::get_ws($workspace); + $tb->ok(defined($con), "Workspace $workspace exists"); + if (!defined($con)) { + $tb->skip("Workspace does not exist, skipping is_num_children"); + return; + } + + my $got_num_children = scalar @{$con->{nodes}}; + + $tb->is_num($got_num_children, $num_children, $name); +} + +=head2 cmp_float($a, $b) + +Compares floating point numbers C<$a> and C<$b> and returns true if they differ +less then 1e-6. + + $tmp = fresh_workspace; + + open_window for (1..4); + + cmd 'resize grow width 10 px or 25 ppt'; + + ($nodes, $focus) = get_ws_content($tmp); + ok(cmp_float($nodes->[0]->{percent}, 0.166666666666667), 'first window got 16%'); + ok(cmp_float($nodes->[1]->{percent}, 0.166666666666667), 'second window got 16%'); + ok(cmp_float($nodes->[2]->{percent}, 0.166666666666667), 'third window got 16%'); + ok(cmp_float($nodes->[3]->{percent}, 0.50), 'fourth window got 50%'); + +=cut +sub cmp_float { + my ($a, $b, $name) = @_; + my $tb = $CLASS->builder; + + $tb->cmp_ok(abs($a - $b), '<', 1e-6, $name); +} + +=head2 does_i3_live + +Returns true if the layout tree can still be received from i3. + + # i3 used to crash on invalid commands in revision X + cmd 'invalid command'; + does_i3_live; + +=cut +sub does_i3_live { + my $tree = i3test::i3(i3test::get_socket_path())->get_tree->recv; + my @nodes = @{$tree->{nodes}}; + my $tb = $CLASS->builder; + $tb->ok((@nodes > 0), 'i3 still lives'); +} + +=head1 AUTHOR + +Michael Stapelberg + +=cut + +1 diff --git a/testcases/t/001-tile.t b/testcases/t/001-tile.t index c13b87c4..61685a64 100644 --- a/testcases/t/001-tile.t +++ b/testcases/t/001-tile.t @@ -1,5 +1,18 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) use i3test; diff --git a/testcases/t/002-i3-sync.t b/testcases/t/002-i3-sync.t index 1377ee94..7d840426 100644 --- a/testcases/t/002-i3-sync.t +++ b/testcases/t/002-i3-sync.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # checks if i3 supports I3_SYNC # use i3test; diff --git a/testcases/t/003-ipc.t b/testcases/t/003-ipc.t index 34359f20..020e19cc 100644 --- a/testcases/t/003-ipc.t +++ b/testcases/t/003-ipc.t @@ -1,5 +1,18 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) use i3test; diff --git a/testcases/t/004-unmanaged.t b/testcases/t/004-unmanaged.t index e998eb46..cb173ac0 100644 --- a/testcases/t/004-unmanaged.t +++ b/testcases/t/004-unmanaged.t @@ -1,5 +1,18 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) use i3test; diff --git a/testcases/t/005-floating.t b/testcases/t/005-floating.t index db5fb6db..2a0d9102 100644 --- a/testcases/t/005-floating.t +++ b/testcases/t/005-floating.t @@ -1,5 +1,18 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) use i3test; diff --git a/testcases/t/100-fullscreen.t b/testcases/t/100-fullscreen.t index cee77132..81a97d06 100644 --- a/testcases/t/100-fullscreen.t +++ b/testcases/t/100-fullscreen.t @@ -1,5 +1,18 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) use i3test; use List::Util qw(first); diff --git a/testcases/t/101-focus.t b/testcases/t/101-focus.t index 8a795c46..d6ce0fb8 100644 --- a/testcases/t/101-focus.t +++ b/testcases/t/101-focus.t @@ -1,5 +1,18 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) use i3test; diff --git a/testcases/t/102-dock.t b/testcases/t/102-dock.t index 20acf49e..1bac40f0 100644 --- a/testcases/t/102-dock.t +++ b/testcases/t/102-dock.t @@ -1,5 +1,18 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) use i3test; use X11::XCB 'PROP_MODE_REPLACE'; diff --git a/testcases/t/103-move.t b/testcases/t/103-move.t index 040faf20..0e01d90b 100644 --- a/testcases/t/103-move.t +++ b/testcases/t/103-move.t @@ -1,5 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Beware that this test uses workspace 9 to perform some tests (it expects # the workspace to be empty). # TODO: skip it by default? diff --git a/testcases/t/104-focus-stack.t b/testcases/t/104-focus-stack.t index 3b3fe74d..38227635 100644 --- a/testcases/t/104-focus-stack.t +++ b/testcases/t/104-focus-stack.t @@ -1,5 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Checks if the focus is correctly restored, when creating a floating client # over an unfocused tiling client and destroying the floating one again. diff --git a/testcases/t/105-stacking.t b/testcases/t/105-stacking.t index ec7b8df8..96c64975 100644 --- a/testcases/t/105-stacking.t +++ b/testcases/t/105-stacking.t @@ -1,5 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Beware that this test uses workspace 9 to perform some tests (it expects # the workspace to be empty). # TODO: skip it by default? diff --git a/testcases/t/111-goto.t b/testcases/t/111-goto.t index 078ab92c..c8064863 100644 --- a/testcases/t/111-goto.t +++ b/testcases/t/111-goto.t @@ -1,5 +1,18 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) use i3test; use File::Temp; diff --git a/testcases/t/112-floating-resize.t b/testcases/t/112-floating-resize.t index 52817d70..ec690b5e 100644 --- a/testcases/t/112-floating-resize.t +++ b/testcases/t/112-floating-resize.t @@ -1,5 +1,18 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) use i3test; diff --git a/testcases/t/113-urgent.t b/testcases/t/113-urgent.t index 04f72c3d..10368532 100644 --- a/testcases/t/113-urgent.t +++ b/testcases/t/113-urgent.t @@ -1,5 +1,18 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) use i3test; use List::Util qw(first); diff --git a/testcases/t/114-client-leader.t b/testcases/t/114-client-leader.t index 497bad9e..63e92c3c 100644 --- a/testcases/t/114-client-leader.t +++ b/testcases/t/114-client-leader.t @@ -1,5 +1,18 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) use i3test; diff --git a/testcases/t/115-ipc-workspaces.t b/testcases/t/115-ipc-workspaces.t index 4d9a0294..ec2ec9d2 100644 --- a/testcases/t/115-ipc-workspaces.t +++ b/testcases/t/115-ipc-workspaces.t @@ -1,5 +1,18 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) use i3test; diff --git a/testcases/t/116-nestedcons.t b/testcases/t/116-nestedcons.t index 3a495e27..79447386 100644 --- a/testcases/t/116-nestedcons.t +++ b/testcases/t/116-nestedcons.t @@ -1,5 +1,18 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) use i3test; use List::Util qw(first); @@ -39,14 +52,16 @@ my $expected = { name => 'root', orientation => $ignore, type => 0, + split => JSON::XS::false, id => $ignore, rect => $ignore, window_rect => $ignore, geometry => $ignore, swallows => $ignore, percent => undef, - layout => 'default', + layout => 'splith', floating => 'auto_off', + last_split_layout => 'splith', scratchpad_state => 'none', focus => $ignore, focused => JSON::XS::false, diff --git a/testcases/t/117-workspace.t b/testcases/t/117-workspace.t index 1d8888c7..7991abe5 100644 --- a/testcases/t/117-workspace.t +++ b/testcases/t/117-workspace.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests whether we can switch to a non-existant workspace # (necessary for further tests) # diff --git a/testcases/t/118-openkill.t b/testcases/t/118-openkill.t index e2a729c5..fa4becc4 100644 --- a/testcases/t/118-openkill.t +++ b/testcases/t/118-openkill.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests whether opening an empty container and killing it again works # use List::Util qw(first); diff --git a/testcases/t/119-match.t b/testcases/t/119-match.t index e6a4e832..7ac622c7 100644 --- a/testcases/t/119-match.t +++ b/testcases/t/119-match.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests all kinds of matching methods # use i3test; @@ -26,8 +39,7 @@ my $win = $content->[0]; cmd q|[class=".*"] kill|; cmd q|[con_id="99999"] kill|; -$content = get_ws_content($tmp); -ok(@{$content} == 1, 'window still there'); +is_num_children($tmp, 1, 'window still there'); # now kill the window cmd 'nop now killing the window'; @@ -37,8 +49,7 @@ cmd qq|[con_id="$id"] kill|; wait_for_unmap $window; cmd 'nop checking if its gone'; -$content = get_ws_content($tmp); -ok(@{$content} == 0, 'window killed'); +is_num_children($tmp, 0, 'window killed'); # TODO: same test, but with pcre expressions @@ -86,15 +97,13 @@ my $right = open_special(name => 'right'); ok($right->mapped, 'right window mapped'); # two windows should be here -$content = get_ws_content($tmp); -ok(@{$content} == 2, 'two windows opened'); +is_num_children($tmp, 2, 'two windows opened'); cmd '[class="special" title="left"] kill'; sync_with_i3; -$content = get_ws_content($tmp); -is(@{$content}, 1, 'one window still there'); +is_num_children($tmp, 1, 'one window still there'); ###################################################################### # check that regular expressions work @@ -104,17 +113,11 @@ $tmp = fresh_workspace; $left = open_special(name => 'left', wm_class => 'special7'); ok($left->mapped, 'left window mapped'); - -# two windows should be here -$content = get_ws_content($tmp); -ok(@{$content} == 1, 'window opened'); +is_num_children($tmp, 1, 'window opened'); cmd '[class="^special[0-9]$"] kill'; - wait_for_unmap $left; - -$content = get_ws_content($tmp); -is(@{$content}, 0, 'window killed'); +is_num_children($tmp, 0, 'window killed'); ###################################################################### # check that UTF-8 works when matching @@ -124,16 +127,10 @@ $tmp = fresh_workspace; $left = open_special(name => 'ä 3', wm_class => 'special7'); ok($left->mapped, 'left window mapped'); - -# two windows should be here -$content = get_ws_content($tmp); -ok(@{$content} == 1, 'window opened'); +is_num_children($tmp, 1, 'window opened'); cmd '[title="^\w [3]$"] kill'; - wait_for_unmap $left; - -$content = get_ws_content($tmp); -is(@{$content}, 0, 'window killed'); +is_num_children($tmp, 0, 'window killed'); done_testing; diff --git a/testcases/t/120-multiple-cmds.t b/testcases/t/120-multiple-cmds.t index 088caf71..2403fe22 100644 --- a/testcases/t/120-multiple-cmds.t +++ b/testcases/t/120-multiple-cmds.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests multiple commands (using ';') and multiple operations (using ',') # use i3test; diff --git a/testcases/t/121-next-prev.t b/testcases/t/121-next-prev.t index 447be315..3228b259 100644 --- a/testcases/t/121-next-prev.t +++ b/testcases/t/121-next-prev.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests focus switching (next/prev) # use i3test; diff --git a/testcases/t/122-split.t b/testcases/t/122-split.t index f672e9d6..01765e1e 100644 --- a/testcases/t/122-split.t +++ b/testcases/t/122-split.t @@ -1,9 +1,23 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests splitting # use i3test; +use List::Util qw(first); my $tmp; my $ws; @@ -19,10 +33,10 @@ sub verify_split_layout { $tmp = fresh_workspace; $ws = get_ws($tmp); - is($ws->{orientation}, 'horizontal', 'orientation horizontal by default'); + is($ws->{layout}, 'splith', 'orientation horizontal by default'); cmd 'split v'; $ws = get_ws($tmp); - is($ws->{orientation}, 'vertical', 'split v changes workspace orientation'); + is($ws->{layout}, 'splitv', 'split v changes workspace orientation'); cmd 'open'; cmd 'open'; @@ -47,7 +61,7 @@ sub verify_split_layout { is(@{$first->{nodes}}, 0, 'first container has no children'); isnt($second->{name}, $old_name, 'second container was replaced'); - is($second->{orientation}, 'horizontal', 'orientation is horizontal'); + is($second->{layout}, 'splith', 'orientation is horizontal'); is(@{$second->{nodes}}, 2, 'second container has 2 children'); is($second->{nodes}->[0]->{name}, $old_name, 'found old second container'); } @@ -66,10 +80,10 @@ verify_split_layout(split_command => 'split horizontal'); $tmp = fresh_workspace; $ws = get_ws($tmp); -is($ws->{orientation}, 'horizontal', 'orientation horizontal by default'); +is($ws->{layout}, 'splith', 'orientation horizontal by default'); cmd 'split v'; $ws = get_ws($tmp); -is($ws->{orientation}, 'vertical', 'split v changes workspace orientation'); +is($ws->{layout}, 'splitv', 'split v changes workspace orientation'); cmd 'open'; my @content = @{get_ws_content($tmp)}; @@ -119,4 +133,29 @@ cmd 'open'; is(scalar @content, 1, 'Still one container on this ws'); is(scalar @{$content[0]->{nodes}}, 1, 'Stacked con still has one child node'); +################################################################################ +# When focusing the workspace, changing the layout should have an effect on the +# workspace, not on the parent (CT_CONTENT) container. +################################################################################ + +sub get_output_content { + my $tree = i3(get_socket_path())->get_tree->recv; + + my @outputs = grep { $_->{name} !~ /^__/ } @{$tree->{nodes}}; + is(scalar @outputs, 1, 'exactly one output (testcase not multi-monitor capable)'); + my $output = $outputs[0]; + # get the first (and only) CT_CON + return first { $_->{type} == 2 } @{$output->{nodes}}; +} + +$tmp = fresh_workspace; + +cmd 'open'; +cmd 'split v'; +cmd 'open'; +cmd 'focus parent'; +is(get_output_content()->{layout}, 'splith', 'content container layout ok'); +cmd 'layout stacked'; +is(get_output_content()->{layout}, 'splith', 'content container layout still ok'); + done_testing; diff --git a/testcases/t/124-move.t b/testcases/t/124-move.t index 052cdbff..739dc605 100644 --- a/testcases/t/124-move.t +++ b/testcases/t/124-move.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests moving. Basically, there are four different code-paths: # 1) move a container which cannot be moved (single container on a workspace) # 2) move a container before another single container @@ -100,14 +113,12 @@ is($nodes->[1]->{id}, $second, 'second container on bottom'); # move it outside again cmd 'move left'; -$content = get_ws_content($tmp); -is(@{$content}, 3, 'three nodes on this workspace'); +is_num_children($tmp, 3, 'three containers after moving left'); # due to automatic flattening/cleanup, the remaining split container # will be replaced by the con itself, so we will still have 3 nodes cmd 'move right'; -$content = get_ws_content($tmp); -is(@{$content}, 2, 'two nodes on this workspace'); +is_num_children($tmp, 2, 'two containers after moving right (flattening)'); ###################################################################### # 4) We create two v-split containers on the workspace, then we move @@ -128,8 +139,7 @@ cmd "move right"; cmd 'focus left'; cmd "move right"; -$content = get_ws_content($otmp); -is(@{$content}, 1, 'only one nodes on this workspace'); +is_num_children($otmp, 1, 'only one node on this workspace'); ###################################################################### # 5) test moving floating containers. diff --git a/testcases/t/126-regress-close.t b/testcases/t/126-regress-close.t index 8aec87d7..76e7f47f 100644 --- a/testcases/t/126-regress-close.t +++ b/testcases/t/126-regress-close.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression: closing of floating clients did crash i3 when closing the # container which contained this client. # diff --git a/testcases/t/127-regress-floating-parent.t b/testcases/t/127-regress-floating-parent.t index c83c0809..40507b51 100644 --- a/testcases/t/127-regress-floating-parent.t +++ b/testcases/t/127-regress-floating-parent.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression: make a container floating, kill its parent, make it tiling again # use i3test; diff --git a/testcases/t/128-open-order.t b/testcases/t/128-open-order.t index ee58968f..e6f8069d 100644 --- a/testcases/t/128-open-order.t +++ b/testcases/t/128-open-order.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Check if new containers are opened after the currently focused one instead # of always at the end use List::Util qw(first); diff --git a/testcases/t/129-focus-after-close.t b/testcases/t/129-focus-after-close.t index 5fc3786e..df226e84 100644 --- a/testcases/t/129-focus-after-close.t +++ b/testcases/t/129-focus-after-close.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Check if the focus is correctly restored after closing windows. # use i3test; diff --git a/testcases/t/130-close-empty-split.t b/testcases/t/130-close-empty-split.t index bf93cc69..bcc83896 100644 --- a/testcases/t/130-close-empty-split.t +++ b/testcases/t/130-close-empty-split.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Check if empty split containers are automatically closed. # use i3test; diff --git a/testcases/t/131-stacking-order.t b/testcases/t/131-stacking-order.t index 9c1e74ca..c04f1b09 100644 --- a/testcases/t/131-stacking-order.t +++ b/testcases/t/131-stacking-order.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Check if stacking containers can be used independantly of # the split mode (horizontal/vertical) of the underlying # container. diff --git a/testcases/t/132-move-workspace.t b/testcases/t/132-move-workspace.t index 3f00428c..ba26c85f 100644 --- a/testcases/t/132-move-workspace.t +++ b/testcases/t/132-move-workspace.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Checks if the 'move [window/container] to workspace' command works correctly # use i3test; @@ -18,20 +31,20 @@ sub move_workspace_test { my $tmp2 = get_unused_workspace(); cmd "workspace $tmp"; - ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); + is_num_children($tmp, 0, 'no containers yet'); my $first = open_empty_con($i3); my $second = open_empty_con($i3); - ok(@{get_ws_content($tmp)} == 2, 'two containers on first ws'); + is_num_children($tmp, 2, 'two containers on first ws'); cmd "workspace $tmp2"; - ok(@{get_ws_content($tmp2)} == 0, 'no containers on second ws yet'); + is_num_children($tmp2, 0, 'no containers on second ws yet'); cmd "workspace $tmp"; cmd "$movecmd $tmp2"; - ok(@{get_ws_content($tmp)} == 1, 'one container on first ws anymore'); - ok(@{get_ws_content($tmp2)} == 1, 'one container on second ws'); + is_num_children($tmp, 1, 'one container on first ws anymore'); + is_num_children($tmp2, 1, 'one container on second ws'); my ($nodes, $focus) = get_ws_content($tmp2); is($focus->[0], $second, 'same container on different ws'); @@ -53,7 +66,7 @@ move_workspace_test('move container to workspace'); cmd 'workspace 13: meh'; cmd 'open'; -ok(@{get_ws_content('13: meh')} == 1, 'one container on 13: meh'); +is_num_children('13: meh', 1, 'one container on 13: meh'); ok(!workspace_exists('13'), 'workspace 13 does not exist yet'); @@ -61,8 +74,8 @@ cmd 'workspace 12'; cmd 'open'; cmd 'move to workspace number 13'; -ok(@{get_ws_content('13: meh')} == 2, 'two containers on 13: meh'); -ok(@{get_ws_content('12')} == 0, 'no container on 12 anymore'); +is_num_children('13: meh', 2, 'one container on 13: meh'); +is_num_children('12', 0, 'no container on 12 anymore'); ok(!workspace_exists('13'), 'workspace 13 does still not exist'); @@ -76,28 +89,48 @@ ok(!workspace_exists('13'), 'workspace 13 does still not exist'); my $tmp = get_unused_workspace(); my $tmp2 = get_unused_workspace(); cmd "workspace $tmp"; -ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); +is_num_children($tmp, 0, 'no containers yet'); my $first = open_empty_con($i3); my $second = open_empty_con($i3); -ok(@{get_ws_content($tmp)} == 2, 'two containers on first ws'); +is_num_children($tmp, 2, 'two containers'); cmd "workspace $tmp2"; -ok(@{get_ws_content($tmp2)} == 0, 'no containers yet'); +is_num_children($tmp2, 0, 'no containers yet'); my $third = open_empty_con($i3); -ok(@{get_ws_content($tmp2)} == 1, 'one container on second ws'); +is_num_children($tmp2, 1, 'one container on second ws'); # go back to the first workspace, move one of the containers to the next one cmd "workspace $tmp"; cmd 'move workspace next'; -ok(@{get_ws_content($tmp)} == 1, 'one container on first ws'); -ok(@{get_ws_content($tmp2)} == 2, 'two containers on second ws'); +is_num_children($tmp, 1, 'one container on first ws'); +is_num_children($tmp2, 2, 'two containers on second ws'); # go to the second workspace and move two containers to the first one cmd "workspace $tmp2"; cmd 'move workspace prev'; cmd 'move workspace prev'; -ok(@{get_ws_content($tmp)} == 3, 'three containers on first ws'); -ok(@{get_ws_content($tmp2)} == 0, 'no containers on second ws'); +is_num_children($tmp, 3, 'three containers on first ws'); +is_num_children($tmp2, 0, 'no containers on second ws'); + +################################################################### +# check if 'move workspace current' works +################################################################### + +$tmp = get_unused_workspace(); +$tmp2 = get_unused_workspace(); + +cmd "workspace $tmp"; +$first = open_window(name => 'win-name'); +is_num_children($tmp, 1, 'one container on first ws'); + +cmd "workspace $tmp2"; +is_num_children($tmp2, 0, 'no containers yet'); + +cmd qq|[title="win-name"] move workspace $tmp2|; +is_num_children($tmp2, 1, 'one container on second ws'); + +cmd qq|[title="win-name"] move workspace $tmp|; +is_num_children($tmp2, 0, 'no containers on second ws'); ################################################################### # check if floating cons are moved to new workspaces properly @@ -121,4 +154,26 @@ $ws = get_ws($tmp2); is(@{$ws->{nodes}}, 0, 'no nodes on workspace'); is(@{$ws->{floating_nodes}}, 1, 'one floating node on workspace'); +################################################################################ +# Check that 'move workspace number' works correctly. +################################################################################ + +$tmp = get_unused_workspace(); +cmd 'open'; + +cmd 'workspace 16'; +cmd 'open'; +is_num_children('16', 1, 'one node on ws 16'); + +cmd "workspace $tmp"; +cmd 'open'; +cmd 'move workspace number 16'; +is_num_children('16', 2, 'two nodes on ws 16'); + +ok(!workspace_exists('17'), 'workspace 17 does not exist yet'); +cmd 'open'; +cmd 'move workspace number 17'; +ok(workspace_exists('17'), 'workspace 17 created by moving'); +is(@{get_ws('17')->{nodes}}, 1, 'one node on ws 16'); + done_testing; diff --git a/testcases/t/133-size-hints.t b/testcases/t/133-size-hints.t index d3736e3c..1d2cf4ce 100644 --- a/testcases/t/133-size-hints.t +++ b/testcases/t/133-size-hints.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Checks if size hints are interpreted correctly. # use i3test; diff --git a/testcases/t/134-invalid-command.t b/testcases/t/134-invalid-command.t index d58985e3..494cf367 100644 --- a/testcases/t/134-invalid-command.t +++ b/testcases/t/134-invalid-command.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) # # use i3test; diff --git a/testcases/t/135-floating-focus.t b/testcases/t/135-floating-focus.t index c7218130..f38a1472 100644 --- a/testcases/t/135-floating-focus.t +++ b/testcases/t/135-floating-focus.t @@ -1,5 +1,18 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) use i3test; diff --git a/testcases/t/136-floating-ws-empty.t b/testcases/t/136-floating-ws-empty.t index fa747718..703707aa 100644 --- a/testcases/t/136-floating-ws-empty.t +++ b/testcases/t/136-floating-ws-empty.t @@ -1,6 +1,21 @@ #!perl # vim:ts=4:sw=4:expandtab -# Regression test: when only having a floating window on a workspace, it should not be deleted. +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Regression test: when only having a floating window on a workspace, it should +# not be deleted. use i3test; diff --git a/testcases/t/137-floating-unmap.t b/testcases/t/137-floating-unmap.t index e91870bc..6861b1f9 100644 --- a/testcases/t/137-floating-unmap.t +++ b/testcases/t/137-floating-unmap.t @@ -1,5 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression test: Floating windows were not correctly unmapped when switching # to a different workspace. diff --git a/testcases/t/138-floating-attach.t b/testcases/t/138-floating-attach.t index db86e1ca..79b0b271 100644 --- a/testcases/t/138-floating-attach.t +++ b/testcases/t/138-floating-attach.t @@ -1,5 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression test: New windows were attached to the container of a floating window # if only a floating window is present on the workspace. diff --git a/testcases/t/139-ws-numbers.t b/testcases/t/139-ws-numbers.t index 78b9191a..6829a147 100644 --- a/testcases/t/139-ws-numbers.t +++ b/testcases/t/139-ws-numbers.t @@ -1,5 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Check if numbered workspaces and named workspaces are sorted in the right way # in get_workspaces IPC output (necessary for i3bar etc.). use i3test; diff --git a/testcases/t/140-focus-lost.t b/testcases/t/140-focus-lost.t index 3d78b1bd..0609fecb 100644 --- a/testcases/t/140-focus-lost.t +++ b/testcases/t/140-focus-lost.t @@ -1,5 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression: Check if the focus stays the same when switching the layout # bug introduced by 77d0d42ed2d7ac8cafe267c92b35a81c1b9491eb use i3test; diff --git a/testcases/t/141-resize.t b/testcases/t/141-resize.t index 4f84f213..e038a87b 100644 --- a/testcases/t/141-resize.t +++ b/testcases/t/141-resize.t @@ -1,5 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests resizing tiling containers use i3test; @@ -22,8 +36,8 @@ cmd 'resize grow up 10 px or 25 ppt'; my ($nodes, $focus) = get_ws_content($tmp); -is($nodes->[0]->{percent}, 0.25, 'top window got only 25%'); -is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%'); +cmp_float($nodes->[0]->{percent}, 0.25, 'top window got only 25%'); +cmp_float($nodes->[1]->{percent}, 0.75, 'bottom window got 75%'); ############################################################ @@ -34,8 +48,8 @@ cmd 'split h'; ($nodes, $focus) = get_ws_content($tmp); -is($nodes->[0]->{percent}, 0.25, 'top window got only 25%'); -is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%'); +cmp_float($nodes->[0]->{percent}, 0.25, 'top window got only 25%'); +cmp_float($nodes->[1]->{percent}, 0.75, 'bottom window got 75%'); ############################################################ # checks that resizing within stacked/tabbed cons works @@ -52,14 +66,14 @@ cmd 'split h'; cmd 'layout stacked'; ($nodes, $focus) = get_ws_content($tmp); -is($nodes->[0]->{percent}, 0.5, 'top window got 50%'); -is($nodes->[1]->{percent}, 0.5, 'bottom window got 50%'); +cmp_float($nodes->[0]->{percent}, 0.5, 'top window got 50%'); +cmp_float($nodes->[1]->{percent}, 0.5, 'bottom window got 50%'); cmd 'resize grow up 10 px or 25 ppt'; ($nodes, $focus) = get_ws_content($tmp); -is($nodes->[0]->{percent}, 0.25, 'top window got 25%'); -is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%'); +cmp_float($nodes->[0]->{percent}, 0.25, 'top window got 25%'); +cmp_float($nodes->[1]->{percent}, 0.75, 'bottom window got 75%'); ############################################################ # Checks that resizing in the parent's parent's orientation works. @@ -79,14 +93,14 @@ $top = open_window; $bottom = open_window; ($nodes, $focus) = get_ws_content($tmp); -is($nodes->[0]->{percent}, 0.5, 'left window got 50%'); -is($nodes->[1]->{percent}, 0.5, 'right window got 50%'); +cmp_float($nodes->[0]->{percent}, 0.5, 'left window got 50%'); +cmp_float($nodes->[1]->{percent}, 0.5, 'right window got 50%'); cmd 'resize grow left 10 px or 25 ppt'; ($nodes, $focus) = get_ws_content($tmp); -is($nodes->[0]->{percent}, 0.25, 'left window got 25%'); -is($nodes->[1]->{percent}, 0.75, 'right window got 75%'); +cmp_float($nodes->[0]->{percent}, 0.25, 'left window got 25%'); +cmp_float($nodes->[1]->{percent}, 0.75, 'right window got 75%'); ################################################################################ # Check that the resize grow/shrink width/height syntax works. @@ -101,8 +115,8 @@ $right = open_window; cmd 'resize grow width 10 px or 25 ppt'; ($nodes, $focus) = get_ws_content($tmp); -is($nodes->[0]->{percent}, 0.25, 'left window got 25%'); -is($nodes->[1]->{percent}, 0.75, 'right window got 75%'); +cmp_float($nodes->[0]->{percent}, 0.25, 'left window got 25%'); +cmp_float($nodes->[1]->{percent}, 0.75, 'right window got 75%'); # Now test it with four windows $tmp = fresh_workspace; @@ -112,19 +126,19 @@ open_window for (1..4); cmd 'resize grow width 10 px or 25 ppt'; ($nodes, $focus) = get_ws_content($tmp); -is($nodes->[0]->{percent}, 0.166666666666667, 'first window got 16%'); -is($nodes->[1]->{percent}, 0.166666666666667, 'second window got 16%'); -is($nodes->[2]->{percent}, 0.166666666666667, 'third window got 16%'); -is($nodes->[3]->{percent}, 0.50, 'fourth window got 50%'); +cmp_float($nodes->[0]->{percent}, 0.166666666666667, 'first window got 16%'); +cmp_float($nodes->[1]->{percent}, 0.166666666666667, 'second window got 16%'); +cmp_float($nodes->[2]->{percent}, 0.166666666666667, 'third window got 16%'); +cmp_float($nodes->[3]->{percent}, 0.50, 'fourth window got 50%'); # height should be a no-op in this situation cmd 'resize grow height 10 px or 25 ppt'; ($nodes, $focus) = get_ws_content($tmp); -is($nodes->[0]->{percent}, 0.166666666666667, 'first window got 16%'); -is($nodes->[1]->{percent}, 0.166666666666667, 'second window got 16%'); -is($nodes->[2]->{percent}, 0.166666666666667, 'third window got 16%'); -is($nodes->[3]->{percent}, 0.50, 'fourth window got 50%'); +cmp_float($nodes->[0]->{percent}, 0.166666666666667, 'first window got 16%'); +cmp_float($nodes->[1]->{percent}, 0.166666666666667, 'second window got 16%'); +cmp_float($nodes->[2]->{percent}, 0.166666666666667, 'third window got 16%'); +cmp_float($nodes->[3]->{percent}, 0.50, 'fourth window got 50%'); ############################################################ diff --git a/testcases/t/142-regress-move-floating.t b/testcases/t/142-regress-move-floating.t index 6b2df806..817b6ae4 100644 --- a/testcases/t/142-regress-move-floating.t +++ b/testcases/t/142-regress-move-floating.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression: move a floating window to a different workspace crashes i3 # use i3test; diff --git a/testcases/t/143-regress-floating-restart.t b/testcases/t/143-regress-floating-restart.t index 03d9ec12..00f0f541 100644 --- a/testcases/t/143-regress-floating-restart.t +++ b/testcases/t/143-regress-floating-restart.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression: floating windows are tiling after restarting, closing them crashes i3 # use i3test; diff --git a/testcases/t/144-regress-floating-resize.t b/testcases/t/144-regress-floating-resize.t index 03318d7a..6e42c883 100644 --- a/testcases/t/144-regress-floating-resize.t +++ b/testcases/t/144-regress-floating-resize.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression: when resizing two containers on a workspace, opening a floating # client, then closing it again, i3 will re-distribute the space on the # workspace as if a tiling container was closed, leading to the containers diff --git a/testcases/t/145-flattening.t b/testcases/t/145-flattening.t index 9d22afc3..33d9f1d1 100644 --- a/testcases/t/145-flattening.t +++ b/testcases/t/145-flattening.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # by moving the window in the opposite orientation that its parent has, we # force i3 to create a new split container with the appropriate orientation. # However, when doing that two times in a row, we end up with two split @@ -22,7 +35,7 @@ cmd 'move up'; cmd 'move right'; my $ws = get_ws($tmp); -is($ws->{orientation}, 'horizontal', 'workspace orientation is horizontal'); +is($ws->{layout}, 'splith', 'workspace layout is splith'); is(@{$ws->{nodes}}, 3, 'all three windows on workspace level'); done_testing; diff --git a/testcases/t/146-floating-reinsert.t b/testcases/t/146-floating-reinsert.t index ca209e1c..e6158f86 100644 --- a/testcases/t/146-floating-reinsert.t +++ b/testcases/t/146-floating-reinsert.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# use i3test; my $tmp = fresh_workspace; diff --git a/testcases/t/147-regress-floatingmove.t b/testcases/t/147-regress-floatingmove.t index ff63711c..7166aef2 100644 --- a/testcases/t/147-regress-floatingmove.t +++ b/testcases/t/147-regress-floatingmove.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression test for moving a con outside of a floating con when there are no # tiling cons on a workspace # diff --git a/testcases/t/148-regress-floatingmovews.t b/testcases/t/148-regress-floatingmovews.t index 3d71b500..248a8ffa 100644 --- a/testcases/t/148-regress-floatingmovews.t +++ b/testcases/t/148-regress-floatingmovews.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression test for correct focus behaviour when moving a floating con to # another workspace. # diff --git a/testcases/t/150-regress-dock-restart.t b/testcases/t/150-regress-dock-restart.t index 3cda6059..cafbaffb 100644 --- a/testcases/t/150-regress-dock-restart.t +++ b/testcases/t/150-regress-dock-restart.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression test for inplace restarting with dock clients # use i3test; diff --git a/testcases/t/151-regress-float-size.t b/testcases/t/151-regress-float-size.t index 881ef8c1..c0fb3a7e 100644 --- a/testcases/t/151-regress-float-size.t +++ b/testcases/t/151-regress-float-size.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression test for setting a window to floating, tiling and opening a new window # use i3test; diff --git a/testcases/t/152-regress-level-up.t b/testcases/t/152-regress-level-up.t index 01009133..771a9f07 100644 --- a/testcases/t/152-regress-level-up.t +++ b/testcases/t/152-regress-level-up.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression test for using level-up to get to the 'content'-container and # toggle floating # diff --git a/testcases/t/153-floating-originalsize.t b/testcases/t/153-floating-originalsize.t index 83f3e85d..d2cf206d 100644 --- a/testcases/t/153-floating-originalsize.t +++ b/testcases/t/153-floating-originalsize.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Test if the requested width/height is set after making the window floating. # use i3test; diff --git a/testcases/t/154-regress-multiple-dock.t b/testcases/t/154-regress-multiple-dock.t index 76577fb3..3bb0dd94 100644 --- a/testcases/t/154-regress-multiple-dock.t +++ b/testcases/t/154-regress-multiple-dock.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression test for closing one of multiple dock clients # use i3test; diff --git a/testcases/t/155-floating-split-size.t b/testcases/t/155-floating-split-size.t index 76c31af6..7475f9c7 100644 --- a/testcases/t/155-floating-split-size.t +++ b/testcases/t/155-floating-split-size.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Test to see if i3 combines the geometry of all children in a split container # when setting the split container to floating # diff --git a/testcases/t/156-fullscreen-focus.t b/testcases/t/156-fullscreen-focus.t index f9dc6dce..29a410d2 100644 --- a/testcases/t/156-fullscreen-focus.t +++ b/testcases/t/156-fullscreen-focus.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Test if new containers get focused when there is a fullscreen container at # the time of launching the new one. Also make sure that focusing containers # in other workspaces work even when there is a fullscreen container. @@ -11,9 +24,9 @@ my $i3 = i3(get_socket_path()); my $tmp = fresh_workspace; -##################################################################### -# open the left window -##################################################################### +################################################################################ +# Open the left window. +################################################################################ my $left = open_window({ background_color => '#ff0000' }); @@ -21,23 +34,26 @@ is($x->input_focus, $left->id, 'left window focused'); diag("left = " . $left->id); -##################################################################### -# Open the right window -##################################################################### +################################################################################ +# Open the right window. +################################################################################ my $right = open_window({ background_color => '#00ff00' }); diag("right = " . $right->id); -##################################################################### -# Set the right window to fullscreen -##################################################################### +################################################################################ +# Set the right window to fullscreen. +################################################################################ + cmd 'nop setting fullscreen'; cmd 'fullscreen'; -##################################################################### -# Open a third window -##################################################################### +################################################################################ +# Open a third window. Since we're fullscreen, the window won't be # mapped, so +# don't wait for it to be mapped. Instead, just send the map request and sync +# with i3 to make sure i3 recognizes it. +################################################################################ my $third = open_window({ background_color => '#0000ff', @@ -51,13 +67,15 @@ sync_with_i3; diag("third = " . $third->id); -# move the fullscreen window to a different ws +################################################################################ +# Move the window to a different workspace, and verify that the third window now +# gets focused in the current workspace. +################################################################################ my $tmp2 = get_unused_workspace; cmd "move workspace $tmp2"; -# verify that the third window has the focus is($x->input_focus, $third->id, 'third window focused'); ################################################################################ @@ -87,20 +105,204 @@ is($nodes->[0]->{id}, $old_id, 'id unchanged'); is($nodes->[0]->{focused}, 1, 'fullscreen window focused'); ################################################################################ -# Make sure it's possible to focus a container in a different workspace even if -# we are currently focusing a fullscreen container. +# Ensure it's possible to change focus if it doesn't escape the fullscreen +# container with fullscreen global. We can't even focus a container in a +# different workspace. ################################################################################ +cmd 'fullscreen'; + +$tmp = fresh_workspace; +cmd "workspace $tmp"; +my $diff_ws = open_window; + $tmp2 = fresh_workspace; -my $focusable_window = open_window; +cmd "workspace $tmp2"; +cmd 'split h'; + +$left = open_window; +my $right1 = open_window; +cmd 'split v'; +my $right2 = open_window; + +cmd 'focus parent'; +cmd 'fullscreen global'; + +cmd '[id="' . $right1->id . '"] focus'; +is($x->input_focus, $right1->id, 'upper right window focused'); + +cmd '[id="' . $right2->id . '"] focus'; +is($x->input_focus, $right2->id, 'bottom right window focused'); + +cmd 'focus parent'; +isnt($x->input_focus, $right2->id, 'bottom right window no longer focused'); + +cmd 'focus child'; +is($x->input_focus, $right2->id, 'bottom right window focused again'); + +cmd '[id="' . $left->id . '"] focus'; +is($x->input_focus, $right2->id, 'prevented focus change to left window'); + +cmd 'focus up'; +is($x->input_focus, $right1->id, 'allowed focus up'); + +cmd 'focus down'; +is($x->input_focus, $right2->id, 'allowed focus down'); + +cmd 'focus left'; +is($x->input_focus, $right2->id, 'prevented focus left'); + +cmd 'focus right'; +is($x->input_focus, $right2->id, 'prevented focus right'); + +cmd 'focus down'; +is($x->input_focus, $right1->id, 'allowed focus wrap (down)'); + +cmd 'focus up'; +is($x->input_focus, $right2->id, 'allowed focus wrap (up)'); + +cmd '[id="' . $diff_ws->id . '"] focus'; +is($x->input_focus, $right2->id, 'prevented focus change to different ws'); + +################################################################################ +# Same tests when we're in non-global fullscreen mode. It should now be possible +# to focus a container in a different workspace. +################################################################################ + +cmd 'focus parent'; +cmd 'fullscreen global'; +cmd 'fullscreen'; + +cmd '[id="' . $right1->id . '"] focus'; +is($x->input_focus, $right1->id, 'upper right window focused'); + +cmd '[id="' . $right2->id . '"] focus'; +is($x->input_focus, $right2->id, 'bottom right window focused'); + +cmd 'focus parent'; +isnt($x->input_focus, $right2->id, 'bottom right window no longer focused'); + +cmd 'focus child'; +is($x->input_focus, $right2->id, 'bottom right window focused again'); + +cmd '[id="' . $left->id . '"] focus'; +is($x->input_focus, $right2->id, 'prevented focus change to left window'); + +cmd 'focus up'; +is($x->input_focus, $right1->id, 'allowed focus up'); + +cmd 'focus down'; +is($x->input_focus, $right2->id, 'allowed focus down'); + +cmd 'focus left'; +is($x->input_focus, $right2->id, 'prevented focus left'); + +cmd 'focus right'; +is($x->input_focus, $right2->id, 'prevented focus right'); + +cmd 'focus down'; +is($x->input_focus, $right1->id, 'allowed focus wrap (down)'); + +cmd 'focus up'; +is($x->input_focus, $right2->id, 'allowed focus wrap (up)'); + +cmd '[id="' . $diff_ws->id . '"] focus'; +is($x->input_focus, $diff_ws->id, 'allowed focus change to different ws'); + +################################################################################ +# More testing of the interaction between wrapping and the fullscreen focus +# restrictions. +################################################################################ + +cmd '[id="' . $right1->id . '"] focus'; +is($x->input_focus, $right1->id, 'upper right window focused'); + +cmd 'focus parent'; +cmd 'fullscreen'; +cmd 'focus child'; + +cmd 'split v'; +my $right12 = open_window; + +cmd 'focus down'; +is($x->input_focus, $right2->id, 'bottom right window focused'); + +cmd 'split v'; +my $right22 = open_window; + +cmd 'focus parent'; +cmd 'fullscreen'; +cmd 'focus child'; + +cmd 'focus down'; +is($x->input_focus, $right2->id, 'focus did not leave parent container (1)'); + +cmd 'focus down'; +is($x->input_focus, $right22->id, 'focus did not leave parent container (2)'); + +cmd 'focus up'; +is($x->input_focus, $right2->id, 'focus did not leave parent container (3)'); + +cmd 'focus up'; +is($x->input_focus, $right22->id, 'focus did not leave parent container (4)'); + +################################################################################ +# Ensure that moving in a direction doesn't violate the focus restrictions. +################################################################################ + +sub verify_move { + my $num = shift; + my $msg = shift; + my $nodes = get_ws_content($tmp2); + my $split = $nodes->[1]; + my $fs = $split->{nodes}->[1]; + is(scalar @{$fs->{nodes}}, $num, $msg); +} + +cmd 'move left'; +verify_move(2, 'prevented move left'); +cmd 'move right'; +verify_move(2, 'prevented move right'); +cmd 'move down'; +verify_move(2, 'prevented move down'); +cmd 'move up'; +cmd 'move up'; +verify_move(2, 'prevented move up'); + +################################################################################ +# Moving to a different workspace is allowed with per-output fullscreen +# containers. +################################################################################ + +cmd "move to workspace $tmp"; +verify_move(1, 'did not prevent move to workspace by name'); cmd "workspace $tmp"; -cmd '[id="' . $focusable_window->id . '"] focus'; +cmd "move to workspace $tmp2"; +cmd "workspace $tmp2"; -is(focused_ws(), $tmp2, 'focus went to a different workspace'); +cmd "move to workspace prev"; +verify_move(1, 'did not prevent move to workspace by position'); -$nodes = get_ws_content($tmp2); -is(scalar @$nodes, 1, 'precisely one window'); -is($nodes->[0]->{focused}, 1, 'focusable window focused'); +################################################################################ +# Ensure that is not allowed with global fullscreen containers. +################################################################################ + +cmd "workspace $tmp"; +cmd "move to workspace $tmp2"; +cmd "workspace $tmp2"; + +cmd 'focus parent'; +cmd 'fullscreen'; +cmd 'fullscreen global'; +cmd 'focus child'; + +cmd "move to workspace $tmp"; +verify_move(2, 'prevented move to workspace by name'); + +cmd "move to workspace prev"; +verify_move(2, 'prevented move to workspace by position'); + +# TODO: Tests for "move to output" and "move workspace to output". done_testing; diff --git a/testcases/t/157-regress-fullscreen-level-up.t b/testcases/t/157-regress-fullscreen-level-up.t deleted file mode 100644 index 316dbcaa..00000000 --- a/testcases/t/157-regress-fullscreen-level-up.t +++ /dev/null @@ -1,41 +0,0 @@ -#!perl -# vim:ts=4:sw=4:expandtab -# -# Regression test: level up should be a noop during fullscreen mode -# -use i3test; - -my $tmp = fresh_workspace; - -##################################################################### -# open a window, verify it’s not in fullscreen mode -##################################################################### - -my $win = open_window; - -my $nodes = get_ws_content $tmp; -is(@$nodes, 1, 'exactly one client'); -is($nodes->[0]->{fullscreen_mode}, 0, 'client not fullscreen'); - -##################################################################### -# make it fullscreen -##################################################################### - -cmd 'nop making fullscreen'; -cmd 'fullscreen'; - -$nodes = get_ws_content $tmp; -is($nodes->[0]->{fullscreen_mode}, 1, 'client fullscreen now'); - -##################################################################### -# send level up, try to un-fullscreen -##################################################################### -cmd 'focus parent'; -cmd 'fullscreen'; - -$nodes = get_ws_content $tmp; -is($nodes->[0]->{fullscreen_mode}, 0, 'client not fullscreen any longer'); - -does_i3_live; - -done_testing; diff --git a/testcases/t/158-wm_take_focus.t b/testcases/t/158-wm_take_focus.t index c4d30575..222c93e4 100644 --- a/testcases/t/158-wm_take_focus.t +++ b/testcases/t/158-wm_take_focus.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests if the WM_TAKE_FOCUS protocol is correctly handled by i3 # use i3test; diff --git a/testcases/t/159-socketpaths.t b/testcases/t/159-socketpaths.t index c63bbbc4..d21581d1 100644 --- a/testcases/t/159-socketpaths.t +++ b/testcases/t/159-socketpaths.t @@ -1,10 +1,24 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests if the various ipc_socket_path options are correctly handled # use i3test i3_autostart => 0; use File::Temp qw(tempfile tempdir); +use File::Basename; use POSIX qw(getuid); use v5.10; @@ -20,21 +34,14 @@ EOT # ensure XDG_RUNTIME_DIR is not set delete $ENV{XDG_RUNTIME_DIR}; -# See which files exist in /tmp before to not mistakenly check an already -# existing tmpdir of another i3 instance. -my @files_before = ; my $pid = launch_with_config($config, dont_add_socket_path => 1, dont_create_temp_dir => 1); -my @files_after = ; -@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); diff --git a/testcases/t/161-regress-borders-restart.t b/testcases/t/161-regress-borders-restart.t index 9ae677e7..1db64575 100644 --- a/testcases/t/161-regress-borders-restart.t +++ b/testcases/t/161-regress-borders-restart.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression test to check if borders are correctly restored after an inplace # restart. # found in eb8ad348b28e243cba1972e802ca8ee636472fc9 diff --git a/testcases/t/162-regress-dock-urgent.t b/testcases/t/162-regress-dock-urgent.t index 3562ba7a..6c349aad 100644 --- a/testcases/t/162-regress-dock-urgent.t +++ b/testcases/t/162-regress-dock-urgent.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression test for setting the urgent hint on dock clients. # found in 4be3178d4d360c2996217d811e61161c84d25898 # diff --git a/testcases/t/163-wm-state.t b/testcases/t/163-wm-state.t index 6df2bcbd..a5966030 100644 --- a/testcases/t/163-wm-state.t +++ b/testcases/t/163-wm-state.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when # unmapped. # diff --git a/testcases/t/164-kill-win-vs-client.t b/testcases/t/164-kill-win-vs-client.t index bce6b23b..be30ca8f 100644 --- a/testcases/t/164-kill-win-vs-client.t +++ b/testcases/t/164-kill-win-vs-client.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when # unmapped. # diff --git a/testcases/t/165-for_window.t b/testcases/t/165-for_window.t index eb266c2b..b01de91d 100644 --- a/testcases/t/165-for_window.t +++ b/testcases/t/165-for_window.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# use i3test i3_autostart => 0; use X11::XCB qw(PROP_MODE_REPLACE); diff --git a/testcases/t/166-assign.t b/testcases/t/166-assign.t index d79c1000..a06bb59d 100644 --- a/testcases/t/166-assign.t +++ b/testcases/t/166-assign.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests if assignments work # use i3test i3_autostart => 0; @@ -57,6 +70,7 @@ my $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); my $window = open_special; +wait_for_map($window); ok(@{get_ws_content($tmp)} == 1, 'special window got managed to current (random) workspace'); @@ -206,24 +220,16 @@ sub i3nagbar_running { $config = < $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), @@ -233,7 +239,7 @@ $content = get_ws($tmp); ok(@{$content->{nodes}} == 0, 'no tiling cons'); ok(@{$content->{floating_nodes}} == 0, 'one floating con'); @docked = get_dock_clients; -is(@docked, 2, 'two dock clients now'); +is(@docked, 1, 'one dock client now'); $window->destroy; diff --git a/testcases/t/167-workspace_layout.t b/testcases/t/167-workspace_layout.t index ee6c9706..033a31f2 100644 --- a/testcases/t/167-workspace_layout.t +++ b/testcases/t/167-workspace_layout.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests the workspace_layout config option. # diff --git a/testcases/t/168-regress-fullscreen-restart.t b/testcases/t/168-regress-fullscreen-restart.t index ec6d4821..ec8c41c8 100644 --- a/testcases/t/168-regress-fullscreen-restart.t +++ b/testcases/t/168-regress-fullscreen-restart.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Verifies that i3 survives inplace restarts with fullscreen containers # use i3test; diff --git a/testcases/t/169-border-toggle.t b/testcases/t/169-border-toggle.t index aec8df6c..7377194d 100644 --- a/testcases/t/169-border-toggle.t +++ b/testcases/t/169-border-toggle.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests if the 'border toggle' command works correctly # use i3test; diff --git a/testcases/t/170-force_focus_wrapping.t b/testcases/t/170-force_focus_wrapping.t index 7949ce66..fd086505 100644 --- a/testcases/t/170-force_focus_wrapping.t +++ b/testcases/t/170-force_focus_wrapping.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests if the 'force_focus_wrapping' config directive works correctly. # use i3test i3_autostart => 0; diff --git a/testcases/t/171-config-migrate.t b/testcases/t/171-config-migrate.t index e791bb01..a0363ef3 100644 --- a/testcases/t/171-config-migrate.t +++ b/testcases/t/171-config-migrate.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests if i3-migrate-config-to-v4 correctly migrates all config file # directives and commands # @@ -195,7 +208,7 @@ EOT $output = migrate_config($input); ok(line_exists($output, qr|^bindsym Mod1\+s layout stacking$|), 's replaced'); -ok(line_exists($output, qr|^bindsym Mod1\+s layout default$|), 'd replaced'); +ok(line_exists($output, qr|^bindsym Mod1\+s layout toggle split$|), 'd replaced'); ok(line_exists($output, qr|^bindsym Mod1\+s layout tabbed$|), 'T replaced'); ok(line_exists($output, qr|^bindsym Mod1\+s fullscreen$|), 'f replaced'); ok(line_exists($output, qr|^bindsym Mod1\+s fullscreen global$|), 'fg replaced'); diff --git a/testcases/t/172-start-on-named-ws.t b/testcases/t/172-start-on-named-ws.t index 42a44459..9e6806a4 100644 --- a/testcases/t/172-start-on-named-ws.t +++ b/testcases/t/172-start-on-named-ws.t @@ -1,5 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # checks if i3 starts up on workspace '1' or the first configured named workspace # use i3test i3_autostart => 0; diff --git a/testcases/t/173-get-marks.t b/testcases/t/173-get-marks.t index e8964d30..3b97feb1 100644 --- a/testcases/t/173-get-marks.t +++ b/testcases/t/173-get-marks.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # checks if the IPC message type get_marks works correctly # use i3test; diff --git a/testcases/t/173-regress-focus-assign.t b/testcases/t/173-regress-focus-assign.t index 22306db6..91d367d1 100644 --- a/testcases/t/173-regress-focus-assign.t +++ b/testcases/t/173-regress-focus-assign.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression: Checks if focus is stolen when a window is managed which is # assigned to an invisible workspace # diff --git a/testcases/t/174-border-config.t b/testcases/t/174-border-config.t index 2586657b..6e837cf0 100644 --- a/testcases/t/174-border-config.t +++ b/testcases/t/174-border-config.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests the new_window and new_float config option. # diff --git a/testcases/t/174-regress-focus-toggle.t b/testcases/t/174-regress-focus-toggle.t index 469d1be8..192e9753 100644 --- a/testcases/t/174-regress-focus-toggle.t +++ b/testcases/t/174-regress-focus-toggle.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression: Checks if i3 still lives after using 'focus mode_toggle' on an # empty workspace. This regression was fixed in # 0848844f2d41055f6ffc69af1149d7a873460976. diff --git a/testcases/t/175-startup-notification.t b/testcases/t/175-startup-notification.t index 3a4dbc81..b27a9a70 100644 --- a/testcases/t/175-startup-notification.t +++ b/testcases/t/175-startup-notification.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Test for the startup notification protocol. # @@ -58,7 +71,7 @@ END_OF_C_CODE my $first_ws = fresh_workspace; -is(@{get_ws_content($first_ws)}, 0, 'no containers on this workspace yet'); +is_num_children($first_ws, 0, 'no containers on this workspace yet'); ###################################################################### # 1) initiate startup, switch workspace, create window @@ -95,7 +108,7 @@ is(get_startup_id(), $startup_id, 'libstartup-notification returns the same id') my $second_ws = fresh_workspace; -is(@{get_ws_content($second_ws)}, 0, 'no containers on the second workspace yet'); +is_num_children($second_ws, 0, 'no containers on the second workspace yet'); my $win = open_window({ dont_map => 1 }); mark_window($win->id); @@ -105,8 +118,8 @@ $win->map; # We sync with i3 here to make sure $x->input_focus is updated. sync_with_i3; -is(@{get_ws_content($second_ws)}, 0, 'still no containers on the second workspace'); -is(@{get_ws_content($first_ws)}, 1, 'one container on the first workspace'); +is_num_children($second_ws, 0, 'still no containers on the second workspace'); +is_num_children($first_ws, 1, 'one container on the first workspace'); ###################################################################### # same thing, but with _NET_STARTUP_ID set on the leader @@ -119,8 +132,8 @@ $win = open_window({ dont_map => 1, client_leader => $leader }); $win->map; sync_with_i3; -is(@{get_ws_content($second_ws)}, 0, 'still no containers on the second workspace'); -is(@{get_ws_content($first_ws)}, 2, 'two containers on the first workspace'); +is_num_children($second_ws, 0, 'still no containers on the second workspace'); +is_num_children($first_ws, 2, 'two containers on the first workspace'); ###################################################################### # 2) open another window after the startup process is completed @@ -131,7 +144,7 @@ complete_startup(); sync_with_i3; my $otherwin = open_window; -is(@{get_ws_content($second_ws)}, 1, 'one container on the second workspace'); +is_num_children($second_ws, 1, 'one container on the second workspace'); ###################################################################### # 3) test that the --no-startup-id flag for exec leads to no DESKTOP_STARTUP_ID @@ -166,5 +179,4 @@ unlink($tmp); is($startup_id, '', 'startup_id empty'); - done_testing; diff --git a/testcases/t/176-workspace-baf.t b/testcases/t/176-workspace-baf.t index 80b2d471..07c3c84a 100644 --- a/testcases/t/176-workspace-baf.t +++ b/testcases/t/176-workspace-baf.t @@ -1,5 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Checks if the 'workspace back_and_forth' command and the # 'workspace_auto_back_and_forth' config directive work correctly. # @@ -66,12 +80,31 @@ ok(get_ws($second_ws)->{focused}, 'second workspace focused'); cmd 'workspace number 5'; ok(get_ws('5')->{focused}, 'workspace 5 focused'); +# ensure it stays open +cmd 'open'; cmd 'workspace number 6'; ok(get_ws('6')->{focused}, 'workspace 6 focused'); +# ensure it stays open +cmd 'open'; + +cmd 'workspace number 6'; +is(focused_ws, '5', 'workspace 5 focused again'); + +################################################################################ +# Rename the workspaces and see if workspace number still works with BAF. +################################################################################ + +cmd 'rename workspace 5 to 5: foo'; +cmd 'rename workspace 6 to 6: baz'; + +is(focused_ws, '5: foo', 'workspace 5 still focused'); + +cmd 'workspace number 6'; +is(focused_ws, '6: baz', 'workspace 6 now focused'); cmd 'workspace number 6'; -ok(get_ws('5')->{focused}, 'workspace 5 focused again'); +is(focused_ws, '5: foo', 'workspace 5 focused again'); exit_gracefully($pid); diff --git a/testcases/t/177-bar-config.t b/testcases/t/177-bar-config.t index 3caa6696..762e52b8 100644 --- a/testcases/t/177-bar-config.t +++ b/testcases/t/177-bar-config.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Checks that the bar config is parsed correctly. # diff --git a/testcases/t/178-regress-workspace-open.t b/testcases/t/178-regress-workspace-open.t index 25fe7d9a..53e67bdc 100644 --- a/testcases/t/178-regress-workspace-open.t +++ b/testcases/t/178-regress-workspace-open.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests if empty workspaces are closed when the last child # exits, as long as they're not empty. # diff --git a/testcases/t/179-regress-multiple-ws.t b/testcases/t/179-regress-multiple-ws.t index 21271170..ae442023 100644 --- a/testcases/t/179-regress-multiple-ws.t +++ b/testcases/t/179-regress-multiple-ws.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # The command "move workspace prev; workspace prev" will lead to an error. # This regression is present in 7f9b65f6a752e454c492447be4e21e2ee8faf8fd use i3test; @@ -15,11 +28,11 @@ my $keep_open_con = open_empty_con($i3); my $tmp = fresh_workspace; my $con = open_empty_con($i3); -is(@{get_ws_content($tmp)}, 1, 'one container'); -is(@{get_ws_content($old)}, 1, 'one container on old ws'); +is_num_children($tmp, 1, 'one container'); +is_num_children($old, 1, 'one container on old ws'); cmd 'move workspace prev; workspace prev'; -is(@{get_ws_content($old)}, 2, 'container moved away'); +is_num_children($old, 2, 'container moved away'); done_testing; diff --git a/testcases/t/180-fd-leaks.t b/testcases/t/180-fd-leaks.t index 487803c4..454bfe7d 100644 --- a/testcases/t/180-fd-leaks.t +++ b/testcases/t/180-fd-leaks.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Verifies that i3 does not leak any file descriptors in 'exec'. # use i3test; diff --git a/testcases/t/181-regress-float-border.t b/testcases/t/181-regress-float-border.t index f77f780a..c6a05424 100644 --- a/testcases/t/181-regress-float-border.t +++ b/testcases/t/181-regress-float-border.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression test: Changing border style should not have an impact on the size # (geometry) of the child window. See ticket http://bugs.i3wm.org/561 # Wrong behaviour manifested itself up to (including) commit diff --git a/testcases/t/182-regress-focus-dock.t b/testcases/t/182-regress-focus-dock.t index 6212a9ea..4aaabb83 100644 --- a/testcases/t/182-regress-focus-dock.t +++ b/testcases/t/182-regress-focus-dock.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression test: Focusing a dock window should just do nothing, not crash i3. # See ticket http://bugs.i3wm.org/575 # Wrong behaviour manifested itself up to (including) commit diff --git a/testcases/t/183-config-variables.t b/testcases/t/183-config-variables.t index 1da25a65..8fbbff70 100644 --- a/testcases/t/183-config-variables.t +++ b/testcases/t/183-config-variables.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Checks that variables are parsed correctly by using for_window rules with # variables in it. # diff --git a/testcases/t/184-regress-float-split-resize.t b/testcases/t/184-regress-float-split-resize.t index 1a21f2b2..d637baf3 100644 --- a/testcases/t/184-regress-float-split-resize.t +++ b/testcases/t/184-regress-float-split-resize.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression: resizing a floating split container leads to a crash. # (Ticket #588, present until 4412ccbe5a4fad8a4cd594e6f10f937515a4d37c) # diff --git a/testcases/t/185-scratchpad.t b/testcases/t/185-scratchpad.t index 06debab3..87bda529 100644 --- a/testcases/t/185-scratchpad.t +++ b/testcases/t/185-scratchpad.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests for the scratchpad functionality. # use i3test; diff --git a/testcases/t/186-regress-assign-focus-parent.t b/testcases/t/186-regress-assign-focus-parent.t index 6f2e584f..7562ad90 100644 --- a/testcases/t/186-regress-assign-focus-parent.t +++ b/testcases/t/186-regress-assign-focus-parent.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Regression test: New windows were not opened in the correct place if they # matched an assignment. # Wrong behaviour manifested itself up to (including) commit @@ -22,29 +35,21 @@ my $i3 = i3(get_socket_path(0)); cmd 'workspace targetws'; open_window(name => "testcase"); - -my $nodes = get_ws_content('targetws'); -is(scalar @$nodes, 1, 'precisely one window'); +is_num_children('targetws', 1, 'precisely one window'); open_window(name => "testcase"); - -$nodes = get_ws_content('targetws'); -is(scalar @$nodes, 2, 'precisely two windows'); +is_num_children('targetws', 2, 'precisely two windows'); cmd 'split v'; open_window(name => "testcase"); - -$nodes = get_ws_content('targetws'); -is(scalar @$nodes, 2, 'still two windows'); +is_num_children('targetws', 2, 'still two windows'); # focus parent. the new window should now be opened right next to the last one. cmd 'focus parent'; open_window(name => "testcase"); - -$nodes = get_ws_content('targetws'); -is(scalar @$nodes, 3, 'new window opened next to last one'); +is_num_children('targetws', 3, 'new window opened next to last one'); exit_gracefully($pid); diff --git a/testcases/t/187-commands-parser.t b/testcases/t/187-commands-parser.t index 8b57a0a1..37deb942 100644 --- a/testcases/t/187-commands-parser.t +++ b/testcases/t/187-commands-parser.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests the standalone parser binary to see if it calls the right code when # confronted with various commands, if it prints proper error messages for # wrong commands and if it terminates in every case. @@ -12,7 +25,7 @@ sub parser_calls { # TODO: use a timeout, so that we can error out if it doesn’t terminate # TODO: better way of passing arguments - my $stdout = qx(../test.commands_parser '$command' 2>&-); + my $stdout = qx(../test.commands_parser '$command' 2>&1 >&-); # Filter out all debugging output. my @lines = split("\n", $stdout); @@ -127,15 +140,15 @@ is(parser_calls("\nworkspace test"), ################################################################################ is(parser_calls('unknown_literal'), - "Expected one of these tokens: , '[', '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: , '[', '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'); ################################################################################ diff --git a/testcases/t/188-regress-focus-restart.t b/testcases/t/188-regress-focus-restart.t index 1de9f366..3d602c11 100644 --- a/testcases/t/188-regress-focus-restart.t +++ b/testcases/t/188-regress-focus-restart.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Verifies that i3 survives inplace restarts with fullscreen containers # use i3test; diff --git a/testcases/t/189-floating-constraints.t b/testcases/t/189-floating-constraints.t index 9b6fb150..a3ce8476 100644 --- a/testcases/t/189-floating-constraints.t +++ b/testcases/t/189-floating-constraints.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests the floating_{minimum,maximum}_size config options. # # Note that the minimum floating window size is already verified in diff --git a/testcases/t/190-scratchpad-diff-ws.t b/testcases/t/190-scratchpad-diff-ws.t index 9b6e6c7a..5451f482 100644 --- a/testcases/t/190-scratchpad-diff-ws.t +++ b/testcases/t/190-scratchpad-diff-ws.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Test for ticket #676: 'scratchpad show' causes a segfault if the scratchpad # window is shown on another workspace. # @@ -45,8 +58,7 @@ my $win = open_window; my $scratch = open_special; cmd '[class="special"] move scratchpad'; -my ($nodes, $focus) = get_ws_content($tmp); -is(scalar @$nodes, 1, 'one window on current ws'); +is_num_children($tmp, 1, 'one window on current ws'); my $otmp = fresh_workspace; cmd 'scratchpad show'; diff --git a/testcases/t/191-resize-levels.t b/testcases/t/191-resize-levels.t new file mode 100644 index 00000000..559a93e9 --- /dev/null +++ b/testcases/t/191-resize-levels.t @@ -0,0 +1,43 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verifies that you can resize across different levels of containers even when +# they are all of the same orientation. +# (Ticket #754) +use i3test; + +my $tmp = fresh_workspace; + +open_window; +open_window; +cmd 'split v'; +my $middle = open_window; +open_window; +cmd 'focus parent'; +cmd 'split h'; +open_window; + +cmd '[id="' . $middle->id . '"] focus'; +is($x->input_focus, $middle->id, 'middle window focused'); + +cmd 'resize grow left 10px or 25ppt'; + +my ($nodes, $focus) = get_ws_content($tmp); + +cmp_float($nodes->[0]->{percent}, 0.25, 'left container got only 25%'); +cmp_float($nodes->[1]->{percent}, 0.75, 'right container got 75%'); + +done_testing; diff --git a/testcases/t/192-layout.t b/testcases/t/192-layout.t new file mode 100644 index 00000000..6fd6eae8 --- /dev/null +++ b/testcases/t/192-layout.t @@ -0,0 +1,98 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verifies that switching between the different layouts works as expected. +use i3test; + +my $tmp = fresh_workspace; + +open_window; +open_window; +cmd 'split v'; +open_window; + +my ($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splitv', 'layout is splitv currently'); + +cmd 'layout stacked'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'stacked', 'layout now stacked'); + +cmd 'layout tabbed'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed'); + +cmd 'layout toggle split'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splitv', 'layout now splitv again'); + +cmd 'layout toggle split'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splith', 'layout now splith'); + +cmd 'layout toggle split'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splitv', 'layout now splitv'); + +cmd 'layout toggle split'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splith', 'layout now splith'); + +cmd 'layout toggle'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'stacked', 'layout now stacked'); + +cmd 'layout toggle'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed'); + +cmd 'layout toggle'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splith', 'layout now splith'); + +cmd 'layout toggle'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'stacked', 'layout now stacked'); + +cmd 'layout toggle all'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed'); + +cmd 'layout toggle all'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splith', 'layout now splith'); + +cmd 'layout toggle all'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splitv', 'layout now splitv'); + +cmd 'layout toggle all'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'stacked', 'layout now stacked'); + +cmd 'layout toggle all'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed'); + +cmd 'layout toggle all'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splith', 'layout now splith'); + +cmd 'layout toggle all'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splitv', 'layout now splitv'); + +done_testing; diff --git a/testcases/t/193-ipc-version.t b/testcases/t/193-ipc-version.t new file mode 100644 index 00000000..d2e082ec --- /dev/null +++ b/testcases/t/193-ipc-version.t @@ -0,0 +1,37 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verifies that we can get the version number of i3 via IPC. +use i3test; + +my $i3 = i3(get_socket_path()); +$i3->connect->recv; +# We explicitly send the version message because AnyEvent::I3’s 'version' sugar +# method has a fallback which tries to parse the version number from i3 +# --version for older versions, and we want to avoid using that. +my $version = $i3->message(7, "")->recv; + +# We need to change this when the major version changes (but we need to touch a +# lot of changes then anyways). +is($version->{major}, 4, 'major version is 4'); + +cmp_ok($version->{minor}, '>', 0, 'minor version > 0'); + +is(int($version->{minor}), $version->{minor}, 'minor version is an integer'); +is(int($version->{patch}), $version->{patch}, 'patch version is an integer'); +like($version->{human_readable}, qr/branch/, 'human readable version contains branch name'); + +done_testing; diff --git a/testcases/t/194-regress-floating-size.t b/testcases/t/194-regress-floating-size.t new file mode 100644 index 00000000..dc6739e5 --- /dev/null +++ b/testcases/t/194-regress-floating-size.t @@ -0,0 +1,57 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verifies that the size requested by floating windows is set by i3, no matter +# to which value the new_window option is set. +# ticket #770, bug still present in commit ae88accf6fe3817ff42d0d51be1965071194766e +use i3test i3_autostart => 0; + +sub test_with_new_window_value { + my ($value) = @_; + + my $config = < [ 0, 0, 400, 150 ] }); + + my ($absolute, $top) = $window->rect; + + ok($window->mapped, 'Window is mapped'); + cmp_ok($absolute->{width}, '==', 400, 'requested width kept'); + cmp_ok($absolute->{height}, '==', 150, 'requested height kept'); + + exit_gracefully($pid); +} + +test_with_new_window_value(undef); +test_with_new_window_value('1pixel'); +test_with_new_window_value('normal'); +test_with_new_window_value('none'); + +done_testing; diff --git a/testcases/t/195-net-active-window.t b/testcases/t/195-net-active-window.t new file mode 100644 index 00000000..c62d4fda --- /dev/null +++ b/testcases/t/195-net-active-window.t @@ -0,0 +1,68 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verifies that the _NET_ACTIVE_WINDOW message only changes focus when the +# window is on a visible workspace. +# ticket #774, bug still present in commit 1e49f1b08a3035c1f238fcd6615e332216ab582e +use i3test; + +sub send_net_active_window { + my ($id) = @_; + + my $msg = pack "CCSLLLLLLL", + X11::XCB::CLIENT_MESSAGE, # response_type + 32, # format + 0, # sequence + $id, # destination window + $x->atom(name => '_NET_ACTIVE_WINDOW')->id, + 0, + 0, + 0, + 0, + 0; + + $x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg); +} + +my $ws1 = fresh_workspace; +my $win1 = open_window; +my $win2 = open_window; + +################################################################################ +# Ensure that the _NET_ACTIVE_WINDOW ClientMessage works when windows are visible +################################################################################ + +is($x->input_focus, $win2->id, 'window 2 has focus'); + +send_net_active_window($win1->id); + +is($x->input_focus, $win1->id, 'window 1 has focus'); + +################################################################################ +# Switch to a different workspace and ensure sending the _NET_ACTIVE_WINDOW +# ClientMessage has no effect anymore. +################################################################################ + +my $ws2 = fresh_workspace; +my $win3 = open_window; + +is($x->input_focus, $win3->id, 'window 3 has focus'); + +send_net_active_window($win1->id); + +is($x->input_focus, $win3->id, 'window 3 still has focus'); + +done_testing; diff --git a/testcases/t/196-randr-output-names.t b/testcases/t/196-randr-output-names.t new file mode 100644 index 00000000..e5049eb8 --- /dev/null +++ b/testcases/t/196-randr-output-names.t @@ -0,0 +1,36 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verify that i3 allows strange RandR output names such as DVI-I_1/digital. +# Ticket: #785 +# Bug still in: 4.2-256-ga007283 +use i3test i3_autostart => 0; +use File::Temp qw(tempfile); + +my ($fh, $filename) = tempfile(UNLINK => 1); +print $fh <{nodes}}, 0, 'no nodes on this ws'); + is_num_children($ws, 0, 'no nodes on this ws'); my $window = open_window; - is(scalar @{get_ws($ws)->{nodes}}, 1, 'one nodes on this ws'); + is_num_children($ws, 1, 'one nodes on this ws'); cmd 'move scratchpad'; - is(scalar @{get_ws($ws)->{nodes}}, 0, 'no nodes on this ws'); + is_num_children($ws, 0, 'no nodes on this ws'); cmd 'scratchpad show'; - is(scalar @{get_ws($ws)->{nodes}}, 0, 'no nodes on this ws'); + is_num_children($ws, 0, 'no nodes on this ws'); is(scalar @{get_ws($ws)->{floating_nodes}}, 1, 'one floating node on this ws'); } @@ -61,21 +74,21 @@ sub verify_scratchpad_switch { cmd "workspace $first"; - is(scalar @{get_ws($first)->{nodes}}, 0, 'no nodes on this ws'); + is_num_children($first, 0, 'no nodes on this ws'); my $window = open_window; - is(scalar @{get_ws($first)->{nodes}}, 1, 'one nodes on this ws'); + is_num_children($first, 1, 'one nodes on this ws'); cmd 'move scratchpad'; - is(scalar @{get_ws($first)->{nodes}}, 0, 'no nodes on this ws'); + is_num_children($first, 0, 'no nodes on this ws'); cmd "workspace $second"; cmd 'scratchpad show'; my $ws = get_ws($second); - is(scalar @{$ws->{nodes}}, 0, 'no nodes on this ws'); + is_num_children($second, 0, 'no nodes on this ws'); is(scalar @{$ws->{floating_nodes}}, 1, 'one floating node on this ws'); # Verify that the coordinates are within bounds. diff --git a/testcases/t/502-focus-output.t b/testcases/t/502-focus-output.t index 4e0fedbb..a6c5583f 100644 --- a/testcases/t/502-focus-output.t +++ b/testcases/t/502-focus-output.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Verifies the 'focus output' command works properly. use i3test i3_autostart => 0; diff --git a/testcases/t/503-workspace.t b/testcases/t/503-workspace.t index 94ba3434..20d4fd2b 100644 --- a/testcases/t/503-workspace.t +++ b/testcases/t/503-workspace.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests whether 'workspace next_on_output' and the like work correctly. # use List::Util qw(first); diff --git a/testcases/t/504-move-workspace-to-output.t b/testcases/t/504-move-workspace-to-output.t index 57e56943..7a976271 100644 --- a/testcases/t/504-move-workspace-to-output.t +++ b/testcases/t/504-move-workspace-to-output.t @@ -1,6 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # Tests whether the 'move workspace to [output] ' command works # use List::Util qw(first); @@ -9,6 +22,10 @@ use i3test i3_autostart => 0; # TODO: # introduce 'move workspace 3 to output ' with synonym 'move workspace 3 to ' +# 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 = < 0; + +my $config = <root->warp_pointer(0, 0); +sync_with_i3; + +sub verify_scratchpad_doesnt_move { + my ($ws) = @_; + + is_num_children($ws, 0, 'no nodes on this ws'); + + my $window = open_window; + is_num_children($ws, 1, 'one node on this ws'); + + cmd 'move scratchpad'; + is_num_children($ws, 0, 'no nodes on this ws'); + + my $last_x = -1; + for (1 .. 20) { + cmd 'scratchpad show'; + is(scalar @{get_ws($ws)->{floating_nodes}}, 1, 'one floating node on this ws'); + + # Verify that the coordinates are within bounds. + my $content = get_ws($ws); + my $srect = $content->{floating_nodes}->[0]->{rect}; + if ($last_x > -1) { + is($srect->{x}, $last_x, 'scratchpad window did not move'); + } + $last_x = $srect->{x}; + cmd 'scratchpad show'; + } + + # We need to kill the scratchpad window, otherwise scratchpad show in + # subsequent calls of verify_scratchpad_doesnt_move will cycle between all + # the windows. + cmd 'scratchpad show'; + cmd 'kill'; +} + +################################################################################ +# test it on the left output first (1366x768) +################################################################################ + +my $second = fresh_workspace(output => 0); +verify_scratchpad_doesnt_move($second); + +################################################################################ +# now on the right output (1024x768) +################################################################################ + +$x->root->warp_pointer(683 + 10, 0); +sync_with_i3; + +my $third = fresh_workspace(output => 1); +verify_scratchpad_doesnt_move($third); + +exit_gracefully($pid); + +done_testing;