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..ca77397a --- /dev/null +++ b/RELEASE-NOTES-4.3 @@ -0,0 +1,186 @@ + + ┌──────────────────────────────┐ + │ Release notes for i3 v4.3 │ + └──────────────────────────────┘ + +This is the i3 v4.3. This version 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 (formerly "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 change is that it’s cleaner to have all + parameters that influence how windows are rendered in the layout itself + rather than having one special layout in combination with an additional + orientation. 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 v3 (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 + enabled. 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 + + +Furthermore, we decided to entirely ignore resize increment size hints for + tiling windows. These are set by terminal emulators (such as urxvt, + gnome-terminal, …) and specify that the window may only be resized in + multiples of the specified size. All terminal emulators cope with the window + manager ignoring these hints and by doing so, they can decide what to do with + the lost space (that is, pseudo-transparency now works without black bars in + urxvt). + + ┌────────────────────────────┐ + │ Changes in v4.3 │ + └────────────────────────────┘ + + • docs: there now is documentation about lib::i3test and lib::i3test::Test, + the main Perl modules used by our testsuite. + • 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/userguide: give 'move to output' its own section + • 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 + • Introduce bindsym --release, which will trigger the binding not on the + KeyPress event, but on the KeyRelease event (useful for import(1) or + xdotool(1)). + • The signalhandler does not offer you to exit i3 anymore. Instead, there is + 'b' for writing a backtrace to a file in /tmp (if gdb is installed) + • Remove support for resize increment hints for tiling windows + • Exit fullscreen mode when 'scratchpad show' is executed while in fullscreen + + ┌────────────────────────────┐ + │ 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 + • Don’t focus the wrong workspace when moving to scratchpad + + ┌────────────────────────────┐ + │ 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, loblik, Marcel + Hellwig, Marcus, mloskot, Moritz Bandemer, oblique, Ondrej Grover, Pavel + Löbl, Philipp Middendorf, prg, Quentin Glidic, Sebastian Ullrich, Simon + Elsbrock, somelauw, stfn, tucos, TunnelWicht, Valentin Haenel + +-- Michael Stapelberg, 2012-09-19 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) +XCB_CFLAGS += $(call cflags_for_lib, xcb-util) +XCB_LIBS += $(call ldflags_for_lib, xcb-util) endif -CFLAGS += $(call cflags_for_lib, xcb-icccm) -CFLAGS += $(call cflags_for_lib, xcb-xinerama) -CFLAGS += $(call cflags_for_lib, xcb-randr) -CFLAGS += $(call cflags_for_lib, xcb) -CFLAGS += $(call cflags_for_lib, xcursor) -CFLAGS += $(call cflags_for_lib, x11) -CFLAGS += $(call cflags_for_lib, yajl) -CFLAGS += $(call cflags_for_lib, libev) -CFLAGS += $(call cflags_for_lib, libpcre) -CFLAGS += $(call cflags_for_lib, libstartup-notification-1.0) -CPPFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" -CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\" +# XCB keyboard stuff +XCB_KBD_CFLAGS := $(call cflags_for_lib, xcb-keysyms) +XCB_KBD_LIBS := $(call ldflags_for_lib, xcb-keysyms,xcb-keysyms) + +# XCB WM stuff +XCB_WM_CFLAGS := $(call cflags_for_lib, xcb-icccm) +XCB_WM_CFLAGS += $(call cflags_for_lib, xcb-xinerama) +XCB_WM_CFLAGS += $(call cflags_for_lib, xcb-randr) +XCB_WM_LIBS := $(call ldflags_for_lib, xcb-icccm,xcb-icccm) +XCB_WM_LIBS += $(call ldflags_for_lib, xcb-xinerama,xcb-xinerama) +XCB_WM_LIBS += $(call ldflags_for_lib, xcb-randr,xcb-randr) + +# Xlib +X11_CFLAGS := $(call cflags_for_lib, x11) +X11_LIBS := $(call ldflags_for_lib, x11,X11) + +# Xcursor +XCURSOR_CFLAGS := $(call cflags_for_lib, xcursor) +XCURSOR_LIBS := $(call ldflags_for_lib, xcursor,Xcursor) + +# yajl +YAJL_CFLAGS := $(call cflags_for_lib, yajl) +# Fallback for libyajl 1 which did not include yajl_version.h. We need +# YAJL_MAJOR from that file to decide which code path should be used. +YAJL_CFLAGS += -idirafter $(TOPDIR)/yajl-fallback +YAJL_LIBS := $(call ldflags_for_lib, yajl,yajl) + +#libev +LIBEV_CFLAGS := $(call cflags_for_lib, libev) +LIBEV_LIBS := $(call ldflags_for_lib, libev,ev) + +# libpcre +PCRE_CFLAGS := $(call cflags_for_lib, libpcre) ifeq ($(shell pkg-config --atleast-version=8.10 libpcre 2>/dev/null && echo 1),1) -CPPFLAGS += -DPCRE_HAS_UCP=1 +I3_CPPFLAGS += -DPCRE_HAS_UCP=1 endif +PCRE_LIBS := $(call ldflags_for_lib, libpcre,pcre) -LIBS += -lm -# Darwin (Mac OS X) doesn’t have librt -ifneq ($(UNAME),Darwin) -LIBS += -lrt -endif -LIBS += -L $(TOPDIR)/libi3 -li3 -LIBS += $(call ldflags_for_lib, xcb-event,xcb-event) -LIBS += $(call ldflags_for_lib, xcb-keysyms,xcb-keysyms) -ifeq ($(shell pkg-config --exists xcb-util 2>/dev/null || echo 1),1) -LIBS += $(call ldflags_for_lib, xcb-atom,xcb-atom) -LIBS += $(call ldflags_for_lib, xcb-aux,xcb-aux) -else -LIBS += $(call ldflags_for_lib, xcb-util) -endif -LIBS += $(call ldflags_for_lib, xcb-icccm,xcb-icccm) -LIBS += $(call ldflags_for_lib, xcb-xinerama,xcb-xinerama) -LIBS += $(call ldflags_for_lib, xcb-randr,xcb-randr) -LIBS += $(call ldflags_for_lib, xcb,xcb) -LIBS += $(call ldflags_for_lib, xcursor,Xcursor) -LIBS += $(call ldflags_for_lib, x11,X11) -LIBS += $(call ldflags_for_lib, yajl,yajl) -LIBS += $(call ldflags_for_lib, libev,ev) -LIBS += $(call ldflags_for_lib, libpcre,pcre) -LIBS += $(call ldflags_for_lib, libstartup-notification-1.0,startup-notification-1) +# startup-notification +LIBSN_CFLAGS := $(call cflags_for_lib, libstartup-notification-1.0) +LIBSN_LIBS := $(call ldflags_for_lib, libstartup-notification-1.0,startup-notification-1) + +# 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..8a246efc 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -77,7 +77,7 @@ workspace, the split container we are talking about is the workspace. To get an impression of how different layouts are represented, just play around and look at the data structures -- they are exposed as a JSON hash. See -http://i3wm.org/docs/ipc.html#_get_tree_reply for documentation on that and an +http://i3wm.org/docs/ipc.html#_tree_reply for documentation on that and an example. == Files @@ -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,76 +42,99 @@

+

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.

+
+ +
+

Changing the container layout

+ + + + +
+ e + default + +
+ s + stacking + +
+ w + tabbed
-
-

Opening other applications

+

Floating

-
+d - open application (with dmenu) -
- - -
-

Closing windows

- + -
+ + + toggle floating
++ q - kill a window + + + drag floating
@@ -120,17 +143,10 @@

Using workspaces

-
+19 + + 0-9 switch to another workspace -
-
- - -
-

Moving windows to workspaces

- -
+ + 19 + + + 0-9 move a window to another workspace
@@ -138,37 +154,33 @@
-

Resizing

-

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

+

Opening applications / Closing windows

+ + + +
+ d + open application launcher (dmenu) +
+ + q + kill a window +
-

Restart / Exit

- + -
+ + r + + + c + reload the configuration file +
+ + r restart i3 inplace
+ + e - - exit i3 -
- - -
-

Floating

- - - -
+ + - toggle floating - -
+ - 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..2214f016 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1,11 +1,11 @@ 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 -question(s) on the mailing list. +window manager. If it does not, please check http://faq.i3wm.org/ first, then +contact us on IRC (preferred) or post your question(s) on the mailing list. == Default keybindings @@ -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,22 +1386,28 @@ 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). + +See <> for how to move a container/workspace to a different +RandR output. [[back_and_forth]] To switch back to the previously focused workspace, use +workspace back_and_forth+. -To move a container to another xrandr output such as +LVDS1+ or +VGA1+, you can -use the +move container 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 -To move a whole workspace to another xrandr output such as +LVDS1+ or +VGA1+, -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. +move [window|container] [to] workspace +move [window|container] [to] workspace number +move [window|container] [to] workspace +----------------------------------- *Examples*: ------------------------- @@ -1345,6 +1424,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 @@ -1376,7 +1458,7 @@ to switch to the workspace which begins with number 1, regardless of which name it has. This is useful in case you are changing the workspace’s name dynamically. -=== Renaming workspaces +==== Renaming workspaces You can rename workspaces. This might be useful to start with the default numbered workspaces, do your work, and rename the workspaces afterwards to @@ -1394,6 +1476,30 @@ i3-msg 'rename workspace 1 to "1: www"' i3-msg 'rename workspace "1: www" to "10: www"' ------------------------------------------------ +=== Moving containers/workspaces to RandR outputs + +[[move_to_outputs]] + +To move a container to another RandR output (addressed by names like +LVDS1+ or ++VGA1+) or to a RandR output identified by a specific direction (like +left+, ++right+, +up+ or +down+), there are two commands: + +*Syntax*: +-------------------------------------------------------- +move container to output <|> +move workspace to output <|> +-------------------------------------------------------- + +*Examples*: +-------------------------------------------------------- +# Move the current workspace to the next output +# (effectively toggles when you only have two outputs) +bindsym mod+x move workspace to output right + +# Put this window on the presentation output. +bindsym mod+x move container to output VGA1 +-------------------------------------------------------- + [[resizingconfig]] === Resizing containers/windows 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..3847786e --- /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,tab.h,y.o} 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 + $(MAKE) -C .. clean-i3bar -distclean: clean - rm -f i3bar - $(MAKE) -C doc distclean - -.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/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'; + params->workspaces_walk->name = i3string_from_utf8_with_length((const char *)val, len); - /* 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; + /* 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..02f781c9 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,29 @@ 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; + /* layout is the layout of this container: one of split[v|h], stacked or + * tabbed. Special containers in the tree (above workspaces) have special + * layouts like dockarea or output. + * + * last_split_layout is one of splitv or splith to support the old "layout + * default" command which by now should be "layout splitv" or "layout + * splith" explicitly. + * + * workspace_layout is only for type == CT_WORKSPACE cons. When you change + * the layout of a workspace without any children, i3 cannot just set the + * layout (because workspaces need to be splitv/splith to allow focus + * parent and opening new containers). Instead, it stores the requested + * layout in workspace_layout and creates a new split container with that + * layout whenever a new container is attached to the workspace. */ + 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, workspace_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(char *text, size_t text_len, bool is_ucs2, xcb_drawable_t drawable, +void draw_text(i3String *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. + * ASCII version of draw_text to print static strings. * */ -int predict_text_width(char *text, size_t text_len, bool is_ucs2); +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 must be + * specified as an i3String. + * + */ +int predict_text_width(i3String *text); + +/** + * Returns the visual type associated with the given screen. + * + */ +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, +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) { - assert(savedFont != NULL); - assert(text_len != 0); - /* 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); } - - /* If we had to convert, free the converted string */ - if (!is_ucs2) - free(input); } -static int xcb_query_text_width(xcb_char2b_t *text, size_t text_len) { +/* + * 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); + + 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(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..70521073 --- /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/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/asciidoc.conf b/man/asciidoc.conf index 79cf2b41..32d2186f 100644 --- a/man/asciidoc.conf +++ b/man/asciidoc.conf @@ -7,7 +7,7 @@ template::[header-declarations] {mantitle} {manvolnum} i3 -4.1.2 +4.3 i3 Manual 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..4358463b 100644 --- a/man/i3.man +++ b/man/i3.man @@ -1,7 +1,7 @@ i3(1) ===== -Michael Stapelberg -v4.0, July 2011 +Michael Stapelberg +v4.3, September 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). @@ -31,6 +32,18 @@ Display version number (and date of the last commit). -V:: Be verbose. +--force-xinerama:: +Use Xinerama instead of RandR. This option should only be used if you are stuck +with the old nVidia closed source driver (older than 302.17) which does not +support RandR. + +--get-socketpath:: +Retrieve the i3 IPC socket path from X11, print it, then exit. + +--shmlog-size :: +Limits the size of the i3 SHM log to bytes. Setting this to 0 disables +SHM logging entirely. The default is 0 bytes. + == DESCRIPTION === INTRODUCTION @@ -47,8 +60,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 +104,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 +280,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 @@ -313,7 +328,7 @@ and the "how to hack" guide. If you are building from source, run: You can also access these documents online at http://i3wm.org/ -i3-input(1), i3-msg(1), i3-wsbar(1), i3-nagbar(1), i3-config-wizard(1), +i3-input(1), i3-msg(1), i3bar(1), i3-nagbar(1), i3-config-wizard(1), i3-migrate-config-to-v4(1) == AUTHOR diff --git a/i3bar/doc/i3bar.man b/man/i3bar.man similarity index 82% rename from i3bar/doc/i3bar.man rename to man/i3bar.man index dcf3022b..fcefce7b 100644 --- a/i3bar/doc/i3bar.man +++ b/man/i3bar.man @@ -39,9 +39,8 @@ Display a short help-message and exit 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. +i3bar supports colors via a JSON protocol starting from v4.2, see +http://i3wm.org/docs/i3bar-protocol.html == ENVIRONMENT @@ -59,7 +58,7 @@ Instead, see the i3 documentation, especially the User’s Guide. == SEE ALSO -+i3status(1)+ or +conky(1)+ for programs generating a statusline. ++i3status(1)+, +j4status(1)+ or +conky(1)+ for programs generating a statusline. +dzen2(1)+ or +xmobar(1)+ for similar programs to i3bar. 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")); + 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, 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; } - 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"); - 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"); + } } - DLOG("level = %s\n", level); + /* Focusing a child should always be allowed. */ + else success = level_down(); - if (strcmp(level, "parent") == 0) - level_up(); - else 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..f5ccfcdd 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 * @@ -133,7 +135,7 @@ void con_attach(Con *con, Con *parent, bool ignore_focus) { */ if (con->window != NULL && parent->type == CT_WORKSPACE && - config.default_layout != L_DEFAULT) { + parent->workspace_layout != L_DEFAULT) { DLOG("Parent is a workspace. Applying default layout...\n"); Con *target = workspace_attach_to(parent); @@ -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; + switch (con->layout) { + case L_SPLITV: + /* stacking containers behave like they are in vertical orientation */ + case L_STACKED: + return VERT; - if (con->layout == L_TABBED) - return HORIZ; + case L_SPLITH: + /* tabbed containers behave like they are in vertical orientation */ + case L_TABBED: + return HORIZ; - return con->orientation; + 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,54 +1084,133 @@ 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) { - 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 */ - 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; + if (con->type == CT_WORKSPACE && + (layout == L_STACKED || layout == L_TABBED)) { + if (con_num_children(con) == 0) { + DLOG("Setting workspace_layout to %d\n", layout); + con->workspace_layout = layout; } else { - new->orientation = config.default_orientation; + 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 container and mark it as + * split. */ + new->layout = layout; + 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; + + /* 3: move the existing cons of this workspace below the new con */ + DLOG("Moving cons\n"); + Con *child; + while (!TAILQ_EMPTY(&(con->nodes_head))) { + child = TAILQ_FIRST(&(con->nodes_head)); + con_detach(child); + con_attach(child, new, true); + } + + /* 4: attach the new split container to the workspace */ + DLOG("Attaching new split to ws\n"); + con_attach(new, con, false); + + if (old_focused) + con_focus(old_focused); + + tree_flatten(croot); } - - 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 */ - DLOG("Moving cons\n"); - Con *child; - while (!TAILQ_EMPTY(&(con->nodes_head))) { - child = TAILQ_FIRST(&(con->nodes_head)); - con_detach(child); - con_attach(child, new, true); - } - - /* 4: attach the new split container to the workspace */ - DLOG("Attaching new split to ws\n"); - con_attach(new, con, false); - - if (old_focused) - con_focus(old_focused); - - tree_flatten(croot); - 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 +1287,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 +1304,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; } - return (bind == TAILQ_END(bindings) ? NULL : bind); + /* 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); } /* @@ -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; + 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++); + /* 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..78e19890 --- /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,tab.h,y.o} src/cmdparse.* diff --git a/src/ipc.c b/src/ipc.c index 60ce8145..7dfbc871 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,33 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { break; } + ystr("workspace_layout"); + switch (con->workspace_layout) { + case L_DEFAULT: + ystr("default"); + break; + case L_STACKED: + ystr("stacked"); + break; + case L_TABBED: + ystr("tabbed"); + break; + default: + DLOG("About to dump workspace_layout=%d (none of default/stacked/tabbed), this is a bug.\n", con->workspace_layout); + assert(false); + 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 +276,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 +555,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 +777,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 +849,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 +857,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..cce1a712 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,44 @@ 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, "workspace_layout") == 0) { + char *buf = NULL; + sasprintf(&buf, "%.*s", (int)len, val); + if (strcasecmp(buf, "default") == 0) + json_node->workspace_layout = L_DEFAULT; + else if (strcasecmp(buf, "stacked") == 0) + json_node->workspace_layout = L_STACKED; + else if (strcasecmp(buf, "tabbed") == 0) + json_node->workspace_layout = L_TABBED; + else LOG("Unhandled \"workspace_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 +327,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..8bae9957 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"); @@ -380,7 +391,8 @@ int main(int argc, char *argv[]) { fprintf(stderr, "\t--force-xinerama\n" "\tUse Xinerama instead of RandR.\n" "\tThis option should only be used if you are stuck with the\n" - "\tnvidia closed source driver which does not support RandR.\n"); + "\told nVidia closed source driver (older than 302.17), which does\n" + "\tnot support RandR.\n"); fprintf(stderr, "\n"); fprintf(stderr, "\t--get-socketpath\n" "\tRetrieve the i3 IPC socket path from X11, print it, then exit.\n"); @@ -424,7 +436,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 +495,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 +668,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 +756,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..94efd47b 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 @@ -52,6 +73,7 @@ Con *workspace_get(const char *num, bool *created) { workspace->type = CT_WORKSPACE; FREE(workspace->name); workspace->name = sstrdup(num); + workspace->workspace_layout = config.default_layout; /* We set ->num to the number if this workspace’s name begins with a * positive number. Otherwise it’s a named ws and num will be -1. */ char *endptr = NULL; @@ -64,16 +86,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 +128,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 +212,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 +693,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 +715,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 */ @@ -735,7 +743,7 @@ void ws_force_orientation(Con *ws, orientation_t orientation) { Con *workspace_attach_to(Con *ws) { DLOG("Attaching a window to workspace %p / %s\n", ws, ws->name); - if (config.default_layout == L_DEFAULT) { + if (ws->workspace_layout == L_DEFAULT) { DLOG("Default layout, just attaching it to the workspace itself.\n"); return ws; } @@ -744,18 +752,10 @@ 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; - } + new->layout = ws->workspace_layout; /* 4: attach the new split container to the workspace */ DLOG("Attaching new split %p to workspace %p\n", new, ws); 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 +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. + +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..70008801 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,20 +52,23 @@ 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, urgent => JSON::XS::false, border => 'normal', 'floating_nodes' => $ignore, + workspace_layout => 'default', }; # a shallow copy is sufficient, since we only ignore values at the root 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'; -ok(get_ws('5')->{focused}, 'workspace 5 focused again'); +is(focused_ws, '5', 'workspace 5 focused again'); + +################################################################################ +# Rename the workspaces and see if workspace number still works with BAF. +################################################################################ + +cmd 'rename workspace 5 to 5: foo'; +cmd 'rename workspace 6 to 6: baz'; + +is(focused_ws, '5: foo', 'workspace 5 still focused'); + +cmd 'workspace number 6'; +is(focused_ws, '6: baz', 'workspace 6 now focused'); + +cmd 'workspace number 6'; +is(focused_ws, '5: foo', 'workspace 5 focused again'); exit_gracefully($pid); 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;