diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..d2254676 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,18 @@ +Output of `i3 --moreversion 2>&- || i3 --version`: + +_REPLACE: i3 version output_ + +URL to a logfile as per http://i3wm.org/docs/debugging.html: + +_REPLACE: URL to logfile_ + +**What I did:** + +_REPLACE: e.g. "I’m pressing Alt+j (focus left)"_ + +**What I saw:** + +_REPLACE: e.g. "i3 changed focus to the window ABOVE the current window"_ + +**What I expected instead:** +_REPLACE: e.g. "Focus should be on the window to the left"_ diff --git a/.travis.yml b/.travis.yml index dd1fb156..aa574d09 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,72 +1,23 @@ +sudo: required +dist: trusty +services: + - docker language: c compiler: - gcc - clang -addons: - apt: - sources: - - llvm-toolchain-precise-3.5 - # ubuntu-toolchain-r-test contains libstdc++6 >= 4.8 which libllvm3.5 needs. - - ubuntu-toolchain-r-test - packages: - - clang-format-3.5 - - libllvm3.5 -before_install: - # The travis VMs run on Ubuntu 12.04 which is very old and a huge pain to get - # into a state where we can build a recent version of i3 :(. - - "echo 'deb http://archive.ubuntu.com/ubuntu/ trusty main universe' | sudo tee /etc/apt/sources.list.d/trusty.list" - - "echo 'APT::Default-Release \"precise\";' | sudo tee /etc/apt/apt.conf.d/default-release" - - - "echo 'Package: libc6' > /tmp/pin" - - "echo 'Pin: release n=trusty' >> /tmp/pin" - - "echo 'Pin-Priority: 999' >> /tmp/pin" - - "echo '' >> /tmp/pin" - - - "echo 'Package: libxkbcommon*' >> /tmp/pin" - - "echo 'Pin: release n=trusty' >> /tmp/pin" - - "echo 'Pin-Priority: 999' >> /tmp/pin" - - "echo '' >> /tmp/pin" - - - "echo 'Package: libyajl*' >> /tmp/pin" - - "echo 'Pin: release n=trusty' >> /tmp/pin" - - "echo 'Pin-Priority: 999' >> /tmp/pin" - - "echo '' >> /tmp/pin" - - - "echo 'Package: libxcb-image*' >> /tmp/pin" - - "echo 'Pin: release n=trusty' >> /tmp/pin" - - "echo 'Pin-Priority: 999' >> /tmp/pin" - - "echo '' >> /tmp/pin" - - - sudo cp /tmp/pin /etc/apt/preferences.d/trustypin - - sudo apt-get update - - sudo apt-get install -t trusty libc6 libc6-dev - - sudo apt-get install --no-install-recommends devscripts equivs xdotool +env: + global: + - BASENAME="i3wm/travis-base:$(date +'%Y-%m')-$(./travis/ha.sh)" + - secure: "B5IICA8MPx/FKaB50rTPqL8P1NU+Q0yuWl+lElL4+a9xSyLikfm3NzUPHoVwx8lNw2AVK6br7p0OmF7vMFjqAgrgc1cajTtEae5uFRKNUrWLpXM046YgNEYLLIHsQOjInxE+S4O6EFVzsUqsu8aeo2Xhq4sm4iUocG7e5isYgYo=" # DOCKER_PASS + - secure: "EIvrq8PG7lRjidppG0RCv4F0X4GP3DT9F5+ixVuGPfhK/hZT3jYC2AVY9G+NnUcXVwQEpW92rlqpftQ/qZ13FoyWokC8ZyoyD06fr5FPCfoFF3OczZwAJzZYkObI/hE9+/hXcylx/Os6N4INd2My1ntGk3JPsWL9riopod5EjSg=" # DOCKER_EMAIL + - secure: "hvhBunS4xXTgnIOsk/BPT7I7FrJhvVwCSt5PfxxvMqNaztOJI9BuK7ZrZ5Cy38KyHwlh3VHAH5AaCygJcPauoSQCV3bpnlbaWn3ruq2F0Q697Q5uNf73liXzyUqb9/Zvfvge4y4WWOhP5tVz1C6ZBe/NfhU7pqKLMA+6ads+99c=" # DOCKER_USER install: - - sudo mk-build-deps --install --remove --tool 'apt-get --no-install-recommends' debian/control - # Install as many dependencies as possible via apt because cpanm is not very reliable/easy to debug. - - sudo apt-get install --no-install-recommends libanyevent-perl libanyevent-i3-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl-modules libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libtest-use-ok-perl libipc-run-perl - - sudo /bin/sh -c 'cpanm -n -v X11::XCB || true' - - sudo /bin/sh -c 'cpanm -n -v AnyEvent::I3 || true' + - if [ -a .git/shallow ]; then git fetch --unshallow; fi + - docker pull ${BASENAME} || ./travis/docker-build-and-push.sh script: - - CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Werror" make -j - - (cd testcases && xvfb-run ./complete-run.pl --parallel=1 || (cat latest/complete-run.log; false)) - - clang-format-3.5 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false) - - | - funcs='malloc|calloc|realloc|strdup|strndup|asprintf|write' - cstring='"([^"\\]|\\.)*"' - cchar="'[^\\\\]'|'\\\\.[^']*'" - regex="^([^'\"]|${cstring}|${cchar})*\<(${funcs})\>" - detected=0 - while IFS= read -r file; do - if { cpp -w -fpreprocessed "$file" || exit "$?"; } | grep -E -- "$regex"; then - echo "^ $file calls a function that has a safe counterpart." - detected=1 - fi - done << EOF - $(find -name '*.c' -not -name safewrappers.c -not -name strndup.c) - EOF - if [ "$detected" -ne 0 ]; then - echo - echo "Calls of functions that have safe counterparts were detected." - exit 1 - fi + - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-safe-wrappers.sh + - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-formatting.sh + - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC -e CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Werror" ${BASENAME} make all mans -j ASAN=1 + - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-spelling.pl + - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/run-tests.sh diff --git a/DEPENDS b/DEPENDS index 0882f147..fad11da5 100644 --- a/DEPENDS +++ b/DEPENDS @@ -24,7 +24,7 @@ │ PCRE │ 8.12 │ 8.35 │ http://www.pcre.org/ │ │ libsn¹ │ 0.10 │ 0.12 │ http://freedesktop.org/wiki/Software/startup-notification │ pango │ 1.30.0 | 1.36.8 │ http://www.pango.org/ │ -│ cairo │ 1.12.2 │ 1.14.0 │ http://cairographics.org/ │ +│ cairo │ 1.14.4 │ 1.14.4 │ http://cairographics.org/ │ └──────────────┴────────┴────────┴────────────────────────────────────────┘ ¹ libsn = libstartup-notification ² Pod::Simple is a Perl module required for converting the testsuite diff --git a/LICENSE b/LICENSE index 05f078e2..6354f065 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright © 2009-2011, Michael Stapelberg and contributors +Copyright © 2009, Michael Stapelberg and contributors All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/PACKAGE-MAINTAINER b/PACKAGE-MAINTAINER index 269ce0fd..22e10997 100644 --- a/PACKAGE-MAINTAINER +++ b/PACKAGE-MAINTAINER @@ -40,7 +40,7 @@ start of i3 (it will automatically exit if it finds a config file). If you have any questions, ideas, hints, problems or whatever, please do not hesitate to contact me. I will help you out. Just drop me an E-Mail (find the -address at http://michael.stapelberg.de/Kontakt, scroll down to bottom), +address at https://michael.stapelberg.de/Impressum/, scroll down to bottom), contact me using the same address in jabber or ask on our IRC channel: (#i3 on irc.twice-irc.de). diff --git a/RELEASE-NOTES-4.11 b/RELEASE-NOTES-4.11 deleted file mode 100644 index 2cbadda9..00000000 --- a/RELEASE-NOTES-4.11 +++ /dev/null @@ -1,115 +0,0 @@ - - ┌────────────────────────────┐ - │ Release notes for i3 v4.11 │ - └────────────────────────────┘ - -This is i3 v4.11. This version is considered stable. All users of i3 are -strongly encouraged to upgrade. - -Aside from plenty of new features, there are several changes that might result -in behavioral changes, depending on your specific configuration and -environment. As usual, we tried hard to keep the current behavior, and when we -changed behavior, we strongly believe it’s for the better. - -Keyboard binding handling has been made more correct, for details see: -https://github.com/i3/i3/commit/bf3cd41b5ddf1e757515ab5fbf811be56e5f69cc - - ┌────────────────────────────┐ - │ Changes in i3 v4.11 │ - └────────────────────────────┘ - - • docs/debugging: provide instructions on how to debug i3bar - • docs/debugging: added a note about sensitive data - • docs/userguide: add a note to both “exec”s about semicolon and comma - • docs/userguide: quoted strings need to be used, escaping isn’t possible - • docs/userguide: make syntax of syntax descriptions consistent - • docs/userguide: recommend “exec exec” for correct signal handling - • docs/userguide: explain i3-config-wizard’s behavior - • i3-nagbar: open on the primary screen - • i3-config-wizard: respect XDG config directories - • i3-input: position i3-input at window with input focus - • i3bar: use a reasonable default sep_block_width if a separator_symbol is given - • i3bar: add binding mode indicator - • i3bar: add bindsym command (deprecates wheel_{up,down}_cmd) - • i3bar: make tray padding configurable - • makefiles: respect EXEC_PREFIX and PKG_CONFIG - • added a --toggle switch to mark: “mark [--toggle] ” - • added “focus_on_window_activation” directive - • added “no_focus” directive - • added “move [container|window] [to] mark ” command - • added “move [window|container] [to] position mouse|cursor|pointer” command - • added “title_format” command - • added “resize set [width] [height]” command - • added “sticky” command (for floating containers) - • added “workspace” criterion - • added “window_type” criterion - • make center coordinates relative to current workspace - • draw marks in window decoration (configure with show_marks) - • only mark a window if only one window is matched - • make floating window mouse handling consistent with tiled windows - • add a --border flag to enable mouse binds to trigger on border click - • set the _NET_WM_STATE_HIDDEN atom on windows that are currently not visible - due to being in the non-focused tab of a stacked or tabbed container - • ignore InputHint when not in WM_HINTS - • display which config is used in i3 --moreversion - • support config file line continuation - • use WM_SIZE_HINTS when present to set the geometry of floating windows - • add “tray_output primary” to the default config - • use libxkbcommon for translating keysyms, support all XKB groups - • support special value “__focused__” in criteria - • support _NET_WM_VISIBLE_NAME - • make sure borders are never counted as adjacent to the edge for floating - containers - • support moving dock clients to another output - • let “focus” report success depending on whether a window was matched - • handle _NET_WM_STATE_STICKY (for floating containers) - • make “debuglog on” command persist over restarts - • randr: use root window in case of no randr outputs - • set proper WM_CLASS on frame windows - - ┌────────────────────────────┐ - │ Bugfixes │ - └────────────────────────────┘ - - • i3bar: only detect clicks within the statusline width - • i3bar: fix flickering shortened status bar on other output(s) - • i3bar: send custom-defined command upon click on the non-statusline part of - i3bar even if workspace_buttons is set to “no”. - • i3-config-wizard: Make window size and click coordinates dependent on font - • i3-save-tree: retain “rect” for floating cons - • move urgency hint when moving container - • fix percents when attaching a window to a ws creates a new split con - • cope with non-null-terminated x class properties - • get workspace name when renaming current workspace - • allow single-child non-default layout cons to be moved between outputs - • allow --whole-window right after 'bindsym' within binding modes - • remove windows from the save set when unmapping (fixes problems with e.g. - owncloud when restarting i3) - • serialize con_id with %p in run_binding() - • initialize workspace rect to the output's upon creation - • mkdirp: do not throw an error if directory exists - • grab all buttons when managing a window to also allow 'bindsym - --whole-window button4 …' to work correctly - • properly clear the urgency hint when set by i3 - • layout restore: load floating containers correctly - • layout restore: remove remaining criteria when swallowing window - • layout restore: When appending a layout containing a marked container, make - sure that any other containers with the same mark are unmarked during - insertion of the new container. - • use the EWMH support window rather than the root window as an input focus fallback - • use the focused container to determine the target window_mode when using - floating mode_toggle - - ┌────────────────────────────┐ - │ Thanks! │ - └────────────────────────────┘ - -Thanks for testing, bugfixes, discussions and everything I forgot go out to: - - Andrzej Pronobis, Chris West (Faux), Deiz, Felix C. Stegerman, Georgiy Tugai, - hwangcc23, Ingo Bürk, Kacper Kowalik (Xarthisius), lasers, lambithal, Michael - Hofmann, Michael Tipton, Micha Rosenbaum, Nikita Mikhailov, Nils Schneider, - PopeLevi, rr-, shdown, Simon Nagl, Theo Buehler, Thomas Anderson, Tim Creech, - Tony Crisci - --- Michael Stapelberg, 2015-09-30 diff --git a/RELEASE-NOTES-4.12 b/RELEASE-NOTES-4.12 new file mode 100644 index 00000000..57cd9553 --- /dev/null +++ b/RELEASE-NOTES-4.12 @@ -0,0 +1,132 @@ + + ┌────────────────────────────┐ + │ Release notes for i3 v4.12 │ + └────────────────────────────┘ + +This is i3 v4.12. This version is considered stable. All users of i3 are +strongly encouraged to upgrade. + +If cairo ≥ 1.14.4 is available, i3 and i3bar will use cairo for rendering +(instead of raw X11 drawing primitives). While this is currently optional, +having cairo ≥ 1.14.4 will be a hard requirement in future release. + +This release contains a good number of detail improvements and fixes. + + ┌────────────────────────────┐ + │ Changes in i3 v4.12 │ + └────────────────────────────┘ + + • use https instead of git/http, update contact information, add GPG key + • docs/hacking-howto: fix old cfgparse.y reference to config_parser.c + • docs/ipc: added link to i3ipcpp (C++ library) + • docs/userguide: clarify no_focus documentation + • docs/userguide: add documentation for binding modes + • docs/userguide: fix rendering of __focused__ + • docs/userguide: improve placement of explicit IDs for headings + • docs/userguide: make rendering of key bindings more consistent + • docs/userguide: clarify quoting of “exec” commands + • man/i3-nagbar: fix example invocation + • man/i3: add “floating window” to terminology + • i3-sensible-*: quote variables correctly + • i3-sensible-editor: add neovim + • i3-sensible-terminal: add termit, st + • i3bar: use cairo for all drawing operations + • i3bar: support per-statusblock border and background colors + • i3bar: support different bar background colors depending on whether the bar + is on the focused output or not + • i3bar: multiple tray_output directives on the same bar are now supported + • i3bar: support disabling the modifier by specifying “modifier none” + • use cairo for all drawing operations + • fix a number of memory leaks, thanks to AddressSanitizer + • no_focus is now suppressed for the first window of a workspace + • “workspace next/prev” now looks for numbered workspaces after reaching the + last workspace (it used to incorrectly only look at named workspaces) + • multiple marks can now be set on a single window (but a mark can still only + be present on one window at a time) + • the “unmark” command now supports criteria + • the “con_id” criterion now supports the special value __focused__ + • the “workspace” command now supports the --no-auto-back-and-forth parameter + • the “move window to workspace” command now supports the + --no-auto-back-and-forth parameter + • the “resize grow|shrink width|height” command now works for a nested split + in the same direction + • support _NET_WM_USER_TIME’s special 0 value, indicating that a window + should not be focused + • use 32-bit visual by default if available. This reduces graphical glitches + when using transparency (which is still not officially supported) + • the “move position center” command now supports criteria + • specifying invalid match criteria now results in an error instead of + blindly applying the operation to the currently focused window + • allow mouse bindings to run on the root window + • support matching _NET_WM_WINDOW_TYPE_NOTIFICATION in criteria + • all criteria are now matched, even when con_id or con_mark are given (used + to be a special case) + • allow the “id” criterion to be specified in any base recognized by + strtol(), not only base 10 + • non-true color displays are now supported again (e.g. the Raspberry Pi) + • the “split” command now has a “toggle” option + • the additional color class “decoration_border” was added + • title_format is now stored on containers instead of windows, allowing the + use of title_format on split containers + • On OpenBSD, i3 now uses pledge(2) + • support _NET_WM_DESKTOP (for pager applications like gnome-panel) + • floating workspaces are no longer available (they were not supported for a + while now) + • floating windows now carry the I3_FLOATING_WINDOW atom so that tools like + compositors can be configured to match on floating windows + + ┌────────────────────────────┐ + │ Bugfixes │ + └────────────────────────────┘ + + • i3bar: display short text only on the monitor(s) on which it is necessary + • i3bar: explicitly set cursor using libxcb-cursor if available + • i3bar: fix XEMBED messages + • i3-nagbar: explicitly set cursor using libxcb-cursor if available + • duplicated keybindings are now also detected when one uses bindcode but the + other(s) use(s) bindsym + • keymap fallback for servers without XKB (e.g. TightVNC) has been added + • using pango markup in mode names is now optional, fixing a regression in i3 + v4.11 where modes which contained characters such as “<” would break. + • moving windows to a workspace by specifying a mark now works + • the root output is now used when any RandR request fails (for x2go) + • assignments are now marked as run before executing them, preventing endless + loops/crashes when assignments cause another assignment evaluation + • splitting/floating a dock container no longer crashes i3 + • correctly compare modifier mask when identifying keybindings (fixes + bindings which use --release) + • no longer fail config validation when there is no newline at the end of + the config file + • scrollwheel buttons are now only grabbed when necessary, allowing the use + of “bindsym button*” or scrolling in windows without focusing them (in case + no “bindsym button*” is present) + • parse con_id in base 16 (affected FreeBSD only) + • fix crash when opening a large number of windows + • reject empty swallow definitions to avoid crashes + • don’t remove SubstructureRedirect event mask temporarily (fixes i3bar + stopping after system suspend) + • move urgent flag before killing the parent to avoid a crash + • correctly validate “kill” command to avoid crashing when “kill” is invoked + on workspace containers + • actually accept the documented “workspace” token as an alternative to “→” + in assign statements + • remove _NET_WM_STATE on withdrawn windows to comply with the spec + • the “border” command now uses logical pixels (relevant for hi-dpi displays) + • “tray_output primary” does not properly fall back and hence was removed + from the default config again + • correctly determine focused workspace when moving workspace to output + • revert to default binding mode before reloading the config file + • correctly interpret _MOTIF_WM_HINTS (endianness-dependent) + + ┌────────────────────────────┐ + │ Thanks! │ + └────────────────────────────┘ + +Thanks for testing, bugfixes, discussions and everything I forgot go out to: + + Adaephon, Airblader, Alexis211, bendem, botovq, brianmillar, DavidMikeSimon, + dcoppa, Florian Merkel, fmthoma, frederik, hwangcc23, jolange, Juuso + Lapinlampi, kneitinger, lotheac, nicklan, norrland, pra85, romanblanco, + sur5r, tbu-, tyll, wodny + +-- Michael Stapelberg, 2016-03-06 diff --git a/common.mk b/common.mk index d2875042..4fe8f2b0 100644 --- a/common.mk +++ b/common.mk @@ -1,5 +1,6 @@ UNAME=$(shell uname) DEBUG=1 +ASAN=0 INSTALL=install LN=ln PKG_CONFIG=pkg-config @@ -42,6 +43,11 @@ else CFLAGS ?= -pipe -O2 -freorder-blocks-and-partition endif +ifeq ($(ASAN),1) +CFLAGS += -fsanitize=address -DI3_ASAN_ENABLED +LDFLAGS += -fsanitize=address +endif + # Default LDFLAGS that users should be able to override LDFLAGS ?= $(as_needed_LDFLAG) @@ -109,15 +115,15 @@ 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) +# XCB cursor +XCB_CURSOR_CFLAGS := $(call cflags_for_lib, xcb-cursor) +XCB_CURSOR_LIBS := $(call ldflags_for_lib, xcb-cursor,xcb-cursor) + XKB_COMMON_CFLAGS := $(call cflags_for_lib, xkbcommon,xkbcommon) XKB_COMMON_LIBS := $(call ldflags_for_lib, xkbcommon,xkbcommon) XKB_COMMON_X11_CFLAGS := $(call cflags_for_lib, xkbcommon-x11,xkbcommon-x11) XKB_COMMON_X11_LIBS := $(call ldflags_for_lib, xkbcommon-x11,xkbcommon-x11) -# Xcursor -XCURSOR_CFLAGS := $(call cflags_for_lib, xcb-cursor) -XCURSOR_LIBS := $(call ldflags_for_lib, xcb-cursor,xcb-cursor) - # yajl YAJL_CFLAGS := $(call cflags_for_lib, yajl) YAJL_LIBS := $(call ldflags_for_lib, yajl,yajl) @@ -141,6 +147,9 @@ LIBSN_LIBS := $(call ldflags_for_lib, libstartup-notification-1.0,startup-noti PANGO_CFLAGS := $(call cflags_for_lib, cairo) PANGO_CFLAGS += $(call cflags_for_lib, pangocairo) I3_CPPFLAGS += -DPANGO_SUPPORT=1 +ifeq ($(shell $(PKG_CONFIG) --atleast-version=1.14.4 cairo 2>/dev/null && echo 1),1) +I3_CPPFLAGS += -DCAIRO_SUPPORT=1 +endif PANGO_LIBS := $(call ldflags_for_lib, cairo) PANGO_LIBS += $(call ldflags_for_lib, pangocairo) diff --git a/debian/changelog b/debian/changelog index aaa7e54e..8857ad01 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +i3-wm (4.11.1-1) unstable; urgency=medium + + * New upstream release. + + -- Michael Stapelberg Wed, 30 Sep 2015 09:03:26 +0200 + i3-wm (4.11-1) unstable; urgency=medium * New upstream release. diff --git a/debian/compat b/debian/compat index 7f8f011e..ec635144 100644 --- a/debian/compat +++ b/debian/compat @@ -1 +1 @@ -7 +9 diff --git a/debian/control b/debian/control index 52fb1538..577bb4d4 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: i3-wm Section: x11 Priority: extra Maintainer: Michael Stapelberg -Build-Depends: debhelper (>= 7.0.50~), +Build-Depends: debhelper (>= 9), libx11-dev, libxcb-util0-dev (>= 0.3.8), libxcb-keysyms1-dev, @@ -24,7 +24,7 @@ Build-Depends: debhelper (>= 7.0.50~), libcairo2-dev, libpango1.0-dev, libpod-simple-perl -Standards-Version: 3.9.5 +Standards-Version: 3.9.7 Homepage: http://i3wm.org/ Package: i3 diff --git a/debian/copyright b/debian/copyright index 46a3d791..f86d850f 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,30 +1,41 @@ -This Debian package is based on a tarball downloaded from -http://i3wm.org/ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: i3 +Upstream-Contact: Michael Stapelberg +Source: https://i3wm.org/ -Copyright: (C) 2009-2011 Michael Stapelberg -All rights reserved. +Files: * +Copyright: 2009 Michael Stapelberg +License: BSD-3-clause -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: +Files: debian/* +Copyright: 2009 Michael Stapelberg +License: BSD-3-clause -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of Michael Stapelberg, i3 nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT -SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR -BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. +License: BSD-3-clause + Copyright: © 2009 Michael Stapelberg + All rights reserved. + . + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + . + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + . + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + . + * Neither the name of Michael Stapelberg, i3 nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + . + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/debian/i3-wm.install b/debian/i3-wm.install new file mode 100644 index 00000000..a75e2371 --- /dev/null +++ b/debian/i3-wm.install @@ -0,0 +1,2 @@ +etc +usr diff --git a/debian/rules b/debian/rules index 7036b90d..843655ee 100755 --- a/debian/rules +++ b/debian/rules @@ -1,47 +1,17 @@ #!/usr/bin/make -f # vi: ts=8 sw=8 noet -DPKG_EXPORT_BUILDFLAGS = 1 --include /usr/share/dpkg/buildflags.mk - -ifneq (,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) - NUMJOBS = $(patsubst parallel=%,%,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) - MAKEFLAGS += -j$(NUMJOBS) -endif - -build: build-arch build-indep -build-arch: build-stamp -build-indep: build-stamp -build-stamp: - dh build - touch build-stamp - -clean: - dh clean - -install: build install-stamp -install-stamp: - dh install - touch install-stamp - -binary-arch: install - dh binary-arch - -binary-indep: install - dh binary-indep - -binary: binary-arch binary-indep +export V:=1 +export DEB_BUILD_MAINT_OPTIONS = hardening=+all override_dh_auto_build: - $(MAKE) - $(MAKE) -C man - $(MAKE) -C docs + dh_auto_build -- all docs mans override_dh_installchangelogs: dh_installchangelogs RELEASE-NOTES-* -override_dh_install: - $(MAKE) DESTDIR=$(CURDIR)/debian/i3-wm/ install - override_dh_strip: dh_strip --dbg-package=i3-wm-dbg + +%: + dh $@ --parallel diff --git a/debian/upstream/signing-key.asc b/debian/upstream/signing-key.asc new file mode 100644 index 00000000..d0ef661b --- /dev/null +++ b/debian/upstream/signing-key.asc @@ -0,0 +1,56 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mQINBEoVG4cBEADX2160pBoUf2vSWKbUa8soEMscBFjmb/NajCxwX/BlD1sVNyDm +twZ74CNPS7X5GgNQoXCzkm7v18zOpON69/pwQ0C4T4P+dvewaDzi2+4/bZsXSor1 +mA3C9lHcKDbpH7jHkN2AbMnY3Z4LD46LA1qfCISAAKtx1h4peBF6Xhu743dKXrBa +zg/TEJwWIWSyPKgIhur95yebD/Tws+gWlOfBKkF1v1PA+5sPmC8LyK5Rd1n9Sg1D +j//4sWl8A4EwM4QUzSliZME775klV4mOBGbsTnhNjCymgDiXVNjoWdEIHoNfDsut +E2czgSwsSrSPls/Kl1KuHyBiOWi4dl6MFaypcuSNEVNi5K+oJ7gmX/sy/TlF5Ofw +KoBEPrcvulVT8aAM3azMfb/Fgo+GcEEYljV1yvSg7jSjCHxXgMyh/yMfZcPkwajp +fNE5D7WAXgygpolM9dLIOBemDJxwWr0G7uhXNv3vSHpuUheb2REaJJwWHw1IuCmn +gigD5mebQWRSmbEl66ygOFkps9FEq6KSmbHkj7dIrSVmK5DtQRRI5fMPI+E+atul +Lnpgm/R2p2yvPKoS/pr9mwvKIf9F5C20wm1iAaGW1pTDSIl2y2ZpzcJIyS+jhyCX +3d6D7FNEFlI2p9Tnbt9aE04ASLlZFGjxNWweU8zAkNOr1MyPTiWrYtsCtwARAQAB +tCpNaWNoYWVsIFN0YXBlbGJlcmcgPG1pY2hhZWxAc3RhcGVsYmVyZy5kZT6JAkAE +EwEKACoCGy8CHgECF4AFCwkIBwMFFQoJCAsFFgIDAQACGQEFAlNdKVoFCRKuD04A +CgkQTnFg7UrI7h1HYw//R7WBr/MrqevbaB6Uh7Koy3rN1GqXXY7L4kQAO1XSrmC9 +IQ/giwg7+655tDWq4cAjefiBWRv0I1WWqZwdgUGwfhzW20DBx2sPkGKZ29pcvU/k +LuMyWs49o2lcsb4cQqgDpH/uzi22fc4BhO91o/uZYOAXrrSlLuzkCa1SDCRymwdw +lIXIXktROd+r6Fpc1FAinOQgn5BQjf7gbSZSlqBLeYZdR+qSxZWufrhsVUy03nVx +mF9hc/aTFNYZHHHh0yFzYfBKisqsuwJW94uW80xw17HoBMSb10eNGEq5xWqh4Owu +8heJePlcoh2F5JnO6cFWoz1bHCZGjeeIm0OdPJXTLDQdcA5Hy4K3ADidqW0y+Iza +Wbs3TpLprLw91LaPcwzZf+vzRgsQCwPKODjhcetEaYGIKweCkNQRQCW6wEl7kRAw +/eG1wdn4YfEcnCz4ye1MW67au3omvBy41BNmGb20rEc9JIQ37HhAJy5MwuguuO1Z +xZWyu3fV39YLwvsa8EYFPb/DOUTmSCBCyvfTOCEC94Vl2kcPXicIpaRnCFZNqVEJ +FAMKY/tSVjPsBEXTFx3aiX3am4CCtc8R95z2DrYtW5UU/yA5o6lDnfRX6Smdl620 +kTM/gFEgAI8+x56XsWJ/CnG//EbgKMy8u2u5y7x1SdpZFxLf722EryF0yPJt+im0 +OU1pY2hhZWwgU3RhcGVsYmVyZyAoUkVOVC1BLUdVUlUpIDxtaWNoYWVsQHJlbnQt +YS1ndXJ1LmRlPokCPQQTAQoAJwIbLwIeAQIXgAULCQgHAwUVCgkICwUWAgMBAAUC +U10pWgUJEq4PTgAKCRBOcWDtSsjuHUTREACcBXnZwinxZ8S0DOl2OR7qm8ao4U/n +h71tkJX9klnXYY8KQD54tuGjYjCA+UvTOX7c0Rzj3WijgyRxefmOhPQzkk/zUheH +bsaYSbj2mCvA/IkMRe6G0+wyFU5ydssLVApx/+bwdL3CiIoFPwyHMgWPjYuijIts +UMbq7jtnF26l7O0GSY5uHSUQb7caz+Mu0CcF95h3oxRxHVAhHIMtwzkilbjbshEf +nxDH5L0s2hT0amkEB2jw3US2v+YrThk0ZQPoB+tgNLL2Li7yAuwbaEaK37aDTtkX +NdFiAwcOHrLhlD0SnNya4nEVwgnCu5B9f+OwalPq3a0+G394L+a+XHWWwXc6MlFz +WhzAMeE1uFJfbIIGEL/Q3URBbhIUf0xsZEagsjNExgYtJY5XJRitgyxPwAuxusia +VhfTmbr3Mr5yu7QEt1oACq3j0bNr7hzcPk+ckHYbsSvuoo21Ef5vhQetXpAzrot/ ++62c7i1xcAvY7MVBT2f/7BC4cYvLXALhcvLAabzOcD8lBPEIBkxgJj42EckEzFzC +c9s5htWdhWYIAIIdIOxZejxfRiTvujg5CaJo+Fg/BL4TTlBgjU7ASzMQBfos3cII +F8O9Yc+W04uMyG9QPqk//rIUFdKgUyd4tzXkQrJs7Jom7duxJPpr5dDMLqTwINL5 +LBBKw/lpA/nAkLQqTWljaGFlbCBTdGFwZWxiZXJnIDxzdGFwZWxiZXJnQGRlYmlh +bi5vcmc+iQI9BBMBCgAnAhsvBQsJCAcDBRUKCQgLBRYCAwEAAh4BAheABQJTXSla +BQkSrg9OAAoJEE5xYO1KyO4dGBkP/353sh8feSyxJNXMdgAfe+okYE4B9iWS2zRy +GAEGKyliaMWDJLRhT4ln1glr05pFoy52Hxe4+NBpSYuZ9NV390HRmPxbFaUvHs74 +HPkuABxy2XKH94IS1nrb+fleR00w0rLEin4aW3mtwKDHCJtNUW0/DNtC7Uz028SL +9TftaqJrsf+paTFJkZF0ShJ2A6XFxwlvBiVz4f2UDEi7GQuAaYI5V5ZosWIAxdYG +631tKf1EU9YtjPYdBk3YF4Q44AhVbe478Ji24Of0G/l+dGFRzbdARNayBwegJoMu +djXQrPfR1jNVSaw65/AinbbtadlULSTrvhoD6ommGeU6fQS3WtNBbF4T5SuNRBeK +QhWuLSOllmjWfERaj6omg8GXd1rctIFnUhT/dl6bmhooMPRUW1DNxVzqd7UFnqXE ++CaJ7cGfI/9/MITTG32QpjkVeowOcuoZ+qLoOu7dBbagDaEn8T6yFCLACyf9LGBm +MC9bNSGsMRCFV33N/PbVcgf6M0z46d0ysPgGiR5MX1O6CQkJGrwolfA5sK3VIsyp +PaCCHpAuPdEiWB57WleURqCIGWV2ccElyA4M4auDGt1SSNl0NfWu8vde6wNIPir/ +DBqh1265QLrLS239UT0u5hYE5hkfiKP2dzgWKh9NT9xm2Dyjjv/PaqWlVedvsVIp +VRx/oRxr +=1n/p +-----END PGP PUBLIC KEY BLOCK----- diff --git a/debian/watch b/debian/watch index ab71a19e..f434b2e6 100644 --- a/debian/watch +++ b/debian/watch @@ -1,2 +1,3 @@ version=3 -http://i3wm.org/downloads/ /downloads/i3-(.*)\.tar\.bz2 +opts=pgpsigurlmangle=s/$/.asc/ \ + https://i3wm.org/downloads/ /downloads/i3-(.*)\.tar\.bz2 diff --git a/docs/hacking-howto b/docs/hacking-howto index d6e2b67e..247a1791 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -117,7 +117,7 @@ containers, searching containers, getting specific properties from containers, src/config.c:: Contains all functions handling the configuration file (calling the parser -(src/cfgparse.y) with the correct path, switching key bindings mode). +src/config_parser.c) with the correct path, switching key bindings mode). src/debug.c:: Contains debugging functions to print unhandled X events. diff --git a/docs/i3bar-protocol b/docs/i3bar-protocol index 6cb04bf6..3ae14453 100644 --- a/docs/i3bar-protocol +++ b/docs/i3bar-protocol @@ -136,6 +136,10 @@ color:: when it is associated. Colors are specified in hex (like in HTML), starting with a leading hash sign. For example, +#ff0000+ means red. +background:: + Overrides the background color for this particular block. +border:: + Overrides the border color for this particular block. min_width:: The minimum width (in pixels) of the block. If the content of the +full_text+ key take less space than the specified min_width, the block @@ -207,6 +211,8 @@ An example of a block which uses all possible entries follows: "full_text": "E: 10.0.0.1 (1000 Mbit/s)", "short_text": "10.0.0.1", "color": "#00ff00", + "background": "#1c1c1c", + "border": "#ee0000", "min_width": 300, "align": "right", "urgent": false, diff --git a/docs/ipc b/docs/ipc index 5113d79b..dc4e6947 100644 --- a/docs/ipc +++ b/docs/ipc @@ -520,6 +520,14 @@ statusline:: Text color to be used for the statusline. separator:: Text color to be used for the separator. +focused_background:: + Background color of the bar on the currently focused monitor output. +focused_statusline:: + Text color to be used for the statusline on the currently focused + monitor output. +focused_separator:: + Text color to be used for the separator on the currently focused + monitor output. focused_workspace_text/focused_workspace_bg/focused_workspace_border:: Text/background/border color for a workspace button when the workspace has focus. @@ -717,11 +725,15 @@ This event consists of a single serialized map containing a property This event consists of a single serialized map containing a property +change (string)+ which holds the name of current mode in use. The name is the same as specified in config when creating a mode. The default -mode is simply named default. +mode is simply named default. It contains a second property, +pango_markup+, which +defines whether pango markup shall be used for displaying this mode. *Example:* --------------------------- -{ "change": "default" } +{ + "change": "default", + "pango_markup": true +} --------------------------- === window event @@ -814,6 +826,8 @@ know): C:: * i3 includes a headerfile +i3/ipc.h+ which provides you all constants. * https://github.com/acrisci/i3ipc-glib +C++:: + * https://github.com/drmgc/i3ipcpp Go:: * https://github.com/proxypoke/i3ipc JavaScript:: diff --git a/docs/userguide b/docs/userguide index eef7d758..f3f80c96 100644 --- a/docs/userguide +++ b/docs/userguide @@ -4,8 +4,9 @@ Michael Stapelberg March 2013 This document contains all the information you need to configure and use the i3 -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. +window manager. If it does not, please check https://www.reddit.com/r/i3wm/ +first, then contact us on IRC (preferred) or post your question(s) on the +mailing list. == Default keybindings @@ -604,9 +605,10 @@ new_window pixel 3 --------------------- -=== Hiding vertical borders +[[_hiding_vertical_borders]] +=== Hiding borders adjacent to the screen edges -You can hide vertical borders adjacent to the screen edges using +You can hide container 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. @@ -652,7 +654,7 @@ The valid criteria are the same as those for commands, see <>. === Don't focus window upon opening When a new window appears, it will be focused. The +no_focus+ directive allows preventing -this from happening and can be used in combination with <>. +this from happening and must be used in combination with <>. Note that this does not apply to all cases, e.g., when feeding data into a running application causing it to request being focused. To configure the behavior in such cases, refer to @@ -784,7 +786,7 @@ keyword. These commands will be run in order. See <> for details on the special meaning of +;+ (semicolon) and +,+ (comma): they chain commands together in i3, so you need to use quoted -strings if they appear in your command. +strings (as shown in <>) if they appear in your command. *Syntax*: --------------------------------------- @@ -835,9 +837,9 @@ workspace "2: vim" output VGA1 You can change all colors which i3 uses to draw the window decorations. *Syntax*: ------------------------------------------------------- - ------------------------------------------------------- +-------------------------------------------------------------------- + +-------------------------------------------------------------------- Where colorclass can be one of: @@ -862,20 +864,20 @@ client.background:: Colors are in HTML hex format (#rrggbb), see the following example: *Examples (default colors)*: ---------------------------------------------------------- -# class border backgr. text indicator -client.focused #4c7899 #285577 #ffffff #2e9ef4 -client.focused_inactive #333333 #5f676a #ffffff #484e50 -client.unfocused #333333 #222222 #888888 #292d2e -client.urgent #2f343a #900000 #ffffff #900000 -client.placeholder #000000 #0c0c0c #ffffff #000000 +---------------------------------------------------------------------- +# class border backgr. text indicator child_border +client.focused #4c7899 #285577 #ffffff #2e9ef4 #285577 +client.focused_inactive #333333 #5f676a #ffffff #484e50 #5f676a +client.unfocused #333333 #222222 #888888 #292d2e #222222 +client.urgent #2f343a #900000 #ffffff #900000 #900000 +client.placeholder #000000 #0c0c0c #ffffff #000000 #0c0c0c client.background #ffffff ---------------------------------------------------------- +---------------------------------------------------------------------- Note that for the window decorations, the color around the child window is the -background color, and the border color is only the two thin lines at the top of -the window. +"child_border", and "border" color is only the two thin lines around the +titlebar. The indicator color is used for indicating where a new window will be opened. For horizontal split containers, the right border will be painted in indicator @@ -1236,7 +1238,7 @@ the windows key). The default value for the hidden_state is hide. ------------------------- mode dock|hide|invisible hidden_state hide|show -modifier +modifier |none ------------------------ *Example*: @@ -1248,7 +1250,8 @@ bar { } ---------------- -Available modifiers are Mod1-Mod5, Shift, Control (see +xmodmap(1)+). +Available modifiers are Mod1-Mod5, Shift, Control (see +xmodmap(1)+). You can +also use "none" if you don't want any modifier to trigger this behavior. === Mouse button commands @@ -1369,6 +1372,11 @@ NetworkManager, VLC, Pidgin, etc. can place little icons. You can configure on which output (monitor) the icons should be displayed or you can turn off the functionality entirely. +You can use mutliple +tray_output+ directives in your config to specify a list +of outputs on which you want the tray to appear. The first available output in +that list as defined by the order of the directives will be used for the tray +output. + *Syntax*: --------------------------------- tray_output none|primary| @@ -1529,6 +1537,15 @@ statusline:: Text color to be used for the statusline. separator:: Text color to be used for the separator. +focused_background:: + Background color of the bar on the currently focused monitor output. If + not used, the color will be taken from +background+. +focused_statusline:: + Text color to be used for the statusline on the currently focused + monitor output. If not used, the color will be taken from +statusline+. +focused_separator:: + Text color to be used for the separator on the currently focused + monitor output. If not used, the color will be taken from +separator+. focused_workspace:: Border, background and text color for a workspace button when the workspace has focus. @@ -1643,7 +1660,7 @@ window_role:: window_type:: Compare the window type (_NET_WM_WINDOW_TYPE). Possible values are +normal+, +dialog+, +utility+, +toolbar+, +splash+, +menu+, +dropdown_menu+, - +popup_menu+ and +tooltip+. + +popup_menu+, +tooltip+ and +notification+. id:: Compares the X11 window ID, which you can get via +xwininfo+ for example. title:: @@ -1659,10 +1676,13 @@ workspace:: the special value +\_\_focused__+ to match all windows in the currently focused workspace. con_mark:: - Compares the mark set for this container, see <>. + Compares the marks set for this container, see <>. A + match is made if any of the container's marks matches the specified + mark. con_id:: Compares the i3-internal container ID, which you can get via the IPC - interface. Handy for scripting. + interface. Handy for scripting. Use the special value +\_\_focused__+ + to match only the currently focused window. The criteria +class+, +instance+, +role+, +title+, +workspace+ and +mark+ are actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for @@ -1678,7 +1698,7 @@ searched in your +$PATH+. See <> for details on the special meaning of +;+ (semicolon) and +,+ (comma): they chain commands together in i3, so you need to use quoted -strings if they appear in your command. +strings (as shown in <>) if they appear in your command. *Syntax*: -------------------------------- @@ -1702,6 +1722,27 @@ launching. So, if an application is not startup-notification aware (most GTK and Qt using applications seem to be, though), you will end up with a watch cursor for 60 seconds. +[[exec_quoting]] +If the command to be executed contains a +;+ (semicolon) and/or a +,+ (comma), +the entire command must be quoted. For example, to have a keybinding for the +shell command +notify-send Hello, i3+, you would add an entry to your +configuration file like this: + +*Example*: +------------------------------ +# Execute a command with a comma in it +bindsym $mod+p exec "notify-send Hello, i3" +------------------------------ + +If however a command with a comma and/or semicolon itself requires quotes, you +must escape the internal quotation marks with double backslashes, like this: + +*Example*: +------------------------------ +# Execute a command with a comma, semicolon and internal quotes +bindsym $mod+p exec "notify-send \\"Hello, i3; from $USER\\"" +------------------------------ + === Splitting containers The split command makes the current window a split container. Split containers @@ -1711,20 +1752,24 @@ 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). Use -+layout toggle split+ to change the layout of any split container from splitv -to splith or vice-versa. +orientation will be changed (if it does not have more than one window). +The +toggle+ option will toggle the orientation of the split container if it +contains a single window. Otherwise it makes the current window a split +container with opposite orientation compared to the parent container. +Use +layout toggle split+ to change the layout of any split container from +splitv to splith or vice-versa. *Syntax*: -------------------------- -split vertical|horizontal -------------------------- +-------------------------------- +split vertical|horizontal|toggle +-------------------------------- *Example*: ------------------------------- +------------------------------- bindsym $mod+v split vertical bindsym $mod+h split horizontal ------------------------------- +bindsym $mod+t split toggle +------------------------------- === Manipulating layout @@ -1881,8 +1926,11 @@ for_window [instance=notepad] sticky enable === Changing (named) workspaces/moving to workspaces To change to a specific workspace, use the +workspace+ command, followed by the -number or name of the workspace. To move containers to specific workspaces, use -+move container to workspace+. +number or name of the workspace. Pass the optional flag ++--no-auto-back-and-forth+ to disable <> for this specific call +only. + +To move containers to specific workspaces, use +move container to workspace+. You can also switch to the next and previous workspace with the commands +workspace next+ and +workspace prev+, which is handy, for example, if you have @@ -1893,6 +1941,10 @@ 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). ++workspace next+ cycles through either numbered or named workspaces. But when it +reaches the last numbered/named workspace, it looks for named workspaces after +exhausting numbered ones and looks for numbered ones after exhausting named ones. + See <> for how to move a container/workspace to a different RandR output. @@ -1906,16 +1958,16 @@ back_and_forth+; likewise, you can move containers to the previously focused workspace using +move container to workspace back_and_forth+. *Syntax*: ------------------------------------ +-------------------------------------------------------------------------------- workspace next|prev|next_on_output|prev_on_output workspace back_and_forth -workspace -workspace number +workspace [--no-auto-back-and-forth] +workspace [--no-auto-back-and-forth] number -move [window|container] [to] workspace -move [window|container] [to] workspace number +move [--no-auto-back-and-forth] [window|container] [to] workspace +move [--no-auto-back-and-forth] [window|container] [to] workspace number move [window|container] [to] workspace prev|next|current ------------------------------------ +-------------------------------------------------------------------------------- *Examples*: ------------------------- @@ -2109,24 +2161,36 @@ for this purpose: It lets you input a command and sends the command to i3. It can also prefix this command and display a custom prompt for the input dialog. The additional +--toggle+ option will remove the mark if the window already has -this mark, add it if the window has none or replace the current mark if it has -another mark. +this mark or add it otherwise. Note that you may need to use this in +combination with +--add+ (see below) as any other marks will otherwise be +removed. + +By default, a window can only have one mark. You can use the +--add+ flag to +put more than one mark on a window. Refer to <> if you don't want marks to be shown in the window decoration. *Syntax*: ------------------------------- -mark [--toggle] +---------------------------------------------- +mark [--add|--replace] [--toggle] [con_mark="identifier"] focus unmark ------------------------------- +---------------------------------------------- *Example (in a terminal)*: ------------------------------- -$ i3-msg mark irssi -$ i3-msg '[con_mark="irssi"] focus' -$ i3-msg unmark irssi ------------------------------- +--------------------------------------------------------- +# marks the focused container +mark irssi + +# focus the container with the mark "irssi" +'[con_mark="irssi"] focus' + +# remove the mark "irssi" from whichever container has it +unmark irssi + +# remove all marks on all firefox windows +[class="(?i)firefox"] unmark +--------------------------------------------------------- /////////////////////////////////////////////////////////////////// TODO: make i3-input replace %s @@ -2153,7 +2217,10 @@ https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup] and the following placeholders which will be replaced: +%title+:: - The X11 window title (_NET_WM_NAME or WM_NAME as fallback). + For normal windows, this is the X11 window title (_NET_WM_NAME or WM_NAME + as fallback). When used on containers without a window (e.g., a split + container inside a tabbed/stacked layout), this will be the tree + representation of the container (e.g., "H[xterm xterm]"). +%class+:: The X11 window class (second part of WM_CLASS). This corresponds to the +class+ criterion, see <>. @@ -2189,6 +2256,10 @@ and +border none+ to make the client borderless. There is also +border toggle+ which will toggle the different border styles. +Note that "pixel" refers to logical pixel. On HiDPI displays, a logical pixel +may be represented by multiple physical pixels, so +pixel 1+ might not +necessarily translate into a single pixel row wide border. + *Syntax*: ----------------------------------------------- border normal|pixel [] diff --git a/generate-command-parser.pl b/generate-command-parser.pl index c0a9a4d4..6208945d 100755 --- a/generate-command-parser.pl +++ b/generate-command-parser.pl @@ -109,7 +109,7 @@ for my $line (@lines) { # Second step: Generate the enum values for all states. # It is important to keep the order the same, so we store the keys once. -# We sort descendingly by length to be able to replace occurences of the state +# We sort descendingly by length to be able to replace occurrences of the state # name even when one state’s name is included in another one’s (like FOR_WINDOW # is in FOR_WINDOW_COMMAND). my @keys = sort { (length($b) <=> length($a)) or ($a cmp $b) } keys %states; diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index bd12cd81..284f15fa 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -479,7 +479,7 @@ static int handle_expose() { if (current_step == STEP_WELCOME) { /* restore font color */ - set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000")); + set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); txt(logical_px(10), 2, "You have not configured i3 yet."); txt(logical_px(10), 3, "Do you want me to generate a config at"); @@ -493,16 +493,16 @@ static int handle_expose() { txt(logical_px(85), 8, "No, I will use the defaults"); /* green */ - set_font_colors(pixmap_gc, get_colorpixel("#00FF00"), get_colorpixel("#000000")); + set_font_colors(pixmap_gc, draw_util_hex_to_color("#00FF00"), draw_util_hex_to_color("#000000")); txt(logical_px(25), 6, ""); /* red */ - set_font_colors(pixmap_gc, get_colorpixel("#FF0000"), get_colorpixel("#000000")); + set_font_colors(pixmap_gc, draw_util_hex_to_color("#FF0000"), draw_util_hex_to_color("#000000")); txt(logical_px(31), 8, ""); } if (current_step == STEP_GENERATE) { - set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000")); + set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); txt(logical_px(10), 2, "Please choose either:"); txt(logical_px(85), 4, "Win as default modifier"); @@ -519,7 +519,7 @@ static int handle_expose() { /* the selected modifier */ set_font(&bold_font); - set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000")); + set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); if (modifier == MOD_Mod4) txt(logical_px(10), 4, "-> "); else @@ -527,11 +527,11 @@ static int handle_expose() { /* green */ set_font(&font); - set_font_colors(pixmap_gc, get_colorpixel("#00FF00"), get_colorpixel("#000000")); + set_font_colors(pixmap_gc, draw_util_hex_to_color("#00FF00"), draw_util_hex_to_color("#000000")); txt(logical_px(25), 9, ""); /* red */ - set_font_colors(pixmap_gc, get_colorpixel("#FF0000"), get_colorpixel("#000000")); + set_font_colors(pixmap_gc, draw_util_hex_to_color("#FF0000"), draw_util_hex_to_color("#000000")); txt(logical_px(31), 10, ""); } diff --git a/i3-dump-log/main.c b/i3-dump-log/main.c index 137554a4..9d4eefc7 100644 --- a/i3-dump-log/main.c +++ b/i3-dump-log/main.c @@ -138,6 +138,7 @@ int main(int argc, char *argv[]) { 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); + free(shmname); walk = logbuffer + header->offset_next_write; /* We first need to print old content in case there was at least one diff --git a/i3-input/main.c b/i3-input/main.c index cf3884e9..64a089dd 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -137,16 +137,16 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner); /* restore font color */ - set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000")); + set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); /* draw the prompt … */ if (prompt != NULL) { - draw_text(prompt, pixmap, pixmap_gc, logical_px(4), logical_px(4), logical_px(492)); + draw_text(prompt, pixmap, pixmap_gc, NULL, logical_px(4), logical_px(4), logical_px(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 + logical_px(4), logical_px(4), logical_px(492)); + draw_text(input, pixmap, pixmap_gc, NULL, prompt_offset + logical_px(4), logical_px(4), logical_px(492)); i3string_free(input); } @@ -176,14 +176,14 @@ static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_rel static void finish_input() { char *command = (char *)concat_strings(glyphs_utf8, input_position); - /* count the occurences of %s in the string */ + /* count the occurrences of %s in the string */ int c; int len = strlen(format); int cnt = 0; for (c = 0; c < (len - 1); c++) if (format[c] == '%' && format[c + 1] == 's') cnt++; - printf("occurences = %d\n", cnt); + printf("occurrences = %d\n", cnt); /* allocate space for the output */ int inputlen = strlen(command); diff --git a/i3-msg/main.c b/i3-msg/main.c index e4a0628c..36691cae 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -119,6 +119,10 @@ static yajl_callbacks reply_callbacks = { }; int main(int argc, char *argv[]) { +#if defined(__OpenBSD__) + if (pledge("stdio rpath unix", NULL) == -1) + err(EXIT_FAILURE, "pledge"); +#endif char *env_socket_path = getenv("I3SOCK"); if (env_socket_path) socket_path = sstrdup(env_socket_path); diff --git a/i3-nagbar/i3-nagbar.mk b/i3-nagbar/i3-nagbar.mk index aba3c09a..b10e389e 100644 --- a/i3-nagbar/i3-nagbar.mk +++ b/i3-nagbar/i3-nagbar.mk @@ -4,8 +4,8 @@ CLEAN_TARGETS += clean-i3-nagbar i3_nagbar_SOURCES := $(wildcard i3-nagbar/*.c) i3_nagbar_HEADERS := $(wildcard i3-nagbar/*.h) -i3_nagbar_CFLAGS = $(XCB_CFLAGS) $(XCB_WM_CFLAGS) $(PANGO_CFLAGS) -i3_nagbar_LIBS = $(XCB_LIBS) $(XCB_WM_LIBS) $(PANGO_LIBS) +i3_nagbar_CFLAGS = $(XCB_CFLAGS) $(XCB_CURSOR_CFLAGS) $(XCB_WM_CFLAGS) $(PANGO_CFLAGS) +i3_nagbar_LIBS = $(XCB_LIBS) $(XCB_CURSOR_LIBS) $(XCB_WM_LIBS) $(PANGO_LIBS) i3_nagbar_OBJECTS := $(i3_nagbar_SOURCES:.c=.o) diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index d86cd69a..674fcb7d 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -28,10 +28,15 @@ #include #include #include +#include #include "libi3.h" #include "i3-nagbar.h" +/** This is the equivalent of XC_left_ptr. I’m not sure why xcb doesn’t have a + * constant for that. */ +#define XCB_CURSOR_LEFT_PTR 68 + static char *argv0 = NULL; typedef struct { @@ -51,11 +56,11 @@ static button_t *buttons; static int buttoncnt; /* Result of get_colorpixel() for the various colors. */ -static uint32_t color_background; /* background of the bar */ -static uint32_t color_button_background; /* background for buttons */ -static uint32_t color_border; /* color of the button border */ -static uint32_t color_border_bottom; /* color of the bottom border */ -static uint32_t color_text; /* color of the text */ +static color_t color_background; /* background of the bar */ +static color_t color_button_background; /* background for buttons */ +static color_t color_border; /* color of the button border */ +static color_t color_border_bottom; /* color of the bottom border */ +static color_t color_text; /* color of the text */ xcb_window_t root; xcb_connection_t *conn; @@ -191,12 +196,12 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve */ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { /* re-draw the background */ - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_background}); + xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_background.colorpixel}); xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect); /* restore font color */ set_font_colors(pixmap_gc, color_text, color_background); - draw_text(prompt, pixmap, pixmap_gc, + draw_text(prompt, pixmap, pixmap_gc, NULL, logical_px(4) + logical_px(4), logical_px(4) + logical_px(4), rect.width - logical_px(4) - logical_px(4)); @@ -210,14 +215,14 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { w += logical_px(8); int y = rect.width; uint32_t values[3]; - values[0] = color_button_background; + values[0] = color_button_background.colorpixel; values[1] = line_width; xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values); xcb_rectangle_t close = {y - w - (2 * line_width), 0, w + (2 * line_width), rect.height}; xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close); - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_border}); + xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_border.colorpixel}); xcb_point_t points[] = { {y - w - (2 * line_width), line_width / 2}, {y - (line_width / 2), line_width / 2}, @@ -245,11 +250,11 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { /* account for left/right padding, which seems to be set to 12px (total) below */ w += logical_px(12); y -= logical_px(30); - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_button_background}); + xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_button_background.colorpixel}); close = (xcb_rectangle_t){y - w - (2 * line_width), logical_px(2), w + (2 * line_width), rect.height - logical_px(6)}; xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close); - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_border}); + xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_border.colorpixel}); buttons[c].x = y - w - (2 * line_width); buttons[c].width = w; xcb_point_t points2[] = { @@ -260,11 +265,11 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { {y - w - (2 * line_width), (line_width / 2) + logical_px(2)}}; xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points2); - values[0] = color_text; - values[1] = color_button_background; + values[0] = color_text.colorpixel; + values[1] = color_button_background.colorpixel; set_font_colors(pixmap_gc, color_text, color_button_background); /* the x term seems to set left/right padding */ - draw_text(buttons[c].label, pixmap, pixmap_gc, + draw_text(buttons[c].label, pixmap, pixmap_gc, NULL, y - w - line_width + logical_px(6), logical_px(4) + logical_px(3), rect.width - y + w + line_width - logical_px(6)); @@ -274,7 +279,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { /* border line at the bottom */ line_width = logical_px(2); - values[0] = color_border_bottom; + values[0] = color_border_bottom.colorpixel; values[1] = line_width; xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values); xcb_point_t bottom[] = { @@ -448,25 +453,49 @@ int main(int argc, char *argv[]) { if (bar_type == TYPE_ERROR) { /* Red theme for error messages */ - color_button_background = get_colorpixel("#680a0a"); - color_background = get_colorpixel("#900000"); - color_text = get_colorpixel("#ffffff"); - color_border = get_colorpixel("#d92424"); - color_border_bottom = get_colorpixel("#470909"); + color_button_background = draw_util_hex_to_color("#680a0a"); + color_background = draw_util_hex_to_color("#900000"); + color_text = draw_util_hex_to_color("#ffffff"); + color_border = draw_util_hex_to_color("#d92424"); + color_border_bottom = draw_util_hex_to_color("#470909"); } else { /* Yellowish theme for warnings */ - color_button_background = get_colorpixel("#ffc100"); - color_background = get_colorpixel("#ffa8000"); - color_text = get_colorpixel("#000000"); - color_border = get_colorpixel("#ab7100"); - color_border_bottom = get_colorpixel("#ab7100"); + color_button_background = draw_util_hex_to_color("#ffc100"); + color_background = draw_util_hex_to_color("#ffa8000"); + color_text = draw_util_hex_to_color("#000000"); + color_border = draw_util_hex_to_color("#ab7100"); + color_border_bottom = draw_util_hex_to_color("#ab7100"); } font = load_font(pattern, true); set_font(&font); +#if defined(__OpenBSD__) + if (pledge("stdio rpath wpath cpath getpw proc exec", NULL) == -1) + err(EXIT_FAILURE, "pledge"); +#endif + xcb_rectangle_t win_pos = get_window_position(); + xcb_cursor_t cursor; + xcb_cursor_context_t *cursor_ctx; + if (xcb_cursor_context_new(conn, root_screen, &cursor_ctx) == 0) { + cursor = xcb_cursor_load_cursor(cursor_ctx, "left_ptr"); + xcb_cursor_context_free(cursor_ctx); + } else { + cursor = xcb_generate_id(conn); + i3Font cursor_font = load_font("cursor", false); + xcb_create_glyph_cursor( + conn, + cursor, + cursor_font.specific.xcb.id, + cursor_font.specific.xcb.id, + XCB_CURSOR_LEFT_PTR, + XCB_CURSOR_LEFT_PTR + 1, + 0, 0, 0, + 65535, 65535, 65535); + } + /* Open an input window */ win = xcb_generate_id(conn); @@ -479,13 +508,14 @@ int main(int argc, char *argv[]) { 0, /* x11 border = 0, we draw our own */ XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */ - XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, + XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_CURSOR, (uint32_t[]){ 0, /* back pixel: black */ XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_BUTTON_PRESS | - XCB_EVENT_MASK_BUTTON_RELEASE}); + XCB_EVENT_MASK_BUTTON_RELEASE, + cursor}); /* Map the window (make it visible) */ xcb_map_window(conn, win); diff --git a/i3-sensible-editor b/i3-sensible-editor index 4e7456b7..b93893a1 100755 --- a/i3-sensible-editor +++ b/i3-sensible-editor @@ -9,8 +9,8 @@ # mechanism to find the preferred editor # Hopefully one of these is installed (no flamewars about preference please!): -for editor in $VISUAL $EDITOR nano vim vi emacs pico qe mg jed gedit mc-edit; do - if command -v $editor > /dev/null 2>&1; then - exec $editor "$@" +for editor in "$VISUAL" "$EDITOR" nano nvim vim vi emacs pico qe mg jed gedit mc-edit; do + if command -v "$editor" > /dev/null 2>&1; then + exec "$editor" "$@" fi done diff --git a/i3-sensible-pager b/i3-sensible-pager index ce71686b..386e2988 100755 --- a/i3-sensible-pager +++ b/i3-sensible-pager @@ -11,8 +11,8 @@ # Hopefully one of these is installed (no flamewars about preference please!): # We don't use 'more' because it will exit if the file is too short. # Worst case scenario we'll open the file in your editor. -for pager in $PAGER less most w3m pg i3-sensible-editor; do - if command -v $pager > /dev/null 2>&1; then - exec $pager "$@" +for pager in "$PAGER" less most w3m pg i3-sensible-editor; do + if command -v "$pager" > /dev/null 2>&1; then + exec "$pager" "$@" fi done diff --git a/i3-sensible-terminal b/i3-sensible-terminal index c80e5ee2..a1ada248 100755 --- a/i3-sensible-terminal +++ b/i3-sensible-terminal @@ -8,9 +8,9 @@ # We welcome patches that add distribution-specific mechanisms to find the # preferred terminal emulator. On Debian, there is the x-terminal-emulator # symlink for example. -for terminal in $TERMINAL x-terminal-emulator urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology; do - if command -v $terminal > /dev/null 2>&1; then - exec $terminal "$@" +for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st; do + if command -v "$terminal" > /dev/null 2>&1; then + exec "$terminal" "$@" fi done diff --git a/i3.config b/i3.config index b2d7fac8..f7722d36 100644 --- a/i3.config +++ b/i3.config @@ -165,7 +165,6 @@ bindsym Mod1+r mode "resize" # finds out, if available) bar { status_command i3status - tray_output primary } ####################################################################### diff --git a/i3.config.keycodes b/i3.config.keycodes index e606d347..0c978d0b 100644 --- a/i3.config.keycodes +++ b/i3.config.keycodes @@ -152,5 +152,4 @@ bindcode $mod+27 mode "resize" # finds out, if available) bar { status_command i3status - tray_output primary } diff --git a/i3bar/i3bar.mk b/i3bar/i3bar.mk index 737b0b69..5aed1902 100644 --- a/i3bar/i3bar.mk +++ b/i3bar/i3bar.mk @@ -4,8 +4,8 @@ CLEAN_TARGETS += clean-i3bar i3bar_SOURCES := $(wildcard i3bar/src/*.c) i3bar_HEADERS := $(wildcard i3bar/include/*.h) -i3bar_CFLAGS = $(XCB_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) -i3bar_LIBS = $(XCB_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) $(XCB_XKB_LIBS) +i3bar_CFLAGS = $(XCB_CFLAGS) $(XCB_CURSOR_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) +i3bar_LIBS = $(XCB_LIBS) $(XCB_CURSOR_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) $(XCB_XKB_LIBS) i3bar_OBJECTS := $(i3bar_SOURCES:.c=.o) diff --git a/i3bar/include/common.h b/i3bar/include/common.h index 90da9388..0d46ab6a 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -31,6 +31,14 @@ typedef enum { ALIGN_RIGHT } blockalign_t; +/* This data structure describes the way a status block should be rendered. These + * variables are updated each time the statusline is re-rendered. */ +struct status_block_render_desc { + uint32_t width; + uint32_t x_offset; + uint32_t x_append; +}; + /* This data structure represents one JSON dictionary, multiple of these make * up one status line. */ struct status_block { @@ -38,6 +46,8 @@ struct status_block { i3String *short_text; char *color; + char *background; + char *border; /* min_width can be specified either as a numeric value (in pixels) or as a * string. For strings, we set min_width to the measured text width of @@ -49,16 +59,14 @@ struct status_block { bool urgent; bool no_separator; - bool is_markup; + bool pango_markup; /* The amount of pixels necessary to render a separater after the block. */ uint32_t sep_block_width; - /* The amount of pixels necessary to render this block. These variables are - * only temporarily used in refresh_statusline(). */ - uint32_t width; - uint32_t x_offset; - uint32_t x_append; + /* Continuously-updated information on how to render this status block. */ + struct status_block_render_desc full_render; + struct status_block_render_desc short_render; /* Optional */ char *name; diff --git a/i3bar/include/config.h b/i3bar/include/config.h index 1ce5dfa6..2a059046 100644 --- a/i3bar/include/config.h +++ b/i3bar/include/config.h @@ -29,6 +29,12 @@ typedef struct binding_t { TAILQ_ENTRY(binding_t) bindings; } binding_t; +typedef struct tray_output_t { + char *output; + + TAILQ_ENTRY(tray_output_t) tray_outputs; +} tray_output_t; + typedef struct config_t { int modifier; TAILQ_HEAD(bindings_head, binding_t) bindings; @@ -42,7 +48,7 @@ typedef struct config_t { char *command; char *fontname; i3String *separator_symbol; - char *tray_output; + TAILQ_HEAD(tray_outputs_head, tray_output_t) tray_outputs; int tray_padding; int num_outputs; char **outputs; diff --git a/i3bar/include/outputs.h b/i3bar/include/outputs.h index e6605e1f..63cfca7f 100644 --- a/i3bar/include/outputs.h +++ b/i3bar/include/outputs.h @@ -10,6 +10,7 @@ #pragma once #include +#include #include "common.h" @@ -36,6 +37,12 @@ void init_outputs(void); */ i3_output* get_output_by_name(char* name); +/* + * Returns true if the output has the currently focused workspace + * + */ +bool output_has_focus(i3_output* output); + struct i3_output { char* name; /* Name of the output */ bool active; /* If the output is active */ @@ -44,9 +51,16 @@ struct i3_output { int ws; /* The number of the currently visible ws */ rect rect; /* The rect (relative to the root window) */ - xcb_window_t bar; /* The id of the bar of the output */ - xcb_pixmap_t buffer; /* An extra pixmap for double-buffering */ - xcb_gcontext_t bargc; /* The graphical context of the bar */ + /* Off-screen buffer for preliminary rendering of the bar. */ + surface_t buffer; + /* Off-screen buffer for pre-rendering the statusline, separated to make clipping easier. */ + surface_t statusline_buffer; + /* How much of statusline_buffer's horizontal space was used on last statusline render. */ + int statusline_width; + /* Whether statusline block short texts where used on last statusline render. */ + bool statusline_short_text; + /* The actual window on which we draw. */ + surface_t bar; struct ws_head* workspaces; /* The workspaces on this output */ struct tc_head* trayclients; /* The tray clients on this output */ diff --git a/i3bar/include/util.h b/i3bar/include/util.h index ba08cf76..dfbfd6bf 100644 --- a/i3bar/include/util.h +++ b/i3bar/include/util.h @@ -15,7 +15,7 @@ #undef MIN #define MIN(x, y) ((x) < (y) ? (x) : (y)) -#define STARTS_WITH(string, len, needle) ((len >= strlen(needle)) && strncasecmp(string, needle, strlen(needle)) == 0) +#define STARTS_WITH(string, len, needle) (((len) >= strlen((needle))) && strncasecmp((string), (needle), strlen((needle))) == 0) /* Securely free p */ #define FREE(p) \ diff --git a/i3bar/include/xcb.h b/i3bar/include/xcb.h index 835105e7..03bfc51e 100644 --- a/i3bar/include/xcb.h +++ b/i3bar/include/xcb.h @@ -24,10 +24,17 @@ #define XEMBED_MAPPED (1 << 0) #define XEMBED_EMBEDDED_NOTIFY 0 +/* We define xcb_request_failed as a macro to include the relevant line number */ +#define xcb_request_failed(cookie, err_msg) _xcb_request_failed(cookie, err_msg, __LINE__) +int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line); + struct xcb_color_strings_t { char *bar_fg; char *bar_bg; char *sep_fg; + char *focus_bar_fg; + char *focus_bar_bg; + char *focus_sep_fg; char *active_ws_fg; char *active_ws_bg; char *active_ws_border; diff --git a/i3bar/src/child.c b/i3bar/src/child.c index cfc96d5f..3570dde9 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -75,6 +75,8 @@ static void clear_statusline(struct statusline_head *head, bool free_resources) FREE(first->name); FREE(first->instance); FREE(first->min_width_str); + FREE(first->background); + FREE(first->border); } TAILQ_REMOVE(head, first, blocks); @@ -205,8 +207,16 @@ static int stdin_string(void *context, const unsigned char *val, size_t len) { sasprintf(&(ctx->block.color), "%.*s", len, val); return 1; } + if (strcasecmp(ctx->last_map_key, "background") == 0) { + sasprintf(&(ctx->block.background), "%.*s", len, val); + return 1; + } + if (strcasecmp(ctx->last_map_key, "border") == 0) { + sasprintf(&(ctx->block.border), "%.*s", len, val); + return 1; + } if (strcasecmp(ctx->last_map_key, "markup") == 0) { - ctx->block.is_markup = (len == strlen("pango") && !strncasecmp((const char *)val, "pango", strlen("pango"))); + ctx->block.pango_markup = (len == strlen("pango") && !strncasecmp((const char *)val, "pango", strlen("pango"))); return 1; } if (strcasecmp(ctx->last_map_key, "align") == 0) { @@ -220,24 +230,15 @@ static int stdin_string(void *context, const unsigned char *val, size_t len) { return 1; } if (strcasecmp(ctx->last_map_key, "min_width") == 0) { - char *copy = (char *)smalloc(len + 1); - strncpy(copy, (const char *)val, len); - copy[len] = 0; - ctx->block.min_width_str = copy; + sasprintf(&(ctx->block.min_width_str), "%.*s", len, val); return 1; } if (strcasecmp(ctx->last_map_key, "name") == 0) { - char *copy = (char *)smalloc(len + 1); - strncpy(copy, (const char *)val, len); - copy[len] = 0; - ctx->block.name = copy; + sasprintf(&(ctx->block.name), "%.*s", len, val); return 1; } if (strcasecmp(ctx->last_map_key, "instance") == 0) { - char *copy = (char *)smalloc(len + 1); - strncpy(copy, (const char *)val, len); - copy[len] = 0; - ctx->block.instance = copy; + sasprintf(&(ctx->block.instance), "%.*s", len, val); return 1; } @@ -275,15 +276,15 @@ static int stdin_end_map(void *context) { if (new_block->min_width_str) { i3String *text = i3string_from_utf8(new_block->min_width_str); - i3string_set_markup(text, new_block->is_markup); + i3string_set_markup(text, new_block->pango_markup); new_block->min_width = (uint32_t)predict_text_width(text); i3string_free(text); } - i3string_set_markup(new_block->full_text, new_block->is_markup); + i3string_set_markup(new_block->full_text, new_block->pango_markup); if (new_block->short_text != NULL) - i3string_set_markup(new_block->short_text, new_block->is_markup); + i3string_set_markup(new_block->short_text, new_block->pango_markup); TAILQ_INSERT_TAIL(&statusline_buffer, new_block, blocks); return 1; diff --git a/i3bar/src/config.c b/i3bar/src/config.c index 0e2dd05a..5c23bc78 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -21,6 +21,7 @@ static char *cur_key; static bool parsing_bindings; +static bool parsing_tray_outputs; /* * Parse a key. @@ -30,19 +31,22 @@ static bool parsing_bindings; */ static int config_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) { FREE(cur_key); + sasprintf(&(cur_key), "%.*s", keyLen, keyVal); - cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1)); - strncpy(cur_key, (const char *)keyVal, keyLen); - cur_key[keyLen] = '\0'; - - if (strcmp(cur_key, "bindings") == 0) + if (strcmp(cur_key, "bindings") == 0) { parsing_bindings = true; + } + + if (strcmp(cur_key, "tray_outputs") == 0) { + parsing_tray_outputs = true; + } return 1; } static int config_end_array_cb(void *params_) { parsing_bindings = false; + parsing_tray_outputs = false; return 1; } @@ -93,6 +97,14 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len return 0; } + if (parsing_tray_outputs) { + DLOG("Adding tray_output = %.*s to the list.\n", len, val); + tray_output_t *tray_output = scalloc(1, sizeof(tray_output_t)); + sasprintf(&(tray_output->output), "%.*s", len, val); + TAILQ_INSERT_TAIL(&(config.tray_outputs), tray_output, tray_outputs); + return 1; + } + if (!strcmp(cur_key, "mode")) { DLOG("mode = %.*s, len = %d\n", len, val, len); config.hide_on_modifier = (len == 4 && !strncmp((const char *)val, "dock", strlen("dock")) ? M_DOCK @@ -109,6 +121,11 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len if (!strcmp(cur_key, "modifier")) { DLOG("modifier = %.*s\n", len, val); + if (len == 4 && !strncmp((const char *)val, "none", strlen("none"))) { + config.modifier = XCB_NONE; + return 1; + } + if (len == 5 && !strncmp((const char *)val, "shift", strlen("shift"))) { config.modifier = ShiftMask; return 1; @@ -128,16 +145,12 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len case '3': config.modifier = Mod3Mask; return 1; - /* - case '4': - config.modifier = Mod4Mask; - return 1; - */ case '5': config.modifier = Mod5Mask; return 1; } } + config.modifier = Mod4Mask; return 1; } @@ -198,10 +211,13 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len return 1; } + /* We keep the old single tray_output working for users who only restart i3bar + * after updating. */ if (!strcmp(cur_key, "tray_output")) { - DLOG("tray_output %.*s\n", len, val); - FREE(config.tray_output); - sasprintf(&config.tray_output, "%.*s", len, val); + DLOG("Found deprecated key tray_output %.*s.\n", len, val); + tray_output_t *tray_output = scalloc(1, sizeof(tray_output_t)); + sasprintf(&(tray_output->output), "%.*s", len, val); + TAILQ_INSERT_TAIL(&(config.tray_outputs), tray_output, tray_outputs); return 1; } @@ -217,6 +233,9 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len COLOR(statusline, bar_fg); COLOR(background, bar_bg); COLOR(separator, sep_fg); + COLOR(focused_statusline, focus_bar_fg); + COLOR(focused_background, focus_bar_bg); + COLOR(focused_separator, focus_sep_fg); COLOR(focused_workspace_border, focus_ws_border); COLOR(focused_workspace_bg, focus_ws_bg); COLOR(focused_workspace_text, focus_ws_fg); @@ -317,6 +336,7 @@ void parse_config_json(char *json) { handle = yajl_alloc(&outputs_callbacks, NULL, NULL); TAILQ_INIT(&(config.bindings)); + TAILQ_INIT(&(config.tray_outputs)); state = yajl_parse(handle, (const unsigned char *)json, strlen(json)); @@ -346,6 +366,9 @@ void free_colors(struct xcb_color_strings_t *colors) { FREE_COLOR(bar_fg); FREE_COLOR(bar_bg); FREE_COLOR(sep_fg); + FREE_COLOR(focus_bar_fg); + FREE_COLOR(focus_bar_bg); + FREE_COLOR(focus_sep_fg); FREE_COLOR(active_ws_fg); FREE_COLOR(active_ws_bg); FREE_COLOR(active_ws_border); diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index eb48afea..34898663 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -17,6 +17,9 @@ #include #include #include +#ifdef I3_ASAN_ENABLED +#include +#endif #include "common.h" @@ -63,7 +66,6 @@ void got_output_reply(char *reply) { DLOG("Parsing outputs JSON...\n"); parse_outputs_json(reply); DLOG("Reconfiguring windows...\n"); - realloc_sl_buffer(); reconfig_windows(false); i3_output *o_walk; @@ -175,7 +177,6 @@ void got_bar_config_update(char *event) { /* update fonts and colors */ init_xcb_late(config.fontname); init_colors(&(config.colors)); - realloc_sl_buffer(); draw_bars(false); } @@ -214,6 +215,9 @@ void got_data(struct ev_loop *loop, ev_io *watcher, int events) { /* EOF received. Since i3 will restart i3bar instances as appropriate, * we exit here. */ DLOG("EOF received, exiting...\n"); +#ifdef I3_ASAN_ENABLED + __lsan_do_leak_check(); +#endif clean_xcb(); exit(EXIT_SUCCESS); } diff --git a/i3bar/src/main.c b/i3bar/src/main.c index 32425319..3b5d7546 100644 --- a/i3bar/src/main.c +++ b/i3bar/src/main.c @@ -137,6 +137,8 @@ int main(int argc, char **argv) { if (socket_path == NULL) { socket_path = atom_sock_path; + } else { + free(atom_sock_path); } if (socket_path == NULL) { @@ -149,6 +151,7 @@ int main(int argc, char **argv) { /* Request the bar configuration. When it arrives, we fill the config array. */ i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG, config.bar_id); } + free(socket_path); /* We listen to SIGTERM/QUIT/INT and try to exit cleanly, by stopping the main loop. * We only need those watchers on the stack, so putting them on the stack saves us diff --git a/i3bar/src/mode.c b/i3bar/src/mode.c index 7f7537af..d6767786 100644 --- a/i3bar/src/mode.c +++ b/i3bar/src/mode.c @@ -20,6 +20,8 @@ struct mode_json_params { char *json; char *cur_key; + char *name; + bool pango_markup; mode *mode; }; @@ -31,17 +33,31 @@ static int mode_string_cb(void *params_, const unsigned char *val, size_t len) { struct mode_json_params *params = (struct mode_json_params *)params_; if (!strcmp(params->cur_key, "change")) { - /* Save the name */ - params->mode->name = i3string_from_markup_with_length((const char *)val, len); - /* Save its rendered width */ - params->mode->width = predict_text_width(params->mode->name); - - DLOG("Got mode change: %s\n", i3string_as_utf8(params->mode->name)); + sasprintf(&(params->name), "%.*s", len, val); FREE(params->cur_key); - return 1; } + FREE(params->cur_key); + return 0; +} + +/* + * Parse a boolean. + * + */ +static int mode_boolean_cb(void *params_, int val) { + struct mode_json_params *params = (struct mode_json_params *)params_; + + if (strcmp(params->cur_key, "pango_markup") == 0) { + DLOG("Setting pango_markup to %d.\n", val); + params->pango_markup = val; + + FREE(params->cur_key); + return 1; + } + + FREE(params->cur_key); return 0; } @@ -54,10 +70,21 @@ static int mode_string_cb(void *params_, const unsigned char *val, size_t len) { static int mode_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) { struct mode_json_params *params = (struct mode_json_params *)params_; FREE(params->cur_key); + sasprintf(&(params->cur_key), "%.*s", keyLen, keyVal); + return 1; +} - params->cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1)); - strncpy(params->cur_key, (const char *)keyVal, keyLen); - params->cur_key[keyLen] = '\0'; +static int mode_end_map_cb(void *params_) { + struct mode_json_params *params = (struct mode_json_params *)params_; + + /* Save the name */ + params->mode->name = i3string_from_utf8(params->name); + i3string_set_markup(params->mode->name, params->pango_markup); + /* Save its rendered width */ + params->mode->width = predict_text_width(params->mode->name); + + DLOG("Got mode change: %s\n", i3string_as_utf8(params->mode->name)); + FREE(params->cur_key); return 1; } @@ -65,7 +92,9 @@ static int mode_map_key_cb(void *params_, const unsigned char *keyVal, size_t ke /* A datastructure to pass all these callbacks to yajl */ static yajl_callbacks mode_callbacks = { .yajl_string = mode_string_cb, + .yajl_boolean = mode_boolean_cb, .yajl_map_key = mode_map_key_cb, + .yajl_end_map = mode_end_map_cb, }; /* diff --git a/i3bar/src/outputs.c b/i3bar/src/outputs.c index b49ff53f..841a7565 100644 --- a/i3bar/src/outputs.c +++ b/i3bar/src/outputs.c @@ -108,9 +108,8 @@ static int outputs_string_cb(void *params_, const unsigned char *val, size_t len struct outputs_json_params *params = (struct outputs_json_params *)params_; if (!strcmp(params->cur_key, "current_workspace")) { - char *copy = smalloc(sizeof(const unsigned char) * (len + 1)); - strncpy(copy, (const char *)val, len); - copy[len] = '\0'; + char *copy = NULL; + sasprintf(©, "%.*s", len, val); char *end; errno = 0; @@ -118,7 +117,8 @@ static int outputs_string_cb(void *params_, const unsigned char *val, size_t len if (errno == 0 && (end && *end == '\0')) params->outputs_walk->ws = parsed_num; - free(copy); + + FREE(copy); FREE(params->cur_key); return 1; } @@ -127,14 +127,9 @@ static int outputs_string_cb(void *params_, const unsigned char *val, size_t len return 0; } - char *name = smalloc(sizeof(const unsigned char) * (len + 1)); - strncpy(name, (const char *)val, len); - name[len] = '\0'; - - params->outputs_walk->name = name; + sasprintf(&(params->outputs_walk->name), "%.*s", len, val); FREE(params->cur_key); - return 1; } @@ -149,9 +144,16 @@ static int outputs_start_map_cb(void *params_) { if (params->cur_key == NULL) { new_output = smalloc(sizeof(i3_output)); new_output->name = NULL; + new_output->active = false; + new_output->primary = false; + new_output->visible = false; new_output->ws = 0, + new_output->statusline_width = 0; + new_output->statusline_short_text = false; memset(&new_output->rect, 0, sizeof(rect)); - new_output->bar = XCB_NONE; + memset(&new_output->bar, 0, sizeof(surface_t)); + memset(&new_output->buffer, 0, sizeof(surface_t)); + memset(&new_output->statusline_buffer, 0, sizeof(surface_t)); new_output->workspaces = smalloc(sizeof(struct ws_head)); TAILQ_INIT(new_output->workspaces); @@ -227,11 +229,7 @@ static int outputs_end_map_cb(void *params_) { static int outputs_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) { struct outputs_json_params *params = (struct outputs_json_params *)params_; FREE(params->cur_key); - - params->cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1)); - strncpy(params->cur_key, (const char *)keyVal, keyLen); - params->cur_key[keyLen] = '\0'; - + sasprintf(&(params->cur_key), "%.*s", keyLen, keyVal); return 1; } @@ -304,3 +302,17 @@ i3_output *get_output_by_name(char *name) { return walk; } + +/* + * Returns true if the output has the currently focused workspace + * + */ +bool output_has_focus(i3_output *output) { + i3_ws *ws_walk; + TAILQ_FOREACH(ws_walk, output->workspaces, tailq) { + if (ws_walk->focused) { + return true; + } + } + return false; +} diff --git a/i3bar/src/workspaces.c b/i3bar/src/workspaces.c index 961a41f5..77b351e8 100644 --- a/i3bar/src/workspaces.c +++ b/i3bar/src/workspaces.c @@ -102,8 +102,6 @@ static int workspaces_integer_cb(void *params_, long long val) { static int workspaces_string_cb(void *params_, const unsigned char *val, size_t len) { struct workspaces_json_params *params = (struct workspaces_json_params *)params_; - char *output_name; - if (!strcmp(params->cur_key, "name")) { const char *ws_name = (const char *)val; params->workspaces_walk->canonical_name = sstrndup(ws_name, len); @@ -147,11 +145,11 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t if (!strcmp(params->cur_key, "output")) { /* We add the ws to the TAILQ of the output, it belongs to */ - output_name = smalloc(sizeof(const unsigned char) * (len + 1)); - strncpy(output_name, (const char *)val, len); - output_name[len] = '\0'; + char *output_name = NULL; + sasprintf(&output_name, "%.*s", len, val); + i3_output *target = get_output_by_name(output_name); - if (target) { + if (target != NULL) { params->workspaces_walk->output = target; TAILQ_INSERT_TAIL(params->workspaces_walk->output->workspaces, @@ -201,11 +199,7 @@ static int workspaces_start_map_cb(void *params_) { static int workspaces_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) { struct workspaces_json_params *params = (struct workspaces_json_params *)params_; FREE(params->cur_key); - - params->cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1)); - strncpy(params->cur_key, (const char *)keyVal, keyLen); - params->cur_key[keyLen] = '\0'; - + sasprintf(&(params->cur_key), "%.*s", keyLen, keyVal); return 1; } diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index f90bbcee..496035c2 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -11,6 +11,7 @@ #include #include #include +#include #ifdef XCB_COMPAT #include "xcb_compat.h" @@ -31,9 +32,17 @@ #include #include +#ifdef I3_ASAN_ENABLED +#include +#endif + #include "common.h" #include "libi3.h" +/** This is the equivalent of XC_left_ptr. I’m not sure why xcb doesn’t have a + * constant for that. */ +#define XCB_CURSOR_LEFT_PTR 68 + /* We save the atoms in an easy to access array, indexed by an enum */ enum { #define ATOM_DO(name) name, @@ -49,6 +58,7 @@ xcb_connection_t *xcb_connection; int screen; xcb_screen_t *root_screen; xcb_window_t xcb_root; +static xcb_cursor_t cursor; /* selection window for tray support */ static xcb_window_t selwin = XCB_NONE; @@ -63,6 +73,10 @@ static i3Font font; /* Icon size (based on font size) */ int icon_size; +xcb_visualtype_t *visual_type; +uint8_t depth; +xcb_colormap_t colormap; + /* Overall height of the bar (based on font size) */ int bar_height; @@ -70,13 +84,6 @@ int bar_height; int xkb_base; int mod_pressed = 0; -/* Because the statusline is the same on all outputs, we have - * global buffer to render it on */ -xcb_gcontext_t statusline_ctx; -xcb_gcontext_t statusline_clear; -xcb_pixmap_t statusline_pm; -uint32_t statusline_width; - /* Event watchers, to interact with the user */ ev_prepare *xcb_prep; ev_check *xcb_chk; @@ -91,24 +98,27 @@ bool activated_mode = false; /* The parsed colors */ struct xcb_colors_t { - uint32_t bar_fg; - uint32_t bar_bg; - uint32_t sep_fg; - uint32_t active_ws_fg; - uint32_t active_ws_bg; - uint32_t active_ws_border; - uint32_t inactive_ws_fg; - uint32_t inactive_ws_bg; - uint32_t inactive_ws_border; - uint32_t urgent_ws_bg; - uint32_t urgent_ws_fg; - uint32_t urgent_ws_border; - uint32_t focus_ws_bg; - uint32_t focus_ws_fg; - uint32_t focus_ws_border; - uint32_t binding_mode_bg; - uint32_t binding_mode_fg; - uint32_t binding_mode_border; + color_t bar_fg; + color_t bar_bg; + color_t sep_fg; + color_t focus_bar_fg; + color_t focus_bar_bg; + color_t focus_sep_fg; + color_t active_ws_fg; + color_t active_ws_bg; + color_t active_ws_border; + color_t inactive_ws_fg; + color_t inactive_ws_bg; + color_t inactive_ws_border; + color_t urgent_ws_bg; + color_t urgent_ws_fg; + color_t urgent_ws_border; + color_t focus_ws_bg; + color_t focus_ws_fg; + color_t focus_ws_border; + color_t binding_mode_bg; + color_t binding_mode_fg; + color_t binding_mode_border; }; struct xcb_colors_t colors; @@ -131,8 +141,6 @@ static const int tray_loff_px = 2; /* Vertical offset between the bar and a separator */ static const int sep_voff_px = 4; -/* We define xcb_request_failed as a macro to include the relevant line number */ -#define xcb_request_failed(cookie, err_msg) _xcb_request_failed(cookie, err_msg, __LINE__) int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) { xcb_generic_error_t *err; if ((err = xcb_request_check(xcb_connection, cookie)) != NULL) { @@ -165,7 +173,10 @@ int get_tray_width(struct tc_head *trayclients) { * Draws a separator for the given block if necessary. * */ -static void draw_separator(uint32_t x, struct status_block *block) { +static void draw_separator(i3_output *output, uint32_t x, struct status_block *block, bool use_focus_colors) { + color_t sep_fg = (use_focus_colors ? colors.focus_sep_fg : colors.sep_fg); + color_t bar_bg = (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg); + uint32_t sep_offset = get_sep_offset(block); if (TAILQ_NEXT(block, blocks) == NULL || sep_offset == 0) return; @@ -173,113 +184,149 @@ static void draw_separator(uint32_t x, struct status_block *block) { uint32_t center_x = x - sep_offset; if (config.separator_symbol == NULL) { /* Draw a classic one pixel, vertical separator. */ - uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_LINE_WIDTH; - uint32_t values[] = {colors.sep_fg, colors.bar_bg, logical_px(1)}; - xcb_change_gc(xcb_connection, statusline_ctx, mask, values); - xcb_poly_line(xcb_connection, XCB_COORD_MODE_ORIGIN, statusline_pm, statusline_ctx, 2, - (xcb_point_t[]){{center_x, logical_px(sep_voff_px)}, - {center_x, bar_height - logical_px(sep_voff_px)}}); + draw_util_rectangle(xcb_connection, &output->statusline_buffer, sep_fg, + center_x, + logical_px(sep_voff_px), + logical_px(1), + bar_height - 2 * logical_px(sep_voff_px)); } else { /* Draw a custom separator. */ uint32_t separator_x = MAX(x - block->sep_block_width, center_x - separator_symbol_width / 2); - set_font_colors(statusline_ctx, colors.sep_fg, colors.bar_bg); - draw_text(config.separator_symbol, statusline_pm, statusline_ctx, - separator_x, logical_px(ws_voff_px), x - separator_x); + draw_util_text(config.separator_symbol, &output->statusline_buffer, sep_fg, bar_bg, + separator_x, logical_px(ws_voff_px), x - separator_x); } } -/* - * Redraws the statusline to the buffer - * - */ -void refresh_statusline(bool use_short_text) { +uint32_t predict_statusline_length(bool use_short_text) { + uint32_t width = 0; struct status_block *block; - uint32_t old_statusline_width = statusline_width; - statusline_width = 0; - - /* Predict the text width of all blocks (in pixels). */ TAILQ_FOREACH(block, &statusline_head, blocks) { - /* Try to use the shorter text if necessary and possible. */ + i3String *text = block->full_text; + struct status_block_render_desc *render = &block->full_render; if (use_short_text && block->short_text != NULL) { - I3STRING_FREE(block->full_text); - block->full_text = i3string_copy(block->short_text); + text = block->short_text; + render = &block->short_render; } - if (i3string_get_num_bytes(block->full_text) == 0) + if (i3string_get_num_bytes(text) == 0) continue; - block->width = predict_text_width(block->full_text); + render->width = predict_text_width(text); + if (block->border) + render->width += logical_px(2); /* Compute offset and append for text aligment in min_width. */ - if (block->min_width <= block->width) { - block->x_offset = 0; - block->x_append = 0; + if (block->min_width <= render->width) { + render->x_offset = 0; + render->x_append = 0; } else { - uint32_t padding_width = block->min_width - block->width; + uint32_t padding_width = block->min_width - render->width; switch (block->align) { case ALIGN_LEFT: - block->x_append = padding_width; + render->x_append = padding_width; break; case ALIGN_RIGHT: - block->x_offset = padding_width; + render->x_offset = padding_width; break; case ALIGN_CENTER: - block->x_offset = padding_width / 2; - block->x_append = padding_width / 2 + padding_width % 2; + render->x_offset = padding_width / 2; + render->x_append = padding_width / 2 + padding_width % 2; break; } } + width += render->width + render->x_offset + render->x_append; + /* If this is not the last block, add some pixels for a separator. */ if (TAILQ_NEXT(block, blocks) != NULL) - statusline_width += block->sep_block_width; - - statusline_width += block->width + block->x_offset + block->x_append; + width += block->sep_block_width; } - /* 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 > root_screen->width_in_pixels && - statusline_width > old_statusline_width) - realloc_sl_buffer(); + return width; +} - /* Clear the statusline pixmap. */ - xcb_rectangle_t rect = {0, 0, MAX(root_screen->width_in_pixels, statusline_width), bar_height}; - xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_clear, 1, &rect); +/* + * Redraws the statusline to the output's statusline_buffer + */ +void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_colors, bool use_short_text) { + struct status_block *block; - /* Draw the text of each block. */ - uint32_t x = 0; + color_t bar_color = (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg); + draw_util_clear_surface(xcb_connection, &output->statusline_buffer, bar_color); + + /* Use unsigned integer wraparound to clip off the left side. + * For example, if clip_left is 75, then x will start at the very large + * number INT_MAX-75, which is way outside the surface dimensions. Drawing + * to that x position is a no-op which XCB and Cairo safely ignore. Once x moves + * up by 75 and goes past INT_MAX, it will wrap around again to 0, and we start + * actually rendering content to the surface. */ + uint32_t x = 0 - clip_left; + + /* Draw the text of each block */ TAILQ_FOREACH(block, &statusline_head, blocks) { - if (i3string_get_num_bytes(block->full_text) == 0) - continue; - uint32_t fg_color; - - /* If this block is urgent, draw it with the defined color and border. */ - if (block->urgent) { - fg_color = colors.urgent_ws_fg; - - uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND; - - /* Draw the background */ - uint32_t bg_color = colors.urgent_ws_bg; - uint32_t bg_values[] = {bg_color, bg_color}; - xcb_change_gc(xcb_connection, statusline_ctx, mask, bg_values); - - /* The urgent background “overshoots” by 2 px so that the text that - * is printed onto it will not be look so cut off. */ - xcb_rectangle_t bg_rect = {x - logical_px(2), logical_px(1), block->width + logical_px(4), bar_height - logical_px(2)}; - xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_ctx, 1, &bg_rect); - } else { - fg_color = (block->color ? get_colorpixel(block->color) : colors.bar_fg); + i3String *text = block->full_text; + struct status_block_render_desc *render = &block->full_render; + if (use_short_text && block->short_text != NULL) { + text = block->short_text; + render = &block->short_render; } - set_font_colors(statusline_ctx, fg_color, colors.bar_bg); - draw_text(block->full_text, statusline_pm, statusline_ctx, x + block->x_offset, logical_px(ws_voff_px), block->width); - x += block->width + block->sep_block_width + block->x_offset + block->x_append; + if (i3string_get_num_bytes(text) == 0) + continue; + + color_t fg_color; + if (block->urgent) { + fg_color = colors.urgent_ws_fg; + } else if (block->color) { + fg_color = draw_util_hex_to_color(block->color); + } else if (use_focus_colors) { + fg_color = colors.focus_bar_fg; + } else { + fg_color = colors.bar_fg; + } + + color_t bg_color = bar_color; + + int border_width = (block->border) ? logical_px(1) : 0; + int full_render_width = render->width + render->x_offset + render->x_append; + if (block->border || block->background || block->urgent) { + /* Let's determine the colors first. */ + color_t border_color = bar_color; + if (block->urgent) { + border_color = colors.urgent_ws_border; + bg_color = colors.urgent_ws_bg; + } else { + if (block->border) + border_color = draw_util_hex_to_color(block->border); + if (block->background) + bg_color = draw_util_hex_to_color(block->background); + } + + /* Draw the border. */ + draw_util_rectangle(xcb_connection, &output->statusline_buffer, border_color, + x, logical_px(1), + full_render_width, + bar_height - logical_px(2)); + + /* Draw the background. */ + draw_util_rectangle(xcb_connection, &output->statusline_buffer, bg_color, + x + border_width, + logical_px(1) + border_width, + full_render_width - 2 * border_width, + bar_height - 2 * border_width - logical_px(2)); + } + + draw_util_text(text, &output->statusline_buffer, fg_color, bg_color, + x + render->x_offset + border_width, logical_px(ws_voff_px), + render->width - 2 * border_width); + x += full_render_width; /* If this is not the last block, draw a separator. */ - draw_separator(x, block); + if (TAILQ_NEXT(block, blocks) != NULL) { + x += block->sep_block_width; + draw_separator(output, x, block, use_focus_colors); + } } } @@ -297,7 +344,7 @@ void hide_bars(void) { if (!walk->active) { continue; } - xcb_unmap_window(xcb_connection, walk->bar); + xcb_unmap_window(xcb_connection, walk->bar.id); } stop_child(); } @@ -319,7 +366,7 @@ void unhide_bars(void) { cont_child(); SLIST_FOREACH(walk, outputs, slist) { - if (walk->bar == XCB_NONE) { + if (walk->bar.id == XCB_NONE) { continue; } mask = XCB_CONFIG_WINDOW_X | @@ -337,14 +384,14 @@ void unhide_bars(void) { values[4] = XCB_STACK_MODE_ABOVE; DLOG("Reconfiguring window for output %s to %d,%d\n", walk->name, values[0], values[1]); cookie = xcb_configure_window_checked(xcb_connection, - walk->bar, + walk->bar.id, mask, values); if (xcb_request_failed(cookie, "Could not reconfigure window")) { exit(EXIT_FAILURE); } - xcb_map_window(xcb_connection, walk->bar); + xcb_map_window(xcb_connection, walk->bar.id); } } @@ -353,9 +400,9 @@ void unhide_bars(void) { * */ void init_colors(const struct xcb_color_strings_t *new_colors) { -#define PARSE_COLOR(name, def) \ - do { \ - colors.name = get_colorpixel(new_colors->name ? new_colors->name : def); \ +#define PARSE_COLOR(name, def) \ + do { \ + colors.name = draw_util_hex_to_color(new_colors->name ? new_colors->name : def); \ } while (0) PARSE_COLOR(bar_fg, "#FFFFFF"); PARSE_COLOR(bar_bg, "#000000"); @@ -374,9 +421,9 @@ void init_colors(const struct xcb_color_strings_t *new_colors) { PARSE_COLOR(focus_ws_border, "#4c7899"); #undef PARSE_COLOR -#define PARSE_COLOR_FALLBACK(name, fallback) \ - do { \ - colors.name = new_colors->name ? get_colorpixel(new_colors->name) : colors.fallback; \ +#define PARSE_COLOR_FALLBACK(name, fallback) \ + do { \ + colors.name = new_colors->name ? draw_util_hex_to_color(new_colors->name) : colors.fallback; \ } while (0) /* For the binding mode indicator colors, we don't hardcode a default. @@ -384,6 +431,12 @@ void init_colors(const struct xcb_color_strings_t *new_colors) { PARSE_COLOR_FALLBACK(binding_mode_fg, urgent_ws_fg); PARSE_COLOR_FALLBACK(binding_mode_bg, urgent_ws_bg); PARSE_COLOR_FALLBACK(binding_mode_border, urgent_ws_border); + + /* Similarly, for unspecified focused bar colors, we fall back to the + * regular bar colors. */ + PARSE_COLOR_FALLBACK(focus_bar_fg, bar_fg); + PARSE_COLOR_FALLBACK(focus_bar_bg, bar_bg); + PARSE_COLOR_FALLBACK(focus_sep_fg, sep_fg); #undef PARSE_COLOR_FALLBACK init_tray_colors(); @@ -401,7 +454,7 @@ void handle_button(xcb_button_press_event_t *event) { i3_output *walk; xcb_window_t bar = event->event; SLIST_FOREACH(walk, outputs, slist) { - if (walk->bar == bar) { + if (walk->bar.id == bar) { break; } } @@ -412,7 +465,6 @@ void handle_button(xcb_button_press_event_t *event) { } int32_t x = event->event_x >= 0 ? event->event_x : 0; - int32_t original_x = x; DLOG("Got button %d\n", event->detail); @@ -435,21 +487,28 @@ void handle_button(xcb_button_press_event_t *event) { * check if a status block has been clicked. */ int tray_width = get_tray_width(walk->trayclients); int block_x = 0, last_block_x; - int offset = walk->rect.w - statusline_width - tray_width - logical_px(sb_hoff_px); + int offset = walk->rect.w - walk->statusline_width - tray_width - logical_px(sb_hoff_px); + int32_t statusline_x = x - offset; - x = original_x - offset; - if (x >= 0 && (size_t)x < statusline_width) { + if (statusline_x >= 0 && statusline_x < walk->statusline_width) { struct status_block *block; int sep_offset_remainder = 0; TAILQ_FOREACH(block, &statusline_head, blocks) { - if (i3string_get_num_bytes(block->full_text) == 0) + i3String *text = block->full_text; + struct status_block_render_desc *render = &block->full_render; + if (walk->statusline_short_text && block->short_text != NULL) { + text = block->short_text; + render = &block->short_render; + } + + if (i3string_get_num_bytes(text) == 0) continue; last_block_x = block_x; - block_x += block->width + block->x_offset + block->x_append + get_sep_offset(block) + sep_offset_remainder; + block_x += render->width + render->x_offset + render->x_append + get_sep_offset(block) + sep_offset_remainder; - if (x <= block_x && x >= last_block_x) { + if (statusline_x <= block_x && statusline_x >= last_block_x) { send_block_clicked(event->detail, block->name, block->instance, event->root_x, event->root_y); return; } @@ -457,7 +516,6 @@ void handle_button(xcb_button_press_event_t *event) { sep_offset_remainder = block->sep_block_width - get_sep_offset(block); } } - x = original_x; } /* If a custom command was specified for this mouse button, it overrides @@ -564,7 +622,7 @@ static void handle_visibility_notify(xcb_visibility_notify_event_t *event) { if (!output->active) { continue; } - if (output->bar == event->window) { + if (output->bar.id == event->window) { if (output->visible == visible) { return; } @@ -680,25 +738,40 @@ static void handle_client_message(xcb_client_message_event_t *event) { } DLOG("X window %08x requested docking\n", client); - i3_output *walk, *output = NULL; - SLIST_FOREACH(walk, outputs, slist) { - if (!walk->active) - continue; - if (config.tray_output) { - if ((strcasecmp(walk->name, config.tray_output) != 0) && - (!walk->primary || strcasecmp("primary", config.tray_output) != 0)) + i3_output *output = NULL; + i3_output *walk = NULL; + tray_output_t *tray_output = NULL; + /* We need to iterate through the tray_output assignments first in + * order to prioritize them. Otherwise, if this bar manages two + * outputs and both are assigned as tray_output as well, the first + * output in our list would receive the tray rather than the first + * one defined via tray_output. */ + TAILQ_FOREACH(tray_output, &(config.tray_outputs), tray_outputs) { + SLIST_FOREACH(walk, outputs, slist) { + if (!walk->active) continue; + + if (strcasecmp(walk->name, tray_output->output) == 0) { + DLOG("Found tray_output assignment for output %s.\n", walk->name); + output = walk; + break; + } + + if (walk->primary && strcasecmp("primary", tray_output->output) == 0) { + DLOG("Found tray_output assignment on primary output %s.\n", walk->name); + output = walk; + break; + } } - DLOG("using output %s\n", walk->name); - output = walk; - break; + /* If we found an output, we're done. */ + if (output != NULL) + break; } - /* In case of tray_output == primary and there is no primary output - * configured, we fall back to the first available output. */ - if (output == NULL && - config.tray_output && - strcasecmp("primary", config.tray_output) == 0) { + + /* If no tray_output has been specified, we fall back to the first + * available output. */ + if (output == NULL && TAILQ_EMPTY(&(config.tray_outputs))) { SLIST_FOREACH(walk, outputs, slist) { if (!walk->active) continue; @@ -707,15 +780,20 @@ static void handle_client_message(xcb_client_message_event_t *event) { break; } } + if (output == NULL) { ELOG("No output found\n"); return; } - xcb_reparent_window(xcb_connection, - client, - output->bar, - output->rect.w - icon_size - logical_px(config.tray_padding), - logical_px(config.tray_padding)); + + xcb_void_cookie_t rcookie = xcb_reparent_window(xcb_connection, + client, + output->bar.id, + output->rect.w - icon_size - logical_px(config.tray_padding), + logical_px(config.tray_padding)); + if (xcb_request_failed(rcookie, "Could not reparent window. Maybe it is using an incorrect depth/visual?")) + return; + /* We reconfigure the window to use a reasonable size. The systray * specification explicitly says: * Tray icons may be assigned any size by the system tray, and @@ -737,8 +815,8 @@ static void handle_client_message(xcb_client_message_event_t *event) { ev->type = atoms[_XEMBED]; ev->format = 32; ev->data.data32[0] = XCB_CURRENT_TIME; - ev->data.data32[1] = atoms[XEMBED_EMBEDDED_NOTIFY]; - ev->data.data32[2] = output->bar; + ev->data.data32[1] = XEMBED_EMBEDDED_NOTIFY; + ev->data.data32[2] = output->bar.id; ev->data.data32[3] = xe_version; xcb_send_event(xcb_connection, 0, @@ -987,17 +1065,27 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) { if (xcb_connection_has_error(xcb_connection)) { ELOG("X11 connection was closed unexpectedly - maybe your X server terminated / crashed?\n"); +#ifdef I3_ASAN_ENABLED + __lsan_do_leak_check(); +#endif exit(1); } while ((event = xcb_poll_for_event(xcb_connection)) != NULL) { + if (event->response_type == 0) { + xcb_generic_error_t *error = (xcb_generic_error_t *)event; + DLOG("Received X11 error, sequence 0x%x, error_code = %d\n", error->sequence, error->error_code); + free(event); + continue; + } + int type = (event->response_type & ~0x80); if (type == xkb_base && xkb_base > -1) { DLOG("received an xkb event\n"); xcb_xkb_state_notify_event_t *state = (xcb_xkb_state_notify_event_t *)event; - if (state->xkbType == XCB_XKB_STATE_NOTIFY) { + if (state->xkbType == XCB_XKB_STATE_NOTIFY && config.modifier != XCB_NONE) { int modstate = state->mods & config.modifier; #define DLOGMOD(modmask, status) \ @@ -1118,32 +1206,27 @@ char *init_xcb_early() { 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 */ - uint32_t mask = XCB_GC_FOREGROUND; - uint32_t vals[] = {colors.bar_bg, colors.bar_bg}; + depth = root_screen->root_depth; + colormap = root_screen->default_colormap; + visual_type = get_visualtype(root_screen); - statusline_clear = xcb_generate_id(xcb_connection); - xcb_void_cookie_t clear_ctx_cookie = xcb_create_gc_checked(xcb_connection, - statusline_clear, - xcb_root, - mask, - vals); - - statusline_ctx = xcb_generate_id(xcb_connection); - xcb_void_cookie_t sl_ctx_cookie = xcb_create_gc_checked(xcb_connection, - statusline_ctx, - xcb_root, - 0, - NULL); - - statusline_pm = xcb_generate_id(xcb_connection); - xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection, - root_screen->root_depth, - statusline_pm, - xcb_root, - root_screen->width_in_pixels, - root_screen->height_in_pixels); + xcb_cursor_context_t *cursor_ctx; + if (xcb_cursor_context_new(conn, root_screen, &cursor_ctx) == 0) { + cursor = xcb_cursor_load_cursor(cursor_ctx, "left_ptr"); + xcb_cursor_context_free(cursor_ctx); + } else { + cursor = xcb_generate_id(xcb_connection); + i3Font cursor_font = load_font("cursor", false); + xcb_create_glyph_cursor( + xcb_connection, + cursor, + cursor_font.specific.xcb.id, + cursor_font.specific.xcb.id, + XCB_CURSOR_LEFT_PTR, + XCB_CURSOR_LEFT_PTR + 1, + 0, 0, 0, + 65535, 65535, 65535); + } /* The various watchers to communicate with xcb */ xcb_io = smalloc(sizeof(ev_io)); @@ -1163,12 +1246,6 @@ char *init_xcb_early() { char *path = root_atom_contents("I3_SOCKET_PATH", xcb_connection, screen); - if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline buffer") || - xcb_request_failed(clear_ctx_cookie, "Could not allocate statusline buffer clearcontext") || - xcb_request_failed(sl_ctx_cookie, "Could not allocate statusline buffer context")) { - exit(EXIT_FAILURE); - } - return path; } @@ -1279,17 +1356,17 @@ void init_tray(void) { /* tray support: we need a window to own the selection */ selwin = xcb_generate_id(xcb_connection); - uint32_t selmask = XCB_CW_OVERRIDE_REDIRECT; - uint32_t selval[] = {1}; + uint32_t selmask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_COLORMAP; + uint32_t selval[] = {root_screen->black_pixel, root_screen->black_pixel, 1, colormap}; xcb_create_window(xcb_connection, - root_screen->root_depth, + depth, selwin, xcb_root, -1, -1, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, - root_screen->root_visual, + visual_type->visual_id, selmask, selval); @@ -1303,6 +1380,14 @@ void init_tray(void) { 32, 1, &orientation); + xcb_change_property(xcb_connection, + XCB_PROP_MODE_REPLACE, + selwin, + atoms[_NET_SYSTEM_TRAY_VISUAL], + XCB_ATOM_VISUALID, + 32, + 1, + &visual_type->visual_id); init_tray_colors(); @@ -1338,6 +1423,8 @@ void init_tray(void) { return; } + free(selreply); + send_tray_clientmessage(); } @@ -1399,6 +1486,9 @@ void clean_xcb(void) { FREE_SLIST(outputs, i3_output); FREE(outputs); + free_font(); + + xcb_free_cursor(xcb_connection, cursor); xcb_flush(xcb_connection); xcb_aux_sync(xcb_connection); xcb_disconnect(xcb_connection); @@ -1483,56 +1573,13 @@ void destroy_window(i3_output *output) { if (output == NULL) { return; } - if (output->bar == XCB_NONE) { + if (output->bar.id == XCB_NONE) { return; } kick_tray_clients(output); - xcb_destroy_window(xcb_connection, output->bar); - output->bar = XCB_NONE; -} - -/* - * Reallocate the statusline buffer - * - */ -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, - root_screen->root_depth, - statusline_pm, - xcb_root, - MAX(root_screen->width_in_pixels, statusline_width), - bar_height); - - uint32_t mask = XCB_GC_FOREGROUND; - uint32_t vals[2] = {colors.bar_bg, colors.bar_bg}; - xcb_free_gc(xcb_connection, statusline_clear); - statusline_clear = xcb_generate_id(xcb_connection); - xcb_void_cookie_t clear_ctx_cookie = xcb_create_gc_checked(xcb_connection, - statusline_clear, - xcb_root, - mask, - vals); - - mask |= XCB_GC_BACKGROUND; - vals[0] = colors.bar_fg; - xcb_free_gc(xcb_connection, statusline_ctx); - statusline_ctx = xcb_generate_id(xcb_connection); - xcb_void_cookie_t sl_ctx_cookie = xcb_create_gc_checked(xcb_connection, - statusline_ctx, - xcb_root, - mask, - vals); - - if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline buffer") || - xcb_request_failed(clear_ctx_cookie, "Could not allocate statusline buffer clearcontext") || - xcb_request_failed(sl_ctx_cookie, "Could not allocate statusline buffer context")) { - exit(EXIT_FAILURE); - } + xcb_destroy_window(xcb_connection, output->bar.id); + output->bar.id = XCB_NONE; } /* Strut partial tells i3 where to reserve space for i3bar. This is determined @@ -1571,7 +1618,7 @@ xcb_void_cookie_t config_strut_partial(i3_output *output) { } return xcb_change_property(xcb_connection, XCB_PROP_MODE_REPLACE, - output->bar, + output->bar.id, atoms[_NET_WM_STRUT_PARTIAL], XCB_ATOM_CARDINAL, 32, @@ -1585,7 +1632,7 @@ xcb_void_cookie_t config_strut_partial(i3_output *output) { */ void reconfig_windows(bool redraw_bars) { uint32_t mask; - uint32_t values[5]; + uint32_t values[6]; static bool tray_configured = false; i3_output *walk; @@ -1597,56 +1644,69 @@ void reconfig_windows(bool redraw_bars) { destroy_window(walk); continue; } - if (walk->bar == XCB_NONE) { + if (walk->bar.id == XCB_NONE) { DLOG("Creating window for output %s\n", walk->name); - walk->bar = xcb_generate_id(xcb_connection); - walk->buffer = xcb_generate_id(xcb_connection); - mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; - /* Black background */ - values[0] = colors.bar_bg; + xcb_window_t bar_id = xcb_generate_id(xcb_connection); + xcb_pixmap_t buffer_id = xcb_generate_id(xcb_connection); + xcb_pixmap_t statusline_buffer_id = xcb_generate_id(xcb_connection); + mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP | XCB_CW_CURSOR; + + values[0] = colors.bar_bg.colorpixel; + values[1] = root_screen->black_pixel; /* If hide_on_modifier is set to hide or invisible mode, i3 is not supposed to manage our bar windows */ - values[1] = (config.hide_on_modifier == M_DOCK ? 0 : 1); + values[2] = (config.hide_on_modifier == M_DOCK ? 0 : 1); /* We enable the following EventMask fields: * EXPOSURE, to get expose events (we have to re-draw then) * SUBSTRUCTURE_REDIRECT, to get ConfigureRequests when the tray * child windows use ConfigureWindow * BUTTON_PRESS, to handle clicks on the workspace buttons * */ - values[2] = XCB_EVENT_MASK_EXPOSURE | + values[3] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_BUTTON_PRESS; if (config.hide_on_modifier == M_DOCK) { /* If the bar is normally visible, catch visibility change events to suspend * the status process when the bar is obscured by full-screened windows. */ - values[2] |= XCB_EVENT_MASK_VISIBILITY_CHANGE; + values[3] |= XCB_EVENT_MASK_VISIBILITY_CHANGE; walk->visible = true; } + values[4] = colormap; + values[5] = cursor; + xcb_void_cookie_t win_cookie = xcb_create_window_checked(xcb_connection, - root_screen->root_depth, - walk->bar, + depth, + bar_id, xcb_root, walk->rect.x, walk->rect.y + walk->rect.h - bar_height, walk->rect.w, bar_height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, - root_screen->root_visual, + visual_type->visual_id, mask, values); /* The double-buffer we use to render stuff off-screen */ xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection, - root_screen->root_depth, - walk->buffer, - walk->bar, + depth, + buffer_id, + bar_id, walk->rect.w, bar_height); + /* The double-buffer we use to render the statusline before copying to buffer */ + xcb_void_cookie_t slpm_cookie = xcb_create_pixmap_checked(xcb_connection, + depth, + statusline_buffer_id, + bar_id, + walk->rect.w, + bar_height); + /* Set the WM_CLASS and WM_NAME (we don't need UTF-8) atoms */ xcb_void_cookie_t class_cookie; class_cookie = xcb_change_property(xcb_connection, XCB_PROP_MODE_REPLACE, - walk->bar, + bar_id, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, @@ -1658,7 +1718,7 @@ void reconfig_windows(bool redraw_bars) { xcb_void_cookie_t name_cookie; name_cookie = xcb_change_property(xcb_connection, XCB_PROP_MODE_REPLACE, - walk->bar, + bar_id, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, @@ -1670,55 +1730,86 @@ void reconfig_windows(bool redraw_bars) { * this one */ xcb_void_cookie_t dock_cookie = xcb_change_property(xcb_connection, XCB_PROP_MODE_REPLACE, - walk->bar, + bar_id, atoms[_NET_WM_WINDOW_TYPE], XCB_ATOM_ATOM, 32, 1, (unsigned char *)&atoms[_NET_WM_WINDOW_TYPE_DOCK]); - xcb_void_cookie_t strut_cookie = config_strut_partial(walk); + draw_util_surface_init(xcb_connection, &walk->bar, bar_id, NULL, walk->rect.w, bar_height); + draw_util_surface_init(xcb_connection, &walk->buffer, buffer_id, NULL, walk->rect.w, bar_height); + draw_util_surface_init(xcb_connection, &walk->statusline_buffer, statusline_buffer_id, NULL, walk->rect.w, bar_height); - /* We also want a graphics context for the bars (it defines the properties - * with which we draw to them) */ - walk->bargc = xcb_generate_id(xcb_connection); - xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(xcb_connection, - walk->bargc, - walk->bar, - 0, - NULL); + xcb_void_cookie_t strut_cookie = config_strut_partial(walk); /* We finally map the bar (display it on screen), unless the modifier-switch is on */ xcb_void_cookie_t map_cookie; if (config.hide_on_modifier == M_DOCK) { - map_cookie = xcb_map_window_checked(xcb_connection, walk->bar); + map_cookie = xcb_map_window_checked(xcb_connection, bar_id); } if (xcb_request_failed(win_cookie, "Could not create window") || xcb_request_failed(pm_cookie, "Could not create pixmap") || + xcb_request_failed(slpm_cookie, "Could not create statusline pixmap") || xcb_request_failed(dock_cookie, "Could not set dock mode") || xcb_request_failed(class_cookie, "Could not set WM_CLASS") || xcb_request_failed(name_cookie, "Could not set WM_NAME") || xcb_request_failed(strut_cookie, "Could not set strut") || - xcb_request_failed(gc_cookie, "Could not create graphical context") || ((config.hide_on_modifier == M_DOCK) && xcb_request_failed(map_cookie, "Could not map window"))) { exit(EXIT_FAILURE); } - const char *tray_output = (config.tray_output ? config.tray_output : SLIST_FIRST(outputs)->name); - if (!tray_configured && strcasecmp(tray_output, "none") != 0) { - /* Configuration sanity check: ensure this i3bar instance handles the output on - * which the tray should appear (e.g. don’t initialize a tray if tray_output == - * VGA-1 but output == [HDMI-1]). - */ - i3_output *output; - SLIST_FOREACH(output, outputs, slist) { - if (strcasecmp(output->name, tray_output) == 0 || - (strcasecmp(tray_output, "primary") == 0 && output->primary)) { - init_tray(); - break; - } + /* Unless "tray_output none" was specified, we need to initialize the tray. */ + bool no_tray = false; + if (!(TAILQ_EMPTY(&(config.tray_outputs)))) { + no_tray = strcasecmp(TAILQ_FIRST(&(config.tray_outputs))->output, "none") == 0; + } + + /* + * There are three scenarios in which we need to initialize the tray: + * 1. A specific output was listed in tray_outputs which is also + * in the list of outputs managed by this bar. + * 2. No tray_output directive was specified. In this case, we + * use the first available output. + * 3. 'tray_output primary' was specified. In this case we use the + * primary output. + * + * Three scenarios in which we specifically don't want to + * initialize the tray are: + * 1. 'tray_output none' was specified. + * 2. A specific output was listed as a tray_output, but is not + * one of the outputs managed by this bar. For example, consider + * tray_outputs == [VGA-1], but outputs == [HDMI-1]. + * 3. 'tray_output primary' was specified and no output in the list + * is primary. + */ + if (!tray_configured && !no_tray) { + /* If no tray_output was specified, we go ahead and initialize the tray as + * we will be using the first available output. */ + if (TAILQ_EMPTY(&(config.tray_outputs))) { + init_tray(); } + + /* If one or more tray_output assignments were specified, we ensure that at least one of + * them is actually an output managed by this instance. */ + tray_output_t *tray_output; + TAILQ_FOREACH(tray_output, &(config.tray_outputs), tray_outputs) { + i3_output *output; + bool found = false; + SLIST_FOREACH(output, outputs, slist) { + if (strcasecmp(output->name, tray_output->output) == 0 || + (strcasecmp(tray_output->output, "primary") == 0 && output->primary)) { + found = true; + init_tray(); + break; + } + } + + if (found) + break; + } + tray_configured = true; } } else { @@ -1741,11 +1832,14 @@ void reconfig_windows(bool redraw_bars) { xcb_void_cookie_t strut_cookie = config_strut_partial(walk); DLOG("Destroying buffer for output %s\n", walk->name); - xcb_free_pixmap(xcb_connection, walk->buffer); + xcb_free_pixmap(xcb_connection, walk->buffer.id); + + DLOG("Destroying statusline buffer for output %s\n", walk->name); + xcb_free_pixmap(xcb_connection, walk->statusline_buffer.id); DLOG("Reconfiguring window for output %s to %d,%d\n", walk->name, values[0], values[1]); xcb_void_cookie_t cfg_cookie = xcb_configure_window_checked(xcb_connection, - walk->bar, + walk->bar.id, mask, values); @@ -1753,25 +1847,40 @@ void reconfig_windows(bool redraw_bars) { values[0] = (config.hide_on_modifier == M_DOCK ? 0 : 1); DLOG("Changing window attribute override_redirect for output %s to %d\n", walk->name, values[0]); xcb_void_cookie_t chg_cookie = xcb_change_window_attributes(xcb_connection, - walk->bar, + walk->bar.id, mask, values); DLOG("Recreating buffer for output %s\n", walk->name); xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection, - root_screen->root_depth, - walk->buffer, - walk->bar, + depth, + walk->buffer.id, + walk->bar.id, walk->rect.w, bar_height); + DLOG("Recreating statusline buffer for output %s\n", walk->name); + xcb_void_cookie_t slpm_cookie = xcb_create_pixmap_checked(xcb_connection, + depth, + walk->statusline_buffer.id, + walk->bar.id, + walk->rect.w, + bar_height); + + draw_util_surface_free(xcb_connection, &(walk->bar)); + draw_util_surface_free(xcb_connection, &(walk->buffer)); + draw_util_surface_free(xcb_connection, &(walk->statusline_buffer)); + draw_util_surface_init(xcb_connection, &(walk->bar), walk->bar.id, NULL, walk->rect.w, bar_height); + draw_util_surface_init(xcb_connection, &(walk->buffer), walk->buffer.id, NULL, walk->rect.w, bar_height); + draw_util_surface_init(xcb_connection, &(walk->statusline_buffer), walk->statusline_buffer.id, NULL, walk->rect.w, bar_height); + xcb_void_cookie_t map_cookie, umap_cookie; if (redraw_bars) { /* Unmap the window, and draw it again when in dock mode */ - umap_cookie = xcb_unmap_window_checked(xcb_connection, walk->bar); + umap_cookie = xcb_unmap_window_checked(xcb_connection, walk->bar.id); if (config.hide_on_modifier == M_DOCK) { cont_child(); - map_cookie = xcb_map_window_checked(xcb_connection, walk->bar); + map_cookie = xcb_map_window_checked(xcb_connection, walk->bar.id); } else { stop_child(); } @@ -1788,6 +1897,7 @@ void reconfig_windows(bool redraw_bars) { if (xcb_request_failed(cfg_cookie, "Could not reconfigure window") || xcb_request_failed(chg_cookie, "Could not change window") || xcb_request_failed(pm_cookie, "Could not create pixmap") || + xcb_request_failed(slpm_cookie, "Could not create statusline pixmap") || xcb_request_failed(strut_cookie, "Could not set strut") || (redraw_bars && (xcb_request_failed(umap_cookie, "Could not unmap window") || (config.hide_on_modifier == M_DOCK && xcb_request_failed(map_cookie, "Could not map window"))))) { @@ -1803,43 +1913,37 @@ void reconfig_windows(bool redraw_bars) { */ void draw_bars(bool unhide) { DLOG("Drawing bars...\n"); - int workspace_width = 0; - /* Is the currently-rendered statusline using short_text items? */ - bool rendered_statusline_is_short = false; - refresh_statusline(false); + uint32_t full_statusline_width = predict_statusline_length(false); + uint32_t short_statusline_width = predict_statusline_length(true); i3_output *outputs_walk; SLIST_FOREACH(outputs_walk, outputs, slist) { + int workspace_width = 0; + if (!outputs_walk->active) { DLOG("Output %s inactive, skipping...\n", outputs_walk->name); continue; } - if (outputs_walk->bar == XCB_NONE) { + if (outputs_walk->bar.id == XCB_NONE) { /* Oh shit, an active output without an own bar. Create it now! */ reconfig_windows(false); } + + bool use_focus_colors = output_has_focus(outputs_walk); + /* First things first: clear the backbuffer */ - uint32_t color = colors.bar_bg; - xcb_change_gc(xcb_connection, - outputs_walk->bargc, - XCB_GC_FOREGROUND, - &color); - xcb_rectangle_t rect = {0, 0, outputs_walk->rect.w, bar_height}; - xcb_poly_fill_rectangle(xcb_connection, - outputs_walk->buffer, - outputs_walk->bargc, - 1, - &rect); + draw_util_clear_surface(xcb_connection, &(outputs_walk->buffer), + (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg)); if (!config.disable_ws) { i3_ws *ws_walk; TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) { DLOG("Drawing button for WS %s at x = %d, len = %d\n", i3string_as_utf8(ws_walk->name), workspace_width, 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; + color_t fg_color = colors.inactive_ws_fg; + color_t bg_color = colors.inactive_ws_bg; + color_t border_color = colors.inactive_ws_border; if (ws_walk->visible) { if (!ws_walk->focused) { fg_color = colors.active_ws_fg; @@ -1858,40 +1962,25 @@ void draw_bars(bool unhide) { border_color = colors.urgent_ws_border; unhide = true; } - uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND; - uint32_t vals_border[] = {border_color, border_color}; - xcb_change_gc(xcb_connection, - outputs_walk->bargc, - mask, - vals_border); - xcb_rectangle_t rect_border = {workspace_width, - logical_px(1), - ws_walk->name_width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1), - font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1)}; - xcb_poly_fill_rectangle(xcb_connection, - outputs_walk->buffer, - outputs_walk->bargc, - 1, - &rect_border); - uint32_t vals[] = {bg_color, bg_color}; - xcb_change_gc(xcb_connection, - outputs_walk->bargc, - mask, - vals); - xcb_rectangle_t rect = {workspace_width + logical_px(1), - 2 * logical_px(1), - ws_walk->name_width + 2 * logical_px(ws_hoff_px), - font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1)}; - xcb_poly_fill_rectangle(xcb_connection, - outputs_walk->buffer, - outputs_walk->bargc, - 1, - &rect); - set_font_colors(outputs_walk->bargc, fg_color, bg_color); - draw_text(ws_walk->name, outputs_walk->buffer, outputs_walk->bargc, - workspace_width + logical_px(ws_hoff_px) + logical_px(1), - logical_px(ws_voff_px), - ws_walk->name_width); + + /* Draw the border of the button. */ + draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), border_color, + workspace_width, + logical_px(1), + ws_walk->name_width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1), + font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1)); + + /* Draw the inside of the button. */ + draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), bg_color, + workspace_width + logical_px(1), + 2 * logical_px(1), + ws_walk->name_width + 2 * logical_px(ws_hoff_px), + font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1)); + + draw_util_text(ws_walk->name, &(outputs_walk->buffer), fg_color, bg_color, + workspace_width + logical_px(ws_hoff_px) + logical_px(1), + logical_px(ws_voff_px), + ws_walk->name_width); workspace_width += 2 * logical_px(ws_hoff_px) + 2 * logical_px(1) + ws_walk->name_width; if (TAILQ_NEXT(ws_walk, tailq) != NULL) @@ -1902,47 +1991,25 @@ void draw_bars(bool unhide) { if (binding.name && !config.disable_binding_mode_indicator) { workspace_width += logical_px(ws_spacing_px); - uint32_t fg_color = colors.binding_mode_fg; - uint32_t bg_color = colors.binding_mode_bg; - uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND; + color_t fg_color = colors.binding_mode_fg; + color_t bg_color = colors.binding_mode_bg; - uint32_t vals_border[] = {colors.binding_mode_border, colors.binding_mode_border}; - xcb_change_gc(xcb_connection, - outputs_walk->bargc, - mask, - vals_border); - xcb_rectangle_t rect_border = {workspace_width, - logical_px(1), - binding.width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1), - font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1)}; - xcb_poly_fill_rectangle(xcb_connection, - outputs_walk->buffer, - outputs_walk->bargc, - 1, - &rect_border); + draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), colors.binding_mode_border, + workspace_width, + logical_px(1), + binding.width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1), + font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1)); - uint32_t vals[] = {bg_color, bg_color}; - xcb_change_gc(xcb_connection, - outputs_walk->bargc, - mask, - vals); - xcb_rectangle_t rect = {workspace_width + logical_px(1), - 2 * logical_px(1), - binding.width + 2 * logical_px(ws_hoff_px), - font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1)}; - xcb_poly_fill_rectangle(xcb_connection, - outputs_walk->buffer, - outputs_walk->bargc, - 1, - &rect); + draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), bg_color, + workspace_width + logical_px(1), + 2 * logical_px(1), + binding.width + 2 * logical_px(ws_hoff_px), + font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1)); - set_font_colors(outputs_walk->bargc, fg_color, bg_color); - draw_text(binding.name, - outputs_walk->buffer, - outputs_walk->bargc, - workspace_width + logical_px(ws_hoff_px) + logical_px(1), - logical_px(ws_voff_px), - binding.width); + draw_util_text(binding.name, &(outputs_walk->buffer), fg_color, bg_color, + workspace_width + logical_px(ws_hoff_px) + logical_px(1), + logical_px(ws_voff_px), + binding.width); unhide = true; workspace_width += 2 * logical_px(ws_hoff_px) + 2 * logical_px(1) + binding.width; @@ -1953,32 +2020,28 @@ void draw_bars(bool unhide) { int tray_width = get_tray_width(outputs_walk->trayclients); uint32_t max_statusline_width = outputs_walk->rect.w - workspace_width - tray_width - 2 * logical_px(sb_hoff_px); + uint32_t clip_left = 0; + uint32_t statusline_width = full_statusline_width; + bool use_short_text = false; - /* If the statusline is too long, try to use short texts. */ if (statusline_width > max_statusline_width) { - /* If the currently rendered statusline is long, render a short status line */ - refresh_statusline(true); - rendered_statusline_is_short = true; - } else if (rendered_statusline_is_short) { - /* If the currently rendered statusline is short, render a long status line */ - refresh_statusline(false); - rendered_statusline_is_short = false; + statusline_width = short_statusline_width; + use_short_text = true; + if (statusline_width > max_statusline_width) { + clip_left = statusline_width - max_statusline_width; + } } - /* Luckily we already prepared a seperate pixmap containing the rendered - * statusline, we just have to copy the relevant parts to the relevant - * position */ - int visible_statusline_width = MIN(statusline_width, max_statusline_width); - xcb_copy_area(xcb_connection, - statusline_pm, - outputs_walk->buffer, - outputs_walk->bargc, - (int16_t)(statusline_width - visible_statusline_width), 0, - (int16_t)(outputs_walk->rect.w - tray_width - logical_px(sb_hoff_px) - visible_statusline_width), 0, - (int16_t)visible_statusline_width, (int16_t)bar_height); - } + int16_t visible_statusline_width = MIN(statusline_width, max_statusline_width); + int x_dest = outputs_walk->rect.w - tray_width - logical_px(sb_hoff_px) - visible_statusline_width; - workspace_width = 0; + draw_statusline(outputs_walk, clip_left, use_focus_colors, use_short_text); + draw_util_copy_surface(xcb_connection, &outputs_walk->statusline_buffer, &outputs_walk->buffer, 0, 0, + x_dest, 0, visible_statusline_width, (int16_t)bar_height); + + outputs_walk->statusline_width = statusline_width; + outputs_walk->statusline_short_text = use_short_text; + } } /* Assure the bar is hidden/unhidden according to the specified hidden_state and mode */ @@ -2003,14 +2066,9 @@ void redraw_bars(void) { if (!outputs_walk->active) { continue; } - xcb_copy_area(xcb_connection, - outputs_walk->buffer, - outputs_walk->bar, - outputs_walk->bargc, - 0, 0, - 0, 0, - outputs_walk->rect.w, - outputs_walk->rect.h); + + draw_util_copy_surface(xcb_connection, &(outputs_walk->buffer), &(outputs_walk->bar), 0, 0, + 0, 0, outputs_walk->rect.w, outputs_walk->rect.h); xcb_flush(xcb_connection); } } diff --git a/include/atoms.xmacro b/include/atoms.xmacro index f856559c..730e569a 100644 --- a/include/atoms.xmacro +++ b/include/atoms.xmacro @@ -1,49 +1,2 @@ -xmacro(_NET_SUPPORTED) -xmacro(_NET_SUPPORTING_WM_CHECK) -xmacro(_NET_WM_NAME) -xmacro(_NET_WM_VISIBLE_NAME) -xmacro(_NET_WM_MOVERESIZE) -xmacro(_NET_WM_STATE_STICKY) -xmacro(_NET_WM_STATE_FULLSCREEN) -xmacro(_NET_WM_STATE_DEMANDS_ATTENTION) -xmacro(_NET_WM_STATE_MODAL) -xmacro(_NET_WM_STATE_HIDDEN) -xmacro(_NET_WM_STATE) -xmacro(_NET_WM_WINDOW_TYPE) -xmacro(_NET_WM_WINDOW_TYPE_NORMAL) -xmacro(_NET_WM_WINDOW_TYPE_DOCK) -xmacro(_NET_WM_WINDOW_TYPE_DIALOG) -xmacro(_NET_WM_WINDOW_TYPE_UTILITY) -xmacro(_NET_WM_WINDOW_TYPE_TOOLBAR) -xmacro(_NET_WM_WINDOW_TYPE_SPLASH) -xmacro(_NET_WM_WINDOW_TYPE_MENU) -xmacro(_NET_WM_WINDOW_TYPE_DROPDOWN_MENU) -xmacro(_NET_WM_WINDOW_TYPE_POPUP_MENU) -xmacro(_NET_WM_WINDOW_TYPE_TOOLTIP) -xmacro(_NET_WM_DESKTOP) -xmacro(_NET_WM_STRUT_PARTIAL) -xmacro(_NET_CLIENT_LIST) -xmacro(_NET_CLIENT_LIST_STACKING) -xmacro(_NET_CURRENT_DESKTOP) -xmacro(_NET_NUMBER_OF_DESKTOPS) -xmacro(_NET_DESKTOP_NAMES) -xmacro(_NET_DESKTOP_VIEWPORT) -xmacro(_NET_ACTIVE_WINDOW) -xmacro(_NET_CLOSE_WINDOW) -xmacro(_NET_STARTUP_ID) -xmacro(_NET_WORKAREA) -xmacro(WM_PROTOCOLS) -xmacro(WM_DELETE_WINDOW) -xmacro(UTF8_STRING) -xmacro(WM_STATE) -xmacro(WM_CLIENT_LEADER) -xmacro(WM_TAKE_FOCUS) -xmacro(WM_WINDOW_ROLE) -xmacro(I3_SOCKET_PATH) -xmacro(I3_CONFIG_PATH) -xmacro(I3_SYNC) -xmacro(I3_SHMLOG_PATH) -xmacro(I3_PID) -xmacro(_NET_REQUEST_FRAME_EXTENTS) -xmacro(_NET_FRAME_EXTENTS) -xmacro(_MOTIF_WM_HINTS) +#include "atoms_NET_SUPPORTED.xmacro" +#include "atoms_rest.xmacro" diff --git a/include/atoms_NET_SUPPORTED.xmacro b/include/atoms_NET_SUPPORTED.xmacro new file mode 100644 index 00000000..1358e0f1 --- /dev/null +++ b/include/atoms_NET_SUPPORTED.xmacro @@ -0,0 +1,33 @@ +xmacro(_NET_SUPPORTED) +xmacro(_NET_SUPPORTING_WM_CHECK) +xmacro(_NET_WM_NAME) +xmacro(_NET_WM_VISIBLE_NAME) +xmacro(_NET_WM_MOVERESIZE) +xmacro(_NET_WM_STATE_STICKY) +xmacro(_NET_WM_STATE_FULLSCREEN) +xmacro(_NET_WM_STATE_DEMANDS_ATTENTION) +xmacro(_NET_WM_STATE_MODAL) +xmacro(_NET_WM_STATE_HIDDEN) +xmacro(_NET_WM_STATE) +xmacro(_NET_WM_WINDOW_TYPE) +xmacro(_NET_WM_WINDOW_TYPE_NORMAL) +xmacro(_NET_WM_WINDOW_TYPE_DOCK) +xmacro(_NET_WM_WINDOW_TYPE_DIALOG) +xmacro(_NET_WM_WINDOW_TYPE_UTILITY) +xmacro(_NET_WM_WINDOW_TYPE_TOOLBAR) +xmacro(_NET_WM_WINDOW_TYPE_SPLASH) +xmacro(_NET_WM_WINDOW_TYPE_MENU) +xmacro(_NET_WM_WINDOW_TYPE_DROPDOWN_MENU) +xmacro(_NET_WM_WINDOW_TYPE_POPUP_MENU) +xmacro(_NET_WM_WINDOW_TYPE_TOOLTIP) +xmacro(_NET_WM_WINDOW_TYPE_NOTIFICATION) +xmacro(_NET_WM_DESKTOP) +xmacro(_NET_WM_STRUT_PARTIAL) +xmacro(_NET_CLIENT_LIST) +xmacro(_NET_CLIENT_LIST_STACKING) +xmacro(_NET_CURRENT_DESKTOP) +xmacro(_NET_NUMBER_OF_DESKTOPS) +xmacro(_NET_DESKTOP_NAMES) +xmacro(_NET_DESKTOP_VIEWPORT) +xmacro(_NET_ACTIVE_WINDOW) +xmacro(_NET_CLOSE_WINDOW) diff --git a/include/atoms_rest.xmacro b/include/atoms_rest.xmacro new file mode 100644 index 00000000..d461dc08 --- /dev/null +++ b/include/atoms_rest.xmacro @@ -0,0 +1,19 @@ +xmacro(_NET_WM_USER_TIME) +xmacro(_NET_STARTUP_ID) +xmacro(_NET_WORKAREA) +xmacro(WM_PROTOCOLS) +xmacro(WM_DELETE_WINDOW) +xmacro(UTF8_STRING) +xmacro(WM_STATE) +xmacro(WM_CLIENT_LEADER) +xmacro(WM_TAKE_FOCUS) +xmacro(WM_WINDOW_ROLE) +xmacro(I3_SOCKET_PATH) +xmacro(I3_CONFIG_PATH) +xmacro(I3_SYNC) +xmacro(I3_SHMLOG_PATH) +xmacro(I3_PID) +xmacro(I3_FLOATING_WINDOW) +xmacro(_NET_REQUEST_FRAME_EXTENTS) +xmacro(_NET_FRAME_EXTENTS) +xmacro(_MOTIF_WM_HINTS) diff --git a/include/bindings.h b/include/bindings.h index 88b4a6cc..9b14e988 100644 --- a/include/bindings.h +++ b/include/bindings.h @@ -15,7 +15,7 @@ extern pid_t command_error_nagbar_pid; * The name of the default mode. * */ -const char *DEFAULT_BINDING_MODE; +extern const char *DEFAULT_BINDING_MODE; /** * Adds a binding from config parameters given as strings and returns a @@ -25,7 +25,7 @@ const char *DEFAULT_BINDING_MODE; */ Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code, const char *release, const char *border, const char *whole_window, - const char *command, const char *mode); + const char *command, const char *mode, bool pango_markup); /** * Grab the bound keys (tell X to send us keypress events for those keycodes) @@ -33,6 +33,13 @@ Binding *configure_binding(const char *bindtype, const char *modifiers, const ch */ void grab_all_keys(xcb_connection_t *conn); +/** + * Release the button grabs on all managed windows and regrab them, + * reevaluating which buttons need to be grabbed. + * + */ +void regrab_all_buttons(xcb_connection_t *conn); + /** * Returns a pointer to the Binding that matches the given xcb event or NULL if * no such binding exists. @@ -95,3 +102,12 @@ CommandResult *run_binding(Binding *bind, Con *con); * */ bool load_keymap(void); + +/** + * Returns true if the current config has any binding to a scroll wheel button + * (4 or 5) which is a whole-window binding. + * We need this to figure out whether we should grab all buttons or just 1-3 + * when managing a window. See #2049. + * + */ +bool bindings_should_grab_scrollwheel_buttons(void); diff --git a/include/commands.h b/include/commands.h index e0bb2f92..9f83cb21 100644 --- a/include/commands.h +++ b/include/commands.h @@ -49,16 +49,16 @@ void cmd_move_con_to_workspace(I3_CMD, const char *which); void cmd_move_con_to_workspace_back_and_forth(I3_CMD); /** - * Implementation of 'move [window|container] [to] workspace '. + * Implementation of 'move [--no-auto-back-and-forth] [window|container] [to] workspace '. * */ -void cmd_move_con_to_workspace_name(I3_CMD, const char *name); +void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *no_auto_back_and_forth); /** - * Implementation of 'move [window|container] [to] workspace number '. + * Implementation of 'move [--no-auto-back-and-forth] [window|container] [to] workspace number '. * */ -void cmd_move_con_to_workspace_number(I3_CMD, const char *which); +void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *no_auto_back_and_forth); /** * Implementation of 'resize set [px] [px]'. @@ -76,7 +76,7 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px, * Implementation of 'border normal|pixel []', 'border none|1pixel|toggle'. * */ -void cmd_border(I3_CMD, const char *border_style_str, const char *border_width); +void cmd_border(I3_CMD, const char *border_style_str, long border_width); /** * Implementation of 'nop '. @@ -97,10 +97,10 @@ void cmd_append_layout(I3_CMD, const char *path); void cmd_workspace(I3_CMD, const char *which); /** - * Implementation of 'workspace number ' + * Implementation of 'workspace [--no-auto-back-and-forth] number ' * */ -void cmd_workspace_number(I3_CMD, const char *which); +void cmd_workspace_number(I3_CMD, const char *which, const char *no_auto_back_and_forth); /** * Implementation of 'workspace back_and_forth'. @@ -109,16 +109,16 @@ void cmd_workspace_number(I3_CMD, const char *which); void cmd_workspace_back_and_forth(I3_CMD); /** - * Implementation of 'workspace ' + * Implementation of 'workspace [--no-auto-back-and-forth] ' * */ -void cmd_workspace_name(I3_CMD, const char *name); +void cmd_workspace_name(I3_CMD, const char *name, const char *no_auto_back_and_forth); /** - * Implementation of 'mark [--toggle] ' + * Implementation of 'mark [--add|--replace] [--toggle] ' * */ -void cmd_mark(I3_CMD, const char *mark, const char *toggle); +void cmd_mark(I3_CMD, const char *mark, const char *mode, const char *toggle); /** * Implementation of 'unmark [mark]' @@ -157,7 +157,7 @@ void cmd_floating(I3_CMD, const char *floating_mode); void cmd_move_workspace_to_output(I3_CMD, const char *name); /** - * Implementation of 'split v|h|vertical|horizontal'. + * Implementation of 'split v|h|t|vertical|horizontal|toggle'. * */ void cmd_split(I3_CMD, const char *direction); diff --git a/include/con.h b/include/con.h index 2e853016..5b48120b 100644 --- a/include/con.h +++ b/include/con.h @@ -30,6 +30,12 @@ Con *con_new(Con *parent, i3Window *window); */ void con_focus(Con *con); +/** + * Closes the given container. + * + */ +void con_close(Con *con, kill_window_t kill_window); + /** * Returns true when this node is a leaf node (has no children) * @@ -152,26 +158,34 @@ Con *con_by_frame_id(xcb_window_t frame); */ Con *con_by_mark(const char *mark); +/** + * Returns true if and only if the given containers holds the mark. + * + */ +bool con_has_mark(Con *con, const char *mark); + /** * Toggles the mark on a container. * If the container already has this mark, the mark is removed. * Otherwise, the mark is assigned to the container. * */ -void con_mark_toggle(Con *con, const char *mark); +void con_mark_toggle(Con *con, const char *mark, mark_mode_t mode); /** * Assigns a mark to the container. * */ -void con_mark(Con *con, const char *mark); +void con_mark(Con *con, const char *mark, mark_mode_t mode); -/** - * If mark is NULL, this removes all existing marks. +/* + * Removes marks from containers. + * If con is NULL, all containers are considered. + * If name is NULL, this removes all existing marks. * Otherwise, it will only remove the given mark (if it is present). * */ -void con_unmark(const char *mark); +void con_unmark(Con *con, const char *name); /** * Returns the first container below 'con' which wants to swallow this window @@ -270,7 +284,7 @@ orientation_t con_orientation(Con *con); /** * Returns the container which will be focused next when the given container - * is not available anymore. Called in tree_close and con_move_to_workspace + * is not available anymore. Called in tree_close_internal and con_move_to_workspace * to properly restore focus. * */ @@ -419,3 +433,9 @@ char *con_get_tree_representation(Con *con); * */ void con_force_split_parents_redraw(Con *con); + +/** + * Returns the window title considering the current title format. + * + */ +i3String *con_parse_title_format(Con *con); diff --git a/include/config.h b/include/config.h index fd6fe6c0..acdd2c89 100644 --- a/include/config.h +++ b/include/config.h @@ -50,10 +50,11 @@ struct context { * */ struct Colortriple { - uint32_t border; - uint32_t background; - uint32_t text; - uint32_t indicator; + color_t border; + color_t background; + color_t text; + color_t indicator; + color_t child_border; }; /** @@ -77,6 +78,7 @@ struct Variable { */ struct Mode { char *name; + bool pango_markup; struct bindings_head *bindings; SLIST_ENTRY(Mode) modes; @@ -92,7 +94,7 @@ struct Config { i3Font font; char *ipc_socket_path; - const char *restart_state_path; + char *restart_state_path; layout_t default_layout; int container_stack_limit; @@ -201,7 +203,7 @@ struct Config { /* Color codes are stored here */ struct config_client { - uint32_t background; + color_t background; struct Colortriple focused; struct Colortriple focused_inactive; struct Colortriple unfocused; @@ -247,9 +249,10 @@ struct Barconfig { * simplicity (since we store just strings). */ char **outputs; - /** Output on which the tray should be shown. The special value of 'no' - * disables the tray (it’s enabled by default). */ - char *tray_output; + /* List of outputs on which the tray is allowed to be shown, in order. + * The special value "none" disables it (per default, it will be shown) and + * the special value "primary" enabled it on the primary output. */ + TAILQ_HEAD(tray_outputs_head, tray_output_t) tray_outputs; /* Padding around the tray icons. */ int tray_padding; @@ -322,6 +325,10 @@ struct Barconfig { char *statusline; char *separator; + char *focused_background; + char *focused_statusline; + char *focused_separator; + char *focused_workspace_border; char *focused_workspace_bg; char *focused_workspace_text; @@ -361,6 +368,12 @@ struct Barbinding { TAILQ_ENTRY(Barbinding) bindings; }; +struct tray_output_t { + char *output; + + TAILQ_ENTRY(tray_output_t) tray_outputs; +}; + /** * Finds the configuration file to use (either the one specified by * override_configpath), the user’s one or the system default) and calls diff --git a/include/config_directives.h b/include/config_directives.h index c0c70bb4..bcbea111 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -59,14 +59,14 @@ CFGFUN(no_focus); CFGFUN(ipc_socket, const char *path); CFGFUN(restart_state, const char *path); CFGFUN(popup_during_fullscreen, const char *value); -CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator); +CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *child_border); CFGFUN(color_single, const char *colorclass, const char *color); CFGFUN(floating_modifier, const char *modifiers); CFGFUN(new_window, const char *windowtype, const char *border, const long width); CFGFUN(workspace, const char *workspace, const char *output); CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command); -CFGFUN(enter_mode, const char *mode); +CFGFUN(enter_mode, const char *pango_markup, const char *mode); CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command); CFGFUN(bar_font, const char *font); diff --git a/include/config_parser.h b/include/config_parser.h index 28c28e48..9c23a11d 100644 --- a/include/config_parser.h +++ b/include/config_parser.h @@ -31,6 +31,11 @@ struct ConfigResultIR { struct ConfigResultIR *parse_config(const char *input, struct context *context); +/** + * launch nagbar to indicate errors in the configuration file. + */ +void start_config_error_nagbar(const char *configpath, bool has_errors); + /** * Parses the given file by first replacing the variables, then calling * parse_config and launching i3-nagbar if use_nagbar is true. diff --git a/include/data.h b/include/data.h index 58e4a00d..3a059e7b 100644 --- a/include/data.h +++ b/include/data.h @@ -46,6 +46,7 @@ typedef struct Con Con; typedef struct Match Match; typedef struct Assignment Assignment; typedef struct Window i3Window; +typedef struct mark_t mark_t; /****************************************************************************** * Helper types @@ -61,7 +62,7 @@ typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_PIXEL = 2 } border_style_t; -/** parameter to specify whether tree_close() and x_window_kill() should kill +/** parameter to specify whether tree_close_internal() and x_window_kill() should kill * only this specific window or the whole X11 client */ typedef enum { DONT_KILL_WINDOW = 0, KILL_WINDOW = 1, @@ -74,6 +75,9 @@ typedef enum { ADJ_NONE = 0, ADJ_UPPER_SCREEN_EDGE = (1 << 2), ADJ_LOWER_SCREEN_EDGE = (1 << 4) } adjacent_t; +typedef enum { MM_REPLACE, + MM_ADD } mark_mode_t; + /** * Container layouts. See Con::layout. */ @@ -175,7 +179,7 @@ struct deco_render_params { struct width_height con_rect; struct width_height con_window_rect; Rect con_deco_rect; - uint32_t background; + color_t background; layout_t parent_layout; bool con_is_leaf; }; @@ -372,8 +376,6 @@ struct Window { /** The name of the window. */ i3String *name; - /** The format with which the window's name should be displayed. */ - char *title_format; /** The WM_WINDOW_ROLE of this window (for example, the pidgin buddy window * sets "buddy list"). Useful to match specific windows in assignments or @@ -396,6 +398,9 @@ struct Window { /** The _NET_WM_WINDOW_TYPE for this window. */ xcb_atom_t window_type; + /** The _NET_WM_DESKTOP for this window. */ + uint32_t wm_desktop; + /** Whether the window says it is a dock window */ enum { W_NODOCK = 0, W_DOCK_TOP = 1, @@ -432,6 +437,9 @@ struct Window { * */ struct Match { + /* Set if a criterion was specified incorrectly. */ + char *error; + struct regex *title; struct regex *application; struct regex *class; @@ -523,6 +531,12 @@ typedef enum { CF_NONE = 0, CF_OUTPUT = 1, CF_GLOBAL = 2 } fullscreen_mode_t; +struct mark_t { + char *name; + + TAILQ_ENTRY(mark_t) marks; +}; + /** * A 'Con' represents everything from the X11 root window down to a single X11 window. * @@ -541,11 +555,10 @@ struct Con { * change. */ uint8_t ignore_unmap; - /* ids/pixmap/graphics context for the frame window */ + /* The surface used for the frame window. */ + surface_t frame; + surface_t frame_buffer; bool pixmap_recreated; - xcb_window_t frame; - xcb_pixmap_t pixmap; - xcb_gcontext_t pm_gc; enum { CT_ROOT = 0, @@ -562,21 +575,30 @@ struct Con { struct Con *parent; + /* The position and size for this con. These coordinates are absolute. Note + * that the rect of a container does not include the decoration. */ struct Rect rect; + /* The position and size of the actual client window. These coordinates are + * relative to the container's rect. */ struct Rect window_rect; + /* The position and size of the container's decoration. These coordinates + * are relative to the container's parent's rect. */ struct Rect deco_rect; /** the geometry this window requested when getting mapped */ struct Rect geometry; char *name; + /** The format with which the window's name should be displayed. */ + char *title_format; + /* a sticky-group is an identifier which bundles several containers to a * group. The contents are shared between all of them, that is they are * displayed on whichever of the containers is currently visible */ char *sticky_group; - /* user-definable mark to jump to this container later */ - char *mark; + /* user-definable marks to jump to this container later */ + TAILQ_HEAD(marks_head, mark_t) marks_head; /* cached to decide whether a redraw is needed */ bool mark_changed; diff --git a/include/ewmh.h b/include/ewmh.h index 7ed9b544..2a55ab9f 100644 --- a/include/ewmh.h +++ b/include/ewmh.h @@ -36,6 +36,13 @@ void ewmh_update_desktop_names(void); */ void ewmh_update_desktop_viewport(void); +/** + * Updates _NET_WM_DESKTOP for all windows. + * A request will only be made if the cached value differs from the calculated value. + * + */ +void ewmh_update_wm_desktop(void); + /** * Updates _NET_ACTIVE_WINDOW with the currently focused window. * @@ -96,3 +103,21 @@ void ewmh_setup_hints(void); * */ void ewmh_update_workarea(void); + +/** + * Returns the workspace container as enumerated by the EWMH desktop model. + * Returns NULL if no workspace could be found for the index. + * + * This is the reverse of ewmh_get_workspace_index. + * + */ +Con *ewmh_get_workspace_by_index(uint32_t idx); + +/** + * Returns the EWMH desktop index for the workspace the given container is on. + * Returns NET_WM_DESKTOP_NONE if the desktop index cannot be determined. + * + * This is the reverse of ewmh_get_workspace_by_index. + * + */ +uint32_t ewmh_get_workspace_index(Con *con); diff --git a/include/i3.h b/include/i3.h index 2a431486..254c4d23 100644 --- a/include/i3.h +++ b/include/i3.h @@ -35,7 +35,6 @@ extern struct rlimit original_rlimit_core; extern bool debug_build; /** The number of file descriptors passed via socket activation. */ extern int listen_fds; -extern xcb_connection_t *conn; extern int conn_screen; /** * The EWMH support window that is used to indicate that an EWMH-compliant @@ -61,7 +60,6 @@ extern TAILQ_HEAD(autostarts_always_head, Autostart) autostarts_always; extern TAILQ_HEAD(ws_assignments_head, Workspace_Assignment) ws_assignments; extern TAILQ_HEAD(assignments_head, Assignment) assignments; extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins; -extern xcb_screen_t *root_screen; /* Color depth, visual id and colormap to use when creating windows and * pixmaps. Will use 32 bit depth and an appropriate visual, if available, diff --git a/include/libi3.h b/include/libi3.h index 9e7ef133..4c722671 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -20,9 +20,19 @@ #if PANGO_SUPPORT #include #endif +#ifdef CAIRO_SUPPORT +#include +#endif #define DEFAULT_DIR_MODE (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) +/** + * XCB connection and root screen + * + */ +extern xcb_connection_t *conn; +extern xcb_screen_t *root_screen; + /** * Opaque data structure for storing strings. * @@ -243,7 +253,7 @@ bool i3string_is_markup(i3String *str); /** * Set whether the i3String should use Pango markup. */ -void i3string_set_markup(i3String *str, bool is_markup); +void i3string_set_markup(i3String *str, bool pango_markup); /** * Escape pango markup characters in the given string. @@ -382,11 +392,24 @@ char *convert_ucs2_to_utf8(xcb_char2b_t *text, size_t num_glyphs); */ xcb_char2b_t *convert_utf8_to_ucs2(char *input, size_t *real_strlen); +/* Represents a color split by color channel. */ +typedef struct color_t { + double red; + double green; + double blue; + double alpha; + + /* The colorpixel we use for direct XCB calls. */ + uint32_t colorpixel; +} color_t; + +#define COLOR_TRANSPARENT ((color_t){.red = 0.0, .green = 0.0, .blue = 0.0, .colorpixel = 0}) + /** * Defines the colors to be used for the forthcoming draw_text calls. * */ -void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background); +void set_font_colors(xcb_gcontext_t gc, color_t foreground, color_t background); /** * Returns true if and only if the current font is a pango font. @@ -402,8 +425,8 @@ bool font_is_pango(void); * 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); +void draw_text(i3String *text, xcb_drawable_t drawable, xcb_gcontext_t gc, + xcb_visualtype_t *visual, int x, int y, int max_width); /** * ASCII version of draw_text to print static strings. @@ -480,3 +503,106 @@ char *get_config_path(const char *override_configpath, bool use_system_paths); */ int mkdirp(const char *path, mode_t mode); #endif + +/** Helper structure for usage in format_placeholders(). */ +typedef struct placeholder_t { + /* The placeholder to be replaced, e.g., "%title". */ + char *name; + /* The value this placeholder should be replaced with. */ + char *value; +} placeholder_t; + +/** + * Replaces occurrences of the defined placeholders in the format string. + * + */ +char *format_placeholders(char *format, placeholder_t *placeholders, int num); + +#ifdef CAIRO_SUPPORT +/* We need to flush cairo surfaces twice to avoid an assertion bug. See #1989 + * and https://bugs.freedesktop.org/show_bug.cgi?id=92455. */ +#define CAIRO_SURFACE_FLUSH(surface) \ + do { \ + cairo_surface_flush(surface); \ + cairo_surface_flush(surface); \ + } while (0) +#endif + +/* A wrapper grouping an XCB drawable and both a graphics context + * and the corresponding cairo objects representing it. */ +typedef struct surface_t { + /* The drawable which is being represented. */ + xcb_drawable_t id; + + /* A classic XCB graphics context. */ + xcb_gcontext_t gc; + + xcb_visualtype_t *visual_type; + + int width; + int height; + +#ifdef CAIRO_SUPPORT + /* A cairo surface representing the drawable. */ + cairo_surface_t *surface; + + /* The cairo object representing the drawable. In general, + * this is what one should use for any drawing operation. */ + cairo_t *cr; +#endif +} surface_t; + +/** + * Initialize the surface to represent the given drawable. + * + */ +void draw_util_surface_init(xcb_connection_t *conn, surface_t *surface, xcb_drawable_t drawable, + xcb_visualtype_t *visual, int width, int height); + +/** + * Resize the surface to the given size. + * + */ +void draw_util_surface_set_size(surface_t *surface, int width, int height); + +/** + * Destroys the surface. + * + */ +void draw_util_surface_free(xcb_connection_t *conn, surface_t *surface); + +/** + * Parses the given color in hex format to an internal color representation. + * Note that the input must begin with a hash sign, e.g., "#3fbc59". + * + */ +color_t draw_util_hex_to_color(const char *color); + +/** + * Draw the given text using libi3. + * This function also marks the surface dirty which is needed if other means of + * drawing are used. This will be the case when using XCB to draw text. + * + */ +void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width); + +/** + * Draws a filled rectangle. + * This function is a convenience wrapper and takes care of flushing the + * surface as well as restoring the cairo state. + * + */ +void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t color, double x, double y, double w, double h); + +/** + * Clears a surface with the given color. + * + */ +void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t color); + +/** + * Copies a surface onto another surface. + * + */ +void draw_util_copy_surface(xcb_connection_t *conn, surface_t *src, surface_t *dest, double src_x, double src_y, + double dest_x, double dest_y, double width, double height); diff --git a/include/render.h b/include/render.h index 717459e9..d32f0310 100644 --- a/include/render.h +++ b/include/render.h @@ -10,6 +10,23 @@ */ #pragma once +/* This is used to keep a state to pass around when rendering a con in render_con(). */ +typedef struct render_params { + /* A copy of the coordinates of the container which is being rendered. */ + int x; + int y; + + /* The computed height for decorations. */ + int deco_height; + /* Container rect, subtract container border. This is the actually usable space + * inside this container for clients. */ + Rect rect; + /* The number of children of the container which is being rendered. */ + int children; + /* A precalculated list of sizes of each child. */ + int *sizes; +} render_params; + /** * "Renders" the given container (and its children), meaning that all rects are * updated correctly. Note that this function does not call any xcb_* diff --git a/include/tree.h b/include/tree.h index af3309e9..c64c173d 100644 --- a/include/tree.h +++ b/include/tree.h @@ -56,12 +56,6 @@ bool level_down(void); */ void tree_render(void); -/** - * Closes the current container using tree_close(). - * - */ -void tree_close_con(kill_window_t kill_window); - /** * Changes focus in the given way (next/previous) and given orientation * (horizontal/vertical). @@ -78,11 +72,11 @@ void tree_next(char way, orientation_t orientation); * recursively while deleting a containers children. * * The force_set_focus flag is specified in the case of killing a floating - * window: tree_close() will be invoked for the CT_FLOATINGCON (the parent + * window: tree_close_internal() will be invoked for the CT_FLOATINGCON (the parent * container) and focus should be set there. * */ -bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool force_set_focus); +bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool force_set_focus); /** * Loads tree from ~/.i3/_restart.json (used for in-place restarts). diff --git a/include/util.h b/include/util.h index 01f732ca..28655178 100644 --- a/include/util.h +++ b/include/util.h @@ -20,7 +20,7 @@ if (pointer == NULL) \ die(__VA_ARGS__); \ } -#define STARTS_WITH(string, needle) (strncasecmp(string, needle, strlen(needle)) == 0) +#define STARTS_WITH(string, needle) (strncasecmp((string), (needle), strlen((needle))) == 0) #define CIRCLEQ_NEXT_OR_NULL(head, elm, field) (CIRCLEQ_NEXT(elm, field) != CIRCLEQ_END(head) ? CIRCLEQ_NEXT(elm, field) : NULL) #define CIRCLEQ_PREV_OR_NULL(head, elm, field) (CIRCLEQ_PREV(elm, field) != CIRCLEQ_END(head) ? CIRCLEQ_PREV(elm, field) : NULL) #define FOR_TABLE(workspace) \ @@ -130,6 +130,13 @@ void *memmem(const void *l, size_t l_len, const void *s, size_t s_len); #endif +/** + * Escapes the given string if a pango font is currently used. + * If the string has to be escaped, the input string will be free'd. + * + */ +char *pango_escape_markup(char *input); + /** * Starts an i3-nagbar instance with the given parameters. Takes care of * handling SIGCHLD and killing i3-nagbar when i3 exits. diff --git a/include/window.h b/include/window.h index 395c9883..d0b97f1d 100644 --- a/include/window.h +++ b/include/window.h @@ -9,6 +9,12 @@ */ #pragma once +/** + * Frees an i3Window and all its members. + * + */ +void window_free(i3Window *win); + /** * Updates the WM_CLASS (consisting of the class and instance) for the * given window. @@ -81,10 +87,3 @@ void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *ur * */ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style); - -/** - * Returns the window title considering the current title format. - * If no format is set, this will simply return the window's name. - * - */ -i3String *window_parse_title_format(i3Window *win); diff --git a/include/workspace.h b/include/workspace.h index 1bee64e0..0ff5cd30 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -14,6 +14,14 @@ #include "tree.h" #include "randr.h" +/* We use NET_WM_DESKTOP_NONE for cases where we cannot determine the EWMH + * desktop index for a window. We cannot use a negative value like -1 since we + * need to use uint32_t as we actually need the full range of it. This is + * technically dangerous, but it's safe to assume that we will never have more + * than 4294967279 workspaces open at a time. */ +#define NET_WM_DESKTOP_NONE 0xFFFFFFF0 +#define NET_WM_DESKTOP_ALL 0xFFFFFFFF + /** * Returns a pointer to the workspace with the given number (starting at 0), * creating the workspace if necessary (by allocating the necessary amount of diff --git a/include/xcb.h b/include/xcb.h index 2c87a19c..86019c5d 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -140,6 +140,12 @@ void xcb_set_root_cursor(int cursor); */ uint16_t get_visual_depth(xcb_visualid_t visual_id); +/** + * Get visual type specified by visualid + * + */ +xcb_visualtype_t *get_visualtype_by_id(xcb_visualid_t visual_id); + /** * Get visualid with specified depth * @@ -160,3 +166,9 @@ void xcb_add_property_atom(xcb_connection_t *conn, xcb_window_t window, xcb_atom * */ void xcb_remove_property_atom(xcb_connection_t *conn, xcb_window_t window, xcb_atom_t property, xcb_atom_t atom); + +/** + * Grab the specified buttons on a window when managing it. + * + */ +void xcb_grab_buttons(xcb_connection_t *conn, xcb_window_t window, bool bind_scrollwheel); diff --git a/libi3/dpi.c b/libi3/dpi.c index a347b08f..897e6e40 100644 --- a/libi3/dpi.c +++ b/libi3/dpi.c @@ -8,8 +8,6 @@ #include "libi3.h" #include -extern xcb_screen_t *root_screen; - /* * Convert a logical amount of pixels (e.g. 2 pixels on a “standard” 96 DPI * screen) to a corresponding amount of physical pixels on a standard or retina diff --git a/libi3/draw_util.c b/libi3/draw_util.c new file mode 100644 index 00000000..dcd881c2 --- /dev/null +++ b/libi3/draw_util.c @@ -0,0 +1,252 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * © 2015 Ingo Bürk and contributors (see also: LICENSE) + * + * draw.c: Utility for drawing. + * + */ +#include +#include +#include +#include +#include +#ifdef CAIRO_SUPPORT +#include +#endif + +#include "libi3.h" + +/* The default visual_type to use if none is specified when creating the surface. Must be defined globally. */ +xcb_visualtype_t *visual_type; + +/* Forward declarations */ +static void draw_util_set_source_color(xcb_connection_t *conn, surface_t *surface, color_t color); + +#define RETURN_UNLESS_SURFACE_INITIALIZED(surface) \ + do { \ + if ((surface)->id == XCB_NONE) { \ + ELOG("Surface %p is not initialized, skipping drawing.\n", surface); \ + return; \ + } \ + } while (0) + +/* + * Initialize the surface to represent the given drawable. + * + */ +void draw_util_surface_init(xcb_connection_t *conn, surface_t *surface, xcb_drawable_t drawable, + xcb_visualtype_t *visual, int width, int height) { + surface->id = drawable; + surface->visual_type = ((visual == NULL) ? visual_type : visual); + surface->width = width; + surface->height = height; + + surface->gc = xcb_generate_id(conn); + xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(conn, surface->gc, surface->id, 0, NULL); + + xcb_generic_error_t *error = xcb_request_check(conn, gc_cookie); + if (error != NULL) { + ELOG("Could not create graphical context. Error code: %d. Please report this bug.\n", error->error_code); + } + +#ifdef CAIRO_SUPPORT + surface->surface = cairo_xcb_surface_create(conn, surface->id, surface->visual_type, width, height); + surface->cr = cairo_create(surface->surface); +#endif +} + +/* + * Destroys the surface. + * + */ +void draw_util_surface_free(xcb_connection_t *conn, surface_t *surface) { + xcb_free_gc(conn, surface->gc); +#ifdef CAIRO_SUPPORT + cairo_surface_destroy(surface->surface); + cairo_destroy(surface->cr); + + /* We need to explicitly set these to NULL to avoid assertion errors in + * cairo when calling this multiple times. This can happen, for example, + * when setting the border of a window to none and then closing it. */ + surface->surface = NULL; + surface->cr = NULL; +#endif +} + +/* + * Resize the surface to the given size. + * + */ +void draw_util_surface_set_size(surface_t *surface, int width, int height) { + surface->width = width; + surface->height = height; +#ifdef CAIRO_SUPPORT + cairo_xcb_surface_set_size(surface->surface, width, height); +#endif +} + +/* + * Parses the given color in hex format to an internal color representation. + * Note that the input must begin with a hash sign, e.g., "#3fbc59". + * + */ +color_t draw_util_hex_to_color(const char *color) { + char alpha[2]; + if (strlen(color) == strlen("#rrggbbaa")) { + alpha[0] = color[7]; + alpha[1] = color[8]; + } else { + alpha[0] = alpha[1] = 'F'; + } + + char groups[4][3] = { + {color[1], color[2], '\0'}, + {color[3], color[4], '\0'}, + {color[5], color[6], '\0'}, + {alpha[0], alpha[1], '\0'}}; + + return (color_t){ + .red = strtol(groups[0], NULL, 16) / 255.0, + .green = strtol(groups[1], NULL, 16) / 255.0, + .blue = strtol(groups[2], NULL, 16) / 255.0, + .alpha = strtol(groups[3], NULL, 16) / 255.0, + .colorpixel = get_colorpixel(color)}; +} + +/* + * Set the given color as the source color on the surface. + * + */ +static void draw_util_set_source_color(xcb_connection_t *conn, surface_t *surface, color_t color) { + RETURN_UNLESS_SURFACE_INITIALIZED(surface); + +#ifdef CAIRO_SUPPORT + cairo_set_source_rgba(surface->cr, color.red, color.green, color.blue, color.alpha); +#else + uint32_t colorpixel = color.colorpixel; + xcb_change_gc(conn, surface->gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, + (uint32_t[]){colorpixel, colorpixel}); +#endif +} + +/** + * Draw the given text using libi3. + * This function also marks the surface dirty which is needed if other means of + * drawing are used. This will be the case when using XCB to draw text. + * + */ +void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width) { + RETURN_UNLESS_SURFACE_INITIALIZED(surface); + +#ifdef CAIRO_SUPPORT + /* Flush any changes before we draw the text as this might use XCB directly. */ + CAIRO_SURFACE_FLUSH(surface->surface); +#endif + + set_font_colors(surface->gc, fg_color, bg_color); + draw_text(text, surface->id, surface->gc, surface->visual_type, x, y, max_width); + +#ifdef CAIRO_SUPPORT + /* Notify cairo that we (possibly) used another way to draw on the surface. */ + cairo_surface_mark_dirty(surface->surface); +#endif +} + +/** + * Draws a filled rectangle. + * This function is a convenience wrapper and takes care of flushing the + * surface as well as restoring the cairo state. + * + */ +void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t color, double x, double y, double w, double h) { + RETURN_UNLESS_SURFACE_INITIALIZED(surface); + +#ifdef CAIRO_SUPPORT + cairo_save(surface->cr); + + /* Using the SOURCE operator will copy both color and alpha information directly + * onto the surface rather than blending it. This is a bit more efficient and + * allows better color control for the user when using opacity. */ + cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE); + draw_util_set_source_color(conn, surface, color); + + cairo_rectangle(surface->cr, x, y, w, h); + cairo_fill(surface->cr); + + /* Make sure we flush the surface for any text drawing operations that could follow. + * Since we support drawing text via XCB, we need this. */ + CAIRO_SURFACE_FLUSH(surface->surface); + + cairo_restore(surface->cr); +#else + draw_util_set_source_color(conn, surface, color); + + xcb_rectangle_t rect = {x, y, w, h}; + xcb_poly_fill_rectangle(conn, surface->id, surface->gc, 1, &rect); +#endif +} + +/** + * Clears a surface with the given color. + * + */ +void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t color) { + RETURN_UNLESS_SURFACE_INITIALIZED(surface); + +#ifdef CAIRO_SUPPORT + cairo_save(surface->cr); + + /* Using the SOURCE operator will copy both color and alpha information directly + * onto the surface rather than blending it. This is a bit more efficient and + * allows better color control for the user when using opacity. */ + cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE); + draw_util_set_source_color(conn, surface, color); + + cairo_paint(surface->cr); + + /* Make sure we flush the surface for any text drawing operations that could follow. + * Since we support drawing text via XCB, we need this. */ + CAIRO_SURFACE_FLUSH(surface->surface); + + cairo_restore(surface->cr); +#else + draw_util_set_source_color(conn, surface, color); + + xcb_rectangle_t rect = {0, 0, surface->width, surface->height}; + xcb_poly_fill_rectangle(conn, surface->id, surface->gc, 1, &rect); +#endif +} + +/** + * Copies a surface onto another surface. + * + */ +void draw_util_copy_surface(xcb_connection_t *conn, surface_t *src, surface_t *dest, double src_x, double src_y, + double dest_x, double dest_y, double width, double height) { + RETURN_UNLESS_SURFACE_INITIALIZED(src); + RETURN_UNLESS_SURFACE_INITIALIZED(dest); + +#ifdef CAIRO_SUPPORT + cairo_save(dest->cr); + + /* Using the SOURCE operator will copy both color and alpha information directly + * onto the surface rather than blending it. This is a bit more efficient and + * allows better color control for the user when using opacity. */ + cairo_set_operator(dest->cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_surface(dest->cr, src->surface, dest_x - src_x, dest_y - src_y); + + cairo_rectangle(dest->cr, dest_x, dest_y, width, height); + cairo_fill(dest->cr); + + /* Make sure we flush the surface for any text drawing operations that could follow. + * Since we support drawing text via XCB, we need this. */ + CAIRO_SURFACE_FLUSH(src->surface); + CAIRO_SURFACE_FLUSH(dest->surface); + + cairo_restore(dest->cr); +#else + xcb_copy_area(conn, src->id, dest->id, dest->gc, (int16_t)src_x, (int16_t)src_y, + (int16_t)dest_x, (int16_t)dest_y, (uint16_t)width, (uint16_t)height); +#endif +} diff --git a/libi3/font.c b/libi3/font.c index b502b52c..5a2504fa 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -12,16 +12,13 @@ #include #include -#if PANGO_SUPPORT #include +#if PANGO_SUPPORT #include #endif #include "libi3.h" -extern xcb_connection_t *conn; -extern xcb_screen_t *root_screen; - static const i3Font *savedFont = NULL; #if PANGO_SUPPORT @@ -102,12 +99,12 @@ static bool load_pango_font(i3Font *font, const char *desc) { * */ static void draw_text_pango(const char *text, size_t text_len, - xcb_drawable_t drawable, int x, int y, - int max_width, bool is_markup) { + xcb_drawable_t drawable, xcb_visualtype_t *visual, int x, int y, + int max_width, bool pango_markup) { /* 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); + visual, x + max_width, y + savedFont->height); cairo_t *cr = cairo_create(surface); PangoLayout *layout = create_layout_with_dpi(cr); gint height; @@ -117,12 +114,13 @@ static void draw_text_pango(const char *text, size_t text_len, pango_layout_set_wrap(layout, PANGO_WRAP_CHAR); pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); - if (is_markup) + if (pango_markup) pango_layout_set_markup(layout, text, text_len); else pango_layout_set_text(layout, text, text_len); /* Do the drawing */ + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgb(cr, pango_font_red, pango_font_green, pango_font_blue); pango_cairo_update_layout(cr, layout); pango_layout_get_pixel_size(layout, NULL, &height); @@ -142,7 +140,7 @@ static void draw_text_pango(const char *text, size_t text_len, * Calculate the text width using Pango rendering. * */ -static int predict_text_width_pango(const char *text, size_t text_len, bool is_markup) { +static int predict_text_width_pango(const char *text, size_t text_len, bool pango_markup) { /* 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); @@ -153,7 +151,7 @@ static int predict_text_width_pango(const char *text, size_t text_len, bool is_m gint width; pango_layout_set_font_description(layout, savedFont->specific.pango_desc); - if (is_markup) + if (pango_markup) pango_layout_set_markup(layout, text, text_len); else pango_layout_set_text(layout, text, text_len); @@ -226,6 +224,7 @@ i3Font load_font(const char *pattern, const bool fallback) { info_cookie = xcb_query_font(conn, font.specific.xcb.id); /* Check if we managed to open 'fixed' */ + free(error); error = xcb_request_check(conn, font_cookie); /* Fall back to '-misc-*' if opening 'fixed' fails. */ @@ -236,12 +235,16 @@ i3Font load_font(const char *pattern, const bool fallback) { strlen(pattern), pattern); info_cookie = xcb_query_font(conn, font.specific.xcb.id); + free(error); if ((error = xcb_request_check(conn, font_cookie)) != NULL) errx(EXIT_FAILURE, "Could open neither requested font nor fallbacks " "(fixed or -misc-*): X11 error %d", error->error_code); } } + if (error != NULL) { + free(error); + } font.pattern = sstrdup(pattern); LOG("Using X font %s\n", pattern); @@ -312,7 +315,7 @@ void free_font(void) { * Defines the colors to be used for the forthcoming draw_text calls. * */ -void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background) { +void set_font_colors(xcb_gcontext_t gc, color_t foreground, color_t background) { assert(savedFont != NULL); switch (savedFont->type) { @@ -322,16 +325,16 @@ void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background 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}; + uint32_t values[] = {foreground.colorpixel, background.colorpixel, 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; + pango_font_red = foreground.red; + pango_font_green = foreground.green; + pango_font_blue = foreground.blue; break; #endif default: @@ -391,9 +394,12 @@ static void draw_text_xcb(const xcb_char2b_t *text, size_t text_len, xcb_drawabl * 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) { +void draw_text(i3String *text, xcb_drawable_t drawable, xcb_gcontext_t gc, + xcb_visualtype_t *visual, int x, int y, int max_width) { assert(savedFont != NULL); + if (visual == NULL) { + visual = root_visual_type; + } switch (savedFont->type) { case FONT_TYPE_NONE: @@ -407,7 +413,7 @@ void draw_text(i3String *text, xcb_drawable_t drawable, 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, i3string_is_markup(text)); + drawable, visual, x, y, max_width, i3string_is_markup(text)); return; #endif default: @@ -432,7 +438,7 @@ void draw_text_ascii(const char *text, xcb_drawable_t drawable, 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); + draw_text(str, drawable, gc, NULL, x, y, max_width); i3string_free(str); } else { /* X11 coordinates for fonts start at the baseline */ @@ -446,7 +452,7 @@ void draw_text_ascii(const char *text, xcb_drawable_t drawable, case FONT_TYPE_PANGO: /* Render the text using Pango */ draw_text_pango(text, strlen(text), - drawable, x, y, max_width, false); + drawable, root_visual_type, x, y, max_width, false); return; #endif default: diff --git a/libi3/format_placeholders.c b/libi3/format_placeholders.c new file mode 100644 index 00000000..c9cbbea4 --- /dev/null +++ b/libi3/format_placeholders.c @@ -0,0 +1,67 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009 Michael Stapelberg and contributors (see also: LICENSE) + * + */ +#include +#include +#include + +#include "libi3.h" + +#ifndef STARTS_WITH +#define STARTS_WITH(string, needle) (strncasecmp((string), (needle), strlen((needle))) == 0) +#endif + +/* + * Replaces occurrences of the defined placeholders in the format string. + * + */ +char *format_placeholders(char *format, placeholder_t *placeholders, int num) { + if (format == NULL) + return NULL; + + /* We have to first iterate over the string to see how much buffer space + * we need to allocate. */ + int buffer_len = strlen(format) + 1; + for (char *walk = format; *walk != '\0'; walk++) { + for (int i = 0; i < num; i++) { + if (!STARTS_WITH(walk, placeholders[i].name)) + continue; + + buffer_len = buffer_len - strlen(placeholders[i].name) + strlen(placeholders[i].value); + walk += strlen(placeholders[i].name) - 1; + break; + } + } + + /* Now we can parse the format string. */ + char buffer[buffer_len]; + char *outwalk = buffer; + for (char *walk = format; *walk != '\0'; walk++) { + if (*walk != '%') { + *(outwalk++) = *walk; + continue; + } + + bool matched = false; + for (int i = 0; i < num; i++) { + if (!STARTS_WITH(walk, placeholders[i].name)) { + continue; + } + + matched = true; + outwalk += sprintf(outwalk, "%s", placeholders[i].value); + walk += strlen(placeholders[i].name) - 1; + break; + } + + if (!matched) + *(outwalk++) = *walk; + } + + *outwalk = '\0'; + return sstrdup(buffer); +} diff --git a/libi3/get_colorpixel.c b/libi3/get_colorpixel.c index 44ad295d..a0a9e345 100644 --- a/libi3/get_colorpixel.c +++ b/libi3/get_colorpixel.c @@ -7,32 +7,77 @@ */ #include #include +#include +#include "queue.h" #include "libi3.h" +struct Colorpixel { + char *hex; + uint32_t pixel; + + SLIST_ENTRY(Colorpixel) + colorpixels; +}; + +SLIST_HEAD(colorpixel_head, Colorpixel) +colorpixels; + /* - * Returns the colorpixel to use for the given hex color (think of HTML). Only - * works for true-color (vast majority of cases) at the moment, avoiding a - * roundtrip to X11. + * Returns the colorpixel to use for the given hex color (think of HTML). * * The hex_color has to start with #, for example #FF00FF. * * NOTE that get_colorpixel() does _NOT_ check the given color code for validity. * This has to be done by the caller. * - * NOTE that this function may in the future rely on a global xcb_connection_t - * variable called 'conn' to be present. - * */ uint32_t get_colorpixel(const char *hex) { - char strgroups[3][3] = {{hex[1], hex[2], '\0'}, - {hex[3], hex[4], '\0'}, - {hex[5], hex[6], '\0'}}; + char strgroups[3][3] = { + {hex[1], hex[2], '\0'}, + {hex[3], hex[4], '\0'}, + {hex[5], hex[6], '\0'}}; uint8_t r = strtol(strgroups[0], NULL, 16); uint8_t g = strtol(strgroups[1], NULL, 16); uint8_t b = strtol(strgroups[2], NULL, 16); - /* We set the first 8 bits high to have 100% opacity in case of a 32 bit - * color depth visual. */ - return (0xFF << 24) | (r << 16 | g << 8 | b); + /* Shortcut: if our screen is true color, no need to do a roundtrip to X11 */ + if (root_screen == NULL || root_screen->root_depth == 24 || root_screen->root_depth == 32) { + return (0xFF << 24) | (r << 16 | g << 8 | b); + } + + /* Lookup this colorpixel in the cache */ + struct Colorpixel *colorpixel; + SLIST_FOREACH(colorpixel, &(colorpixels), colorpixels) { + if (strcmp(colorpixel->hex, hex) == 0) + return colorpixel->pixel; + } + +#define RGB_8_TO_16(i) (65535 * ((i)&0xFF) / 255) + int r16 = RGB_8_TO_16(r); + int g16 = RGB_8_TO_16(g); + int b16 = RGB_8_TO_16(b); + + xcb_alloc_color_reply_t *reply; + + reply = xcb_alloc_color_reply(conn, xcb_alloc_color(conn, root_screen->default_colormap, + r16, g16, b16), + NULL); + + if (!reply) { + LOG("Could not allocate color\n"); + exit(1); + } + + uint32_t pixel = reply->pixel; + free(reply); + + /* Store the result in the cache */ + struct Colorpixel *cache_pixel = scalloc(1, sizeof(struct Colorpixel)); + cache_pixel->hex = sstrdup(hex); + cache_pixel->pixel = pixel; + + SLIST_INSERT_HEAD(&(colorpixels), cache_pixel, colorpixels); + + return pixel; } diff --git a/libi3/get_mod_mask.c b/libi3/get_mod_mask.c index 3b6976ad..ac71e6b2 100644 --- a/libi3/get_mod_mask.c +++ b/libi3/get_mod_mask.c @@ -12,8 +12,6 @@ #include "libi3.h" -extern xcb_connection_t *conn; - /* * All-in-one function which returns the modifier mask (XCB_MOD_MASK_*) for the * given keysymbol, for example for XCB_NUM_LOCK (usually configured to mod2). diff --git a/libi3/root_atom_contents.c b/libi3/root_atom_contents.c index d91f1e15..8d26c307 100644 --- a/libi3/root_atom_contents.c +++ b/libi3/root_atom_contents.c @@ -31,14 +31,15 @@ char *root_atom_contents(const char *atomname, xcb_connection_t *provided_conn, int screen) { xcb_intern_atom_cookie_t atom_cookie; xcb_intern_atom_reply_t *atom_reply; - char *content; + char *content = NULL; size_t content_max_words = 256; xcb_connection_t *conn = provided_conn; if (provided_conn == NULL && ((conn = xcb_connect(NULL, &screen)) == NULL || - xcb_connection_has_error(conn))) + xcb_connection_has_error(conn))) { return NULL; + } atom_cookie = xcb_intern_atom(conn, 0, strlen(atomname), atomname); @@ -46,8 +47,9 @@ char *root_atom_contents(const char *atomname, xcb_connection_t *provided_conn, xcb_window_t root = root_screen->root; atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL); - if (atom_reply == NULL) - return NULL; + if (atom_reply == NULL) { + goto out_conn; + } xcb_get_property_cookie_t prop_cookie; xcb_get_property_reply_t *prop_reply; @@ -55,8 +57,7 @@ char *root_atom_contents(const char *atomname, xcb_connection_t *provided_conn, XCB_GET_PROPERTY_TYPE_ANY, 0, content_max_words); prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); if (prop_reply == NULL) { - free(atom_reply); - return NULL; + goto out_atom; } if (xcb_get_property_value_length(prop_reply) > 0 && prop_reply->bytes_after > 0) { /* We received an incomplete value. Ask again but with a properly @@ -68,14 +69,11 @@ char *root_atom_contents(const char *atomname, xcb_connection_t *provided_conn, XCB_GET_PROPERTY_TYPE_ANY, 0, content_max_words); prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); if (prop_reply == NULL) { - free(atom_reply); - return NULL; + goto out_atom; } } if (xcb_get_property_value_length(prop_reply) == 0) { - free(atom_reply); - free(prop_reply); - return NULL; + goto out; } if (prop_reply->type == XCB_ATOM_CARDINAL) { /* We treat a CARDINAL as a >= 32-bit unsigned int. The only CARDINAL @@ -85,9 +83,13 @@ char *root_atom_contents(const char *atomname, xcb_connection_t *provided_conn, sasprintf(&content, "%.*s", xcb_get_property_value_length(prop_reply), (char *)xcb_get_property_value(prop_reply)); } + +out: + free(prop_reply); +out_atom: + free(atom_reply); +out_conn: if (provided_conn == NULL) xcb_disconnect(conn); - free(atom_reply); - free(prop_reply); return content; } diff --git a/libi3/string.c b/libi3/string.c index 7741fde0..328b41c0 100644 --- a/libi3/string.c +++ b/libi3/string.c @@ -24,7 +24,7 @@ struct _i3String { xcb_char2b_t *ucs2; size_t num_glyphs; size_t num_bytes; - bool is_markup; + bool pango_markup; }; /* @@ -52,7 +52,7 @@ i3String *i3string_from_markup(const char *from_markup) { i3String *str = i3string_from_utf8(from_markup); /* Set the markup flag */ - str->is_markup = true; + str->pango_markup = true; return str; } @@ -86,7 +86,7 @@ i3String *i3string_from_markup_with_length(const char *from_markup, size_t num_b i3String *str = i3string_from_utf8_with_length(from_markup, num_bytes); /* set the markup flag */ - str->is_markup = true; + str->pango_markup = true; return str; } @@ -118,7 +118,7 @@ i3String *i3string_from_ucs2(const xcb_char2b_t *from_ucs2, size_t num_glyphs) { */ i3String *i3string_copy(i3String *str) { i3String *copy = i3string_from_utf8(i3string_as_utf8(str)); - copy->is_markup = str->is_markup; + copy->pango_markup = str->pango_markup; return copy; } @@ -178,14 +178,14 @@ size_t i3string_get_num_bytes(i3String *str) { * Whether the given i3String is in Pango markup. */ bool i3string_is_markup(i3String *str) { - return str->is_markup; + return str->pango_markup; } /* * Set whether the i3String should use Pango markup. */ -void i3string_set_markup(i3String *str, bool is_markup) { - str->is_markup = is_markup; +void i3string_set_markup(i3String *str, bool pango_markup) { + str->pango_markup = pango_markup; } /* diff --git a/man/asciidoc.conf b/man/asciidoc.conf index 6cbcebe8..11e76501 100644 --- a/man/asciidoc.conf +++ b/man/asciidoc.conf @@ -7,7 +7,7 @@ template::[header-declarations] {mantitle} {manvolnum} i3 -4.11 +4.12 i3 Manual diff --git a/man/i3-input.man b/man/i3-input.man index b67a1403..07a91783 100644 --- a/man/i3-input.man +++ b/man/i3-input.man @@ -26,7 +26,7 @@ Specify the path to the i3 IPC socket (it should not be necessary to use this option, i3-input will figure out the path on its own). -F :: -Every occurence of "%s" in the string is replaced by the user input, +Every occurrence of "%s" in the string is replaced by the user input, and the result is sent to i3 as a command. Default value is "%s". -l :: diff --git a/man/i3-msg.man b/man/i3-msg.man index 911fc995..e0c70c44 100644 --- a/man/i3-msg.man +++ b/man/i3-msg.man @@ -89,7 +89,7 @@ i3-msg -t get_tree === I3SOCK If no ipc-socket is specified on the commandline, this variable is used -to determine the path, at wich the unix domain socket is expected, on which +to determine the path, at which the unix domain socket is expected, on which to connect to i3. == SEE ALSO diff --git a/man/i3-nagbar.man b/man/i3-nagbar.man index 9e6619fa..77fdd80b 100644 --- a/man/i3-nagbar.man +++ b/man/i3-nagbar.man @@ -44,7 +44,7 @@ after modifying the configuration file. ------------------------------------------------ i3-nagbar -m 'You have an error in your i3 config file!' \ --b 'edit config' 'xterm $EDITOR ~/.i3/config' +-b 'edit config' 'i3-sensible-editor ~/.config/i3/config' ------------------------------------------------ == SEE ALSO diff --git a/man/i3-sensible-terminal.man b/man/i3-sensible-terminal.man index 6fa91ac8..b231f808 100644 --- a/man/i3-sensible-terminal.man +++ b/man/i3-sensible-terminal.man @@ -25,6 +25,7 @@ It tries to start one of the following (in that order): * x-terminal-emulator (only present on Debian and derivatives) * urxvt * rxvt +* termit * terminator * Eterm * aterm @@ -32,6 +33,11 @@ It tries to start one of the following (in that order): * gnome-terminal * roxterm * xfce4-terminal +* termite +* lxterminal +* mate-terminal +* terminology +* st Please don’t complain about the order: If the user has any preference, they will have $TERMINAL set or modified their i3 configuration file. diff --git a/man/i3.man b/man/i3.man index 203b42ee..16302e08 100644 --- a/man/i3.man +++ b/man/i3.man @@ -77,6 +77,12 @@ i3 keeps your layout in a tree data structure. Window:: An X11 window, like the Firefox browser window or a terminal emulator. +Floating Window:: +A window which "floats" on top of other windows. This style is used by i3 to +display X11 windows with type "dialog", such as the "Print" or "Open File" +dialog boxes in many GUI applications. Use of floating windows can be +fine-tuned with the for_window command (see HTML userguide). + Split container:: A split container contains multiple other split containers or windows. + diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index b3b5e338..ec7fbabf 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -86,15 +86,15 @@ state BORDER: border_style = 'normal', 'pixel' -> BORDER_WIDTH border_style = 'none', 'toggle' - -> call cmd_border($border_style, "0") + -> call cmd_border($border_style, 0) border_style = '1pixel' - -> call cmd_border($border_style, "1") + -> call cmd_border($border_style, 1) state BORDER_WIDTH: end - -> call cmd_border($border_style, "2") - border_width = word - -> call cmd_border($border_style, $border_width) + -> call cmd_border($border_style, 2) + border_width = number + -> call cmd_border($border_style, &border_width) # layout default|stacked|stacking|tabbed|splitv|splith # layout toggle [split|all] @@ -117,9 +117,11 @@ state APPEND_LAYOUT: # workspace next|prev|next_on_output|prev_on_output # workspace back_and_forth -# workspace -# workspace number +# workspace [--no-auto-back-and-forth] +# workspace [--no-auto-back-and-forth] number state WORKSPACE: + no_auto_back_and_forth = '--no-auto-back-and-forth' + -> direction = 'next_on_output', 'prev_on_output', 'next', 'prev' -> call cmd_workspace($direction) 'back_and_forth' @@ -127,11 +129,11 @@ state WORKSPACE: 'number' -> WORKSPACE_NUMBER workspace = string - -> call cmd_workspace_name($workspace) + -> call cmd_workspace_name($workspace, $no_auto_back_and_forth) state WORKSPACE_NUMBER: workspace = string - -> call cmd_workspace_number($workspace) + -> call cmd_workspace_number($workspace, $no_auto_back_and_forth) # focus left|right|up|down # focus output @@ -189,9 +191,9 @@ state STICKY: action = 'enable', 'disable', 'toggle' -> call cmd_sticky($action) -# split v|h|vertical|horizontal +# split v|h|t|vertical|horizontal|toggle state SPLIT: - direction = 'horizontal', 'vertical', 'v', 'h' + direction = 'horizontal', 'vertical', 'toggle', 'v', 'h', 't' -> call cmd_split($direction) # floating enable|disable|toggle @@ -199,12 +201,14 @@ state FLOATING: floating = 'enable', 'disable', 'toggle' -> call cmd_floating($floating) -# mark [--toggle] +# mark [--add|--replace] [--toggle] state MARK: + mode = '--add', '--replace' + -> toggle = '--toggle' -> mark = string - -> call cmd_mark($mark, $toggle) + -> call cmd_mark($mark, $mode, $toggle) # unmark [mark] state UNMARK: @@ -304,6 +308,8 @@ state MOVE: -> 'to' -> + no_auto_back_and_forth = '--no-auto-back-and-forth' + -> 'workspace' -> MOVE_WORKSPACE 'output' @@ -341,11 +347,11 @@ state MOVE_WORKSPACE: 'number' -> MOVE_WORKSPACE_NUMBER workspace = string - -> call cmd_move_con_to_workspace_name($workspace) + -> call cmd_move_con_to_workspace_name($workspace, $no_auto_back_and_forth) state MOVE_WORKSPACE_NUMBER: number = string - -> call cmd_move_con_to_workspace_number($number) + -> call cmd_move_con_to_workspace_number($number, $no_auto_back_and_forth) state MOVE_TO_OUTPUT: output = string diff --git a/parser-specs/config.spec b/parser-specs/config.spec index b9542c8c..ef3bc2e0 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -146,6 +146,8 @@ state ASSIGN: state ASSIGN_WORKSPACE: '→' -> + 'workspace' + -> workspace = string -> call cfg_assign($workspace) @@ -280,9 +282,15 @@ state COLOR_TEXT: state COLOR_INDICATOR: indicator = word - -> call cfg_color($colorclass, $border, $background, $text, $indicator) + -> COLOR_CHILD_BORDER end - -> call cfg_color($colorclass, $border, $background, $text, NULL) + -> call cfg_color($colorclass, $border, $background, $text, NULL, NULL) + +state COLOR_CHILD_BORDER: + child_border = word + -> call cfg_color($colorclass, $border, $background, $text, $indicator, $child_border) + end + -> call cfg_color($colorclass, $border, $background, $text, $indicator, NULL) # [--no-startup-id] command state EXEC: @@ -326,8 +334,10 @@ state BINDCOMMAND: ################################################################################ state MODENAME: + pango_markup = '--pango_markup' + -> modename = word - -> call cfg_enter_mode($modename); MODEBRACE + -> call cfg_enter_mode($pango_markup, $modename); MODEBRACE state MODEBRACE: end @@ -443,7 +453,7 @@ state BAR_ID: -> call cfg_bar_id($bar_id); BAR state BAR_MODIFIER: - modifier = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Control', 'Ctrl', 'Shift' + modifier = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Control', 'Ctrl', 'Shift', 'none', 'off' -> call cfg_bar_modifier($modifier); BAR state BAR_WHEEL_UP_CMD: @@ -518,7 +528,7 @@ state BAR_COLORS: end -> '#' -> BAR_COLORS_IGNORE_LINE 'set' -> BAR_COLORS_IGNORE_LINE - colorclass = 'background', 'statusline', 'separator' + colorclass = 'background', 'statusline', 'separator', 'focused_background', 'focused_statusline', 'focused_separator' -> BAR_COLORS_SINGLE colorclass = 'focused_workspace', 'active_workspace', 'inactive_workspace', 'urgent_workspace', 'binding_mode' -> BAR_COLORS_BORDER diff --git a/release.sh b/release.sh index 33b8dcd6..766f4d30 100755 --- a/release.sh +++ b/release.sh @@ -1,14 +1,14 @@ #!/bin/zsh # This script is used to prepare a new release of i3. -export RELEASE_VERSION="4.10.4" -export PREVIOUS_VERSION="4.10.3" -export RELEASE_BRANCH="master" +export RELEASE_VERSION="4.11" +export PREVIOUS_VERSION="4.10.4" +export RELEASE_BRANCH="next" if [ ! -e "../i3.github.io" ] then echo "../i3.github.io does not exist." - echo "Use git clone git://github.com/i3/i3.github.io" + echo "Use git clone https://github.com/i3/i3.github.io" exit 1 fi @@ -41,7 +41,7 @@ STARTDIR=$PWD TMPDIR=$(mktemp -d) cd $TMPDIR -if ! wget http://i3wm.org/downloads/i3-${PREVIOUS_VERSION}.tar.bz2; then +if ! wget https://i3wm.org/downloads/i3-${PREVIOUS_VERSION}.tar.bz2; then echo "Could not download i3-${PREVIOUS_VERSION}.tar.bz2 (required for comparing files)." exit 1 fi diff --git a/src/bindings.c b/src/bindings.c index a3ea7cf6..351a5862 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -27,7 +27,7 @@ const char *DEFAULT_BINDING_MODE = "default"; * the list of modes. * */ -static struct Mode *mode_from_name(const char *name) { +static struct Mode *mode_from_name(const char *name, bool pango_markup) { struct Mode *mode; /* Try to find the mode in the list of modes and return it */ @@ -39,6 +39,7 @@ static struct Mode *mode_from_name(const char *name) { /* If the mode was not found, create a new one */ mode = scalloc(1, sizeof(struct Mode)); mode->name = sstrdup(name); + mode->pango_markup = pango_markup; mode->bindings = scalloc(1, sizeof(struct bindings_head)); TAILQ_INIT(mode->bindings); SLIST_INSERT_HEAD(&modes, mode, modes); @@ -54,7 +55,7 @@ static struct Mode *mode_from_name(const char *name) { */ Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code, const char *release, const char *border, const char *whole_window, - const char *command, const char *modename) { + const char *command, const char *modename, bool pango_markup) { Binding *new_binding = scalloc(1, sizeof(Binding)); DLOG("bindtype %s, modifiers %s, input code %s, release %s\n", bindtype, modifiers, input_code, release); new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS); @@ -91,7 +92,7 @@ Binding *configure_binding(const char *bindtype, const char *modifiers, const ch if (group_bits_set > 1) ELOG("Keybinding has more than one Group specified, but your X server is always in precisely one group. The keybinding can never trigger.\n"); - struct Mode *mode = mode_from_name(modename); + struct Mode *mode = mode_from_name(modename, pango_markup); TAILQ_INSERT_TAIL(mode->bindings, new_binding, bindings); return new_binding; @@ -145,6 +146,27 @@ void grab_all_keys(xcb_connection_t *conn) { } } +/* + * Release the button grabs on all managed windows and regrab them, + * reevaluating which buttons need to be grabbed. + * + */ +void regrab_all_buttons(xcb_connection_t *conn) { + bool grab_scrollwheel = bindings_should_grab_scrollwheel_buttons(); + xcb_grab_server(conn); + + Con *con; + TAILQ_FOREACH(con, &all_cons, all_cons) { + if (con->window == NULL) + continue; + + xcb_ungrab_button(conn, XCB_BUTTON_INDEX_ANY, con->window->id, XCB_BUTTON_MASK_ANY); + xcb_grab_buttons(conn, con->window->id, grab_scrollwheel); + } + + xcb_ungrab_server(conn); +} + /* * Returns a pointer to the Binding with the specified modifiers and * keycode or NULL if no such binding exists. @@ -164,18 +186,28 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas } } + const uint32_t xkb_group_state = (state_filtered & 0xFFFF0000); + const uint32_t modifiers_state = (state_filtered & 0x0000FFFF); TAILQ_FOREACH(bind, bindings, bindings) { - bool state_matches; - if (bind->event_state_mask == 0) { + const uint32_t xkb_group_mask = (bind->event_state_mask & 0xFFFF0000); + /* modifiers_mask is a special case: a value of 0 does not mean “match all”, + * but rather “match exactly when no modifiers are present”. */ + const uint32_t modifiers_mask = (bind->event_state_mask & 0x0000FFFF); + const bool groups_match = ((xkb_group_state & xkb_group_mask) == xkb_group_mask); + bool mods_match; + if (modifiers_mask == 0) { /* Verify no modifiers are pressed. A bitwise AND would lead to * false positives, see issue #2002. */ - state_matches = (state_filtered == 0); + mods_match = (modifiers_state == 0); } else { - state_matches = ((state_filtered & bind->event_state_mask) == bind->event_state_mask); + mods_match = ((modifiers_state & modifiers_mask) == modifiers_mask); } + const bool state_matches = (groups_match && mods_match); - DLOG("binding with event_state_mask 0x%x, state_filtered 0x%x, match: %s\n", - bind->event_state_mask, state_filtered, (state_matches ? "yes" : "no")); + DLOG("binding groups_match = %s, mods_match = %s, state_matches = %s\n", + (groups_match ? "yes" : "no"), + (mods_match ? "yes" : "no"), + (state_matches ? "yes" : "no")); /* First compare the state_filtered (unless this is a * B_UPON_KEYRELEASE_IGNORE_MODS binding and this is a KeyRelease * event) */ @@ -327,6 +359,7 @@ void translate_keysyms(void) { return; } + bool has_errors = false; Binding *bind; TAILQ_FOREACH(bind, bindings, bindings) { if (bind->input_type == B_MOUSE) { @@ -397,6 +430,21 @@ void translate_keysyms(void) { sasprintf(&tmp, "%s %d", keycodes, bind->translated_to[n]); free(keycodes); keycodes = tmp; + + /* check for duplicate bindings */ + Binding *check; + TAILQ_FOREACH(check, bindings, bindings) { + if (check == bind) + continue; + if (check->symbol != NULL) + continue; + if (check->keycode != bind->translated_to[n] || + check->event_state_mask != bind->event_state_mask || + check->release != bind->release) + continue; + has_errors = true; + ELOG("Duplicate keybinding in config file:\n keysym = %s, keycode = %d, state_mask = 0x%x\n", bind->symbol, check->keycode, bind->event_state_mask); + } } DLOG("state=0x%x, cfg=\"%s\", sym=0x%x → keycodes%s (%d)\n", bind->event_state_mask, bind->symbol, keysym, keycodes, bind->number_keycodes); @@ -405,6 +453,10 @@ void translate_keysyms(void) { xkb_state_unref(dummy_state); xkb_state_unref(dummy_state_no_shift); + + if (has_errors) { + start_config_error_nagbar(current_configpath, true); + } } /* @@ -426,7 +478,8 @@ void switch_mode(const char *new_mode) { grab_all_keys(conn); char *event_msg; - sasprintf(&event_msg, "{\"change\":\"%s\"}", mode->name); + sasprintf(&event_msg, "{\"change\":\"%s\", \"pango_markup\":%s}", + mode->name, (mode->pango_markup ? "true" : "false")); ipc_send_event("mode", I3_IPC_EVENT_MODE, event_msg); FREE(event_msg); @@ -523,8 +576,6 @@ void check_for_duplicate_bindings(struct context *context) { /* Check if one is using keysym while the other is using bindsym. * If so, skip. */ - /* XXX: It should be checked at a later place (when translating the - * keysym to keycodes) if there are any duplicates */ if ((bind->symbol == NULL && current->symbol != NULL) || (bind->symbol != NULL && current->symbol == NULL)) continue; @@ -588,8 +639,8 @@ void binding_free(Binding *bind) { /* * Runs the given binding and handles parse errors. If con is passed, it will * execute the command binding with that container selected by criteria. - * Returns a CommandResult for running the binding's command. Caller should - * render tree if needs_tree_render is true. Free with command_result_free(). + * Returns a CommandResult for running the binding's command. Free with + * command_result_free(). * */ CommandResult *run_binding(Binding *bind, Con *con) { @@ -758,3 +809,33 @@ bool load_keymap(void) { return true; } + +/* + * Returns true if the current config has any binding to a scroll wheel button + * (4 or 5) which is a whole-window binding. + * We need this to figure out whether we should grab all buttons or just 1-3 + * when managing a window. See #2049. + * + */ +bool bindings_should_grab_scrollwheel_buttons(void) { + Binding *bind; + TAILQ_FOREACH(bind, bindings, bindings) { + /* We are only interested in whole window mouse bindings. */ + if (bind->input_type != B_MOUSE || !bind->whole_window) + continue; + + char *endptr; + long button = strtol(bind->symbol + (sizeof("button") - 1), &endptr, 10); + if (button == LONG_MAX || button == LONG_MIN || button < 0 || *endptr != '\0' || endptr == bind->symbol) { + ELOG("Could not parse button number, skipping this binding. Please report this bug in i3.\n"); + continue; + } + + /* If the binding is for either scrollwheel button, we need to grab everything. */ + if (button == 4 || button == 5) { + return true; + } + } + + return false; +} diff --git a/src/click.c b/src/click.c index 92a1a1fa..a670120f 100644 --- a/src/click.c +++ b/src/click.c @@ -198,11 +198,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod xcb_allow_events(conn, XCB_ALLOW_ASYNC_POINTER, event->time); xcb_flush(conn); - if (result->needs_tree_render) - tree_render(); - command_result_free(result); - return 0; } } @@ -356,13 +352,24 @@ int handle_button_press(xcb_button_press_event_t *event) { last_timestamp = event->time; - const uint32_t mod = config.floating_modifier; + const uint32_t mod = (config.floating_modifier & 0xFFFF); const bool mod_pressed = (mod != 0 && (event->state & mod) == mod); DLOG("floating_mod = %d, detail = %d\n", mod_pressed, event->detail); if ((con = con_by_window_id(event->event))) return route_click(con, event, mod_pressed, CLICK_INSIDE); if (!(con = con_by_frame_id(event->event))) { + /* Run bindings on the root window as well, see #2097. We only run it + * if --whole-window was set as that's the equivalent for a normal + * window. */ + if (event->event == root) { + Binding *bind = get_binding_from_xcb_event((xcb_generic_event_t *)event); + if (bind != NULL && bind->whole_window) { + CommandResult *result = run_binding(bind, NULL); + command_result_free(result); + } + } + /* If the root window is clicked, find the relevant output from the * click coordinates and focus the output's active workspace. */ if (event->event == root && event->response_type == XCB_BUTTON_PRESS) { diff --git a/src/commands.c b/src/commands.c index 78dc2159..c9580c28 100644 --- a/src/commands.c +++ b/src/commands.c @@ -12,6 +12,10 @@ #include #include +#ifdef I3_ASAN_ENABLED +#include +#endif + #include "all.h" #include "shmlog.h" @@ -42,6 +46,16 @@ } \ } while (0) +/** If an error occured during parsing of the criteria, we want to exit instead + * of relying on fallback behavior. See #2091. */ +#define HANDLE_INVALID_MATCH \ + do { \ + if (current_match->error != NULL) { \ + yerror("Invalid match: %s", current_match->error); \ + return; \ + } \ + } while (0) + /** When the command did not include match criteria (!), we use the currently * 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 @@ -49,7 +63,14 @@ */ #define HANDLE_EMPTY_MATCH \ do { \ + HANDLE_INVALID_MATCH; \ + \ if (match_is_empty(current_match)) { \ + while (!TAILQ_EMPTY(&owindows)) { \ + owindow *ow = TAILQ_FIRST(&owindows); \ + TAILQ_REMOVE(&owindows, ow, owindows); \ + free(ow); \ + } \ owindow *ow = smalloc(sizeof(owindow)); \ ow->con = focused; \ TAILQ_INIT(&owindows); \ @@ -121,102 +142,6 @@ static Con *maybe_auto_back_and_forth_workspace(Con *workspace) { return workspace; } -// This code is commented out because we might recycle it for popping up error -// messages on parser errors. -#if 0 -static pid_t migration_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 (!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"); - } - - migration_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 (migration_pid != -1) { - LOG("Sending SIGKILL (9) to i3-nagbar with PID %d\n", migration_pid); - kill(migration_pid, SIGKILL); - } -} -#endif - -void cmd_MIGRATION_start_nagbar(void) { - if (migration_pid != -1) { - fprintf(stderr, "i3-nagbar already running.\n"); - return; - } - fprintf(stderr, "Starting i3-nagbar, command parsing differs from expected output.\n"); - ELOG("Please report this on IRC or in the bugtracker. Make sure to include the full debug level logfile:\n"); - ELOG("i3-dump-log | gzip -9c > /tmp/i3.log.gz\n"); - ELOG("FYI: Your i3 version is " I3_VERSION "\n"); - migration_pid = fork(); - if (migration_pid == -1) { - warn("Could not fork()"); - return; - } - - /* child */ - if (migration_pid == 0) { - char *pageraction; - sasprintf(&pageraction, "i3-sensible-terminal -e i3-sensible-pager \"%s\"", errorfilename); - char *argv[] = { - NULL, /* will be replaced by the executable path */ - "-t", - "error", - "-m", - "You found a parsing error. Please, please, please, report it!", - "-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, migration_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 -} - -#endif - /******************************************************************************* * Criteria functions. ******************************************************************************/ @@ -282,26 +207,60 @@ void cmd_criteria_match_windows(I3_CMD) { next = TAILQ_NEXT(next, owindows); DLOG("checking if con %p / %s matches\n", current->con, current->con->name); + + /* We use this flag to prevent matching on window-less containers if + * only window-specific criteria were specified. */ + bool accept_match = false; + if (current_match->con_id != NULL) { + accept_match = true; + if (current_match->con_id == current->con) { - DLOG("matches container!\n"); - TAILQ_INSERT_TAIL(&owindows, current, owindows); + DLOG("con_id matched.\n"); } else { - DLOG("doesnt match\n"); - free(current); + DLOG("con_id does not match.\n"); + FREE(current); + continue; } - } else if (current_match->mark != NULL && current->con->mark != NULL && - regex_matches(current_match->mark, current->con->mark)) { - DLOG("match by mark\n"); + } + + if (current_match->mark != NULL && !TAILQ_EMPTY(&(current->con->marks_head))) { + accept_match = true; + bool matched_by_mark = false; + + mark_t *mark; + TAILQ_FOREACH(mark, &(current->con->marks_head), marks) { + if (!regex_matches(current_match->mark, mark->name)) + continue; + + DLOG("match by mark\n"); + matched_by_mark = true; + break; + } + + if (!matched_by_mark) { + DLOG("mark does not match.\n"); + FREE(current); + continue; + } + } + + if (current->con->window != NULL) { + if (match_matches_window(current_match, current->con->window)) { + DLOG("matches window!\n"); + accept_match = true; + } else { + DLOG("doesn't match\n"); + FREE(current); + continue; + } + } + + if (accept_match) { TAILQ_INSERT_TAIL(&owindows, current, owindows); } else { - if (current->con->window && match_matches_window(current_match, current->con->window)) { - DLOG("matches window!\n"); - TAILQ_INSERT_TAIL(&owindows, current, owindows); - } else { - DLOG("doesnt match\n"); - free(current); - } + FREE(current); + continue; } } @@ -397,16 +356,17 @@ void cmd_move_con_to_workspace_back_and_forth(I3_CMD) { } /* - * Implementation of 'move [window|container] [to] workspace '. + * Implementation of 'move [--no-auto-back-and-forth] [window|container] [to] workspace '. * */ -void cmd_move_con_to_workspace_name(I3_CMD, const char *name) { +void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *_no_auto_back_and_forth) { if (strncasecmp(name, "__", strlen("__")) == 0) { LOG("You cannot move containers to i3-internal workspaces (\"%s\").\n", name); ysuccess(false); return; } + const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL); owindow *current; /* We have nothing to move: @@ -426,7 +386,8 @@ void cmd_move_con_to_workspace_name(I3_CMD, const char *name) { /* get the workspace */ Con *ws = workspace_get(name, NULL); - ws = maybe_auto_back_and_forth_workspace(ws); + if (!no_auto_back_and_forth) + ws = maybe_auto_back_and_forth_workspace(ws); HANDLE_EMPTY_MATCH; @@ -441,10 +402,11 @@ void cmd_move_con_to_workspace_name(I3_CMD, const char *name) { } /* - * Implementation of 'move [window|container] [to] workspace number '. + * Implementation of 'move [--no-auto-back-and-forth] [window|container] [to] workspace number '. * */ -void cmd_move_con_to_workspace_number(I3_CMD, const char *which) { +void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_and_forth) { + const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL); owindow *current; /* We have nothing to move: @@ -477,7 +439,8 @@ void cmd_move_con_to_workspace_number(I3_CMD, const char *which) { workspace = workspace_get(which, NULL); } - workspace = maybe_auto_back_and_forth_workspace(workspace); + if (!no_auto_back_and_forth) + workspace = maybe_auto_back_and_forth_workspace(workspace); HANDLE_EMPTY_MATCH; @@ -609,7 +572,7 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way while (current->type != CT_WORKSPACE && current->type != CT_FLOATING_CON && - con_orientation(current->parent) != search_orientation) + (con_orientation(current->parent) != search_orientation || con_num_children(current->parent) == 1)) current = current->parent; /* get the default percentage */ @@ -753,8 +716,8 @@ void cmd_resize_set(I3_CMD, long cwidth, long cheight) { * Implementation of 'border normal|pixel []', 'border none|1pixel|toggle'. * */ -void cmd_border(I3_CMD, const char *border_style_str, const char *border_width) { - DLOG("border style should be changed to %s with border width %s\n", border_style_str, border_width); +void cmd_border(I3_CMD, const char *border_style_str, long border_width) { + DLOG("border style should be changed to %s with border width %ld\n", border_style_str, border_width); owindow *current; HANDLE_EMPTY_MATCH; @@ -762,39 +725,35 @@ void cmd_border(I3_CMD, const char *border_style_str, const char *border_width) TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); int border_style = current->con->border_style; - char *end; - int tmp_border_width = -1; - tmp_border_width = strtol(border_width, &end, 10); - if (end == border_width) { - /* no valid digits found */ - tmp_border_width = -1; - } + int con_border_width = border_width; + if (strcmp(border_style_str, "toggle") == 0) { border_style++; border_style %= 3; if (border_style == BS_NORMAL) - tmp_border_width = 2; + con_border_width = 2; else if (border_style == BS_NONE) - tmp_border_width = 0; + con_border_width = 0; else if (border_style == BS_PIXEL) - tmp_border_width = 1; + con_border_width = 1; } else { - if (strcmp(border_style_str, "normal") == 0) + if (strcmp(border_style_str, "normal") == 0) { border_style = BS_NORMAL; - else if (strcmp(border_style_str, "pixel") == 0) + } else if (strcmp(border_style_str, "pixel") == 0) { border_style = BS_PIXEL; - else if (strcmp(border_style_str, "1pixel") == 0) { + } else if (strcmp(border_style_str, "1pixel") == 0) { border_style = BS_PIXEL; - tmp_border_width = 1; - } else if (strcmp(border_style_str, "none") == 0) + con_border_width = 1; + } else if (strcmp(border_style_str, "none") == 0) { border_style = BS_NONE; - else { + } else { ELOG("BUG: called with border_style=%s\n", border_style_str); ysuccess(false); return; } } - con_set_border_style(current->con, border_style, tmp_border_width); + + con_set_border_style(current->con, border_style, logical_px(con_border_width)); } cmd_output->needs_tree_render = true; @@ -911,10 +870,11 @@ void cmd_workspace(I3_CMD, const char *which) { } /* - * Implementation of 'workspace number ' + * Implementation of 'workspace [--no-auto-back-and-forth] number ' * */ -void cmd_workspace_number(I3_CMD, const char *which) { +void cmd_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_and_forth) { + const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL); Con *output, *workspace = NULL; if (con_get_fullscreen_con(croot, CF_GLOBAL)) { @@ -942,7 +902,7 @@ void cmd_workspace_number(I3_CMD, const char *which) { cmd_output->needs_tree_render = true; return; } - if (maybe_back_and_forth(cmd_output, workspace->name)) + if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, workspace->name)) return; workspace_show(workspace); @@ -970,10 +930,12 @@ void cmd_workspace_back_and_forth(I3_CMD) { } /* - * Implementation of 'workspace ' + * Implementation of 'workspace [--no-auto-back-and-forth] ' * */ -void cmd_workspace_name(I3_CMD, const char *name) { +void cmd_workspace_name(I3_CMD, const char *name, const char *_no_auto_back_and_forth) { + const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL); + if (strncasecmp(name, "__", strlen("__")) == 0) { LOG("You cannot switch to the i3-internal workspaces (\"%s\").\n", name); ysuccess(false); @@ -987,7 +949,7 @@ void cmd_workspace_name(I3_CMD, const char *name) { } DLOG("should switch to workspace %s\n", name); - if (maybe_back_and_forth(cmd_output, name)) + if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, name)) return; workspace_show_by_name(name); @@ -997,10 +959,10 @@ void cmd_workspace_name(I3_CMD, const char *name) { } /* - * Implementation of 'mark [--toggle] ' + * Implementation of 'mark [--add|--replace] [--toggle] ' * */ -void cmd_mark(I3_CMD, const char *mark, const char *toggle) { +void cmd_mark(I3_CMD, const char *mark, const char *mode, const char *toggle) { HANDLE_EMPTY_MATCH; owindow *current = TAILQ_FIRST(&owindows); @@ -1016,10 +978,12 @@ void cmd_mark(I3_CMD, const char *mark, const char *toggle) { } DLOG("matching: %p / %s\n", current->con, current->con->name); + + mark_mode_t mark_mode = (mode == NULL || strcmp(mode, "--replace") == 0) ? MM_REPLACE : MM_ADD; if (toggle != NULL) { - con_mark_toggle(current->con, mark); + con_mark_toggle(current->con, mark, mark_mode); } else { - con_mark(current->con, mark); + con_mark(current->con, mark, mark_mode); } cmd_output->needs_tree_render = true; @@ -1032,7 +996,14 @@ void cmd_mark(I3_CMD, const char *mark, const char *toggle) { * */ void cmd_unmark(I3_CMD, const char *mark) { - con_unmark(mark); + if (match_is_empty(current_match)) { + con_unmark(NULL, mark); + } else { + owindow *current; + TAILQ_FOREACH(current, &owindows, owindows) { + con_unmark(current->con, mark); + } + } cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply @@ -1166,7 +1137,7 @@ void cmd_move_workspace_to_output(I3_CMD, const char *name) { } /* - * Implementation of 'split v|h|vertical|horizontal'. + * Implementation of 'split v|h|t|vertical|horizontal|toggle'. * */ void cmd_split(I3_CMD, const char *direction) { @@ -1180,6 +1151,24 @@ void cmd_split(I3_CMD, const char *direction) { continue; } + DLOG("matching: %p / %s\n", current->con, current->con->name); + if (direction[0] == 't') { + layout_t current_layout; + if (current->con->type == CT_WORKSPACE) { + current_layout = current->con->layout; + } else { + current_layout = current->con->parent->layout; + } + /* toggling split orientation */ + if (current_layout == L_SPLITH) { + tree_split(current->con, VERT); + } else { + tree_split(current->con, HORIZ); + } + } else { + tree_split(current->con, (direction[0] == 'v' ? VERT : HORIZ)); + } + DLOG("matching: %p / %s\n", current->con, current->con->name); tree_split(current->con, (direction[0] == 'v' ? VERT : HORIZ)); } @@ -1196,7 +1185,6 @@ void cmd_split(I3_CMD, const char *direction) { void cmd_kill(I3_CMD, const char *kill_mode_str) { if (kill_mode_str == NULL) kill_mode_str = "window"; - owindow *current; DLOG("kill_mode=%s\n", kill_mode_str); @@ -1211,14 +1199,11 @@ void cmd_kill(I3_CMD, const char *kill_mode_str) { return; } - /* check if the match is empty, not if the result is empty */ - if (match_is_empty(current_match)) - tree_close_con(kill_mode); - else { - TAILQ_FOREACH(current, &owindows, owindows) { - DLOG("matching: %p / %s\n", current->con, current->con->name); - tree_close(current->con, kill_mode, false, false); - } + HANDLE_EMPTY_MATCH; + + owindow *current; + TAILQ_FOREACH(current, &owindows, owindows) { + con_close(current->con, kill_mode); } cmd_output->needs_tree_render = true; @@ -1460,6 +1445,8 @@ void cmd_sticky(I3_CMD, const char *action) { * sure it gets pushed to the front now. */ output_push_sticky_windows(focused); + ewmh_update_wm_desktop(); + cmd_output->needs_tree_render = true; ysuccess(true); } @@ -1580,6 +1567,9 @@ void cmd_layout_toggle(I3_CMD, const char *toggle_mode) { */ void cmd_exit(I3_CMD) { LOG("Exiting due to user command.\n"); +#ifdef I3_ASAN_ENABLED + __lsan_do_leak_check(); +#endif ipc_shutdown(); unlink(config.ipc_socket_path); xcb_disconnect(conn); @@ -1738,29 +1728,43 @@ void cmd_move_window_to_position(I3_CMD, const char *method, long x, long y) { * */ void cmd_move_window_to_center(I3_CMD, const char *method) { - if (!con_is_floating(focused)) { - ELOG("Cannot change position. The window/container is not floating\n"); - yerror("Cannot change position. The window/container is not floating."); - return; - } + bool has_error = false; + HANDLE_EMPTY_MATCH; - if (strcmp(method, "absolute") == 0) { - DLOG("moving to absolute center\n"); - floating_center(focused->parent, croot->rect); + owindow *current; + TAILQ_FOREACH(current, &owindows, owindows) { + Con *floating_con = con_inside_floating(current->con); + if (floating_con == NULL) { + ELOG("con %p / %s is not floating, cannot move it to the center.\n", + current->con, current->con->name); - floating_maybe_reassign_ws(focused->parent); - cmd_output->needs_tree_render = true; - } + if (!has_error) { + yerror("Cannot change position of a window/container because it is not floating."); + has_error = true; + } - if (strcmp(method, "position") == 0) { - DLOG("moving to center\n"); - floating_center(focused->parent, con_get_workspace(focused)->rect); + continue; + } - cmd_output->needs_tree_render = true; + if (strcmp(method, "absolute") == 0) { + DLOG("moving to absolute center\n"); + floating_center(floating_con, croot->rect); + + floating_maybe_reassign_ws(floating_con); + cmd_output->needs_tree_render = true; + } + + if (strcmp(method, "position") == 0) { + DLOG("moving to center\n"); + floating_center(floating_con, con_get_workspace(floating_con)->rect); + + cmd_output->needs_tree_render = true; + } } // XXX: default reply for now, make this a better reply - ysuccess(true); + if (!has_error) + ysuccess(true); } /* @@ -1839,27 +1843,33 @@ void cmd_title_format(I3_CMD, const char *format) { owindow *current; TAILQ_FOREACH(current, &owindows, owindows) { - if (current->con->window == NULL) - continue; - DLOG("setting title_format for %p / %s\n", current->con, current->con->name); - FREE(current->con->window->title_format); + FREE(current->con->title_format); /* If we only display the title without anything else, we can skip the parsing step, * so we remove the title format altogether. */ if (strcasecmp(format, "%title") != 0) { - current->con->window->title_format = sstrdup(format); + current->con->title_format = sstrdup(format); - i3String *formatted_title = window_parse_title_format(current->con->window); - ewmh_update_visible_name(current->con->window->id, i3string_as_utf8(formatted_title)); - I3STRING_FREE(formatted_title); + if (current->con->window != NULL) { + i3String *formatted_title = con_parse_title_format(current->con); + ewmh_update_visible_name(current->con->window->id, i3string_as_utf8(formatted_title)); + I3STRING_FREE(formatted_title); + } } else { - /* We can remove _NET_WM_VISIBLE_NAME since we don't display a custom title. */ - ewmh_update_visible_name(current->con->window->id, NULL); + if (current->con->window != NULL) { + /* We can remove _NET_WM_VISIBLE_NAME since we don't display a custom title. */ + ewmh_update_visible_name(current->con->window->id, NULL); + } } - /* Make sure the window title is redrawn immediately. */ - current->con->window->name_x_changed = true; + if (current->con->window != NULL) { + /* Make sure the window title is redrawn immediately. */ + current->con->window->name_x_changed = true; + } else { + /* For windowless containers we also need to force the redrawing. */ + FREE(current->con->deco_render_params); + } } cmd_output->needs_tree_render = true; @@ -1902,12 +1912,16 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) { GREP_FIRST(check_dest, output_get_content(output), !strcasecmp(child->name, new_name)); - if (check_dest != NULL) { + /* If check_dest == workspace, the user might be changing the case of the + * workspace, or it might just be a no-op. */ + if (check_dest != NULL && check_dest != workspace) { yerror("New workspace \"%s\" already exists", new_name); return; } /* Change the name and try to parse it as a number. */ + /* old_name might refer to workspace->name, so copy it before free()ing */ + char *old_name_copy = sstrdup(old_name); FREE(workspace->name); workspace->name = sstrdup(new_name); @@ -1948,7 +1962,8 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) { ewmh_update_desktop_viewport(); ewmh_update_current_desktop(); - startup_sequence_rename_workspace(old_name, new_name); + startup_sequence_rename_workspace(old_name_copy, new_name); + free(old_name_copy); } /* diff --git a/src/con.c b/src/con.c index 4233a8a8..cd17f9e5 100644 --- a/src/con.c +++ b/src/con.c @@ -47,7 +47,7 @@ Con *con_new_skeleton(Con *parent, i3Window *window) { new->depth = window->depth; new->window->aspect_ratio = 0.0; } else { - new->depth = XCB_COPY_FROM_PARENT; + new->depth = root_depth; } DLOG("opening window\n"); @@ -55,6 +55,7 @@ Con *con_new_skeleton(Con *parent, i3Window *window) { TAILQ_INIT(&(new->nodes_head)); TAILQ_INIT(&(new->focus_head)); TAILQ_INIT(&(new->swallow_head)); + TAILQ_INIT(&(new->marks_head)); if (parent != NULL) con_attach(new, parent, false); @@ -219,6 +220,36 @@ void con_focus(Con *con) { } } +/* + * Closes the given container. + * + */ +void con_close(Con *con, kill_window_t kill_window) { + assert(con != NULL); + DLOG("Closing con = %p.\n", con); + + /* We never close output or root containers. */ + if (con->type == CT_OUTPUT || con->type == CT_ROOT) { + DLOG("con = %p is of type %d, not closing anything.\n", con, con->type); + return; + } + + if (con->type == CT_WORKSPACE) { + DLOG("con = %p is a workspace, closing all children instead.\n", con); + Con *child, *nextchild; + for (child = TAILQ_FIRST(&(con->focus_head)); child;) { + nextchild = TAILQ_NEXT(child, focused); + DLOG("killing child = %p.\n", child); + tree_close_internal(child, kill_window, false, false); + child = nextchild; + } + + return; + } + + tree_close_internal(con, kill_window, false, false); +} + /* * Returns true when this node is a leaf node (has no children) * @@ -513,7 +544,7 @@ Con *con_by_window_id(xcb_window_t window) { Con *con_by_frame_id(xcb_window_t frame) { Con *con; TAILQ_FOREACH(con, &all_cons, all_cons) - if (con->frame == frame) + if (con->frame.id == frame) return con; return NULL; } @@ -526,27 +557,41 @@ Con *con_by_frame_id(xcb_window_t frame) { Con *con_by_mark(const char *mark) { Con *con; TAILQ_FOREACH(con, &all_cons, all_cons) { - if (con->mark != NULL && strcmp(con->mark, mark) == 0) + if (con_has_mark(con, mark)) return con; } return NULL; } +/* + * Returns true if and only if the given containers holds the mark. + * + */ +bool con_has_mark(Con *con, const char *mark) { + mark_t *current; + TAILQ_FOREACH(current, &(con->marks_head), marks) { + if (strcmp(current->name, mark) == 0) + return true; + } + + return false; +} + /* * Toggles the mark on a container. * If the container already has this mark, the mark is removed. * Otherwise, the mark is assigned to the container. * */ -void con_mark_toggle(Con *con, const char *mark) { +void con_mark_toggle(Con *con, const char *mark, mark_mode_t mode) { assert(con != NULL); DLOG("Toggling mark \"%s\" on con = %p.\n", mark, con); - if (con->mark != NULL && strcmp(con->mark, mark) == 0) { - con_unmark(mark); + if (con_has_mark(con, mark)) { + con_unmark(con, mark); } else { - con_mark(con, mark); + con_mark(con, mark, mode); } } @@ -554,55 +599,77 @@ void con_mark_toggle(Con *con, const char *mark) { * Assigns a mark to the container. * */ -void con_mark(Con *con, const char *mark) { +void con_mark(Con *con, const char *mark, mark_mode_t mode) { assert(con != NULL); DLOG("Setting mark \"%s\" on con = %p.\n", mark, con); - FREE(con->mark); - con->mark = sstrdup(mark); - con->mark_changed = true; + con_unmark(NULL, mark); + if (mode == MM_REPLACE) { + DLOG("Removing all existing marks on con = %p.\n", con); - DLOG("Clearing the mark from all other windows.\n"); - Con *other; - TAILQ_FOREACH(other, &all_cons, all_cons) { - /* Skip the window we actually handled since we took care of it already. */ - if (con == other) - continue; - - if (other->mark != NULL && strcmp(other->mark, mark) == 0) { - FREE(other->mark); - other->mark_changed = true; + mark_t *current; + while (!TAILQ_EMPTY(&(con->marks_head))) { + current = TAILQ_FIRST(&(con->marks_head)); + con_unmark(con, current->name); } } + + mark_t *new = scalloc(1, sizeof(mark_t)); + new->name = sstrdup(mark); + TAILQ_INSERT_TAIL(&(con->marks_head), new, marks); + + con->mark_changed = true; } /* - * If mark is NULL, this removes all existing marks. + * Removes marks from containers. + * If con is NULL, all containers are considered. + * If name is NULL, this removes all existing marks. * Otherwise, it will only remove the given mark (if it is present). * */ -void con_unmark(const char *mark) { - Con *con; - if (mark == NULL) { +void con_unmark(Con *con, const char *name) { + Con *current; + if (name == NULL) { DLOG("Unmarking all containers.\n"); - TAILQ_FOREACH(con, &all_cons, all_cons) { - if (con->mark == NULL) + TAILQ_FOREACH(current, &all_cons, all_cons) { + if (con != NULL && current != con) continue; - FREE(con->mark); - con->mark_changed = true; + if (TAILQ_EMPTY(&(current->marks_head))) + continue; + + mark_t *mark; + while (!TAILQ_EMPTY(&(current->marks_head))) { + mark = TAILQ_FIRST(&(current->marks_head)); + FREE(mark->name); + TAILQ_REMOVE(&(current->marks_head), mark, marks); + FREE(mark); + } + + current->mark_changed = true; } } else { - DLOG("Removing mark \"%s\".\n", mark); - con = con_by_mark(mark); - if (con == NULL) { + DLOG("Removing mark \"%s\".\n", name); + current = (con == NULL) ? con_by_mark(name) : con; + if (current == NULL) { DLOG("No container found with this mark, so there is nothing to do.\n"); return; } - DLOG("Found mark on con = %p. Removing it now.\n", con); - FREE(con->mark); - con->mark_changed = true; + DLOG("Found mark on con = %p. Removing it now.\n", current); + current->mark_changed = true; + + mark_t *mark; + TAILQ_FOREACH(mark, &(current->marks_head), marks) { + if (strcmp(mark->name, name) != 0) + continue; + + FREE(mark->name); + TAILQ_REMOVE(&(current->marks_head), mark, marks); + FREE(mark); + break; + } } } @@ -1004,15 +1071,16 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi startup_sequence_delete(sequence); } - CALL(parent, on_remove_child); - /* 9. If the container was marked urgent, move the urgency hint. */ if (urgent) { workspace_update_urgent_flag(source_ws); con_set_urgency(con, true); } + CALL(parent, on_remove_child); + ipc_send_window_event("move", con); + ewmh_update_wm_desktop(); return true; } @@ -1126,7 +1194,7 @@ orientation_t con_orientation(Con *con) { /* * Returns the container which will be focused next when the given container - * is not available anymore. Called in tree_close and con_move_to_workspace + * is not available anymore. Called in tree_close_internal and con_move_to_workspace * to properly restore focus. * */ @@ -1642,7 +1710,7 @@ static void con_on_remove_child(Con *con) { if (TAILQ_EMPTY(&(con->focus_head)) && !workspace_is_visible(con)) { LOG("Closing old workspace (%p / %s), it is empty\n", con, con->name); yajl_gen gen = ipc_marshal_workspace_event("empty", con, NULL); - tree_close(con, DONT_KILL_WINDOW, false, false); + tree_close_internal(con, DONT_KILL_WINDOW, false, false); const unsigned char *payload; ylength length; @@ -1663,7 +1731,7 @@ static void con_on_remove_child(Con *con) { int children = con_num_children(con); if (children == 0) { DLOG("Container empty, closing\n"); - tree_close(con, DONT_KILL_WINDOW, false, false); + tree_close_internal(con, DONT_KILL_WINDOW, false, false); return; } } @@ -1925,6 +1993,7 @@ char *con_get_tree_representation(Con *con) { (TAILQ_FIRST(&(con->nodes_head)) == child ? "" : " "), child_txt); free(buf); buf = tmp_buf; + free(child_txt); } /* 3) close the brackets */ @@ -1934,3 +2003,47 @@ char *con_get_tree_representation(Con *con) { return complete_buf; } + +/* + * Returns the container's title considering the current title format. + * + */ +i3String *con_parse_title_format(Con *con) { + assert(con->title_format != NULL); + + i3Window *win = con->window; + + /* We need to ensure that we only escape the window title if pango + * is used by the current font. */ + const bool pango_markup = font_is_pango(); + + char *title; + char *class; + char *instance; + if (win == NULL) { + title = pango_escape_markup(con_get_tree_representation(con)); + class = sstrdup("i3-frame"); + instance = sstrdup("i3-frame"); + } else { + title = pango_escape_markup(sstrdup((win->name == NULL) ? "" : i3string_as_utf8(win->name))); + class = pango_escape_markup(sstrdup((win->class_class == NULL) ? "" : win->class_class)); + instance = pango_escape_markup(sstrdup((win->class_instance == NULL) ? "" : win->class_instance)); + } + + placeholder_t placeholders[] = { + {.name = "%title", .value = title}, + {.name = "%class", .value = class}, + {.name = "%instance", .value = instance}}; + const size_t num = sizeof(placeholders) / sizeof(placeholder_t); + + char *formatted_str = format_placeholders(con->title_format, &placeholders[0], num); + i3String *formatted = i3string_from_utf8(formatted_str); + i3string_set_markup(formatted, pango_markup); + FREE(formatted_str); + + for (size_t i = 0; i < num; i++) { + FREE(placeholders[i].value); + } + + return formatted; +} diff --git a/src/config.c b/src/config.c index d8db85e6..9028a881 100644 --- a/src/config.c +++ b/src/config.c @@ -71,24 +71,29 @@ bool parse_configuration(const char *override_configpath, bool use_nagbar) { */ void load_configuration(xcb_connection_t *conn, const char *override_configpath, bool reload) { if (reload) { + /* If we are currently in a binding mode, we first revert to the + * default since we have no guarantee that the current mode will even + * still exist after parsing the config again. See #2228. */ + switch_mode("default"); + /* First ungrab the keys */ ungrab_all_keys(conn); struct Mode *mode; - Binding *bind; while (!SLIST_EMPTY(&modes)) { mode = SLIST_FIRST(&modes); FREE(mode->name); /* Clear the old binding list */ - bindings = mode->bindings; - while (!TAILQ_EMPTY(bindings)) { - bind = TAILQ_FIRST(bindings); - TAILQ_REMOVE(bindings, bind, bindings); + while (!TAILQ_EMPTY(mode->bindings)) { + Binding *bind = TAILQ_FIRST(mode->bindings); + TAILQ_REMOVE(mode->bindings, bind, bindings); binding_free(bind); } - FREE(bindings); + FREE(mode->bindings); + SLIST_REMOVE(&modes, mode, Mode, modes); + FREE(mode); } struct Assignment *assign; @@ -110,14 +115,32 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, FREE(barconfig->id); for (int c = 0; c < barconfig->num_outputs; c++) free(barconfig->outputs[c]); + + while (!TAILQ_EMPTY(&(barconfig->bar_bindings))) { + struct Barbinding *binding = TAILQ_FIRST(&(barconfig->bar_bindings)); + FREE(binding->command); + TAILQ_REMOVE(&(barconfig->bar_bindings), binding, bindings); + FREE(binding); + } + + while (!TAILQ_EMPTY(&(barconfig->tray_outputs))) { + struct tray_output_t *tray_output = TAILQ_FIRST(&(barconfig->tray_outputs)); + FREE(tray_output->output); + TAILQ_REMOVE(&(barconfig->tray_outputs), tray_output, tray_outputs); + FREE(tray_output); + } + FREE(barconfig->outputs); - FREE(barconfig->tray_output); FREE(barconfig->socket_path); FREE(barconfig->status_command); FREE(barconfig->i3bar_command); FREE(barconfig->font); FREE(barconfig->colors.background); FREE(barconfig->colors.statusline); + FREE(barconfig->colors.separator); + FREE(barconfig->colors.focused_background); + FREE(barconfig->colors.focused_statusline); + FREE(barconfig->colors.focused_separator); FREE(barconfig->colors.focused_workspace_border); FREE(barconfig->colors.focused_workspace_bg); FREE(barconfig->colors.focused_workspace_text); @@ -151,6 +174,10 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, /* Get rid of the current font */ free_font(); + + free(config.ipc_socket_path); + free(config.restart_state_path); + free(config.fake_outputs); } SLIST_INIT(&modes); @@ -173,13 +200,14 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, /* Initialize default colors */ #define INIT_COLOR(x, cborder, cbackground, ctext, cindicator) \ do { \ - x.border = get_colorpixel(cborder); \ - x.background = get_colorpixel(cbackground); \ - x.text = get_colorpixel(ctext); \ - x.indicator = get_colorpixel(cindicator); \ + x.border = draw_util_hex_to_color(cborder); \ + x.background = draw_util_hex_to_color(cbackground); \ + x.text = draw_util_hex_to_color(ctext); \ + x.indicator = draw_util_hex_to_color(cindicator); \ + x.child_border = draw_util_hex_to_color(cbackground); \ } while (0) - config.client.background = get_colorpixel("#000000"); + config.client.background = draw_util_hex_to_color("#000000"); INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff", "#2e9ef4"); INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff", "#484e50"); INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888", "#292d2e"); @@ -211,6 +239,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, if (reload) { translate_keysyms(); grab_all_keys(conn); + regrab_all_buttons(conn); } if (config.font.type == FONT_TYPE_NONE) { diff --git a/src/config_directives.c b/src/config_directives.c index fdfc1c7d..ec99321a 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -109,7 +109,7 @@ CFGFUN(font, const char *font) { } CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command) { - configure_binding(bindtype, modifiers, key, release, border, whole_window, command, DEFAULT_BINDING_MODE); + configure_binding(bindtype, modifiers, key, release, border, whole_window, command, DEFAULT_BINDING_MODE, false); } /******************************************************************************* @@ -117,12 +117,13 @@ CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, co ******************************************************************************/ static char *current_mode; +static bool current_mode_pango_markup; CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command) { - configure_binding(bindtype, modifiers, key, release, border, whole_window, command, current_mode); + configure_binding(bindtype, modifiers, key, release, border, whole_window, command, current_mode, current_mode_pango_markup); } -CFGFUN(enter_mode, const char *modename) { +CFGFUN(enter_mode, const char *pango_markup, const char *modename) { if (strcasecmp(modename, DEFAULT_BINDING_MODE) == 0) { ELOG("You cannot use the name %s for your mode\n", DEFAULT_BINDING_MODE); exit(1); @@ -130,6 +131,7 @@ CFGFUN(enter_mode, const char *modename) { DLOG("\t now in mode %s\n", modename); FREE(current_mode); current_mode = sstrdup(modename); + current_mode_pango_markup = (pango_markup != NULL); } CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *command) { @@ -259,6 +261,7 @@ CFGFUN(workspace_back_and_forth, const char *value) { } CFGFUN(fake_outputs, const char *outputs) { + free(config.fake_outputs); config.fake_outputs = sstrdup(outputs); } @@ -311,10 +314,12 @@ CFGFUN(workspace, const char *workspace, const char *output) { } CFGFUN(ipc_socket, const char *path) { + free(config.ipc_socket_path); config.ipc_socket_path = sstrdup(path); } CFGFUN(restart_state, const char *path) { + free(config.restart_state_path); config.restart_state_path = sstrdup(path); } @@ -330,20 +335,25 @@ CFGFUN(popup_during_fullscreen, const char *value) { CFGFUN(color_single, const char *colorclass, const char *color) { /* used for client.background only currently */ - config.client.background = get_colorpixel(color); + config.client.background = draw_util_hex_to_color(color); } -CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator) { -#define APPLY_COLORS(classname) \ - do { \ - if (strcmp(colorclass, "client." #classname) == 0) { \ - config.client.classname.border = get_colorpixel(border); \ - config.client.classname.background = get_colorpixel(background); \ - config.client.classname.text = get_colorpixel(text); \ - if (indicator != NULL) { \ - config.client.classname.indicator = get_colorpixel(indicator); \ - } \ - } \ +CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *child_border) { +#define APPLY_COLORS(classname) \ + do { \ + if (strcmp(colorclass, "client." #classname) == 0) { \ + config.client.classname.border = draw_util_hex_to_color(border); \ + config.client.classname.background = draw_util_hex_to_color(background); \ + config.client.classname.text = draw_util_hex_to_color(text); \ + if (indicator != NULL) { \ + config.client.classname.indicator = draw_util_hex_to_color(indicator); \ + } \ + if (child_border != NULL) { \ + config.client.classname.child_border = draw_util_hex_to_color(child_border); \ + } else { \ + config.client.classname.child_border = config.client.classname.background; \ + } \ + } \ } while (0) APPLY_COLORS(focused_inactive); @@ -385,57 +395,60 @@ CFGFUN(no_focus) { * Bar configuration (i3bar) ******************************************************************************/ -static Barconfig current_bar; +static Barconfig *current_bar; CFGFUN(bar_font, const char *font) { - FREE(current_bar.font); - current_bar.font = sstrdup(font); + FREE(current_bar->font); + current_bar->font = sstrdup(font); } CFGFUN(bar_separator_symbol, const char *separator) { - FREE(current_bar.separator_symbol); - current_bar.separator_symbol = sstrdup(separator); + FREE(current_bar->separator_symbol); + current_bar->separator_symbol = sstrdup(separator); } CFGFUN(bar_mode, const char *mode) { - current_bar.mode = (strcmp(mode, "dock") == 0 ? M_DOCK : (strcmp(mode, "hide") == 0 ? M_HIDE : M_INVISIBLE)); + current_bar->mode = (strcmp(mode, "dock") == 0 ? M_DOCK : (strcmp(mode, "hide") == 0 ? M_HIDE : M_INVISIBLE)); } CFGFUN(bar_hidden_state, const char *hidden_state) { - current_bar.hidden_state = (strcmp(hidden_state, "hide") == 0 ? S_HIDE : S_SHOW); + current_bar->hidden_state = (strcmp(hidden_state, "hide") == 0 ? S_HIDE : S_SHOW); } CFGFUN(bar_id, const char *bar_id) { - current_bar.id = sstrdup(bar_id); + current_bar->id = sstrdup(bar_id); } CFGFUN(bar_output, const char *output) { - int new_outputs = current_bar.num_outputs + 1; - current_bar.outputs = srealloc(current_bar.outputs, sizeof(char *) * new_outputs); - current_bar.outputs[current_bar.num_outputs] = sstrdup(output); - current_bar.num_outputs = new_outputs; + int new_outputs = current_bar->num_outputs + 1; + current_bar->outputs = srealloc(current_bar->outputs, sizeof(char *) * new_outputs); + current_bar->outputs[current_bar->num_outputs] = sstrdup(output); + current_bar->num_outputs = new_outputs; } CFGFUN(bar_verbose, const char *verbose) { - current_bar.verbose = eval_boolstr(verbose); + current_bar->verbose = eval_boolstr(verbose); } CFGFUN(bar_modifier, const char *modifier) { if (strcmp(modifier, "Mod1") == 0) - current_bar.modifier = M_MOD1; + current_bar->modifier = M_MOD1; else if (strcmp(modifier, "Mod2") == 0) - current_bar.modifier = M_MOD2; + current_bar->modifier = M_MOD2; else if (strcmp(modifier, "Mod3") == 0) - current_bar.modifier = M_MOD3; + current_bar->modifier = M_MOD3; else if (strcmp(modifier, "Mod4") == 0) - current_bar.modifier = M_MOD4; + current_bar->modifier = M_MOD4; else if (strcmp(modifier, "Mod5") == 0) - current_bar.modifier = M_MOD5; + current_bar->modifier = M_MOD5; else if (strcmp(modifier, "Control") == 0 || strcmp(modifier, "Ctrl") == 0) - current_bar.modifier = M_CONTROL; + current_bar->modifier = M_CONTROL; else if (strcmp(modifier, "Shift") == 0) - current_bar.modifier = M_SHIFT; + current_bar->modifier = M_SHIFT; + else if (strcmp(modifier, "none") == 0 || + strcmp(modifier, "off") == 0) + current_bar->modifier = M_NONE; } static void bar_configure_binding(const char *button, const char *command) { @@ -451,7 +464,7 @@ static void bar_configure_binding(const char *button, const char *command) { } struct Barbinding *current; - TAILQ_FOREACH(current, &(current_bar.bar_bindings), bindings) { + TAILQ_FOREACH(current, &(current_bar->bar_bindings), bindings) { if (current->input_code == input_code) { ELOG("command for button %s was already specified, ignoring.\n", button); return; @@ -461,7 +474,7 @@ static void bar_configure_binding(const char *button, const char *command) { struct Barbinding *new_binding = scalloc(1, sizeof(struct Barbinding)); new_binding->input_code = input_code; new_binding->command = sstrdup(command); - TAILQ_INSERT_TAIL(&(current_bar.bar_bindings), new_binding, bindings); + TAILQ_INSERT_TAIL(&(current_bar->bar_bindings), new_binding, bindings); } CFGFUN(bar_wheel_up_cmd, const char *command) { @@ -479,29 +492,29 @@ CFGFUN(bar_bindsym, const char *button, const char *command) { } CFGFUN(bar_position, const char *position) { - current_bar.position = (strcmp(position, "top") == 0 ? P_TOP : P_BOTTOM); + current_bar->position = (strcmp(position, "top") == 0 ? P_TOP : P_BOTTOM); } CFGFUN(bar_i3bar_command, const char *i3bar_command) { - FREE(current_bar.i3bar_command); - current_bar.i3bar_command = sstrdup(i3bar_command); + FREE(current_bar->i3bar_command); + current_bar->i3bar_command = sstrdup(i3bar_command); } CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text) { -#define APPLY_COLORS(classname) \ - do { \ - if (strcmp(colorclass, #classname) == 0) { \ - if (text != NULL) { \ - /* New syntax: border, background, text */ \ - current_bar.colors.classname##_border = sstrdup(border); \ - current_bar.colors.classname##_bg = sstrdup(background); \ - current_bar.colors.classname##_text = sstrdup(text); \ - } else { \ - /* Old syntax: text, background */ \ - current_bar.colors.classname##_bg = sstrdup(background); \ - current_bar.colors.classname##_text = sstrdup(border); \ - } \ - } \ +#define APPLY_COLORS(classname) \ + do { \ + if (strcmp(colorclass, #classname) == 0) { \ + if (text != NULL) { \ + /* New syntax: border, background, text */ \ + current_bar->colors.classname##_border = sstrdup(border); \ + current_bar->colors.classname##_bg = sstrdup(background); \ + current_bar->colors.classname##_text = sstrdup(text); \ + } else { \ + /* Old syntax: text, background */ \ + current_bar->colors.classname##_bg = sstrdup(background); \ + current_bar->colors.classname##_text = sstrdup(border); \ + } \ + } \ } while (0) APPLY_COLORS(focused_workspace); @@ -514,67 +527,73 @@ CFGFUN(bar_color, const char *colorclass, const char *border, const char *backgr } CFGFUN(bar_socket_path, const char *socket_path) { - FREE(current_bar.socket_path); - current_bar.socket_path = sstrdup(socket_path); + FREE(current_bar->socket_path); + current_bar->socket_path = sstrdup(socket_path); } CFGFUN(bar_tray_output, const char *output) { - FREE(current_bar.tray_output); - current_bar.tray_output = sstrdup(output); + struct tray_output_t *tray_output = scalloc(1, sizeof(struct tray_output_t)); + tray_output->output = sstrdup(output); + TAILQ_INSERT_TAIL(&(current_bar->tray_outputs), tray_output, tray_outputs); } CFGFUN(bar_tray_padding, const long padding_px) { - current_bar.tray_padding = padding_px; + current_bar->tray_padding = padding_px; } CFGFUN(bar_color_single, const char *colorclass, const char *color) { if (strcmp(colorclass, "background") == 0) - current_bar.colors.background = sstrdup(color); + current_bar->colors.background = sstrdup(color); else if (strcmp(colorclass, "separator") == 0) - current_bar.colors.separator = sstrdup(color); + current_bar->colors.separator = sstrdup(color); + else if (strcmp(colorclass, "statusline") == 0) + current_bar->colors.statusline = sstrdup(color); + else if (strcmp(colorclass, "focused_background") == 0) + current_bar->colors.focused_background = sstrdup(color); + else if (strcmp(colorclass, "focused_separator") == 0) + current_bar->colors.focused_separator = sstrdup(color); else - current_bar.colors.statusline = sstrdup(color); + current_bar->colors.focused_statusline = sstrdup(color); } CFGFUN(bar_status_command, const char *command) { - FREE(current_bar.status_command); - current_bar.status_command = sstrdup(command); + FREE(current_bar->status_command); + current_bar->status_command = sstrdup(command); } CFGFUN(bar_binding_mode_indicator, const char *value) { - current_bar.hide_binding_mode_indicator = !eval_boolstr(value); + current_bar->hide_binding_mode_indicator = !eval_boolstr(value); } CFGFUN(bar_workspace_buttons, const char *value) { - current_bar.hide_workspace_buttons = !eval_boolstr(value); + current_bar->hide_workspace_buttons = !eval_boolstr(value); } CFGFUN(bar_strip_workspace_numbers, const char *value) { - current_bar.strip_workspace_numbers = eval_boolstr(value); + current_bar->strip_workspace_numbers = eval_boolstr(value); } CFGFUN(bar_start) { - TAILQ_INIT(&(current_bar.bar_bindings)); - current_bar.tray_padding = 2; + current_bar = scalloc(1, sizeof(struct Barconfig)); + TAILQ_INIT(&(current_bar->bar_bindings)); + TAILQ_INIT(&(current_bar->tray_outputs)); + current_bar->tray_padding = 2; + current_bar->modifier = M_MOD4; } CFGFUN(bar_finish) { DLOG("\t new bar configuration finished, saving.\n"); /* Generate a unique ID for this bar if not already configured */ - if (!current_bar.id) - sasprintf(¤t_bar.id, "bar-%d", config.number_barconfigs); + if (current_bar->id == NULL) + sasprintf(¤t_bar->id, "bar-%d", config.number_barconfigs); config.number_barconfigs++; /* If no font was explicitly set, we use the i3 font as default */ - if (!current_bar.font && font_pattern) - current_bar.font = sstrdup(font_pattern); + if (current_bar->font == NULL && font_pattern != NULL) + current_bar->font = sstrdup(font_pattern); - /* Copy the current (static) structure into a dynamically allocated - * one, then cleanup our static one. */ - Barconfig *bar_config = scalloc(1, sizeof(Barconfig)); - memcpy(bar_config, ¤t_bar, sizeof(Barconfig)); - TAILQ_INSERT_TAIL(&barconfigs, bar_config, configs); - - memset(¤t_bar, '\0', sizeof(Barconfig)); + TAILQ_INSERT_TAIL(&barconfigs, current_bar, configs); + /* Simply reset the pointer, but don't free the resources. */ + current_bar = NULL; } diff --git a/src/config_parser.c b/src/config_parser.c index 705a3e24..e97a37e1 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -783,6 +783,34 @@ static char *migrate_config(char *input, off_t size) { return converted; } +/** + * Launch nagbar to indicate errors in the configuration file. + */ +void start_config_error_nagbar(const char *configpath, bool has_errors) { + char *editaction, *pageraction; + sasprintf(&editaction, "i3-sensible-editor \"%s\" && i3-msg reload\n", configpath); + sasprintf(&pageraction, "i3-sensible-pager \"%s\"\n", errorfilename); + char *argv[] = { + NULL, /* will be replaced by the executable path */ + "-f", + (config.font.pattern ? config.font.pattern : "fixed"), + "-t", + (has_errors ? "error" : "warning"), + "-m", + (has_errors ? "You have an error in your i3 config file!" : "Your config is outdated. Please fix the warnings to make sure everything works."), + "-b", + "edit config", + editaction, + (errorfilename ? "-b" : NULL), + (has_errors ? "show errors" : "show warnings"), + pageraction, + NULL}; + + start_nagbar(&config_error_nagbar_pid, argv); + free(editaction); + free(pageraction); +} + /* * Parses the given file by first replacing the variables, then calling * parse_config and possibly launching i3-nagbar. @@ -815,7 +843,7 @@ bool parse_file(const char *f, bool use_nagbar) { break; die("Could not read configuration file\n"); } - if (buffer[strlen(buffer) - 1] != '\n') { + if (buffer[strlen(buffer) - 1] != '\n' && !feof(fstr)) { ELOG("Your line continuation is too long, it exceeds %zd bytes\n", sizeof(buffer)); } continuation = strstr(buffer, "\\\n"); @@ -882,7 +910,7 @@ bool parse_file(const char *f, bool use_nagbar) { FREE(bufcopy); /* Then, allocate a new buffer and copy the file over to the new one, - * but replace occurences of our variables */ + * but replace occurrences of our variables */ char *walk = buf, *destwalk; char *new = smalloc(stbuf.st_size + extra_bytes + 1); destwalk = new; @@ -958,29 +986,7 @@ bool parse_file(const char *f, bool use_nagbar) { if (version == 3) ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n"); - char *editaction, - *pageraction; - sasprintf(&editaction, "i3-sensible-editor \"%s\" && i3-msg reload\n", f); - sasprintf(&pageraction, "i3-sensible-pager \"%s\"\n", errorfilename); - char *argv[] = { - NULL, /* will be replaced by the executable path */ - "-f", - (config.font.pattern ? config.font.pattern : "fixed"), - "-t", - (context->has_errors ? "error" : "warning"), - "-m", - (context->has_errors ? "You have an error in your i3 config file!" : "Your config is outdated. Please fix the warnings to make sure everything works."), - "-b", - "edit config", - editaction, - (errorfilename ? "-b" : NULL), - (context->has_errors ? "show errors" : "show warnings"), - pageraction, - NULL}; - - start_nagbar(&config_error_nagbar_pid, argv); - free(editaction); - free(pageraction); + start_config_error_nagbar(f, context->has_errors); } bool has_errors = context->has_errors; diff --git a/src/ewmh.c b/src/ewmh.c index b2260d64..c4ae844e 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -21,24 +21,9 @@ xcb_window_t ewmh_window; * */ void ewmh_update_current_desktop(void) { - Con *focused_ws = con_get_workspace(focused); - Con *output; - uint32_t idx = 0; - /* We count to get the index of this workspace because named workspaces - * don’t have the ->num property */ - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { - Con *ws; - TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { - if (STARTS_WITH(ws->name, "__")) - continue; - - if (ws == focused_ws) { - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, - A__NET_CURRENT_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, &idx); - return; - } - ++idx; - } + const uint32_t idx = ewmh_get_workspace_index(focused); + if (idx != NET_WM_DESKTOP_NONE) { + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_CURRENT_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, &idx); } } @@ -138,6 +123,71 @@ void ewmh_update_desktop_viewport(void) { A__NET_DESKTOP_VIEWPORT, XCB_ATOM_CARDINAL, 32, current_position, &viewports); } +static void ewmh_update_wm_desktop_recursively(Con *con, const uint32_t desktop) { + /* Recursively call this to descend through the entire subtree. */ + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + ewmh_update_wm_desktop_recursively(child, desktop); + } + /* If con is a workspace, we also need to go through the floating windows on it. */ + if (con->type == CT_WORKSPACE) { + TAILQ_FOREACH(child, &(con->floating_head), floating_windows) { + ewmh_update_wm_desktop_recursively(child, desktop); + } + } + + if (!con_has_managed_window(con)) + return; + + const xcb_window_t window = con->window->id; + + uint32_t wm_desktop = desktop; + /* Sticky windows are only actually sticky when they are floating or inside + * a floating container. This is technically still slightly wrong, since + * sticky windows will only be on all workspaces on this output, but we + * ignore multi-monitor situations for this since the spec isn't too + * precise on this anyway. */ + if (con_is_sticky(con) && con_is_floating(con)) { + wm_desktop = NET_WM_DESKTOP_ALL; + } + + /* If this is the cached value, we don't need to do anything. */ + if (con->window->wm_desktop == wm_desktop) + return; + con->window->wm_desktop = wm_desktop; + + if (wm_desktop != NET_WM_DESKTOP_NONE) { + DLOG("Setting _NET_WM_DESKTOP = %d for window 0x%08x.\n", wm_desktop, window); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window, A__NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, &wm_desktop); + } else { + /* If we can't determine the workspace index, delete the property. We'd + * rather not set it than lie. */ + ELOG("Failed to determine the proper EWMH desktop index for window 0x%08x, deleting _NET_WM_DESKTOP.\n", window); + xcb_delete_property(conn, window, A__NET_WM_DESKTOP); + } +} + +/* + * Updates _NET_WM_DESKTOP for all windows. + * A request will only be made if the cached value differs from the calculated value. + * + */ +void ewmh_update_wm_desktop(void) { + uint32_t desktop = 0; + + Con *output; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + Con *workspace; + TAILQ_FOREACH(workspace, &(output_get_content(output)->nodes_head), nodes) { + if (con_is_internal(workspace)) + continue; + + ewmh_update_wm_desktop_recursively(workspace, desktop); + ++desktop; + } + } +} + /* * Updates _NET_ACTIVE_WINDOW with the currently focused window. * @@ -234,7 +284,7 @@ void ewmh_update_sticky(xcb_window_t window, bool sticky) { void ewmh_setup_hints(void) { xcb_atom_t supported_atoms[] = { #define xmacro(atom) A_##atom, -#include "atoms.xmacro" +#include "atoms_NET_SUPPORTED.xmacro" #undef xmacro }; @@ -263,10 +313,67 @@ void ewmh_setup_hints(void) { /* I’m not entirely sure if we need to keep _NET_WM_NAME on root. */ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3"); - /* only send the first 31 atoms (last one is _NET_CLOSE_WINDOW) increment that number when adding supported atoms */ - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, /* number of atoms */ 32, supported_atoms); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, /* number of atoms */ sizeof(supported_atoms) / sizeof(xcb_atom_t), supported_atoms); /* We need to map this window to be able to set the input focus to it if no other window is available to be focused. */ xcb_map_window(conn, ewmh_window); xcb_configure_window(conn, ewmh_window, XCB_CONFIG_WINDOW_STACK_MODE, (uint32_t[]){XCB_STACK_MODE_BELOW}); } + +/* + * Returns the workspace container as enumerated by the EWMH desktop model. + * Returns NULL if no workspace could be found for the index. + * + * This is the reverse of ewmh_get_workspace_index. + * + */ +Con *ewmh_get_workspace_by_index(uint32_t idx) { + if (idx == NET_WM_DESKTOP_NONE) + return NULL; + + uint32_t current_index = 0; + + Con *output; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + Con *workspace; + TAILQ_FOREACH(workspace, &(output_get_content(output)->nodes_head), nodes) { + if (con_is_internal(workspace)) + continue; + + if (current_index == idx) + return workspace; + + ++current_index; + } + } + + return NULL; +} + +/* + * Returns the EWMH desktop index for the workspace the given container is on. + * Returns NET_WM_DESKTOP_NONE if the desktop index cannot be determined. + * + * This is the reverse of ewmh_get_workspace_by_index. + * + */ +uint32_t ewmh_get_workspace_index(Con *con) { + uint32_t index = 0; + + Con *workspace = con_get_workspace(con); + Con *output; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + Con *current; + TAILQ_FOREACH(current, &(output_get_content(output)->nodes_head), nodes) { + if (con_is_internal(current)) + continue; + + if (current == workspace) + return index; + + ++index; + } + } + + return NET_WM_DESKTOP_NONE; +} diff --git a/src/floating.c b/src/floating.c index 77bc9e17..231577fd 100644 --- a/src/floating.c +++ b/src/floating.c @@ -11,8 +11,6 @@ */ #include "all.h" -extern xcb_connection_t *conn; - /* * Calculates sum of heights and sum of widths of all currently active outputs * @@ -31,6 +29,34 @@ static Rect total_outputs_dimensions(void) { return outputs_dimensions; } +/* + * Updates I3_FLOATING_WINDOW by either setting or removing it on the con and + * all its children. + * + */ +static void floating_set_hint_atom(Con *con, bool floating) { + if (!con_is_leaf(con)) { + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + floating_set_hint_atom(child, floating); + } + } + + if (con->window == NULL) { + return; + } + + if (floating) { + uint32_t val = 1; + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id, + A_I3_FLOATING_WINDOW, XCB_ATOM_CARDINAL, 32, 1, &val); + } else { + xcb_delete_property(conn, con->window->id, A_I3_FLOATING_WINDOW); + } + + xcb_flush(conn); +} + /** * Called when a floating window is created or resized. * This function resizes the window if its size is higher or lower than the @@ -118,51 +144,13 @@ void floating_enable(Con *con, bool automatic) { return; } - /* 1: If the container is a workspace container, we need to create a new - * 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) { - LOG("This is a workspace, creating new container around content\n"); - if (con_num_children(con) == 0) { - LOG("Workspace is empty, aborting\n"); - return; - } - /* TODO: refactor this with src/con.c:con_set_layout */ - Con *new = con_new(NULL, NULL); - new->parent = con; - new->layout = con->layout; - - /* since the new container will be set into floating mode directly - * afterwards, we need to copy the workspace rect. */ - memcpy(&(new->rect), &(con->rect), sizeof(Rect)); - - 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); - - con = new; - set_focus = false; + LOG("Container is a workspace, not enabling floating mode.\n"); + return; } /* 1: detach the container from its parent */ - /* TODO: refactor this with tree_close() */ + /* TODO: refactor this with tree_close_internal() */ TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes); TAILQ_REMOVE(&(con->parent->focus_head), con, focused); @@ -180,7 +168,7 @@ void floating_enable(Con *con, bool automatic) { 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. */ + * closed in tree_close_internal()) even though it’s not. */ TAILQ_INSERT_TAIL(&(ws->floating_head), nc, floating_windows); TAILQ_INSERT_TAIL(&(ws->focus_head), nc, focused); @@ -188,7 +176,7 @@ void floating_enable(Con *con, bool automatic) { if ((con->parent->type == CT_CON || con->parent->type == CT_FLOATING_CON) && con_num_children(con->parent) == 0) { DLOG("Old container empty after setting this child to floating, closing\n"); - tree_close(con->parent, DONT_KILL_WINDOW, false, false); + tree_close_internal(con->parent, DONT_KILL_WINDOW, false, false); } char *name; @@ -300,19 +288,19 @@ void floating_enable(Con *con, bool automatic) { /* Check if we need to re-assign it to a different workspace because of its * coordinates and exit if that was done successfully. */ if (floating_maybe_reassign_ws(nc)) { - ipc_send_window_event("floating", con); - return; + goto done; } /* Sanitize coordinates: Check if they are on any output */ if (get_output_containing(nc->rect.x, nc->rect.y) != NULL) { - ipc_send_window_event("floating", con); - return; + goto done; } ELOG("No output found at destination coordinates, centering floating window on current ws\n"); floating_center(nc, ws->rect); +done: + floating_set_hint_atom(nc, true); ipc_send_window_event("floating", con); } @@ -333,7 +321,7 @@ void floating_disable(Con *con, bool automatic) { /* 2: kill parent container */ TAILQ_REMOVE(&(con->parent->parent->floating_head), con->parent, floating_windows); TAILQ_REMOVE(&(con->parent->parent->focus_head), con->parent, focused); - tree_close(con->parent, DONT_KILL_WINDOW, true, false); + tree_close_internal(con->parent, DONT_KILL_WINDOW, true, false); /* 3: re-attach to the parent of the currently focused con on the workspace * this floating con was on */ @@ -358,6 +346,7 @@ void floating_disable(Con *con, bool automatic) { if (set_focus) con_focus(con); + floating_set_hint_atom(con, false); ipc_send_window_event("floating", con); } diff --git a/src/handlers.c b/src/handlers.c index e3ddf701..2991d7c3 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -503,7 +503,12 @@ static void handle_unmap_notify_event(xcb_unmap_notify_event_t *event) { goto ignore_end; } - tree_close(con, DONT_KILL_WINDOW, false, false); + /* Since we close the container, we need to unset _NET_WM_DESKTOP and + * _NET_WM_STATE according to the spec. */ + xcb_delete_property(conn, event->window, A__NET_WM_DESKTOP); + xcb_delete_property(conn, event->window, A__NET_WM_STATE); + + tree_close_internal(con, DONT_KILL_WINDOW, false, false); tree_render(); ignore_end: @@ -656,15 +661,14 @@ static void handle_expose_event(xcb_expose_event_t *event) { return; } - /* Since we render to our pixmap on every change anyways, expose events + /* Since we render to our surface on every change anyways, expose events * only tell us that the X server lost (parts of) the window contents. We - * can handle that by copying the appropriate part from our pixmap to the + * can handle that by copying the appropriate part from our surface to the * window. */ - xcb_copy_area(conn, parent->pixmap, parent->frame, parent->pm_gc, - event->x, event->y, event->x, event->y, - event->width, event->height); + draw_util_copy_surface(conn, &(parent->frame_buffer), &(parent->frame), + event->x, event->y, event->x, event->y, + event->width, event->height); xcb_flush(conn); - return; } @@ -736,7 +740,9 @@ static void handle_client_message(xcb_client_message_event_t *event) { con->sticky = !con->sticky; DLOG("New sticky status for con = %p is %i.\n", con, con->sticky); + ewmh_update_sticky(con->window->id, con->sticky); output_push_sticky_windows(focused); + ewmh_update_wm_desktop(); } tree_render(); @@ -840,32 +846,48 @@ static void handle_client_message(xcb_client_message_event_t *event) { * a request to focus the given workspace. See * http://standards.freedesktop.org/wm-spec/latest/ar01s03.html#idm140251368135008 * */ - Con *output; - uint32_t idx = 0; DLOG("Request to change current desktop to index %d\n", event->data.data32[0]); - - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { - Con *ws; - TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { - if (STARTS_WITH(ws->name, "__")) - continue; - - if (idx == event->data.data32[0]) { - /* data32[1] is a timestamp used to prevent focus race conditions */ - if (event->data.data32[1]) - last_timestamp = event->data.data32[1]; - - DLOG("Handling request to focus workspace %s\n", ws->name); - - workspace_show(ws); - tree_render(); - - return; - } - - ++idx; - } + Con *ws = ewmh_get_workspace_by_index(event->data.data32[0]); + if (ws == NULL) { + ELOG("Could not determine workspace for this index, ignoring request.\n"); + return; } + + DLOG("Handling request to focus workspace %s\n", ws->name); + workspace_show(ws); + tree_render(); + } else if (event->type == A__NET_WM_DESKTOP) { + uint32_t index = event->data.data32[0]; + DLOG("Request to move window %d to EWMH desktop index %d\n", event->window, index); + + Con *con = con_by_window_id(event->window); + if (con == NULL) { + DLOG("Couldn't find con for window %d, ignoring the request.\n", event->window); + return; + } + + if (index == NET_WM_DESKTOP_ALL) { + /* The window is requesting to be visible on all workspaces, so + * let's float it and make it sticky. */ + DLOG("The window was requested to be visible on all workspaces, making it sticky and floating.\n"); + + floating_enable(con, false); + + con->sticky = true; + ewmh_update_sticky(con->window->id, true); + output_push_sticky_windows(focused); + } else { + Con *ws = ewmh_get_workspace_by_index(index); + if (ws == NULL) { + ELOG("Could not determine workspace for this index, ignoring request.\n"); + return; + } + + con_move_to_workspace(con, ws, true, false, false); + } + + tree_render(); + ewmh_update_wm_desktop(); } else if (event->type == A__NET_CLOSE_WINDOW) { /* * Pagers wanting to close a window MUST send a _NET_CLOSE_WINDOW @@ -879,7 +901,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { if (event->data.data32[0]) last_timestamp = event->data.data32[0]; - tree_close(con, KILL_WINDOW, false, false); + tree_close_internal(con, KILL_WINDOW, false, false); tree_render(); } else { DLOG("Couldn't find con for _NET_CLOSE_WINDOW request. (window = %d)\n", event->window); @@ -916,8 +938,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { break; } } else { - DLOG("unhandled clientmessage\n"); - return; + DLOG("Skipping client message for unhandled type %d\n", event->type); } } diff --git a/src/i3.mk b/src/i3.mk index 8472106e..ed8e3ae9 100644 --- a/src/i3.mk +++ b/src/i3.mk @@ -5,8 +5,8 @@ CLEAN_TARGETS += clean-i3 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 = $(XKB_COMMON_CFLAGS) $(XKB_COMMON_X11_CFLAGS) $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(XCB_WM_CFLAGS) $(XCURSOR_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) $(PCRE_CFLAGS) $(LIBSN_CFLAGS) -i3_LIBS = $(XKB_COMMON_LIBS) $(XKB_COMMON_X11_LIBS) $(XCB_LIBS) $(XCB_XKB_LIBS) $(XCB_KBD_LIBS) $(XCB_WM_LIBS) $(XCURSOR_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) $(PCRE_LIBS) $(LIBSN_LIBS) -lm -lpthread +i3_CFLAGS = $(XKB_COMMON_CFLAGS) $(XKB_COMMON_X11_CFLAGS) $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(XCB_WM_CFLAGS) $(XCB_CURSOR_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) $(PCRE_CFLAGS) $(LIBSN_CFLAGS) +i3_LIBS = $(XKB_COMMON_LIBS) $(XKB_COMMON_X11_LIBS) $(XCB_LIBS) $(XCB_XKB_LIBS) $(XCB_KBD_LIBS) $(XCB_WM_LIBS) $(XCB_CURSOR_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. diff --git a/src/ipc.c b/src/ipc.c index c884cc8e..566fe52a 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -71,6 +71,9 @@ void ipc_shutdown(void) { current = TAILQ_FIRST(&all_clients); shutdown(current->fd, SHUT_RDWR); close(current->fd); + for (int i = 0; i < current->num_events; i++) + free(current->events[i]); + free(current->events); TAILQ_REMOVE(&all_clients, current, clients); free(current); } @@ -275,9 +278,16 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { ystr("urgent"); y(bool, con->urgent); - if (con->mark != NULL) { - ystr("mark"); - ystr(con->mark); + if (!TAILQ_EMPTY(&(con->marks_head))) { + ystr("marks"); + y(array_open); + + mark_t *mark; + TAILQ_FOREACH(mark, &(con->marks_head), marks) { + ystr(mark->name); + } + + y(array_close); } ystr("focused"); @@ -365,6 +375,11 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { else y(null); + if (con->title_format != NULL) { + ystr("title_format"); + ystr(con->title_format); + } + if (con->type == CT_WORKSPACE) { ystr("num"); y(integer, con->num); @@ -544,6 +559,18 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) { y(array_close); } + if (!TAILQ_EMPTY(&(config->tray_outputs))) { + ystr("tray_outputs"); + y(array_open); + + struct tray_output_t *tray_output; + TAILQ_FOREACH(tray_output, &(config->tray_outputs), tray_outputs) { + ystr(tray_output->output); + } + + y(array_close); + } + #define YSTR_IF_SET(name) \ do { \ if (config->name) { \ @@ -552,8 +579,6 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) { } \ } while (0) - YSTR_IF_SET(tray_output); - ystr("tray_padding"); y(integer, config->tray_padding); @@ -586,6 +611,9 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) { ystr("modifier"); switch (config->modifier) { + case M_NONE: + ystr("none"); + break; case M_CONTROL: ystr("ctrl"); break; @@ -601,11 +629,6 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) { case M_MOD3: ystr("Mod3"); break; - /* - case M_MOD4: - ystr("Mod4"); - break; - */ case M_MOD5: ystr("Mod5"); break; @@ -656,6 +679,9 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) { YSTR_IF_SET(background); YSTR_IF_SET(statusline); YSTR_IF_SET(separator); + YSTR_IF_SET(focused_background); + YSTR_IF_SET(focused_statusline); + YSTR_IF_SET(focused_separator); YSTR_IF_SET(focused_workspace_border); YSTR_IF_SET(focused_workspace_bg); YSTR_IF_SET(focused_workspace_text); @@ -819,9 +845,12 @@ IPC_HANDLER(get_marks) { y(array_open); Con *con; - TAILQ_FOREACH(con, &all_cons, all_cons) - if (con->mark != NULL) - ystr(con->mark); + TAILQ_FOREACH(con, &all_cons, all_cons) { + mark_t *mark; + TAILQ_FOREACH(mark, &(con->marks_head), marks) { + ystr(mark->name); + } + } y(array_close); @@ -1051,6 +1080,7 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { for (int i = 0; i < current->num_events; i++) free(current->events[i]); + free(current->events); /* We can call TAILQ_REMOVE because we break out of the * TAILQ_FOREACH afterwards */ TAILQ_REMOVE(&all_clients, current, clients); diff --git a/src/key_press.c b/src/key_press.c index aa6d8150..6760e35b 100644 --- a/src/key_press.c +++ b/src/key_press.c @@ -31,9 +31,5 @@ void handle_key_press(xcb_key_press_event_t *event) { return; CommandResult *result = run_binding(bind, NULL); - - if (result->needs_tree_render) - tree_render(); - command_result_free(result); } diff --git a/src/load_layout.c b/src/load_layout.c index 4a67e6b1..173e573b 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -28,7 +28,9 @@ static bool parsing_deco_rect; static bool parsing_window_rect; static bool parsing_geometry; static bool parsing_focus; +static bool parsing_marks; struct Match *current_swallow; +static bool swallow_is_empty; /* This list is used for reordering the focus stack after parsing the 'focus' * array. */ @@ -47,6 +49,7 @@ static int json_start_map(void *ctx) { current_swallow = smalloc(sizeof(Match)); match_init(current_swallow); TAILQ_INSERT_TAIL(&(json_node->swallow_head), current_swallow, matches); + swallow_is_empty = true; } else { if (!parsing_rect && !parsing_deco_rect && !parsing_window_rect && !parsing_geometry) { if (last_key && strcasecmp(last_key, "floating_nodes") == 0) { @@ -84,6 +87,7 @@ static int json_end_map(void *ctx) { Match *match = TAILQ_FIRST(&(json_node->swallow_head)); TAILQ_REMOVE(&(json_node->swallow_head), match, matches); match_free(match); + free(match); } } @@ -150,6 +154,13 @@ static int json_end_map(void *ctx) { json_node = json_node->parent; } + if (parsing_swallows && swallow_is_empty) { + /* We parsed an empty swallow definition. This is an invalid layout + * definition, hence we reject it. */ + ELOG("Layout file is invalid: found an empty swallow definition.\n"); + return 0; + } + parsing_rect = false; parsing_deco_rect = false; parsing_window_rect = false; @@ -159,12 +170,16 @@ static int json_end_map(void *ctx) { static int json_end_array(void *ctx) { LOG("end of array\n"); - if (!parsing_swallows && !parsing_focus) { + if (!parsing_swallows && !parsing_focus && !parsing_marks) { con_fix_percent(json_node); } if (parsing_swallows) { parsing_swallows = false; } + if (parsing_marks) { + parsing_marks = false; + } + if (parsing_focus) { /* Clear the list of focus mappings */ struct focus_mapping *mapping; @@ -214,6 +229,9 @@ static int json_key(void *ctx, const unsigned char *val, size_t len) { if (strcasecmp(last_key, "focus") == 0) parsing_focus = true; + if (strcasecmp(last_key, "marks") == 0) + parsing_marks = true; + return 1; } @@ -224,20 +242,32 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) { sasprintf(&sval, "%.*s", len, val); if (strcasecmp(last_key, "class") == 0) { current_swallow->class = regex_new(sval); + swallow_is_empty = false; } else if (strcasecmp(last_key, "instance") == 0) { current_swallow->instance = regex_new(sval); + swallow_is_empty = false; } else if (strcasecmp(last_key, "window_role") == 0) { current_swallow->window_role = regex_new(sval); + swallow_is_empty = false; } else if (strcasecmp(last_key, "title") == 0) { current_swallow->title = regex_new(sval); + swallow_is_empty = false; } else { ELOG("swallow key %s unknown\n", last_key); } free(sval); + } else if (parsing_marks) { + char *mark; + sasprintf(&mark, "%.*s", (int)len, val); + + con_mark(json_node, mark, MM_ADD); } else { if (strcasecmp(last_key, "name") == 0) { json_node->name = scalloc(len + 1, 1); memcpy(json_node->name, val, len); + } else if (strcasecmp(last_key, "title_format") == 0) { + json_node->title_format = scalloc(len + 1, 1); + memcpy(json_node->title_format, val, len); } else if (strcasecmp(last_key, "sticky_group") == 0) { json_node->sticky_group = scalloc(len + 1, 1); memcpy(json_node->sticky_group, val, len); @@ -336,13 +366,12 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) { LOG("Unhandled \"last_splitlayout\": %s\n", buf); free(buf); } else if (strcasecmp(last_key, "mark") == 0) { + DLOG("Found deprecated key \"mark\".\n"); + char *buf = NULL; sasprintf(&buf, "%.*s", (int)len, val); - /* We unmark any containers using this mark to avoid duplicates. */ - con_unmark(buf); - - json_node->mark = buf; + con_mark(json_node, buf, MM_REPLACE); } else if (strcasecmp(last_key, "floating") == 0) { char *buf = NULL; sasprintf(&buf, "%.*s", (int)len, val); @@ -421,12 +450,15 @@ static int json_int(void *ctx, long long val) { if (parsing_swallows) { if (strcasecmp(last_key, "id") == 0) { current_swallow->id = val; + swallow_is_empty = false; } if (strcasecmp(last_key, "dock") == 0) { current_swallow->dock = val; + swallow_is_empty = false; } if (strcasecmp(last_key, "insert_where") == 0) { current_swallow->insert_where = val; + swallow_is_empty = false; } } @@ -443,8 +475,10 @@ static int json_bool(void *ctx, int val) { json_node->sticky = val; if (parsing_swallows) { - if (strcasecmp(last_key, "restart_mode") == 0) + if (strcasecmp(last_key, "restart_mode") == 0) { current_swallow->restart_mode = val; + swallow_is_empty = false; + } } return 1; @@ -589,6 +623,7 @@ void tree_append_json(Con *con, const char *filename, char **errormsg) { parsing_window_rect = false; parsing_geometry = false; parsing_focus = false; + parsing_marks = false; setlocale(LC_NUMERIC, "C"); stat = yajl_parse(hand, (const unsigned char *)buf, n); if (stat != yajl_status_ok) { @@ -606,8 +641,11 @@ void tree_append_json(Con *con, const char *filename, char **errormsg) { setlocale(LC_NUMERIC, ""); yajl_complete_parse(hand); + yajl_free(hand); + yajl_gen_free(g); fclose(f); + free(buf); if (to_focus) con_focus(to_focus); } diff --git a/src/log.c b/src/log.c index 856330b6..e8a08b53 100644 --- a/src/log.c +++ b/src/log.c @@ -58,6 +58,8 @@ static char *loglastwrap; static int logbuffer_size; /* File descriptor for shm_open. */ static int logbuffer_shm; +/* Size (in bytes) of physical memory */ +static long long physical_mem_bytes; /* * Writes the offsets for the next write and for the last wrap to the @@ -89,6 +91,16 @@ void init_logging(void) { } } } + if (physical_mem_bytes == 0) { +#if defined(__APPLE__) + int mib[2] = {CTL_HW, HW_MEMSIZE}; + size_t length = sizeof(long long); + sysctl(mib, 2, &physical_mem_bytes, &length, NULL, 0); +#else + physical_mem_bytes = (long long)sysconf(_SC_PHYS_PAGES) * + sysconf(_SC_PAGESIZE); +#endif + } /* Start SHM logging if shmlog_size is > 0. shmlog_size is SHMLOG_SIZE by * default on development versions, and 0 on release versions. If it is * not > 0, the user has turned it off, so let's close the logbuffer. */ @@ -108,15 +120,6 @@ void open_logbuffer(void) { * For 512 MiB of RAM this will lead to a 5 MiB log buffer. * At the moment (2011-12-10), no testcase leads to an i3 log * of more than ~ 600 KiB. */ - long long physical_mem_bytes; -#if defined(__APPLE__) - int mib[2] = {CTL_HW, HW_MEMSIZE}; - size_t length = sizeof(long long); - sysctl(mib, 2, &physical_mem_bytes, &length, NULL, 0); -#else - physical_mem_bytes = (long long)sysconf(_SC_PHYS_PAGES) * - sysconf(_SC_PAGESIZE); -#endif logbuffer_size = min(physical_mem_bytes * 0.01, shmlog_size); #if defined(__FreeBSD__) sasprintf(&shmlogname, "/tmp/i3-log-%d", getpid()); @@ -172,6 +175,7 @@ void open_logbuffer(void) { void close_logbuffer(void) { close(logbuffer_shm); shm_unlink(shmlogname); + free(shmlogname); logbuffer = NULL; shmlogname = ""; } diff --git a/src/main.c b/src/main.c index 563fb00c..b2ce17d8 100644 --- a/src/main.c +++ b/src/main.c @@ -59,7 +59,7 @@ xcb_window_t root; * pixmaps. Will use 32 bit depth and an appropriate visual, if available, * otherwise the root window’s default (usually 24 bit TrueColor). */ uint8_t root_depth; -xcb_visualid_t visual_id; +xcb_visualtype_t *visual_type; xcb_colormap_t colormap; struct ev_loop *main_loop; @@ -481,15 +481,29 @@ int main(int argc, char *argv[]) { #include "atoms.xmacro" #undef xmacro - /* By default, we use the same depth and visual as the root window, which - * usually is TrueColor (24 bit depth) and the corresponding visual. - * However, we also check if a 32 bit depth and visual are available (for - * transparency) and use it if so. */ root_depth = root_screen->root_depth; - visual_id = root_screen->root_visual; colormap = root_screen->default_colormap; + visual_type = xcb_aux_find_visual_by_attrs(root_screen, -1, 32); + if (visual_type != NULL) { + root_depth = xcb_aux_get_depth_of_visual(root_screen, visual_type->visual_id); + colormap = xcb_generate_id(conn); - DLOG("root_depth = %d, visual_id = 0x%08x.\n", root_depth, visual_id); + xcb_void_cookie_t cm_cookie = xcb_create_colormap_checked(conn, + XCB_COLORMAP_ALLOC_NONE, + colormap, + root, + visual_type->visual_id); + + xcb_generic_error_t *error = xcb_request_check(conn, cm_cookie); + if (error != NULL) { + ELOG("Could not create colormap. Error code: %d\n", error->error_code); + exit(EXIT_FAILURE); + } + } else { + visual_type = get_visualtype(root_screen); + } + + DLOG("root_depth = %d, visual_id = 0x%08x.\n", root_depth, visual_type->visual_id); DLOG("root_screen->height_in_pixels = %d, root_screen->height_in_millimeters = %d, dpi = %d\n", root_screen->height_in_pixels, root_screen->height_in_millimeters, (int)((double)root_screen->height_in_pixels * 25.4 / (double)root_screen->height_in_millimeters)); @@ -660,6 +674,7 @@ int main(int argc, char *argv[]) { } con_focus(con_descend_focused(output_get_content(output->con))); + free(pointerreply); } tree_render(); @@ -787,6 +802,11 @@ int main(int argc, char *argv[]) { xcb_free_pixmap(conn, pixmap); } +#if defined(__OpenBSD__) + if (pledge("stdio rpath wpath cpath proc exec unix", NULL) == -1) + err(EXIT_FAILURE, "pledge"); +#endif + struct sigaction action; action.sa_sigaction = handle_signal; @@ -819,18 +839,28 @@ int main(int argc, char *argv[]) { /* Autostarting exec-lines */ if (autostart) { - struct Autostart *exec; - TAILQ_FOREACH(exec, &autostarts, autostarts) { + while (!TAILQ_EMPTY(&autostarts)) { + struct Autostart *exec = TAILQ_FIRST(&autostarts); + LOG("auto-starting %s\n", exec->command); start_application(exec->command, exec->no_startup_id); + + FREE(exec->command); + TAILQ_REMOVE(&autostarts, exec, autostarts); + FREE(exec); } } /* Autostarting exec_always-lines */ - struct Autostart *exec_always; - TAILQ_FOREACH(exec_always, &autostarts_always, autostarts_always) { + while (!TAILQ_EMPTY(&autostarts_always)) { + struct Autostart *exec_always = TAILQ_FIRST(&autostarts_always); + LOG("auto-starting (always!) %s\n", exec_always->command); start_application(exec_always->command, exec_always->no_startup_id); + + FREE(exec_always->command); + TAILQ_REMOVE(&autostarts_always, exec_always, autostarts_always); + FREE(exec_always); } /* Start i3bar processes for all configured bars */ diff --git a/src/manage.c b/src/manage.c index 0dec2844..93272f1b 100644 --- a/src/manage.c +++ b/src/manage.c @@ -90,7 +90,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki utf8_title_cookie, title_cookie, class_cookie, leader_cookie, transient_cookie, role_cookie, startup_id_cookie, wm_hints_cookie, - wm_normal_hints_cookie, motif_wm_hints_cookie; + wm_normal_hints_cookie, motif_wm_hints_cookie, wm_user_time_cookie, wm_desktop_cookie; geomc = xcb_get_geometry(conn, d); @@ -161,6 +161,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki wm_hints_cookie = xcb_icccm_get_wm_hints(conn, window); wm_normal_hints_cookie = xcb_icccm_get_wm_normal_hints(conn, window); motif_wm_hints_cookie = GET_PROPERTY(A__MOTIF_WM_HINTS, 5 * sizeof(uint64_t)); + wm_user_time_cookie = GET_PROPERTY(A__NET_WM_USER_TIME, UINT32_MAX); + wm_desktop_cookie = GET_PROPERTY(A__NET_WM_DESKTOP, UINT32_MAX); DLOG("Managing window 0x%08x\n", window); @@ -168,12 +170,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki cwindow->id = window; cwindow->depth = get_visual_depth(attr->visual); - /* We need to grab buttons 1-3 for click-to-focus and buttons 1-5 - * to allow for mouse bindings using --whole-window to work correctly. */ - xcb_grab_button(conn, false, window, XCB_EVENT_MASK_BUTTON_PRESS, - XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, - XCB_BUTTON_INDEX_ANY, - XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */); + xcb_grab_buttons(conn, window, bindings_should_grab_scrollwheel_buttons()); /* update as much information as possible so far (some replies may be NULL) */ window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL), true); @@ -198,6 +195,16 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki char *startup_ws = startup_workspace_for_window(cwindow, startup_id_reply); DLOG("startup workspace = %s\n", startup_ws); + /* Get _NET_WM_DESKTOP if it was set. */ + xcb_get_property_reply_t *wm_desktop_reply; + wm_desktop_reply = xcb_get_property_reply(conn, wm_desktop_cookie, NULL); + cwindow->wm_desktop = NET_WM_DESKTOP_NONE; + if (wm_desktop_reply != NULL && xcb_get_property_value_length(wm_desktop_reply) != 0) { + uint32_t *wm_desktops = xcb_get_property_value(wm_desktop_reply); + cwindow->wm_desktop = (int32_t)wm_desktops[0]; + } + FREE(wm_desktop_reply); + /* check if the window needs WM_TAKE_FOCUS */ cwindow->needs_take_focus = window_supports_protocol(cwindow->id, A_WM_TAKE_FOCUS); @@ -246,7 +253,10 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki /* See if any container swallows this new window */ nc = con_for_window(search_at, cwindow, &match); + const bool match_from_restart_mode = (match && match->restart_mode); if (nc == NULL) { + Con *wm_desktop_ws = NULL; + /* If not, check if it is assigned to a specific workspace */ if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE))) { DLOG("Assignment matches (%p)\n", match); @@ -261,9 +271,23 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki /* set the urgency hint on the window if the workspace is not visible */ if (!workspace_is_visible(assigned_ws)) urgency_hint = true; + } else if (cwindow->wm_desktop != NET_WM_DESKTOP_NONE && + cwindow->wm_desktop != NET_WM_DESKTOP_ALL && + (wm_desktop_ws = ewmh_get_workspace_by_index(cwindow->wm_desktop)) != NULL) { + /* If _NET_WM_DESKTOP is set to a specific desktop, we open it + * there. Note that we ignore the special value 0xFFFFFFFF here + * since such a window will be made sticky anyway. */ + + DLOG("Using workspace %p / %s because _NET_WM_DESKTOP = %d.\n", + wm_desktop_ws, wm_desktop_ws->name, cwindow->wm_desktop); + + nc = con_descend_tiling_focused(wm_desktop_ws); + if (nc->type == CT_WORKSPACE) + nc = tree_open_con(nc, cwindow); + else + nc = tree_open_con(nc->parent, cwindow); } else if (startup_ws) { - /* If it’s not assigned, but was started on a specific workspace, - * we want to open it there */ + /* If it was started on a specific workspace, we want to open it there. */ DLOG("Using workspace on which this application was started (%s)\n", startup_ws); nc = con_descend_tiling_focused(workspace_get(startup_ws, NULL)); DLOG("focused on ws %s: %p / %s\n", startup_ws, nc, nc->name); @@ -295,6 +319,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki DLOG("Removing match %p from container %p\n", match, nc); TAILQ_REMOVE(&(nc->swallow_head), match, matches); match_free(match); + FREE(match); } } @@ -308,9 +333,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki Match *first = TAILQ_FIRST(&(nc->swallow_head)); TAILQ_REMOVE(&(nc->swallow_head), first, matches); match_free(first); + free(first); } } } + if (nc->window != cwindow && nc->window != NULL) { + window_free(nc->window); + } nc->window = cwindow; x_reinit(nc); @@ -347,14 +376,17 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki Con *target_output = con_get_output(ws); if (workspace_is_visible(ws) && current_output == target_output) { - if (!match || !match->restart_mode) { + if (!match_from_restart_mode) { set_focus = true; - } else + } else { DLOG("not focusing, matched with restart_mode == true\n"); - } else + } + } else { DLOG("workspace not visible, not focusing\n"); - } else + } + } else { DLOG("dock, not focusing\n"); + } } else { DLOG("fs = %p, ws = %p, not focusing\n", fs, ws); /* Insert the new container in focus stack *after* the currently @@ -388,6 +420,12 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki if (xcb_reply_contains_atom(state_reply, A__NET_WM_STATE_STICKY)) nc->sticky = true; + if (cwindow->wm_desktop == NET_WM_DESKTOP_ALL) { + DLOG("This window has _NET_WM_DESKTOP = 0xFFFFFFFF. Will float it and make it sticky.\n"); + nc->sticky = true; + want_floating = true; + } + FREE(state_reply); FREE(type_reply); @@ -477,7 +515,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki values[0] = XCB_NONE; xcb_change_window_attributes(conn, window, XCB_CW_EVENT_MASK, values); - xcb_void_cookie_t rcookie = xcb_reparent_window_checked(conn, window, nc->frame, 0, 0); + xcb_void_cookie_t rcookie = xcb_reparent_window_checked(conn, window, nc->frame.id, 0, 0); if (xcb_request_check(conn, rcookie) != NULL) { LOG("Could not reparent the window, aborting\n"); goto geom_out; @@ -537,6 +575,23 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki } } + if (set_focus) { + DLOG("Checking con = %p for _NET_WM_USER_TIME.\n", nc); + + uint32_t *wm_user_time; + xcb_get_property_reply_t *wm_user_time_reply = xcb_get_property_reply(conn, wm_user_time_cookie, NULL); + if (wm_user_time_reply != NULL && xcb_get_property_value_length(wm_user_time_reply) != 0 && + (wm_user_time = xcb_get_property_value(wm_user_time_reply)) && + wm_user_time[0] == 0) { + DLOG("_NET_WM_USER_TIME set to 0, not focusing con = %p.\n", nc); + set_focus = false; + } + + FREE(wm_user_time_reply); + } else { + xcb_discard_reply(conn, wm_user_time_cookie.sequence); + } + /* Defer setting focus after the 'new' event has been sent to ensure the * proper window event sequence. */ if (set_focus && !nc->window->doesnt_accept_focus && nc->mapped) { @@ -552,6 +607,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki * needs to be on the final workspace first. */ con_set_urgency(nc, urgency_hint); + /* Update _NET_WM_DESKTOP. We invalidate the cached value first to force an update. */ + cwindow->wm_desktop = NET_WM_DESKTOP_NONE; + ewmh_update_wm_desktop(); + + /* If a sticky window was mapped onto another workspace, make sure to pop it to the front. */ + output_push_sticky_windows(focused); + geom_out: free(geom); out: diff --git a/src/match.c b/src/match.c index 2cdf4f45..d072b85f 100644 --- a/src/match.c +++ b/src/match.c @@ -223,11 +223,25 @@ bool match_matches_window(Match *match, i3Window *window) { } } - /* We don’t check the mark because this function is not even called when - * the mark would have matched - it is checked in cmdparse.y itself */ if (match->mark != NULL) { - LOG("mark does not match\n"); - return false; + if ((con = con_by_window_id(window->id)) == NULL) + return false; + + bool matched = false; + mark_t *mark; + TAILQ_FOREACH(mark, &(con->marks_head), marks) { + if (regex_matches(match->mark, mark->name)) { + matched = true; + break; + } + } + + if (matched) { + LOG("mark matches\n"); + } else { + LOG("mark does not match\n"); + return false; + } } return true; @@ -238,6 +252,7 @@ bool match_matches_window(Match *match, i3Window *window) { * */ void match_free(Match *match) { + FREE(match->error); regex_free(match->title); regex_free(match->application); regex_free(match->class); @@ -274,13 +289,19 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) { } if (strcmp(ctype, "con_id") == 0) { + if (strcmp(cvalue, "__focused__") == 0) { + match->con_id = focused; + return; + } + char *end; - long parsed = strtol(cvalue, &end, 10); + long parsed = strtol(cvalue, &end, 0); if (parsed == LONG_MIN || parsed == LONG_MAX || parsed < 0 || (end && *end != '\0')) { ELOG("Could not parse con id \"%s\"\n", cvalue); + match->error = sstrdup("invalid con_id"); } else { match->con_id = (Con *)parsed; DLOG("id as int = %p\n", match->con_id); @@ -290,12 +311,13 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) { if (strcmp(ctype, "id") == 0) { char *end; - long parsed = strtol(cvalue, &end, 10); + long parsed = strtol(cvalue, &end, 0); if (parsed == LONG_MIN || parsed == LONG_MAX || parsed < 0 || (end && *end != '\0')) { ELOG("Could not parse window id \"%s\"\n", cvalue); + match->error = sstrdup("invalid id"); } else { match->id = parsed; DLOG("window id as int = %d\n", match->id); @@ -304,26 +326,30 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) { } if (strcmp(ctype, "window_type") == 0) { - if (strcasecmp(cvalue, "normal") == 0) + if (strcasecmp(cvalue, "normal") == 0) { match->window_type = A__NET_WM_WINDOW_TYPE_NORMAL; - else if (strcasecmp(cvalue, "dialog") == 0) + } else if (strcasecmp(cvalue, "dialog") == 0) { match->window_type = A__NET_WM_WINDOW_TYPE_DIALOG; - else if (strcasecmp(cvalue, "utility") == 0) + } else if (strcasecmp(cvalue, "utility") == 0) { match->window_type = A__NET_WM_WINDOW_TYPE_UTILITY; - else if (strcasecmp(cvalue, "toolbar") == 0) + } else if (strcasecmp(cvalue, "toolbar") == 0) { match->window_type = A__NET_WM_WINDOW_TYPE_TOOLBAR; - else if (strcasecmp(cvalue, "splash") == 0) + } else if (strcasecmp(cvalue, "splash") == 0) { match->window_type = A__NET_WM_WINDOW_TYPE_SPLASH; - else if (strcasecmp(cvalue, "menu") == 0) + } else if (strcasecmp(cvalue, "menu") == 0) { match->window_type = A__NET_WM_WINDOW_TYPE_MENU; - else if (strcasecmp(cvalue, "dropdown_menu") == 0) + } else if (strcasecmp(cvalue, "dropdown_menu") == 0) { match->window_type = A__NET_WM_WINDOW_TYPE_DROPDOWN_MENU; - else if (strcasecmp(cvalue, "popup_menu") == 0) + } else if (strcasecmp(cvalue, "popup_menu") == 0) { match->window_type = A__NET_WM_WINDOW_TYPE_POPUP_MENU; - else if (strcasecmp(cvalue, "tooltip") == 0) + } else if (strcasecmp(cvalue, "tooltip") == 0) { match->window_type = A__NET_WM_WINDOW_TYPE_TOOLTIP; - else + } else if (strcasecmp(cvalue, "notification") == 0) { + match->window_type = A__NET_WM_WINDOW_TYPE_NOTIFICATION; + } else { ELOG("unknown window_type value \"%s\"\n", cvalue); + match->error = sstrdup("unknown window_type value"); + } return; } diff --git a/src/move.c b/src/move.c index bd228a1c..87f78ee3 100644 --- a/src/move.c +++ b/src/move.c @@ -206,6 +206,7 @@ void tree_move(Con *con, int direction) { DLOG("Swapped.\n"); ipc_send_window_event("move", con); + ewmh_update_wm_desktop(); return; } @@ -214,6 +215,7 @@ void tree_move(Con *con, int direction) { * try to move it to a workspace on a different output */ move_to_output_directed(con, direction); ipc_send_window_event("move", con); + ewmh_update_wm_desktop(); return; } @@ -274,4 +276,5 @@ end: tree_flatten(croot); ipc_send_window_event("move", con); + ewmh_update_wm_desktop(); } diff --git a/src/randr.c b/src/randr.c index 81a33e62..6753f8a6 100644 --- a/src/randr.c +++ b/src/randr.c @@ -741,7 +741,7 @@ void randr_query_outputs(void) { if (current != next && TAILQ_EMPTY(&(current->focus_head))) { /* the workspace is empty and not focused, get rid of it */ DLOG("Getting rid of current = %p / %s (empty, unfocused)\n", current, current->name); - tree_close(current, DONT_KILL_WINDOW, false, false); + tree_close_internal(current, DONT_KILL_WINDOW, false, false); continue; } DLOG("Detaching current = %p / %s\n", current, current->name); @@ -783,7 +783,7 @@ void randr_query_outputs(void) { } DLOG("destroying disappearing con %p\n", output->con); - tree_close(output->con, DONT_KILL_WINDOW, true, false); + tree_close_internal(output->con, DONT_KILL_WINDOW, true, false); DLOG("Done. Should be fine now\n"); output->con = NULL; } diff --git a/src/render.c b/src/render.c index 7ada19eb..9fa40f03 100644 --- a/src/render.c +++ b/src/render.c @@ -12,9 +12,14 @@ */ #include "all.h" -/* change this to 'true' if you want to have additional borders around every - * container (for debugging purposes) */ -static bool show_debug_borders = false; +/* Forward declarations */ +static int *precalculate_sizes(Con *con, render_params *p); +static void render_root(Con *con, Con *fullscreen); +static void render_output(Con *con); +static void render_con_split(Con *con, Con *child, render_params *p, int i); +static void render_con_stacked(Con *con, Con *child, render_params *p, int i); +static void render_con_tabbed(Con *con, Con *child, render_params *p, int i); +static void render_con_dockarea(Con *con, Con *child, render_params *p); /* * Returns the height for the decorations @@ -26,12 +31,270 @@ int render_deco_height(void) { return deco_height; } +/* + * "Renders" the given container (and its children), meaning that all rects are + * updated correctly. Note that this function does not call any xcb_* + * functions, so the changes are completely done in memory only (and + * side-effect free). As soon as you call x_push_changes(), the changes will be + * updated in X11. + * + */ +void render_con(Con *con, bool render_fullscreen) { + render_params params = { + .rect = con->rect, + .x = con->rect.x, + .y = con->rect.y, + .children = con_num_children(con)}; + + DLOG("Rendering %snode %p / %s / layout %d / children %d\n", + (render_fullscreen ? "fullscreen " : ""), con, con->name, con->layout, + params.children); + + int i = 0; + con->mapped = true; + + /* if this container contains a window, set the coordinates */ + if (con->window) { + /* depending on the border style, the rect of the child window + * needs to be smaller */ + Rect *inset = &(con->window_rect); + *inset = (Rect){0, 0, con->rect.width, con->rect.height}; + if (!render_fullscreen) + *inset = rect_add(*inset, con_border_style_rect(con)); + + /* Obey x11 border */ + inset->width -= (2 * con->border_width); + inset->height -= (2 * con->border_width); + + /* Obey the aspect ratio, if any, unless we are in fullscreen mode. + * + * The spec isn’t explicit on whether the aspect ratio hints should be + * respected during fullscreen mode. Other WMs such as Openbox don’t do + * that, and this post suggests that this is the correct way to do it: + * http://mail.gnome.org/archives/wm-spec-list/2003-May/msg00007.html + * + * Ignoring aspect ratio during fullscreen was necessary to fix MPlayer + * subtitle rendering, see http://bugs.i3wm.org/594 */ + if (!render_fullscreen && con->window->aspect_ratio > 0.0) { + DLOG("aspect_ratio = %f, current width/height are %d/%d\n", + con->window->aspect_ratio, inset->width, inset->height); + double new_height = inset->height + 1; + int new_width = inset->width; + + while (new_height > inset->height) { + new_height = (1.0 / con->window->aspect_ratio) * new_width; + + if (new_height > inset->height) + new_width--; + } + /* Center the window */ + inset->y += ceil(inset->height / 2) - floor((new_height + .5) / 2); + inset->x += ceil(inset->width / 2) - floor(new_width / 2); + + inset->height = new_height + .5; + inset->width = new_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); + } + + /* Check for fullscreen nodes */ + Con *fullscreen = NULL; + if (con->type != CT_OUTPUT) { + fullscreen = con_get_fullscreen_con(con, (con->type == CT_ROOT ? CF_GLOBAL : CF_OUTPUT)); + } + if (fullscreen) { + fullscreen->rect = params.rect; + x_raise_con(fullscreen); + render_con(fullscreen, true); + /* Fullscreen containers are either global (underneath the CT_ROOT + * container) or per-output (underneath the CT_CONTENT container). For + * global fullscreen containers, we cannot abort rendering here yet, + * because the floating windows (with popup_during_fullscreen smart) + * have not yet been rendered (see the CT_ROOT code path below). See + * also http://bugs.i3wm.org/1393 */ + if (con->type != CT_ROOT) { + return; + } + } + + /* find the height for the decorations */ + params.deco_height = render_deco_height(); + + /* precalculate the sizes to be able to correct rounding errors */ + params.sizes = precalculate_sizes(con, ¶ms); + + if (con->layout == L_OUTPUT) { + /* Skip i3-internal outputs */ + if (con_is_internal(con)) + goto free_params; + render_output(con); + } else if (con->type == CT_ROOT) { + render_root(con, fullscreen); + } else { + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + assert(params.children > 0); + + if (con->layout == L_SPLITH || con->layout == L_SPLITV) { + render_con_split(con, child, ¶ms, i); + } else if (con->layout == L_STACKED) { + render_con_stacked(con, child, ¶ms, i); + } else if (con->layout == L_TABBED) { + render_con_tabbed(con, child, ¶ms, i); + } else if (con->layout == L_DOCKAREA) { + render_con_dockarea(con, child, ¶ms); + } + + DLOG("child at (%d, %d) with (%d x %d)\n", + child->rect.x, child->rect.y, child->rect.width, child->rect.height); + x_raise_con(child); + render_con(child, false); + i++; + } + + /* in a stacking or tabbed container, we ensure the focused client is raised */ + if (con->layout == L_STACKED || con->layout == L_TABBED) { + TAILQ_FOREACH_REVERSE(child, &(con->focus_head), focus_head, focused) + x_raise_con(child); + if ((child = TAILQ_FIRST(&(con->focus_head)))) { + /* By rendering the stacked container again, we handle the case + * that we have a non-leaf-container inside the stack. In that + * case, the children of the non-leaf-container need to be raised + * aswell. */ + render_con(child, false); + } + + if (params.children != 1) + /* Raise the stack con itself. This will put the stack decoration on + * top of every stack window. That way, when a new window is opened in + * the stack, the old window will not obscure part of the decoration + * (it’s unmapped afterwards). */ + x_raise_con(con); + } + } + +free_params: + FREE(params.sizes); +} + +static int *precalculate_sizes(Con *con, render_params *p) { + int *sizes = smalloc(p->children * sizeof(int)); + if ((con->layout == L_SPLITH || con->layout == L_SPLITV) && p->children > 0) { + assert(!TAILQ_EMPTY(&con->nodes_head)); + + Con *child; + int i = 0, assigned = 0; + int total = con_orientation(con) == HORIZ ? p->rect.width : p->rect.height; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + double percentage = child->percent > 0.0 ? child->percent : 1.0 / p->children; + assigned += sizes[i++] = percentage * total; + } + assert(assigned == total || + (assigned > total && assigned - total <= p->children * 2) || + (assigned < total && total - assigned <= p->children * 2)); + int signal = assigned < total ? 1 : -1; + while (assigned != total) { + for (i = 0; i < p->children && assigned != total; ++i) { + sizes[i] += signal; + assigned += signal; + } + } + } + + return sizes; +} + +static void render_root(Con *con, Con *fullscreen) { + Con *output; + if (!fullscreen) { + TAILQ_FOREACH(output, &(con->nodes_head), nodes) { + render_con(output, false); + } + } + + /* We need to render floating windows after rendering all outputs’ + * tiling windows because they need to be on top of *every* output at + * all times. This is important when the user places floating + * windows/containers so that they overlap on another output. */ + DLOG("Rendering floating windows:\n"); + TAILQ_FOREACH(output, &(con->nodes_head), nodes) { + if (con_is_internal(output)) + continue; + /* Get the active workspace of that output */ + Con *content = output_get_content(output); + if (!content || TAILQ_EMPTY(&(content->focus_head))) { + DLOG("Skipping this output because it is currently being destroyed.\n"); + continue; + } + Con *workspace = TAILQ_FIRST(&(content->focus_head)); + Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT); + Con *child; + TAILQ_FOREACH(child, &(workspace->floating_head), floating_windows) { + /* Don’t render floating windows when there is a fullscreen window + * on that workspace. Necessary to make floating fullscreen work + * correctly (ticket #564). */ + /* If there is no fullscreen->window, this cannot be a + * transient window, so we _know_ we need to skip it. This + * happens during restarts where the container already exists, + * but the window was not yet associated. */ + if (fullscreen != NULL && fullscreen->window == NULL) + continue; + if (fullscreen != NULL && fullscreen->window != NULL) { + Con *floating_child = con_descend_focused(child); + Con *transient_con = floating_child; + bool is_transient_for = false; + /* Exception to the above rule: smart + * popup_during_fullscreen handling (popups belonging to + * the fullscreen app will be rendered). */ + while (transient_con != NULL && + transient_con->window != NULL && + transient_con->window->transient_for != XCB_NONE) { + DLOG("transient_con = 0x%08x, transient_con->window->transient_for = 0x%08x, fullscreen_id = 0x%08x\n", + transient_con->window->id, transient_con->window->transient_for, fullscreen->window->id); + if (transient_con->window->transient_for == fullscreen->window->id) { + is_transient_for = true; + break; + } + Con *next_transient = con_by_window_id(transient_con->window->transient_for); + if (next_transient == NULL) + break; + /* Some clients (e.g. x11-ssh-askpass) actually set + * WM_TRANSIENT_FOR to their own window id, so break instead of + * looping endlessly. */ + if (transient_con == next_transient) + break; + transient_con = next_transient; + } + + if (!is_transient_for) + continue; + else { + DLOG("Rendering floating child even though in fullscreen mode: " + "floating->transient_for (0x%08x) --> fullscreen->id (0x%08x)\n", + floating_child->window->transient_for, fullscreen->window->id); + } + } + DLOG("floating child at (%d,%d) with %d x %d\n", + child->rect.x, child->rect.y, child->rect.width, child->rect.height); + x_raise_con(child); + render_con(child, false); + } + } +} + /* * Renders a container with layout L_OUTPUT. In this layout, all CT_DOCKAREAs * get the height of their content and the remaining CT_CON gets the rest. * */ -static void render_l_output(Con *con) { +static void render_output(Con *con) { Con *child, *dockchild; int x = con->rect.x; @@ -115,347 +378,101 @@ static void render_l_output(Con *con) { } } -/* - * "Renders" the given container (and its children), meaning that all rects are - * updated correctly. Note that this function does not call any xcb_* - * functions, so the changes are completely done in memory only (and - * side-effect free). As soon as you call x_push_changes(), the changes will be - * updated in X11. - * - */ -void render_con(Con *con, bool render_fullscreen) { - int children = con_num_children(con); - DLOG("Rendering %snode %p / %s / layout %d / children %d\n", - (render_fullscreen ? "fullscreen " : ""), con, con->name, con->layout, - children); - - /* Copy container rect, subtract container border */ - /* This is the actually usable space inside this container for clients */ - Rect rect = con->rect; - - /* Display a border if this is a leaf node. For container nodes, we don’t - * draw borders (except when in debug mode) */ - if (show_debug_borders) { - rect.x += 2; - rect.y += 2; - rect.width -= 2 * 2; - rect.height -= 2 * 2; - } - - int x = rect.x; - int y = rect.y; - - int i = 0; - - con->mapped = true; - - /* if this container contains a window, set the coordinates */ - if (con->window) { - /* depending on the border style, the rect of the child window - * needs to be smaller */ - Rect *inset = &(con->window_rect); - *inset = (Rect){0, 0, con->rect.width, con->rect.height}; - if (!render_fullscreen) - *inset = rect_add(*inset, con_border_style_rect(con)); - - /* Obey x11 border */ - inset->width -= (2 * con->border_width); - inset->height -= (2 * con->border_width); - - /* Obey the aspect ratio, if any, unless we are in fullscreen mode. - * - * The spec isn’t explicit on whether the aspect ratio hints should be - * respected during fullscreen mode. Other WMs such as Openbox don’t do - * that, and this post suggests that this is the correct way to do it: - * http://mail.gnome.org/archives/wm-spec-list/2003-May/msg00007.html - * - * Ignoring aspect ratio during fullscreen was necessary to fix MPlayer - * subtitle rendering, see http://bugs.i3wm.org/594 */ - if (!render_fullscreen && - con->window->aspect_ratio > 0.0) { - DLOG("aspect_ratio = %f, current width/height are %d/%d\n", - con->window->aspect_ratio, inset->width, inset->height); - double new_height = inset->height + 1; - int new_width = inset->width; - - while (new_height > inset->height) { - new_height = (1.0 / con->window->aspect_ratio) * new_width; - - if (new_height > inset->height) - new_width--; - } - /* Center the window */ - inset->y += ceil(inset->height / 2) - floor((new_height + .5) / 2); - inset->x += ceil(inset->width / 2) - floor(new_width / 2); - - inset->height = new_height + .5; - inset->width = new_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); - } - - /* Check for fullscreen nodes */ - Con *fullscreen = NULL; - if (con->type != CT_OUTPUT) { - fullscreen = con_get_fullscreen_con(con, (con->type == CT_ROOT ? CF_GLOBAL : CF_OUTPUT)); - } - if (fullscreen) { - fullscreen->rect = rect; - x_raise_con(fullscreen); - render_con(fullscreen, true); - /* Fullscreen containers are either global (underneath the CT_ROOT - * container) or per-output (underneath the CT_CONTENT container). For - * global fullscreen containers, we cannot abort rendering here yet, - * because the floating windows (with popup_during_fullscreen smart) - * have not yet been rendered (see the CT_ROOT code path below). See - * also http://bugs.i3wm.org/1393 */ - if (con->type != CT_ROOT) { - return; - } - } - - /* find the height for the decorations */ - int deco_height = render_deco_height(); - - /* precalculate the sizes to be able to correct rounding errors */ - int sizes[children]; - memset(sizes, 0, children * sizeof(int)); - 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(con) == HORIZ ? rect.width : rect.height; - TAILQ_FOREACH(child, &(con->nodes_head), nodes) { - double percentage = child->percent > 0.0 ? child->percent : 1.0 / children; - assigned += sizes[i++] = percentage * total; - } - assert(assigned == total || - (assigned > total && assigned - total <= children * 2) || - (assigned < total && total - assigned <= children * 2)); - int signal = assigned < total ? 1 : -1; - while (assigned != total) { - for (i = 0; i < children && assigned != total; ++i) { - sizes[i] += signal; - assigned += signal; - } - } - } - - if (con->layout == L_OUTPUT) { - /* Skip i3-internal outputs */ - if (con_is_internal(con)) - return; - render_l_output(con); - } else if (con->type == CT_ROOT) { - Con *output; - if (!fullscreen) { - TAILQ_FOREACH(output, &(con->nodes_head), nodes) { - render_con(output, false); - } - } - - /* We need to render floating windows after rendering all outputs’ - * tiling windows because they need to be on top of *every* output at - * all times. This is important when the user places floating - * windows/containers so that they overlap on another output. */ - DLOG("Rendering floating windows:\n"); - TAILQ_FOREACH(output, &(con->nodes_head), nodes) { - if (con_is_internal(output)) - continue; - /* Get the active workspace of that output */ - Con *content = output_get_content(output); - if (!content || TAILQ_EMPTY(&(content->focus_head))) { - DLOG("Skipping this output because it is currently being destroyed.\n"); - continue; - } - Con *workspace = TAILQ_FIRST(&(content->focus_head)); - Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT); - Con *child; - TAILQ_FOREACH(child, &(workspace->floating_head), floating_windows) { - /* Don’t render floating windows when there is a fullscreen window - * on that workspace. Necessary to make floating fullscreen work - * correctly (ticket #564). */ - /* If there is no fullscreen->window, this cannot be a - * transient window, so we _know_ we need to skip it. This - * happens during restarts where the container already exists, - * but the window was not yet associated. */ - if (fullscreen != NULL && fullscreen->window == NULL) - continue; - if (fullscreen != NULL && fullscreen->window != NULL) { - Con *floating_child = con_descend_focused(child); - Con *transient_con = floating_child; - bool is_transient_for = false; - /* Exception to the above rule: smart - * popup_during_fullscreen handling (popups belonging to - * the fullscreen app will be rendered). */ - while (transient_con != NULL && - transient_con->window != NULL && - transient_con->window->transient_for != XCB_NONE) { - DLOG("transient_con = 0x%08x, transient_con->window->transient_for = 0x%08x, fullscreen_id = 0x%08x\n", - transient_con->window->id, transient_con->window->transient_for, fullscreen->window->id); - if (transient_con->window->transient_for == fullscreen->window->id) { - is_transient_for = true; - break; - } - Con *next_transient = con_by_window_id(transient_con->window->transient_for); - if (next_transient == NULL) - break; - /* Some clients (e.g. x11-ssh-askpass) actually set - * WM_TRANSIENT_FOR to their own window id, so break instead of - * looping endlessly. */ - if (transient_con == next_transient) - break; - transient_con = next_transient; - } - - if (!is_transient_for) - continue; - else { - DLOG("Rendering floating child even though in fullscreen mode: " - "floating->transient_for (0x%08x) --> fullscreen->id (0x%08x)\n", - floating_child->window->transient_for, fullscreen->window->id); - } - } - DLOG("floating child at (%d,%d) with %d x %d\n", - child->rect.x, child->rect.y, child->rect.width, child->rect.height); - x_raise_con(child); - render_con(child, false); - } - } +static void render_con_split(Con *con, Con *child, render_params *p, int i) { + assert(con->layout == L_SPLITH || con->layout == L_SPLITV); + if (con->layout == L_SPLITH) { + child->rect.x = p->x; + child->rect.y = p->y; + child->rect.width = p->sizes[i]; + child->rect.height = p->rect.height; + p->x += child->rect.width; } else { - /* FIXME: refactor this into separate functions: */ - Con *child; - TAILQ_FOREACH(child, &(con->nodes_head), nodes) { - assert(children > 0); + child->rect.x = p->x; + child->rect.y = p->y; + child->rect.width = p->rect.width; + child->rect.height = p->sizes[i]; + p->y += child->rect.height; + } - /* default layout */ - 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]; - child->rect.height = rect.height; - x += child->rect.width; - } else { - child->rect.x = x; - child->rect.y = y; - child->rect.width = rect.width; - child->rect.height = sizes[i]; - y += child->rect.height; - } + /* first we have the decoration, if this is a leaf node */ + if (con_is_leaf(child)) { + if (child->border_style == BS_NORMAL) { + /* TODO: make a function for relative coords? */ + child->deco_rect.x = child->rect.x - con->rect.x; + child->deco_rect.y = child->rect.y - con->rect.y; - /* first we have the decoration, if this is a leaf node */ - if (con_is_leaf(child)) { - if (child->border_style == BS_NORMAL) { - /* TODO: make a function for relative coords? */ - child->deco_rect.x = child->rect.x - con->rect.x; - child->deco_rect.y = child->rect.y - con->rect.y; + child->rect.y += p->deco_height; + child->rect.height -= p->deco_height; - child->rect.y += deco_height; - child->rect.height -= deco_height; - - child->deco_rect.width = child->rect.width; - child->deco_rect.height = deco_height; - } else { - child->deco_rect.x = 0; - child->deco_rect.y = 0; - child->deco_rect.width = 0; - child->deco_rect.height = 0; - } - } - } - - /* stacked layout */ - else if (con->layout == L_STACKED) { - child->rect.x = x; - child->rect.y = y; - child->rect.width = rect.width; - child->rect.height = rect.height; - - child->deco_rect.x = x - con->rect.x; - child->deco_rect.y = y - con->rect.y + (i * deco_height); - child->deco_rect.width = child->rect.width; - child->deco_rect.height = deco_height; - - if (children > 1 || (child->border_style != BS_PIXEL && child->border_style != BS_NONE)) { - child->rect.y += (deco_height * children); - child->rect.height -= (deco_height * children); - } - } - - /* tabbed layout */ - else if (con->layout == L_TABBED) { - child->rect.x = x; - child->rect.y = y; - child->rect.width = rect.width; - child->rect.height = rect.height; - - child->deco_rect.width = floor((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; - - /* Since the tab width may be something like 31,6 px per tab, we - * let the last tab have all the extra space (0,6 * children). */ - if (i == (children - 1)) { - child->deco_rect.width += (child->rect.width - (child->deco_rect.x + child->deco_rect.width)); - } - - if (children > 1 || (child->border_style != BS_PIXEL && child->border_style != BS_NONE)) { - child->rect.y += deco_height; - child->rect.height -= deco_height; - child->deco_rect.height = deco_height; - } else { - child->deco_rect.height = (child->border_style == BS_PIXEL ? 1 : 0); - } - } - - /* dockarea layout */ - else if (con->layout == L_DOCKAREA) { - child->rect.x = x; - child->rect.y = y; - child->rect.width = rect.width; - child->rect.height = child->geometry.height; - - child->deco_rect.x = 0; - child->deco_rect.y = 0; - child->deco_rect.width = 0; - child->deco_rect.height = 0; - y += child->rect.height; - } - - DLOG("child at (%d, %d) with (%d x %d)\n", - child->rect.x, child->rect.y, child->rect.width, child->rect.height); - x_raise_con(child); - render_con(child, false); - i++; - } - - /* in a stacking or tabbed container, we ensure the focused client is raised */ - if (con->layout == L_STACKED || con->layout == L_TABBED) { - TAILQ_FOREACH_REVERSE(child, &(con->focus_head), focus_head, focused) - x_raise_con(child); - if ((child = TAILQ_FIRST(&(con->focus_head)))) { - /* By rendering the stacked container again, we handle the case - * that we have a non-leaf-container inside the stack. In that - * case, the children of the non-leaf-container need to be raised - * aswell. */ - render_con(child, false); - } - - if (children != 1) - /* Raise the stack con itself. This will put the stack decoration on - * top of every stack window. That way, when a new window is opened in - * the stack, the old window will not obscure part of the decoration - * (it’s unmapped afterwards). */ - x_raise_con(con); + child->deco_rect.width = child->rect.width; + child->deco_rect.height = p->deco_height; + } else { + child->deco_rect.x = 0; + child->deco_rect.y = 0; + child->deco_rect.width = 0; + child->deco_rect.height = 0; } } } + +static void render_con_stacked(Con *con, Con *child, render_params *p, int i) { + assert(con->layout == L_STACKED); + + child->rect.x = p->x; + child->rect.y = p->y; + child->rect.width = p->rect.width; + child->rect.height = p->rect.height; + + child->deco_rect.x = p->x - con->rect.x; + child->deco_rect.y = p->y - con->rect.y + (i * p->deco_height); + child->deco_rect.width = child->rect.width; + child->deco_rect.height = p->deco_height; + + if (p->children > 1 || (child->border_style != BS_PIXEL && child->border_style != BS_NONE)) { + child->rect.y += (p->deco_height * p->children); + child->rect.height -= (p->deco_height * p->children); + } +} + +static void render_con_tabbed(Con *con, Con *child, render_params *p, int i) { + assert(con->layout == L_TABBED); + + child->rect.x = p->x; + child->rect.y = p->y; + child->rect.width = p->rect.width; + child->rect.height = p->rect.height; + + child->deco_rect.width = floor((float)child->rect.width / p->children); + child->deco_rect.x = p->x - con->rect.x + i * child->deco_rect.width; + child->deco_rect.y = p->y - con->rect.y; + + /* Since the tab width may be something like 31,6 px per tab, we + * let the last tab have all the extra space (0,6 * children). */ + if (i == (p->children - 1)) { + child->deco_rect.width += (child->rect.width - (child->deco_rect.x + child->deco_rect.width)); + } + + if (p->children > 1 || (child->border_style != BS_PIXEL && child->border_style != BS_NONE)) { + child->rect.y += p->deco_height; + child->rect.height -= p->deco_height; + child->deco_rect.height = p->deco_height; + } else { + child->deco_rect.height = (child->border_style == BS_PIXEL ? 1 : 0); + } +} + +static void render_con_dockarea(Con *con, Con *child, render_params *p) { + assert(con->layout == L_DOCKAREA); + + child->rect.x = p->x; + child->rect.y = p->y; + child->rect.width = p->rect.width; + child->rect.height = child->geometry.height; + + child->deco_rect.x = 0; + child->deco_rect.y = 0; + child->deco_rect.width = 0; + child->deco_rect.height = 0; + p->y += child->rect.height; +} diff --git a/src/resize.c b/src/resize.c index 05fe5055..5d9eb2eb 100644 --- a/src/resize.c +++ b/src/resize.c @@ -11,8 +11,6 @@ */ #include "all.h" -extern xcb_connection_t *conn; - /* * This is an ugly data structure which we need because there is no standard * way of having nested functions (only available as a gcc extension at the @@ -146,7 +144,7 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, } mask = XCB_CW_BACK_PIXEL; - values[0] = config.client.focused.border; + values[0] = config.client.focused.border.colorpixel; mask |= XCB_CW_OVERRIDE_REDIRECT; values[1] = 1; diff --git a/src/restore_layout.c b/src/restore_layout.c index 439d23cc..7e1b78ae 100644 --- a/src/restore_layout.c +++ b/src/restore_layout.c @@ -13,6 +13,10 @@ */ #include "all.h" +#ifdef I3_ASAN_ENABLED +#include +#endif + typedef struct placeholder_state { /** The X11 placeholder window. */ xcb_window_t window; @@ -98,7 +102,11 @@ void restore_connect(void) { free(state); } - free(restore_conn); + /* xcb_disconnect leaks memory in libxcb versions earlier than 1.11, + * but it’s the right function to call. See + * http://cgit.freedesktop.org/xcb/libxcb/commit/src/xcb_conn.c?id=4dcbfd77b + */ + xcb_disconnect(restore_conn); free(xcb_watcher); free(xcb_check); free(xcb_prepare); @@ -106,8 +114,15 @@ void restore_connect(void) { int screen; restore_conn = xcb_connect(NULL, &screen); - if (restore_conn == NULL || xcb_connection_has_error(restore_conn)) + if (restore_conn == NULL || xcb_connection_has_error(restore_conn)) { + if (restore_conn != NULL) { + xcb_disconnect(restore_conn); + } +#ifdef I3_ASAN_ENABLED + __lsan_do_leak_check(); +#endif errx(EXIT_FAILURE, "Cannot open display\n"); + } xcb_watcher = scalloc(1, sizeof(struct ev_io)); xcb_check = scalloc(1, sizeof(struct ev_check)); @@ -125,7 +140,7 @@ void restore_connect(void) { static void update_placeholder_contents(placeholder_state *state) { xcb_change_gc(restore_conn, state->gc, XCB_GC_FOREGROUND, - (uint32_t[]){config.client.placeholder.background}); + (uint32_t[]){config.client.placeholder.background.colorpixel}); xcb_poly_fill_rectangle(restore_conn, state->pixmap, state->gc, 1, (xcb_rectangle_t[]){{0, 0, state->rect.width, state->rect.height}}); @@ -161,7 +176,7 @@ static void update_placeholder_contents(placeholder_state *state) { DLOG("con %p (placeholder 0x%08x) line %d: %s\n", state->con, state->window, n, serialized); i3String *str = i3string_from_utf8(serialized); - draw_text(str, state->pixmap, state->gc, 2, (n * (config.font.height + 2)) + 2, state->rect.width - 2); + draw_text(str, state->pixmap, state->gc, NULL, 2, (n * (config.font.height + 2)) + 2, state->rect.width - 2); i3string_free(str); n++; free(serialized); @@ -172,7 +187,7 @@ static void update_placeholder_contents(placeholder_state *state) { int text_width = predict_text_width(line); int x = (state->rect.width / 2) - (text_width / 2); int y = (state->rect.height / 2) - (config.font.height / 2); - draw_text(line, state->pixmap, state->gc, x, y, text_width); + draw_text(line, state->pixmap, state->gc, NULL, x, y, text_width); i3string_free(line); xcb_flush(conn); xcb_aux_sync(conn); @@ -193,7 +208,7 @@ static void open_placeholder_window(Con *con) { true, XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, (uint32_t[]){ - config.client.placeholder.background, + config.client.placeholder.background.colorpixel, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY, }); /* Make i3 not focus this window. */ diff --git a/src/sighandler.c b/src/sighandler.c index ceaa4842..80d2fae2 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -141,7 +141,7 @@ static int sig_draw_window(xcb_window_t win, int width, int height, int font_hei xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner); /* restore font color */ - set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000")); + set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); char *bt_colour = "#FFFFFF"; if (backtrace_done < 0) @@ -152,14 +152,14 @@ static int sig_draw_window(xcb_window_t win, int width, int height, int font_hei 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")); + set_font_colors(pixmap_gc, draw_util_hex_to_color(bt_colour), draw_util_hex_to_color("#000000")); - draw_text(crash_text_i3strings[i], pixmap, pixmap_gc, + draw_text(crash_text_i3strings[i], pixmap, pixmap_gc, NULL, 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")); + set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); } /* Copy the contents of the pixmap to the real window */ diff --git a/src/tree.c b/src/tree.c index 1d06d874..0c301209 100644 --- a/src/tree.c +++ b/src/tree.c @@ -84,6 +84,7 @@ bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry) { focused = croot; tree_append_json(focused, globbed, NULL); + free(globbed); DLOG("appended tree, using new root\n"); croot = TAILQ_FIRST(&(croot->nodes_head)); @@ -185,11 +186,11 @@ static bool _is_con_mapped(Con *con) { * recursively while deleting a containers children. * * The force_set_focus flag is specified in the case of killing a floating - * window: tree_close() will be invoked for the CT_FLOATINGCON (the parent + * window: tree_close_internal() will be invoked for the CT_FLOATINGCON (the parent * container) and focus should be set there. * */ -bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool force_set_focus) { +bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool force_set_focus) { bool was_mapped = con->mapped; Con *parent = con->parent; @@ -219,7 +220,7 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool for (child = TAILQ_FIRST(&(con->nodes_head)); child;) { nextchild = TAILQ_NEXT(child, nodes); DLOG("killing child=%p\n", child); - if (!tree_close(child, kill_window, true, false)) + if (!tree_close_internal(child, kill_window, true, false)) abort_kill = true; child = nextchild; } @@ -265,11 +266,8 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool add_ignore_event(cookie.sequence, 0); } ipc_send_window_event("close", con); - FREE(con->window->class_class); - FREE(con->window->class_instance); - i3string_free(con->window->name); - FREE(con->window->ran_assignments); - FREE(con->window); + window_free(con->window); + con->window = NULL; } Con *ws = con_get_workspace(con); @@ -310,7 +308,7 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool * underlying container, see ticket #660. * * Rendering has to be avoided when dont_kill_parent is set (when - * tree_close calls itself recursively) because the tree is in a + * tree_close_internal calls itself recursively) because the tree is in a * non-renderable state during that time. */ if (!dont_kill_parent) tree_render(); @@ -320,13 +318,19 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool if (con_is_floating(con)) { DLOG("Container was floating, killing floating container\n"); - tree_close(parent, DONT_KILL_WINDOW, false, (con == focused)); + tree_close_internal(parent, DONT_KILL_WINDOW, false, (con == focused)); DLOG("parent container killed\n"); } free(con->name); FREE(con->deco_render_params); TAILQ_REMOVE(&all_cons, con, all_cons); + while (!TAILQ_EMPTY(&(con->swallow_head))) { + Match *match = TAILQ_FIRST(&(con->swallow_head)); + TAILQ_REMOVE(&(con->swallow_head), match, matches); + match_free(match); + free(match); + } free(con); /* in the case of floating windows, we already focused another container @@ -362,34 +366,6 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool return true; } -/* - * Closes the current container using tree_close(). - * - */ -void tree_close_con(kill_window_t kill_window) { - assert(focused != NULL); - - /* There *should* be no possibility to focus outputs / root container */ - assert(focused->type != CT_OUTPUT); - assert(focused->type != CT_ROOT); - - if (focused->type == CT_WORKSPACE) { - DLOG("Workspaces cannot be close, closing all children instead\n"); - Con *child, *nextchild; - for (child = TAILQ_FIRST(&(focused->focus_head)); child;) { - nextchild = TAILQ_NEXT(child, focused); - DLOG("killing child=%p\n", child); - tree_close(child, kill_window, false, false); - child = nextchild; - } - - return; - } - - /* Kill con */ - tree_close(focused, kill_window, false, false); -} - /* * Splits (horizontally or vertically) the given container by creating a new * container which contains the old one and the future ones. @@ -773,7 +749,7 @@ void tree_flatten(Con *con) { /* 4: close the redundant cons */ DLOG("closing redundant cons\n"); - tree_close(con, DONT_KILL_WINDOW, true, false); + tree_close_internal(con, DONT_KILL_WINDOW, true, false); /* Well, we got to abort the recursion here because we destroyed the * container. However, if tree_flatten() is called sufficiently often, diff --git a/src/util.c b/src/util.c index 7a73011d..35ce8b19 100644 --- a/src/util.c +++ b/src/util.c @@ -332,6 +332,21 @@ void *memmem(const void *l, size_t l_len, const void *s, size_t s_len) { #endif +/* + * Escapes the given string if a pango font is currently used. + * If the string has to be escaped, the input string will be free'd. + * + */ +char *pango_escape_markup(char *input) { + if (!font_is_pango()) + return input; + + char *escaped = g_markup_escape_text(input, -1); + FREE(input); + + return escaped; +} + /* * 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). diff --git a/src/window.c b/src/window.c index 5898333f..d10811f5 100644 --- a/src/window.c +++ b/src/window.c @@ -11,6 +11,18 @@ */ #include "all.h" +/* + * Frees an i3Window and all its members. + * + */ +void window_free(i3Window *win) { + FREE(win->class_class); + FREE(win->class_instance); + i3string_free(win->name); + FREE(win->ran_assignments); + FREE(win); +} + /* * Updates the WM_CLASS (consisting of the class and instance) for the * given window. @@ -66,8 +78,13 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo i3string_free(win->name); win->name = i3string_from_utf8_with_length(xcb_get_property_value(prop), xcb_get_property_value_length(prop)); - if (win->title_format != NULL) - ewmh_update_visible_name(win->id, i3string_as_utf8(window_parse_title_format(win))); + + Con *con = con_by_window_id(win->id); + if (con != NULL && con->title_format != NULL) { + i3String *name = con_parse_title_format(con); + ewmh_update_visible_name(win->id, i3string_as_utf8(name)); + I3STRING_FREE(name); + } win->name_x_changed = true; LOG("_NET_WM_NAME changed to \"%s\"\n", i3string_as_utf8(win->name)); @@ -106,8 +123,13 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bo i3string_free(win->name); win->name = i3string_from_utf8_with_length(xcb_get_property_value(prop), xcb_get_property_value_length(prop)); - if (win->title_format != NULL) - ewmh_update_visible_name(win->id, i3string_as_utf8(window_parse_title_format(win))); + + Con *con = con_by_window_id(win->id); + if (con != NULL && con->title_format != NULL) { + i3String *name = con_parse_title_format(con); + ewmh_update_visible_name(win->id, i3string_as_utf8(name)); + I3STRING_FREE(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 " @@ -234,6 +256,7 @@ void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool befo */ void window_update_type(i3Window *window, xcb_get_property_reply_t *reply) { xcb_atom_t new_type = xcb_get_preferred_window_type(reply); + free(reply); if (new_type == XCB_NONE) { DLOG("cannot read _NET_WM_WINDOW_TYPE from window.\n"); return; @@ -297,6 +320,9 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo * https://people.gnome.org/~tthurman/docs/metacity/xprops_8h-source.html * http://stackoverflow.com/questions/13787553/detect-if-a-x11-window-has-decorations */ +#define MWM_HINTS_FLAGS_FIELD 0 +#define MWM_HINTS_DECORATIONS_FIELD 2 + #define MWM_HINTS_DECORATIONS (1 << 1) #define MWM_DECOR_ALL (1 << 0) #define MWM_DECOR_BORDER (1 << 1) @@ -310,17 +336,23 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo return; } - /* The property consists of an array of 5 uint64_t's. The first value is a bit - * mask of what properties the hint will specify. We are only interested in - * MWM_HINTS_DECORATIONS because it indicates that the second value of the + /* The property consists of an array of 5 uint32_t's. The first value is a + * bit mask of what properties the hint will specify. We are only interested + * in MWM_HINTS_DECORATIONS because it indicates that the third value of the * array tells us which decorations the window should have, each flag being - * a particular decoration. */ - uint64_t *motif_hints = (uint64_t *)xcb_get_property_value(prop); + * a particular decoration. Notice that X11 (Xlib) often mentions 32-bit + * fields which in reality are implemented using unsigned long variables + * (64-bits long on amd64 for example). On the other hand, + * xcb_get_property_value() behaves strictly according to documentation, + * i.e. returns 32-bit data fields. */ + uint32_t *motif_hints = (uint32_t *)xcb_get_property_value(prop); - if (motif_border_style != NULL && motif_hints[0] & MWM_HINTS_DECORATIONS) { - if (motif_hints[1] & MWM_DECOR_ALL || motif_hints[1] & MWM_DECOR_TITLE) + if (motif_border_style != NULL && + motif_hints[MWM_HINTS_FLAGS_FIELD] & MWM_HINTS_DECORATIONS) { + if (motif_hints[MWM_HINTS_DECORATIONS_FIELD] & MWM_DECOR_ALL || + motif_hints[MWM_HINTS_DECORATIONS_FIELD] & MWM_DECOR_TITLE) *motif_border_style = BS_NORMAL; - else if (motif_hints[1] & MWM_DECOR_BORDER) + else if (motif_hints[MWM_HINTS_DECORATIONS_FIELD] & MWM_DECOR_BORDER) *motif_border_style = BS_PIXEL; else *motif_border_style = BS_NONE; @@ -328,81 +360,10 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo FREE(prop); +#undef MWM_HINTS_FLAGS_FIELD +#undef MWM_HINTS_DECORATIONS_FIELD #undef MWM_HINTS_DECORATIONS #undef MWM_DECOR_ALL #undef MWM_DECOR_BORDER #undef MWM_DECOR_TITLE } - -/* - * Returns the window title considering the current title format. - * If no format is set, this will simply return the window's name. - * - */ -i3String *window_parse_title_format(i3Window *win) { - /* We need to ensure that we only escape the window title if pango - * is used by the current font. */ - const bool is_markup = font_is_pango(); - - char *format = win->title_format; - if (format == NULL) - return i3string_copy(win->name); - - /* We initialize these lazily so we only escape them if really necessary. */ - const char *escaped_title = NULL; - const char *escaped_class = NULL; - const char *escaped_instance = NULL; - - /* We have to first iterate over the string to see how much buffer space - * we need to allocate. */ - int buffer_len = strlen(format) + 1; - for (char *walk = format; *walk != '\0'; walk++) { - if (STARTS_WITH(walk, "%title")) { - if (escaped_title == NULL) - escaped_title = win->name == NULL ? "" : i3string_as_utf8(is_markup ? i3string_escape_markup(win->name) : win->name); - - buffer_len = buffer_len - strlen("%title") + strlen(escaped_title); - walk += strlen("%title") - 1; - } else if (STARTS_WITH(walk, "%class")) { - if (escaped_class == NULL) - escaped_class = is_markup ? g_markup_escape_text(win->class_class, -1) : win->class_class; - - buffer_len = buffer_len - strlen("%class") + strlen(escaped_class); - walk += strlen("%class") - 1; - } else if (STARTS_WITH(walk, "%instance")) { - if (escaped_instance == NULL) - escaped_instance = is_markup ? g_markup_escape_text(win->class_instance, -1) : win->class_instance; - - buffer_len = buffer_len - strlen("%instance") + strlen(escaped_instance); - walk += strlen("%instance") - 1; - } - } - - /* Now we can parse the format string. */ - char buffer[buffer_len]; - char *outwalk = buffer; - for (char *walk = format; *walk != '\0'; walk++) { - if (*walk != '%') { - *(outwalk++) = *walk; - continue; - } - - if (STARTS_WITH(walk + 1, "title")) { - outwalk += sprintf(outwalk, "%s", escaped_title); - walk += strlen("title"); - } else if (STARTS_WITH(walk + 1, "class")) { - outwalk += sprintf(outwalk, "%s", escaped_class); - walk += strlen("class"); - } else if (STARTS_WITH(walk + 1, "instance")) { - outwalk += sprintf(outwalk, "%s", escaped_instance); - walk += strlen("instance"); - } else { - *(outwalk++) = *walk; - } - } - *outwalk = '\0'; - - i3String *formatted = i3string_from_utf8(buffer); - i3string_set_markup(formatted, is_markup); - return formatted; -} diff --git a/src/workspace.c b/src/workspace.c index e7a09c70..f8d15ba1 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -101,6 +101,7 @@ Con *workspace_get(const char *num, bool *created) { ewmh_update_number_of_desktops(); ewmh_update_desktop_names(); ewmh_update_desktop_viewport(); + ewmh_update_wm_desktop(); if (created != NULL) *created = true; } else if (created != NULL) { @@ -442,7 +443,7 @@ static void _workspace_show(Con *workspace) { DLOG("old = %p / %s\n", old, (old ? old->name : "(null)")); /* Close old workspace if necessary. This must be done *after* doing - * urgency handling, because tree_close() will do a con_focus() on the next + * urgency handling, because tree_close_internal() will do a con_focus() on the next * client, which will clear the urgency flag too early. Also, there is no * way for con_focus() to know about when to clear urgency immediately and * when to defer it. */ @@ -451,7 +452,7 @@ static void _workspace_show(Con *workspace) { if (!workspace_is_visible(old)) { LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name); yajl_gen gen = ipc_marshal_workspace_event("empty", old, NULL); - tree_close(old, DONT_KILL_WINDOW, false, false); + tree_close_internal(old, DONT_KILL_WINDOW, false, false); const unsigned char *payload; ylength length; @@ -463,6 +464,7 @@ static void _workspace_show(Con *workspace) { ewmh_update_number_of_desktops(); ewmh_update_desktop_names(); ewmh_update_desktop_viewport(); + ewmh_update_wm_desktop(); } } @@ -506,12 +508,33 @@ void workspace_show_by_name(const char *num) { */ Con *workspace_next(void) { Con *current = con_get_workspace(focused); - Con *next = NULL; + Con *next = NULL, *first = NULL, *first_opposite = NULL; Con *output; if (current->num == -1) { /* If currently a named workspace, find next named workspace. */ - next = TAILQ_NEXT(current, nodes); + if ((next = TAILQ_NEXT(current, nodes)) != NULL) + return next; + bool found_current = false; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + /* Skip outputs starting with __, they are internal. */ + if (con_is_internal(output)) + continue; + NODES_FOREACH(output_get_content(output)) { + if (child->type != CT_WORKSPACE) + continue; + if (!first) + first = child; + if (!first_opposite && child->num != -1) + first_opposite = child; + if (child == current) { + found_current = true; + } else if (child->num == -1 && found_current) { + next = child; + return next; + } + } + } } else { /* If currently a numbered workspace, find next numbered workspace. */ TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { @@ -521,6 +544,10 @@ Con *workspace_next(void) { NODES_FOREACH(output_get_content(output)) { if (child->type != CT_WORKSPACE) continue; + if (!first) + first = child; + if (!first_opposite && child->num == -1) + first_opposite = child; if (child->num == -1) break; /* Need to check child against current and next because we are @@ -532,41 +559,9 @@ Con *workspace_next(void) { } } - /* Find next named workspace. */ - if (!next) { - bool found_current = false; - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { - /* Skip outputs starting with __, they are internal. */ - if (con_is_internal(output)) - continue; - NODES_FOREACH(output_get_content(output)) { - if (child->type != CT_WORKSPACE) - continue; - if (child == current) { - found_current = 1; - } else if (child->num == -1 && (current->num != -1 || found_current)) { - next = child; - goto workspace_next_end; - } - } - } - } + if (!next) + next = first_opposite ? first_opposite : first; - /* Find first workspace. */ - if (!next) { - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { - /* Skip outputs starting with __, they are internal. */ - if (con_is_internal(output)) - continue; - NODES_FOREACH(output_get_content(output)) { - if (child->type != CT_WORKSPACE) - continue; - if (!next || (child->num != -1 && child->num < next->num)) - next = child; - } - } - } -workspace_next_end: return next; } @@ -576,7 +571,7 @@ workspace_next_end: */ Con *workspace_prev(void) { Con *current = con_get_workspace(focused); - Con *prev = NULL; + Con *prev = NULL, *first_opposite = NULL, *last = NULL; Con *output; if (current->num == -1) { @@ -584,6 +579,28 @@ Con *workspace_prev(void) { prev = TAILQ_PREV(current, nodes_head, nodes); if (prev && prev->num != -1) prev = NULL; + if (!prev) { + bool found_current = false; + TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { + /* Skip outputs starting with __, they are internal. */ + if (con_is_internal(output)) + continue; + NODES_FOREACH_REVERSE(output_get_content(output)) { + if (child->type != CT_WORKSPACE) + continue; + if (!last) + last = child; + if (!first_opposite && child->num != -1) + first_opposite = child; + if (child == current) { + found_current = true; + } else if (child->num == -1 && found_current) { + prev = child; + goto workspace_prev_end; + } + } + } + } } else { /* If numbered workspace, find previous numbered workspace. */ TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { @@ -591,7 +608,13 @@ Con *workspace_prev(void) { if (con_is_internal(output)) continue; NODES_FOREACH_REVERSE(output_get_content(output)) { - if (child->type != CT_WORKSPACE || child->num == -1) + if (child->type != CT_WORKSPACE) + continue; + if (!last) + last = child; + if (!first_opposite && child->num == -1) + first_opposite = child; + if (child->num == -1) continue; /* Need to check child against current and previous because we * are traversing multiple lists and thus are not guaranteed @@ -602,40 +625,8 @@ Con *workspace_prev(void) { } } - /* Find previous named workspace. */ - if (!prev) { - bool found_current = false; - TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { - /* Skip outputs starting with __, they are internal. */ - if (con_is_internal(output)) - continue; - NODES_FOREACH_REVERSE(output_get_content(output)) { - if (child->type != CT_WORKSPACE) - continue; - if (child == current) { - found_current = true; - } else if (child->num == -1 && (current->num != -1 || found_current)) { - prev = child; - goto workspace_prev_end; - } - } - } - } - - /* Find last workspace. */ - if (!prev) { - TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { - /* Skip outputs starting with __, they are internal. */ - if (con_is_internal(output)) - continue; - NODES_FOREACH_REVERSE(output_get_content(output)) { - if (child->type != CT_WORKSPACE) - continue; - if (!prev || child->num > prev->num) - prev = child; - } - } - } + if (!prev) + prev = first_opposite ? first_opposite : last; workspace_prev_end: return prev; @@ -675,7 +666,7 @@ Con *workspace_next_on_output(void) { if (child->type != CT_WORKSPACE) continue; if (child == current) { - found_current = 1; + found_current = true; } else if (child->num == -1 && (current->num != -1 || found_current)) { next = child; goto workspace_next_on_output_end; @@ -941,7 +932,7 @@ bool workspace_move_to_output(Con *ws, const char *name) { Con *content = output_get_content(output->con); LOG("got output %p with content %p\n", output, content); - Con *previously_visible_ws = TAILQ_FIRST(&(content->nodes_head)); + Con *previously_visible_ws = TAILQ_FIRST(&(content->focus_head)); LOG("Previously visible workspace = %p / %s\n", previously_visible_ws, previously_visible_ws->name); bool workspace_was_visible = workspace_is_visible(ws); diff --git a/src/x.c b/src/x.c index 9b9ba6aa..f44bc37a 100644 --- a/src/x.c +++ b/src/x.c @@ -101,53 +101,39 @@ void x_con_init(Con *con, uint16_t depth) { uint32_t mask = 0; uint32_t values[5]; - xcb_visualid_t visual = XCB_COPY_FROM_PARENT; - xcb_colormap_t win_colormap = XCB_NONE; - if (depth != root_depth && depth != XCB_COPY_FROM_PARENT) { - /* For custom visuals, we need to create a colormap before creating - * this window. It will be freed directly after creating the window. */ - visual = get_visualid_by_depth(depth); - win_colormap = xcb_generate_id(conn); - xcb_create_colormap_checked(conn, XCB_COLORMAP_ALLOC_NONE, win_colormap, root, visual); + /* For custom visuals, we need to create a colormap before creating + * this window. It will be freed directly after creating the window. */ + xcb_visualid_t visual = get_visualid_by_depth(depth); + xcb_colormap_t win_colormap = xcb_generate_id(conn); + xcb_create_colormap_checked(conn, XCB_COLORMAP_ALLOC_NONE, win_colormap, root, visual); - /* We explicitly set a background color and border color (even though we - * don’t even have a border) because the X11 server requires us to when - * using 32 bit color depths, see - * http://stackoverflow.com/questions/3645632 */ - mask |= XCB_CW_BACK_PIXEL; - values[0] = root_screen->black_pixel; + /* We explicitly set a background color and border color (even though we + * don’t even have a border) because the X11 server requires us to when + * using 32 bit color depths, see + * http://stackoverflow.com/questions/3645632 */ + mask |= XCB_CW_BACK_PIXEL; + values[0] = root_screen->black_pixel; - mask |= XCB_CW_BORDER_PIXEL; - values[1] = root_screen->black_pixel; + mask |= XCB_CW_BORDER_PIXEL; + values[1] = root_screen->black_pixel; - /* our own frames should not be managed */ - mask |= XCB_CW_OVERRIDE_REDIRECT; - values[2] = 1; + /* our own frames should not be managed */ + mask |= XCB_CW_OVERRIDE_REDIRECT; + values[2] = 1; - /* see include/xcb.h for the FRAME_EVENT_MASK */ - mask |= XCB_CW_EVENT_MASK; - values[3] = FRAME_EVENT_MASK & ~XCB_EVENT_MASK_ENTER_WINDOW; + /* see include/xcb.h for the FRAME_EVENT_MASK */ + mask |= XCB_CW_EVENT_MASK; + values[3] = FRAME_EVENT_MASK & ~XCB_EVENT_MASK_ENTER_WINDOW; - mask |= XCB_CW_COLORMAP; - values[4] = win_colormap; - } else { - /* our own frames should not be managed */ - mask = XCB_CW_OVERRIDE_REDIRECT; - values[0] = 1; - - /* see include/xcb.h for the FRAME_EVENT_MASK */ - mask |= XCB_CW_EVENT_MASK; - values[1] = FRAME_EVENT_MASK & ~XCB_EVENT_MASK_ENTER_WINDOW; - - mask |= XCB_CW_COLORMAP; - values[2] = colormap; - } + mask |= XCB_CW_COLORMAP; + values[4] = win_colormap; Rect dims = {-15, -15, 10, 10}; - con->frame = create_window(conn, dims, depth, visual, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_POINTER, false, mask, values); + xcb_window_t frame_id = create_window(conn, dims, depth, visual, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_POINTER, false, mask, values); + draw_util_surface_init(conn, &(con->frame), frame_id, get_visualtype_by_id(visual), dims.width, dims.height); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, - con->frame, + con->frame.id, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, @@ -158,7 +144,7 @@ void x_con_init(Con *con, uint16_t depth) { xcb_free_colormap(conn, win_colormap); struct con_state *state = scalloc(1, sizeof(struct con_state)); - state->id = con->frame; + state->id = con->frame.id; state->mapped = false; state->initial = true; DLOG("Adding window 0x%08x to lists\n", state->id); @@ -177,7 +163,7 @@ void x_con_init(Con *con, uint16_t depth) { void x_reinit(Con *con) { struct con_state *state; - if ((state = state_for_frame(con->frame)) == NULL) { + if ((state = state_for_frame(con->frame.id)) == NULL) { ELOG("window state not found\n"); return; } @@ -196,13 +182,13 @@ void x_reinit(Con *con) { */ void x_reparent_child(Con *con, Con *old) { struct con_state *state; - if ((state = state_for_frame(con->frame)) == NULL) { + if ((state = state_for_frame(con->frame.id)) == NULL) { ELOG("window state for con not found\n"); return; } state->need_reparent = true; - state->old_frame = old->frame; + state->old_frame = old->frame.id; } /* @@ -212,12 +198,12 @@ void x_reparent_child(Con *con, Con *old) { void x_move_win(Con *src, Con *dest) { struct con_state *state_src, *state_dest; - if ((state_src = state_for_frame(src->frame)) == NULL) { + if ((state_src = state_for_frame(src->frame.id)) == NULL) { ELOG("window state for src not found\n"); return; } - if ((state_dest = state_for_frame(dest->frame)) == NULL) { + if ((state_dest = state_for_frame(dest->frame.id)) == NULL) { ELOG("window state for dest not found\n"); return; } @@ -239,10 +225,11 @@ void x_move_win(Con *src, Con *dest) { void x_con_kill(Con *con) { con_state *state; - xcb_destroy_window(conn, con->frame); - xcb_free_pixmap(conn, con->pixmap); - xcb_free_gc(conn, con->pm_gc); - state = state_for_frame(con->frame); + draw_util_surface_free(conn, &(con->frame)); + draw_util_surface_free(conn, &(con->frame_buffer)); + xcb_destroy_window(conn, con->frame.id); + xcb_free_pixmap(conn, con->frame_buffer.id); + state = state_for_frame(con->frame.id); CIRCLEQ_REMOVE(&state_head, state, state); CIRCLEQ_REMOVE(&old_state_head, state, old_state); TAILQ_REMOVE(&initial_mapping_head, state, initial_mapping_order); @@ -312,6 +299,54 @@ void x_window_kill(xcb_window_t window, kill_window_t kill_window) { free(event); } +static void x_draw_title_border(Con *con, struct deco_render_params *p) { + assert(con->parent != NULL); + + Rect *dr = &(con->deco_rect); + adjacent_t borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders; + int deco_diff_l = borders_to_hide & ADJ_LEFT_SCREEN_EDGE ? 0 : con->current_border_width; + int deco_diff_r = borders_to_hide & ADJ_RIGHT_SCREEN_EDGE ? 0 : con->current_border_width; + if (con->parent->layout == L_TABBED || + (con->parent->layout == L_STACKED && TAILQ_NEXT(con, nodes) != NULL)) { + deco_diff_l = 0; + deco_diff_r = 0; + } + + draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border, + dr->x, dr->y, dr->width, 1); + + draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border, + dr->x + deco_diff_l, dr->y + dr->height - 1, dr->width - (deco_diff_l + deco_diff_r), 1); +} + +static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p) { + assert(con->parent != NULL); + + Rect *dr = &(con->deco_rect); + Rect br = con_border_style_rect(con); + + /* Redraw the right border to cut off any text that went past it. + * This is necessary when the text was drawn using XCB since cutting text off + * automatically does not work there. For pango rendering, this isn't necessary. */ + draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->background, + dr->x + dr->width + br.width, dr->y, -br.width, dr->height); + + /* Draw a 1px separator line before and after every tab, so that tabs can + * be easily distinguished. */ + if (con->parent->layout == L_TABBED) { + /* Left side */ + draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border, + dr->x, dr->y, 1, dr->height); + + /* Right side */ + draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border, + dr->x + dr->width - 1, dr->y, 1, dr->height); + } + + /* Redraw the border. */ + x_draw_title_border(con, p); +} + /* * Draws the decoration of the given container onto its parent. * @@ -343,7 +378,7 @@ void x_draw_decoration(Con *con) { /* Skip containers whose pixmap has not yet been created (can happen when * decoration rendering happens recursively for a window for which * x_push_node() was not yet called) */ - if (leaf && con->pixmap == XCB_NONE) + if (leaf && con->frame_buffer.id == XCB_NONE) return; /* 1: build deco_params and compare with cache */ @@ -395,29 +430,20 @@ void x_draw_decoration(Con *con) { con->pixmap_recreated = false; con->mark_changed = false; - /* 2: draw the client.background, but only for the parts around the client_rect */ + /* 2: draw the client.background, but only for the parts around the window_rect */ if (con->window != NULL) { - xcb_rectangle_t background[] = { - /* top area */ - {0, 0, r->width, w->y}, - /* bottom area */ - {0, (w->y + w->height), r->width, r->height - (w->y + w->height)}, - /* left area */ - {0, 0, w->x, r->height}, - /* right area */ - {w->x + w->width, 0, r->width - (w->x + w->width), r->height}}; -#if 0 - for (int i = 0; i < 4; i++) - DLOG("rect is (%d, %d) with %d x %d\n", - background[i].x, - background[i].y, - background[i].width, - background[i].height - ); -#endif - - xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){config.client.background}); - xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, sizeof(background) / sizeof(xcb_rectangle_t), background); + /* top area */ + draw_util_rectangle(conn, &(con->frame_buffer), config.client.background, + 0, 0, r->width, w->y); + /* bottom area */ + draw_util_rectangle(conn, &(con->frame_buffer), config.client.background, + 0, w->y + w->height, r->width, r->height - (w->y + w->height)); + /* left area */ + draw_util_rectangle(conn, &(con->frame_buffer), config.client.background, + 0, 0, w->x, r->height); + /* right area */ + draw_util_rectangle(conn, &(con->frame_buffer), config.client.background, + w->x + w->width, 0, r->width - (w->x + w->width), r->height); } /* 3: draw a rectangle in border color around the client */ @@ -433,27 +459,27 @@ void x_draw_decoration(Con *con) { DLOG("window_rect spans (%d, %d) with %d x %d\n", con->window_rect.x, con->window_rect.y, con->window_rect.width, con->window_rect.height); #endif - /* These rectangles represents the border around the child window + /* These rectangles represent the border around the child window * (left, bottom and right part). We don’t just fill the whole * rectangle because some childs are not freely resizable and we want * their background color to "shine through". */ - xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){p->color->background}); 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); + draw_util_rectangle(conn, &(con->frame_buffer), p->color->child_border, 0, 0, br.x, r->height); } if (!(borders_to_hide & ADJ_RIGHT_SCREEN_EDGE)) { - xcb_rectangle_t rightline = {r->width + (br.width + br.x), 0, -(br.width + br.x), r->height}; - xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &rightline); + draw_util_rectangle(conn, &(con->frame_buffer), + p->color->child_border, r->width + (br.width + br.x), 0, + -(br.width + br.x), r->height); } if (!(borders_to_hide & ADJ_LOWER_SCREEN_EDGE)) { - xcb_rectangle_t bottomline = {br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y)}; - xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &bottomline); + draw_util_rectangle(conn, &(con->frame_buffer), + p->color->child_border, br.x, r->height + (br.height + br.y), + r->width + br.width, -(br.height + br.y)); } - /* 1pixel border needs an additional line at the top */ + /* pixel border needs an additional line at the top */ if (p->border_style == BS_PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) { - xcb_rectangle_t topline = {br.x, 0, r->width + br.width, br.y}; - xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &topline); + draw_util_rectangle(conn, &(con->frame_buffer), + p->color->child_border, br.x, 0, r->width + br.width, br.y); } /* Highlight the side of the border at which the next window will be @@ -463,13 +489,13 @@ void x_draw_decoration(Con *con) { if (TAILQ_NEXT(con, nodes) == NULL && TAILQ_PREV(con, nodes_head, nodes) == NULL && con->parent->type != CT_FLOATING_CON) { - xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){p->color->indicator}); - if (p->parent_layout == L_SPLITH) - xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, (xcb_rectangle_t[]){ - {r->width + (br.width + br.x), br.y, -(br.width + br.x), r->height + br.height}}); - else if (p->parent_layout == L_SPLITV) - xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, (xcb_rectangle_t[]){ - {br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y)}}); + if (p->parent_layout == L_SPLITH) { + draw_util_rectangle(conn, &(con->frame_buffer), p->color->indicator, + r->width + (br.width + br.x), br.y, -(br.width + br.x), r->height + br.height); + } else if (p->parent_layout == L_SPLITV) { + draw_util_rectangle(conn, &(con->frame_buffer), p->color->indicator, + br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y)); + } } } @@ -478,48 +504,49 @@ void x_draw_decoration(Con *con) { if (p->border_style != BS_NORMAL) goto copy_pixmaps; + /* If the parent hasn't been set up yet, skip the decoration rendering + * for now. */ + if (parent->frame_buffer.id == XCB_NONE) + goto copy_pixmaps; + + /* For the first child, we clear the parent pixmap to ensure there's no + * garbage left on there. This is important to avoid tearing when using + * transparency. */ + if (con == TAILQ_FIRST(&(con->parent->nodes_head))) { + draw_util_clear_surface(conn, &(con->parent->frame_buffer), COLOR_TRANSPARENT); + FREE(con->parent->deco_render_params); + } + /* 4: paint the bar */ - xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){p->color->background}); - xcb_rectangle_t drect = {con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height}; - xcb_poly_fill_rectangle(conn, parent->pixmap, parent->pm_gc, 1, &drect); + draw_util_rectangle(conn, &(parent->frame_buffer), p->color->background, + con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height); /* 5: draw two unconnected horizontal lines in border color */ - xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){p->color->border}); - Rect *dr = &(con->deco_rect); - adjacent_t borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders; - int deco_diff_l = borders_to_hide & ADJ_LEFT_SCREEN_EDGE ? 0 : con->current_border_width; - int deco_diff_r = borders_to_hide & ADJ_RIGHT_SCREEN_EDGE ? 0 : con->current_border_width; - if (parent->layout == L_TABBED || - (parent->layout == L_STACKED && TAILQ_NEXT(con, nodes) != NULL)) { - deco_diff_l = 0; - deco_diff_r = 0; - } - xcb_segment_t segments[] = { - {dr->x, dr->y, - dr->x + dr->width - 1, dr->y}, - {dr->x + deco_diff_l, dr->y + dr->height - 1, - dr->x - deco_diff_r + dr->width - 1, dr->y + dr->height - 1}}; - xcb_poly_segment(conn, parent->pixmap, parent->pm_gc, 2, segments); + x_draw_title_border(con, p); /* 6: draw the title */ - set_font_colors(parent->pm_gc, p->color->text, p->color->background); int text_offset_y = (con->deco_rect.height - config.font.height) / 2; struct Window *win = con->window; if (win == NULL) { - /* we have a split container which gets a representation - * of its children as title - */ - char *title; - char *tree = con_get_tree_representation(con); - sasprintf(&title, "i3: %s", tree); - free(tree); + i3String *title; + if (con->title_format == NULL) { + char *_title; + char *tree = con_get_tree_representation(con); + sasprintf(&_title, "i3: %s", tree); + free(tree); - draw_text_ascii(title, - parent->pixmap, parent->pm_gc, - con->deco_rect.x + 2, con->deco_rect.y + text_offset_y, - con->deco_rect.width - 2); - free(title); + title = i3string_from_utf8(_title); + FREE(_title); + } else { + title = con_parse_title_format(con); + } + + draw_util_text(title, &(parent->frame_buffer), + p->color->text, p->color->background, + con->deco_rect.x + 2, con->deco_rect.y + text_offset_y, + con->deco_rect.width - 2); + I3STRING_FREE(title); goto after_title; } @@ -545,56 +572,49 @@ void x_draw_decoration(Con *con) { int indent_px = (indent_level * 5) * indent_mult; int mark_width = 0; - if (config.show_marks && con->mark != NULL && (con->mark)[0] != '_') { - char *formatted_mark; - sasprintf(&formatted_mark, "[%s]", con->mark); - i3String *mark = i3string_from_utf8(formatted_mark); + if (config.show_marks && !TAILQ_EMPTY(&(con->marks_head))) { + char *formatted_mark = sstrdup(""); + bool had_visible_mark = false; + + mark_t *mark; + TAILQ_FOREACH(mark, &(con->marks_head), marks) { + if (mark->name[0] == '_') + continue; + had_visible_mark = true; + + char *buf; + sasprintf(&buf, "%s[%s]", formatted_mark, mark->name); + free(formatted_mark); + formatted_mark = buf; + } + + if (had_visible_mark) { + i3String *mark = i3string_from_utf8(formatted_mark); + mark_width = predict_text_width(mark); + + draw_util_text(mark, &(parent->frame_buffer), + p->color->text, p->color->background, + con->deco_rect.x + con->deco_rect.width - mark_width - logical_px(2), + con->deco_rect.y + text_offset_y, mark_width); + + I3STRING_FREE(mark); + } + FREE(formatted_mark); - mark_width = predict_text_width(mark); - - draw_text(mark, parent->pixmap, parent->pm_gc, - con->deco_rect.x + con->deco_rect.width - mark_width - logical_px(2), - con->deco_rect.y + text_offset_y, mark_width); - - I3STRING_FREE(mark); } - i3String *title = win->title_format == NULL ? win->name : window_parse_title_format(win); - draw_text(title, - parent->pixmap, parent->pm_gc, - con->deco_rect.x + logical_px(2) + indent_px, con->deco_rect.y + text_offset_y, - con->deco_rect.width - logical_px(2) - indent_px - mark_width - logical_px(2)); - if (win->title_format != NULL) + i3String *title = con->title_format == NULL ? win->name : con_parse_title_format(con); + draw_util_text(title, &(parent->frame_buffer), + p->color->text, p->color->background, + con->deco_rect.x + logical_px(2) + indent_px, con->deco_rect.y + text_offset_y, + con->deco_rect.width - logical_px(2) - indent_px - mark_width - logical_px(2)); + if (con->title_format != NULL) I3STRING_FREE(title); after_title: - /* Since we don’t clip the text at all, it might in some cases be painted - * on the border pixels on the right side of a window. Therefore, we draw - * the right border again after rendering the text (and the unconnected - * lines in border color). */ - - /* Draw a 1px separator line before and after every tab, so that tabs can - * be easily distinguished. */ - if (parent->layout == L_TABBED) { - xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){p->color->border}); - } else { - xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){p->color->background}); - } - xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, parent->pixmap, parent->pm_gc, 6, - (xcb_point_t[]){ - {dr->x + dr->width, dr->y}, - {dr->x + dr->width, dr->y + dr->height}, - {dr->x + dr->width - 1, dr->y}, - {dr->x + dr->width - 1, dr->y + dr->height}, - {dr->x, dr->y + dr->height}, - {dr->x, dr->y}, - }); - - xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){p->color->border}); - xcb_poly_segment(conn, parent->pixmap, parent->pm_gc, 2, segments); - + x_draw_decoration_after_title(con, p); copy_pixmaps: - xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height); + draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); } /* @@ -607,7 +627,7 @@ void x_deco_recurse(Con *con) { Con *current; bool leaf = TAILQ_EMPTY(&(con->nodes_head)) && TAILQ_EMPTY(&(con->floating_head)); - con_state *state = state_for_frame(con->frame); + con_state *state = state_for_frame(con->frame.id); if (!leaf) { TAILQ_FOREACH(current, &(con->nodes_head), nodes) @@ -616,8 +636,9 @@ void x_deco_recurse(Con *con) { TAILQ_FOREACH(current, &(con->floating_head), floating_windows) x_deco_recurse(current); - if (state->mapped) - xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height); + if (state->mapped) { + draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); + } } if ((con->type != CT_ROOT && con->type != CT_OUTPUT) && @@ -634,7 +655,7 @@ static void set_hidden_state(Con *con) { return; } - con_state *state = state_for_frame(con->frame); + con_state *state = state_for_frame(con->frame.id); bool should_be_hidden = con_is_hidden(con); if (should_be_hidden == state->is_hidden) return; @@ -662,12 +683,12 @@ void x_push_node(Con *con) { Rect rect = con->rect; //DLOG("Pushing changes for node %p / %s\n", con, con->name); - state = state_for_frame(con->frame); + state = state_for_frame(con->frame.id); if (state->name != NULL) { DLOG("pushing name %s for con %p\n", state->name, con); - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->frame, + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->frame.id, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, strlen(state->name), state->name); FREE(state->name); } @@ -700,7 +721,7 @@ void x_push_node(Con *con) { xcb_change_window_attributes(conn, state->old_frame, XCB_CW_EVENT_MASK, values); xcb_change_window_attributes(conn, con->window->id, XCB_CW_EVENT_MASK, values); - xcb_reparent_window(conn, con->window->id, con->frame, 0, 0); + xcb_reparent_window(conn, con->window->id, con->frame.id, 0, 0); values[0] = FRAME_EVENT_MASK; xcb_change_window_attributes(conn, state->old_frame, XCB_CW_EVENT_MASK, values); @@ -722,11 +743,17 @@ void x_push_node(Con *con) { con->parent->layout == L_STACKED || con->parent->layout == L_TABBED); + /* The root con and output cons will never require a pixmap. In particular for the + * __i3 output, this will likely not work anyway because it might be ridiculously + * large, causing an XCB_ALLOC error. */ + if (con->type == CT_ROOT || con->type == CT_OUTPUT) + is_pixmap_needed = false; + bool fake_notify = false; /* Set new position if rect changed (and if height > 0) or if the pixmap * needs to be recreated */ - if ((is_pixmap_needed && con->pixmap == XCB_NONE) || (memcmp(&(state->rect), &rect, sizeof(Rect)) != 0 && - rect.height > 0)) { + if ((is_pixmap_needed && con->frame_buffer.id == XCB_NONE) || (memcmp(&(state->rect), &rect, sizeof(Rect)) != 0 && + rect.height > 0)) { /* We first create the new pixmap, then render to it, set it as the * background and only afterwards change the window size. This reduces * flickering. */ @@ -739,38 +766,47 @@ void x_push_node(Con *con) { /* Check if the container has an unneeded pixmap left over from * previously having a border or titlebar. */ - if (!is_pixmap_needed && con->pixmap != XCB_NONE) { - xcb_free_pixmap(conn, con->pixmap); - con->pixmap = XCB_NONE; + if (!is_pixmap_needed && con->frame_buffer.id != XCB_NONE) { + draw_util_surface_free(conn, &(con->frame_buffer)); + xcb_free_pixmap(conn, con->frame_buffer.id); + con->frame_buffer.id = XCB_NONE; } - if (is_pixmap_needed && (has_rect_changed || con->pixmap == XCB_NONE)) { - if (con->pixmap == 0) { - con->pixmap = xcb_generate_id(conn); - con->pm_gc = xcb_generate_id(conn); + if (is_pixmap_needed && (has_rect_changed || con->frame_buffer.id == XCB_NONE)) { + if (con->frame_buffer.id == XCB_NONE) { + con->frame_buffer.id = xcb_generate_id(conn); } else { - xcb_free_pixmap(conn, con->pixmap); - xcb_free_gc(conn, con->pm_gc); + draw_util_surface_free(conn, &(con->frame_buffer)); + xcb_free_pixmap(conn, con->frame_buffer.id); } uint16_t win_depth = root_depth; if (con->window) win_depth = con->window->depth; - xcb_create_pixmap(conn, win_depth, con->pixmap, con->frame, rect.width, rect.height); + /* Ensure we have valid dimensions for our surface. */ + // TODO This is probably a bug in the condition above as we should never enter this path + // for height == 0. Also, we should probably handle width == 0 the same way. + int width = MAX((int32_t)rect.width, 1); + int height = MAX((int32_t)rect.height, 1); + + xcb_create_pixmap_checked(conn, win_depth, con->frame_buffer.id, con->frame.id, width, height); + draw_util_surface_init(conn, &(con->frame_buffer), con->frame_buffer.id, + get_visualtype_by_id(get_visualid_by_depth(win_depth)), width, height); /* For the graphics context, we disable GraphicsExposure events. * Those will be sent when a CopyArea request cannot be fulfilled * properly due to parts of the source being unmapped or otherwise * unavailable. Since we always copy from pixmaps to windows, this * is not a concern for us. */ - uint32_t values[] = {0}; - xcb_create_gc(conn, con->pm_gc, con->pixmap, XCB_GC_GRAPHICS_EXPOSURES, values); + xcb_change_gc(conn, con->frame_buffer.gc, XCB_GC_GRAPHICS_EXPOSURES, (uint32_t[]){0}); + draw_util_surface_set_size(&(con->frame), width, height); con->pixmap_recreated = true; /* Don’t render the decoration for windows inside a stack which are * not visible right now */ + // TODO Should this work the same way for L_TABBED? if (!con->parent || con->parent->layout != L_STACKED || TAILQ_FIRST(&(con->parent->focus_head)) == con) @@ -786,9 +822,10 @@ void x_push_node(Con *con) { * window get lost when resizing it, therefore we want to provide it as * fast as possible) */ xcb_flush(conn); - xcb_set_window_rect(conn, con->frame, rect); - if (con->pixmap != XCB_NONE) - xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height); + xcb_set_window_rect(conn, con->frame.id, rect); + if (con->frame_buffer.id != XCB_NONE) { + draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); + } xcb_flush(conn); memcpy(&(state->rect), &rect, sizeof(Rect)); @@ -832,17 +869,18 @@ void x_push_node(Con *con) { state->child_mapped = true; } - cookie = xcb_map_window(conn, con->frame); + cookie = xcb_map_window(conn, con->frame.id); values[0] = FRAME_EVENT_MASK; - xcb_change_window_attributes(conn, con->frame, XCB_CW_EVENT_MASK, values); + xcb_change_window_attributes(conn, con->frame.id, XCB_CW_EVENT_MASK, values); /* copy the pixmap contents to the frame window immediately after mapping */ - if (con->pixmap != XCB_NONE) - xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height); + if (con->frame_buffer.id != XCB_NONE) { + draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); + } xcb_flush(conn); - DLOG("mapping container %08x (serial %d)\n", con->frame, cookie.sequence); + DLOG("mapping container %08x (serial %d)\n", con->frame.id, cookie.sequence); state->mapped = con->mapped; } @@ -876,7 +914,7 @@ static void x_push_node_unmaps(Con *con) { con_state *state; //DLOG("Pushing changes (with unmaps) for node %p / %s\n", con, con->name); - state = state_for_frame(con->frame); + state = state_for_frame(con->frame.id); /* map/unmap if map state changed, also ensure that the child window * is changed if we are mapped *and* in initial state (meaning the @@ -890,14 +928,14 @@ static void x_push_node_unmaps(Con *con) { A_WM_STATE, A_WM_STATE, 32, 2, data); } - cookie = xcb_unmap_window(conn, con->frame); + cookie = xcb_unmap_window(conn, con->frame.id); DLOG("unmapping container %p / %s (serial %d)\n", con, con->name, cookie.sequence); /* we need to increase ignore_unmap for this container (if it * contains a window) and for every window "under" this one which * contains a window */ if (con->window != NULL) { con->ignore_unmap++; - DLOG("ignore_unmap for con %p (frame 0x%08x) now %d\n", con, con->frame, con->ignore_unmap); + DLOG("ignore_unmap for con %p (frame 0x%08x) now %d\n", con, con->frame.id, con->ignore_unmap); } state->mapped = con->mapped; } @@ -950,7 +988,10 @@ void x_push_changes(Con *con) { DLOG("-- PUSHING WINDOW STACK --\n"); //DLOG("Disabling EnterNotify\n"); - uint32_t values[1] = {XCB_NONE}; + /* We need to keep SubstructureRedirect around, otherwise clients can send + * ConfigureWindow requests and get them applied directly instead of having + * them become ConfigureRequests that i3 handles. */ + uint32_t values[1] = {XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT}; CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { if (state->mapped) xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); @@ -1037,6 +1078,8 @@ void x_push_changes(Con *con) { xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, mid_x, mid_y); xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ROOT_EVENT_MASK}); } + + free(pointerreply); } warp_to = NULL; } @@ -1051,7 +1094,7 @@ void x_push_changes(Con *con) { x_deco_recurse(con); - xcb_window_t to_focus = focused->frame; + xcb_window_t to_focus = focused->frame.id; if (focused->window != NULL) to_focus = focused->window->id; @@ -1145,7 +1188,7 @@ void x_push_changes(Con *con) { */ void x_raise_con(Con *con) { con_state *state; - state = state_for_frame(con->frame); + state = state_for_frame(con->frame.id); //DLOG("raising in new stack: %p / %s / %s / xid %08x\n", con, con->name, con->window ? con->window->name_json : "", state->id); CIRCLEQ_REMOVE(&state_head, state, state); @@ -1161,7 +1204,7 @@ void x_raise_con(Con *con) { void x_set_name(Con *con, const char *name) { struct con_state *state; - if ((state = state_for_frame(con->frame)) == NULL) { + if ((state = state_for_frame(con->frame.id)) == NULL) { ELOG("window state not found\n"); return; } diff --git a/src/xcb.c b/src/xcb.c index f98115f5..9d181cfa 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -175,7 +175,8 @@ xcb_atom_t xcb_get_preferred_window_type(xcb_get_property_reply_t *reply) { atoms[i] == A__NET_WM_WINDOW_TYPE_MENU || atoms[i] == A__NET_WM_WINDOW_TYPE_DROPDOWN_MENU || atoms[i] == A__NET_WM_WINDOW_TYPE_POPUP_MENU || - atoms[i] == A__NET_WM_WINDOW_TYPE_TOOLTIP) { + atoms[i] == A__NET_WM_WINDOW_TYPE_TOOLTIP || + atoms[i] == A__NET_WM_WINDOW_TYPE_NOTIFICATION) { return atoms[i]; } } @@ -253,6 +254,27 @@ uint16_t get_visual_depth(xcb_visualid_t visual_id) { return 0; } +/* + * Get visual type specified by visualid + * + */ +xcb_visualtype_t *get_visualtype_by_id(xcb_visualid_t visual_id) { + xcb_depth_iterator_t depth_iter; + + depth_iter = xcb_screen_allowed_depths_iterator(root_screen); + for (; depth_iter.rem; xcb_depth_next(&depth_iter)) { + xcb_visualtype_iterator_t visual_iter; + + visual_iter = xcb_depth_visuals_iterator(depth_iter.data); + for (; visual_iter.rem; xcb_visualtype_next(&visual_iter)) { + if (visual_id == visual_iter.data->visual_id) { + return visual_iter.data; + } + } + } + return 0; +} + /* * Get visualid with specified depth * @@ -319,3 +341,25 @@ release_grab: FREE(reply); xcb_ungrab_server(conn); } + +/* + * Grab the specified buttons on a window when managing it. + * + */ +void xcb_grab_buttons(xcb_connection_t *conn, xcb_window_t window, bool bind_scrollwheel) { + uint8_t buttons[3]; + int num = 0; + + if (bind_scrollwheel) { + buttons[num++] = XCB_BUTTON_INDEX_ANY; + } else { + buttons[num++] = XCB_BUTTON_INDEX_1; + buttons[num++] = XCB_BUTTON_INDEX_2; + buttons[num++] = XCB_BUTTON_INDEX_3; + } + + for (int i = 0; i < num; i++) { + xcb_grab_button(conn, false, window, XCB_EVENT_MASK_BUTTON_PRESS, XCB_GRAB_MODE_SYNC, + XCB_GRAB_MODE_ASYNC, root, XCB_NONE, buttons[i], XCB_BUTTON_MASK_ANY); + } +} diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index c1244e08..2d61e993 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -30,6 +30,7 @@ BEGIN { # these are shipped with the testsuite use lib $dirname . 'lib'; +use i3test::Util qw(slurp); use StartXServer; use StatusLine; use TestWorker; @@ -156,7 +157,7 @@ for my $display (@displays) { # Read previous timing information, if available. We will be able to roughly # predict the test duration and schedule a good order for the tests. -my $timingsjson = StartXServer::slurp('.last_run_timings.json'); +my $timingsjson = slurp('.last_run_timings.json') if -e '.last_run_timings.json'; %timings = %{decode_json($timingsjson)} if length($timingsjson) > 0; # Re-order the files so that those which took the longest time in the previous @@ -245,7 +246,7 @@ printf("\t%s with %.2f seconds\n", $_, $timings{$_}) if ($numtests == 1) { say ''; say 'Test output:'; - say StartXServer::slurp($logfile); + say slurp($logfile); } END { cleanup() } @@ -261,6 +262,20 @@ if ($options{coverage}) { } } +# Report logfiles that match “(Leak|Address)Sanitizer:”. +my @logs_with_leaks; +for my $log (<$outdir/i3-log-for-*>) { + if (slurp($log) =~ /(Leak|Address)Sanitizer:/) { + push @logs_with_leaks, $log; + } +} +if (scalar @logs_with_leaks > 0) { + say "\nThe following test logfiles contain AddressSanitizer or LeakSanitizer reports:"; + for my $log (sort @logs_with_leaks) { + say "\t$log"; + } +} + exit ($aggregator->failed > 0); # diff --git a/testcases/lib/SocketActivation.pm b/testcases/lib/SocketActivation.pm index 83ca9255..b58707a4 100644 --- a/testcases/lib/SocketActivation.pm +++ b/testcases/lib/SocketActivation.pm @@ -96,8 +96,13 @@ sub activate_i3 { # the interactive signalhandler to make it crash immediately instead. # Also disable logging to SHM since we redirect the logs anyways. # Force Xinerama because we use Xdmx for multi-monitor tests. - my $i3cmd = abs_path("../i3") . q| -V -d all --disable-signalhandler| . - q| --shmlog-size=0 --force-xinerama|; + my $i3cmd = abs_path("../i3") . q| --shmlog-size=0 --disable-signalhandler --force-xinerama|; + if (!$args{validate_config}) { + # We only set logging if i3 is actually started, but not if we only + # validate the config file. This is to keep logging to a minimum as + # such a test will likely want to inspect the log file. + $i3cmd .= q| -V -d all|; + } # For convenience: my $outdir = $args{outdir}; @@ -107,6 +112,10 @@ sub activate_i3 { $i3cmd .= ' -L ' . abs_path('restart-state.golden'); } + if ($args{validate_config}) { + $i3cmd .= ' -C'; + } + if ($args{valgrind}) { $i3cmd = qq|valgrind --log-file="$outdir/valgrind-for-$test.log" | . @@ -149,6 +158,11 @@ sub activate_i3 { # descriptor on the listening socket. $socket->close; + if ($args{validate_config}) { + $args{cv}->send(1); + return $pid; + } + # We now connect (will succeed immediately) and send a request afterwards. # As soon as the reply is there, i3 is considered ready. my $cl = IO::Socket::UNIX->new(Peer => $args{unix_socket_path}); diff --git a/testcases/lib/StartXServer.pm b/testcases/lib/StartXServer.pm index 032f58c6..86a37d92 100644 --- a/testcases/lib/StartXServer.pm +++ b/testcases/lib/StartXServer.pm @@ -5,6 +5,7 @@ use strict; use warnings; use Exporter 'import'; use Time::HiRes qw(sleep); +use i3test::Util qw(slurp); use v5.10; our @EXPORT = qw(start_xserver); @@ -12,13 +13,6 @@ our @EXPORT = qw(start_xserver); my @pids; my $x_socketpath = '/tmp/.X11-unix/X'; -# reads in a whole file -sub slurp { - open(my $fh, '<', shift) or return ''; - local $/; - <$fh>; -} - # forks an X server process sub fork_xserver { my $keep_xserver_output = shift; @@ -86,8 +80,11 @@ sub start_xserver { # Yeah, I know it’s non-standard, but Perl’s POSIX module doesn’t have # _SC_NPROCESSORS_CONF. - my $cpuinfo = slurp('/proc/cpuinfo'); - my $num_cores = scalar grep { /model name/ } split("\n", $cpuinfo); + my $num_cores; + if (-e '/proc/cpuinfo') { + my $cpuinfo = slurp('/proc/cpuinfo'); + $num_cores = scalar grep { /model name/ } split("\n", $cpuinfo); + } # If /proc/cpuinfo does not exist, we fall back to 2 cores. $num_cores ||= 2; diff --git a/testcases/lib/i3test.pm b/testcases/lib/i3test.pm index ac1a26ca..98486122 100644 --- a/testcases/lib/i3test.pm +++ b/testcases/lib/i3test.pm @@ -13,6 +13,7 @@ use Time::HiRes qw(sleep); use Cwd qw(abs_path); use Scalar::Util qw(blessed); use SocketActivation; +use i3test::Util qw(slurp); use v5.10; @@ -39,6 +40,7 @@ our @EXPORT = qw( focused_ws get_socket_path launch_with_config + get_i3_log wait_for_event wait_for_map wait_for_unmap @@ -827,6 +829,7 @@ sub launch_with_config { $tmp_socket_path = "/tmp/nested-$ENV{DISPLAY}"; $args{dont_create_temp_dir} //= 0; + $args{validate_config} //= 0; my ($fh, $tmpfile) = tempfile("i3-cfg-for-$ENV{TESTNAME}-XXXXX", UNLINK => 1); @@ -857,8 +860,21 @@ sub launch_with_config { restart => $ENV{RESTART}, cv => $cv, dont_create_temp_dir => $args{dont_create_temp_dir}, + validate_config => $args{validate_config}, ); + # If we called i3 with -C, we wait for it to exit and then return as + # there's nothing else we need to do. + if ($args{validate_config}) { + $cv->recv; + waitpid $i3_pid, 0; + + # We need this since exit_gracefully will not be called in this case. + undef $i3_pid; + + return ${^CHILD_ERROR_NATIVE}; + } + # force update of the cached socket path in lib/i3test # as soon as i3 has started $cv->cb(sub { get_socket_path(0) }); @@ -871,6 +887,16 @@ sub launch_with_config { return $i3_pid; } +=head2 get_i3_log + +Returns the content of the log file for the current test. + +=cut +sub get_i3_log { + my $logfile = "$ENV{OUTDIR}/i3-log-for-$ENV{TESTNAME}"; + return slurp($logfile); +} + =head1 AUTHOR Michael Stapelberg diff --git a/testcases/lib/i3test/Util.pm b/testcases/lib/i3test/Util.pm new file mode 100644 index 00000000..74913681 --- /dev/null +++ b/testcases/lib/i3test/Util.pm @@ -0,0 +1,47 @@ +package i3test::Util; +# vim:ts=4:sw=4:expandtab + +use strict; +use warnings; +use v5.10; + +use Exporter qw(import); +our @EXPORT = qw( + slurp +); + +=encoding utf-8 + +=head1 NAME + +i3test::Util - General utility functions + +=cut + +=head1 EXPORT + +=cut + +=head2 slurp($fn) + +Reads the entire file specified in the arguments and returns the content. + +=cut +sub slurp { + my ($file) = @_; + my $content = do { + local $/ = undef; + open my $fh, "<", $file or die "could not open $file: $!"; + <$fh>; + }; + + return $content; +} + +=head1 AUTHOR + +Michael Stapelberg + +=cut + +1 diff --git a/testcases/lib/i3test/XTEST.pm b/testcases/lib/i3test/XTEST.pm new file mode 100644 index 00000000..92adde42 --- /dev/null +++ b/testcases/lib/i3test/XTEST.pm @@ -0,0 +1,290 @@ +package i3test::XTEST; +# vim:ts=4:sw=4:expandtab + +use strict; +use warnings; +use v5.10; + +use i3test i3_autostart => 0; +use AnyEvent::I3; +use ExtUtils::PkgConfig; + +use Exporter (); +our @EXPORT = qw( + inlinec_connect + set_xkb_group + xtest_key_press + xtest_key_release + xtest_button_press + xtest_button_release + listen_for_binding + start_binding_capture + binding_events +); + +=encoding utf-8 + +=head1 NAME + +i3test::XTEST - Inline::C wrappers for xcb-xtest and xcb-xkb + +=cut + +# We need to use libxcb-xkb because xdotool cannot trigger ISO_Next_Group +# anymore: it contains code to set the XKB group to 1 and then restore the +# previous group, effectively rendering any keys that switch groups +# ineffective. +my %sn_config; +BEGIN { + %sn_config = ExtUtils::PkgConfig->find('xcb-xkb xcb-xtest'); +} + +use Inline C => Config => LIBS => $sn_config{libs}, CCFLAGS => $sn_config{cflags}; +use Inline C => <<'END_OF_C_CODE'; +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static xcb_connection_t *conn = NULL; + +bool inlinec_connect() { + int screen; + + if ((conn = xcb_connect(NULL, &screen)) == NULL || + xcb_connection_has_error(conn)) { + if (conn != NULL) { + xcb_disconnect(conn); + } + fprintf(stderr, "Could not connect to X11\n"); + return false; + } + + if (!xcb_get_extension_data(conn, &xcb_xkb_id)->present) { + fprintf(stderr, "XKB not present\n"); + return false; + } + + if (!xcb_get_extension_data(conn, &xcb_test_id)->present) { + fprintf(stderr, "XTEST not present\n"); + return false; + } + + xcb_generic_error_t *err = NULL; + xcb_xkb_use_extension_reply_t *usereply; + usereply = xcb_xkb_use_extension_reply( + conn, xcb_xkb_use_extension(conn, XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION), &err); + if (err != NULL || usereply == NULL) { + fprintf(stderr, "xcb_xkb_use_extension() failed\n"); + free(err); + return false; + } + free(usereply); + + return true; +} + +// NOTE: while |group| should be a uint8_t, Inline::C will not define the +// function unless we use an int. +bool set_xkb_group(int group) { + xcb_generic_error_t *err = NULL; + // Needs libxcb ≥ 1.11 so that we have the following bug fix: + // http://cgit.freedesktop.org/xcb/proto/commit/src/xkb.xml?id=8d7ee5b6ba4cf343f7df70372a3e1f85b82aeed7 + xcb_void_cookie_t cookie = xcb_xkb_latch_lock_state_checked( + conn, + XCB_XKB_ID_USE_CORE_KBD, /* deviceSpec */ + 0, /* affectModLocks */ + 0, /* modLocks */ + 1, /* lockGroup */ + group, /* groupLock */ + 0, /* affectModLatches */ + 0, /* latchGroup */ + 0); /* groupLatch */ + if ((err = xcb_request_check(conn, cookie)) != NULL) { + fprintf(stderr, "X error code %d\n", err->error_code); + free(err); + return false; + } + return true; +} + +bool xtest_input(int type, int detail, int x, int y) { + xcb_generic_error_t *err; + xcb_void_cookie_t cookie; + + cookie = xcb_test_fake_input_checked( + conn, + type, /* type */ + detail, /* detail */ + XCB_CURRENT_TIME, /* time */ + XCB_NONE, /* root */ + x, /* rootX */ + y, /* rootY */ + XCB_NONE); /* deviceid */ + if ((err = xcb_request_check(conn, cookie)) != NULL) { + fprintf(stderr, "X error code %d\n", err->error_code); + free(err); + return false; + } + + return true; +} + +bool xtest_key(int type, int detail) { + return xtest_input(type, detail, 0, 0); +} + +bool xtest_key_press(int detail) { + return xtest_key(XCB_KEY_PRESS, detail); +} + +bool xtest_key_release(int detail) { + return xtest_key(XCB_KEY_RELEASE, detail); +} + +bool xtest_button_press(int button, int x, int y) { + return xtest_input(XCB_BUTTON_PRESS, button, x, y); +} + +bool xtest_button_release(int button, int x, int y) { + return xtest_input(XCB_BUTTON_RELEASE, button, x, y); +} + +END_OF_C_CODE + +sub import { + my ($class, %args) = @_; + ok(inlinec_connect(), 'Connect to X11, verify XKB and XTEST are present (via Inline::C)'); + goto \&Exporter::import; +} + +=head1 EXPORT + +=cut + +my $i3; +our @binding_events; + +=head2 start_binding_capture() + +Captures all binding events sent by i3 in the C<@binding_events> symbol, so +that you can verify the correct number of binding events was generated. + + my $pid = launch_with_config($config); + start_binding_capture; + # … + sync_with_i3; + is(scalar @i3test::XTEST::binding_events, 2, 'Received exactly 2 binding events'); + +=cut + +sub start_binding_capture { + # Store a copy of each binding event so that we can count the expected + # events in test cases. + $i3 = i3(get_socket_path()); + $i3->connect()->recv; + $i3->subscribe({ + binding => sub { + my ($event) = @_; + @binding_events = (@binding_events, $event); + }, + })->recv; +} + +=head2 listen_for_binding($cb) + +Helper function to evaluate whether sending KeyPress/KeyRelease events via +XTEST triggers an i3 key binding or not (with a timeout of 0.5s). Expects key +bindings to be configured in the form “bindsym nop ”, e.g. +“bindsym Mod4+Return nop Mod4+Return”. + + is(listen_for_binding( + sub { + xtest_key_press(133); # Super_L + xtest_key_press(36); # Return + xtest_key_release(36); # Return + xtest_key_release(133); # Super_L + }, + ), + 'Mod4+Return', + 'triggered the "Mod4+Return" keybinding'); + +=cut + +sub listen_for_binding { + my ($cb) = @_; + my $triggered = AnyEvent->condvar; + my $i3 = i3(get_socket_path()); + $i3->connect()->recv; + $i3->subscribe({ + binding => sub { + my ($event) = @_; + return unless $event->{change} eq 'run'; + # We look at the command (which is “nop ”) because that is + # easier than re-assembling the string representation of + # $event->{binding}. + $triggered->send($event->{binding}->{command}); + }, + })->recv; + + my $t; + $t = AnyEvent->timer( + after => 0.5, + cb => sub { + $triggered->send('timeout'); + } + ); + + $cb->(); + + my $recv = $triggered->recv; + $recv =~ s/^nop //g; + return $recv; +} + +=head2 set_xkb_group($group) + +Changes the current XKB group from the default of 1 to C<$group>, which must be +one of 1, 2, 3, 4. + +Returns false when there was an X11 error changing the group, true otherwise. + +=head2 xtest_key_press($detail) + +Sends a KeyPress event via XTEST, with the specified C<$detail>, i.e. key code. +Use C to find key codes. + +Returns false when there was an X11 error, true otherwise. + +=head2 xtest_key_release($detail) + +Sends a KeyRelease event via XTEST, with the specified C<$detail>, i.e. key code. +Use C to find key codes. + +Returns false when there was an X11 error, true otherwise. + +=head2 xtest_button_press($button, $x, $y) + +Sends a ButtonPress event via XTEST, with the specified C<$button>. + +Returns false when there was an X11 error, true otherwise. + +=head2 xtest_button_release($button, $x, $y) + +Sends a ButtonRelease event via XTEST, with the specified C<$button>. + +Returns false when there was an X11 error, true otherwise. + +=head1 AUTHOR + +Michael Stapelberg + +=cut + +1 diff --git a/testcases/t/117-workspace.t b/testcases/t/117-workspace.t index 04d9b9dd..01d51cc0 100644 --- a/testcases/t/117-workspace.t +++ b/testcases/t/117-workspace.t @@ -288,5 +288,10 @@ ok(!$result->[0]->{success}, 'renaming workspace to an already existing one fail $result = cmd 'rename workspace notexistant to bleh'; ok(!$result->[0]->{success}, 'renaming workspace which does not exist failed'); +# 8: change case +ok(!workspace_exists('11: BAR'), 'workspace 11: BAR does not exist yet'); +$result = cmd 'rename workspace "11: bar" to "11: BAR"'; +ok($result->[0]->{success}, 'renaming workspace from 11: bar to 11: BAR worked'); +ok(workspace_exists('11: BAR'), 'workspace 11: BAR now exists'); done_testing; diff --git a/testcases/t/122-split.t b/testcases/t/122-split.t index e9d06938..6fd9b0e7 100644 --- a/testcases/t/122-split.t +++ b/testcases/t/122-split.t @@ -178,4 +178,34 @@ is(@{$content}, 2, 'two containers on workspace'); is(@{$fst->{nodes}}, 2, 'first child has two children'); is(@{$snd->{nodes}}, 0, 'second child has no children'); +###################################################################### +# Test split toggle +###################################################################### + +$tmp = fresh_workspace; +cmd 'split h'; +cmd 'split toggle'; +$ws = get_ws($tmp); +is($ws->{layout}, 'splitv', 'toggled workspace split'); + +$tmp = fresh_workspace; +cmd 'split h'; +cmd 'split toggle'; +cmd 'split toggle'; +$ws = get_ws($tmp); +is($ws->{layout}, 'splith', 'toggled workspace back and forth'); + +cmd 'open'; +cmd 'open'; +cmd 'split toggle'; + +$content = get_ws_content($tmp); +my $second = $content->[1]; +is($second->{layout}, 'splitv', 'toggled container orientation to vertical'); + +cmd 'split toggle'; +$content = get_ws_content($tmp); +$second = $content->[1]; +is($second->{layout}, 'splith', 'toggled container orientation back to horizontal'); + done_testing; diff --git a/testcases/t/141-resize.t b/testcases/t/141-resize.t index c5e61a32..44ad9bbf 100644 --- a/testcases/t/141-resize.t +++ b/testcases/t/141-resize.t @@ -141,6 +141,21 @@ 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%'); +################################################################################ +# Check that the resize grow/shrink width/height syntax works if a nested split +# was set on the container, but no sibling has been opened yet. See #2015. +################################################################################ + +$tmp = fresh_workspace; +$left = open_window; +$right = open_window; + +cmd 'split h'; +cmd 'resize grow width 10px or 25 ppt'; + +($nodes, $focus) = get_ws_content($tmp); +cmp_float($nodes->[0]->{percent}, 0.25, 'left window got 25%'); +cmp_float($nodes->[1]->{percent}, 0.75, 'right window got 75%'); ############################################################ # checks that resizing floating windows works diff --git a/testcases/t/155-floating-split-size.t b/testcases/t/155-floating-split-size.t index 7475f9c7..604b661e 100644 --- a/testcases/t/155-floating-split-size.t +++ b/testcases/t/155-floating-split-size.t @@ -21,6 +21,9 @@ use i3test; my $tmp = fresh_workspace; +open_window; +cmd 'split v'; + ##################################################################### # open a window with 200x80 ##################################################################### @@ -30,6 +33,8 @@ my $first = open_window({ background_color => '#FF0000', }); +cmd 'split h'; + ##################################################################### # Open a second window with 300x90 ##################################################################### diff --git a/testcases/t/165-for_window.t b/testcases/t/165-for_window.t index 985a7bfd..025fe21c 100644 --- a/testcases/t/165-for_window.t +++ b/testcases/t/165-for_window.t @@ -380,7 +380,8 @@ my %window_types = ( 'menu' => '_NET_WM_WINDOW_TYPE_MENU', 'dropdown_menu' => '_NET_WM_WINDOW_TYPE_DROPDOWN_MENU', 'popup_menu' => '_NET_WM_WINDOW_TYPE_POPUP_MENU', - 'tooltip' => '_NET_WM_WINDOW_TYPE_TOOLTIP' + 'tooltip' => '_NET_WM_WINDOW_TYPE_TOOLTIP', + 'notification' => '_NET_WM_WINDOW_TYPE_NOTIFICATION' ); while (my ($window_type, $atom) = each %window_types) { @@ -398,7 +399,7 @@ EOT my @nodes = @{get_ws($tmp)->{floating_nodes}}; cmp_ok(@nodes, '==', 1, 'one floating container on this workspace'); - is($nodes[0]->{nodes}[0]->{mark}, 'branded', "mark set (window_type = $atom)"); + is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'branded' ], "mark set (window_type = $atom)"); exit_gracefully($pid); @@ -431,7 +432,7 @@ EOT my @nodes = @{get_ws($tmp)->{floating_nodes}}; cmp_ok(@nodes, '==', 1, 'one floating container on this workspace'); - is($nodes[0]->{nodes}[0]->{mark}, 'branded', "mark set (window_type = $atom)"); + is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'branded' ], "mark set (window_type = $atom)"); exit_gracefully($pid); @@ -454,7 +455,7 @@ $window = open_window; @nodes = @{get_ws('trigger')->{floating_nodes}}; cmp_ok(@nodes, '==', 1, 'one floating container on this workspace'); -is($nodes[0]->{nodes}[0]->{mark}, 'triggered', "mark set for workspace criterion"); +is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'triggered' ], "mark set for workspace criterion"); exit_gracefully($pid); diff --git a/testcases/t/171-config-migrate.t b/testcases/t/171-config-migrate.t index d8f9d289..d098ae58 100644 --- a/testcases/t/171-config-migrate.t +++ b/testcases/t/171-config-migrate.t @@ -22,13 +22,6 @@ use Cwd qw(abs_path); use File::Temp qw(tempfile tempdir); use v5.10; -# reads in a whole file -sub slurp { - open my $fh, '<', shift; - local $/; - <$fh>; -} - sub migrate_config { my ($config) = @_; diff --git a/testcases/t/177-bar-config.t b/testcases/t/177-bar-config.t index cc4826c1..956b0caa 100644 --- a/testcases/t/177-bar-config.t +++ b/testcases/t/177-bar-config.t @@ -109,6 +109,9 @@ bar { colors { background #ff0000 statusline #00ff00 + focused_background #cc0000 + focused_statusline #cccc00 + focused_separator #0000cc focused_workspace #4c7899 #285577 #ffffff active_workspace #333333 #222222 #888888 @@ -135,7 +138,7 @@ ok(!$bar_config->{binding_mode_indicator}, 'mode indicator disabled'); is($bar_config->{mode}, 'dock', 'dock mode'); is($bar_config->{position}, 'top', 'position top'); is_deeply($bar_config->{outputs}, [ 'HDMI1', 'HDMI2' ], 'outputs ok'); -is($bar_config->{tray_output}, 'HDMI2', 'tray_output ok'); +is_deeply($bar_config->{tray_outputs}, [ 'LVDS1', 'HDMI2' ], 'tray_output ok'); is($bar_config->{tray_padding}, 0, 'tray_padding ok'); is($bar_config->{font}, 'Terminus', 'font ok'); is($bar_config->{socket_path}, '/tmp/foobar', 'socket_path ok'); @@ -143,6 +146,9 @@ is_deeply($bar_config->{colors}, { background => '#ff0000', statusline => '#00ff00', + focused_background => '#cc0000', + focused_statusline=> '#cccc00', + focused_separator => '#0000cc', focused_workspace_border => '#4c7899', focused_workspace_text => '#ffffff', focused_workspace_bg => '#285577', @@ -288,7 +294,7 @@ ok($bar_config->{binding_mode_indicator}, 'mode indicator enabled'); is($bar_config->{mode}, 'dock', 'dock mode'); is($bar_config->{position}, 'top', 'position top'); is_deeply($bar_config->{outputs}, [ 'HDMI1', 'HDMI2' ], 'outputs ok'); -is($bar_config->{tray_output}, 'HDMI2', 'tray_output ok'); +is_deeply($bar_config->{tray_outputs}, [ 'LVDS1', 'HDMI2' ], 'tray_output ok'); is($bar_config->{font}, 'Terminus', 'font ok'); is($bar_config->{socket_path}, '/tmp/foobar', 'socket_path ok'); is_deeply($bar_config->{colors}, diff --git a/testcases/t/187-commands-parser.t b/testcases/t/187-commands-parser.t index a56d668e..e3481b0a 100644 --- a/testcases/t/187-commands-parser.t +++ b/testcases/t/187-commands-parser.t @@ -44,7 +44,7 @@ sub parser_calls { # The first call has only a single command, the following ones are consolidated # for performance. is(parser_calls('move workspace 3'), - 'cmd_move_con_to_workspace_name(3)', + 'cmd_move_con_to_workspace_name(3, (null))', 'single number (move workspace 3) ok'); is(parser_calls( @@ -57,19 +57,19 @@ is(parser_calls( 'move workspace 3: foobar; ' . 'move workspace "3: foobar"; ' . 'move workspace "3: foobar, baz"; '), - "cmd_move_con_to_workspace_name(3)\n" . - "cmd_move_con_to_workspace_name(3)\n" . - "cmd_move_con_to_workspace_name(3)\n" . - "cmd_move_con_to_workspace_name(foobar)\n" . - "cmd_move_con_to_workspace_name(torrent)\n" . + "cmd_move_con_to_workspace_name(3, (null))\n" . + "cmd_move_con_to_workspace_name(3, (null))\n" . + "cmd_move_con_to_workspace_name(3, (null))\n" . + "cmd_move_con_to_workspace_name(foobar, (null))\n" . + "cmd_move_con_to_workspace_name(torrent, (null))\n" . "cmd_move_workspace_to_output(LVDS1)\n" . - "cmd_move_con_to_workspace_name(3: foobar)\n" . - "cmd_move_con_to_workspace_name(3: foobar)\n" . - "cmd_move_con_to_workspace_name(3: foobar, baz)", + "cmd_move_con_to_workspace_name(3: foobar, (null))\n" . + "cmd_move_con_to_workspace_name(3: foobar, (null))\n" . + "cmd_move_con_to_workspace_name(3: foobar, baz, (null))", 'move ok'); is(parser_calls('move workspace 3: foobar, nop foo'), - "cmd_move_con_to_workspace_name(3: foobar)\n" . + "cmd_move_con_to_workspace_name(3: foobar, (null))\n" . "cmd_nop(foo)", 'multiple ops (move workspace 3: foobar, nop foo) ok'); @@ -131,11 +131,11 @@ is(parser_calls('[con_mark="yay"] focus'), # commands being parsed due to the configuration, people might send IPC # commands with leading or trailing newlines. is(parser_calls("workspace test\n"), - 'cmd_workspace_name(test)', + 'cmd_workspace_name(test, (null))', 'trailing whitespace stripped off ok'); is(parser_calls("\nworkspace test"), - 'cmd_workspace_name(test)', + 'cmd_workspace_name(test, (null))', 'trailing whitespace stripped off ok'); ################################################################################ @@ -177,7 +177,7 @@ is(parser_calls('unknown_literal'), 'error for unknown literal ok'); is(parser_calls('move something to somewhere'), - "ERROR: Expected one of these tokens: 'window', 'container', 'to', 'workspace', 'output', 'mark', 'scratchpad', 'left', 'right', 'up', 'down', 'position', 'absolute'\n" . + "ERROR: Expected one of these tokens: 'window', 'container', 'to', '--no-auto-back-and-forth', 'workspace', 'output', 'mark', 'scratchpad', 'left', 'right', 'up', 'down', 'position', 'absolute'\n" . "ERROR: Your command: move something to somewhere\n" . "ERROR: ^^^^^^^^^^^^^^^^^^^^^^", 'error for unknown literal ok'); @@ -187,27 +187,27 @@ is(parser_calls('move something to somewhere'), ################################################################################ is(parser_calls('workspace "foo"'), - 'cmd_workspace_name(foo)', + 'cmd_workspace_name(foo, (null))', 'Command with simple double quotes ok'); is(parser_calls('workspace "foo'), - 'cmd_workspace_name(foo)', + 'cmd_workspace_name(foo, (null))', 'Command without ending double quotes ok'); is(parser_calls('workspace "foo \"bar"'), - 'cmd_workspace_name(foo "bar)', + 'cmd_workspace_name(foo "bar, (null))', 'Command with escaped double quotes ok'); is(parser_calls('workspace "foo \\'), - 'cmd_workspace_name(foo \\)', + 'cmd_workspace_name(foo \\, (null))', 'Command with single backslash in the end ok'); is(parser_calls('workspace "foo\\\\bar"'), - 'cmd_workspace_name(foo\\bar)', + 'cmd_workspace_name(foo\\bar, (null))', 'Command with escaped backslashes ok'); is(parser_calls('workspace "foo\\\\\\"bar"'), - 'cmd_workspace_name(foo\\"bar)', + 'cmd_workspace_name(foo\\"bar, (null))', 'Command with escaped double quotes after escaped backslashes ok'); ################################################################################ diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index a2b0a3a9..e8835005 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -53,7 +53,7 @@ mode "meh" { EOT my $expected = <<'EOT'; -cfg_enter_mode(meh) +cfg_enter_mode((null), meh) cfg_mode_binding(bindsym, Mod1,Shift, x, (null), (null), (null), resize grow) cfg_mode_binding(bindcode, Mod1, 44, (null), (null), (null), resize shrink) cfg_mode_binding(bindsym, Mod1, x, --release, (null), (null), exec foo) @@ -412,19 +412,19 @@ is(parser_calls($config), ################################################################################ $config = <<'EOT'; -client.focused #4c7899 #285577 #ffffff #2e9ef4 +client.focused #4c7899 #285577 #ffffff #2e9ef4 #b34d4c client.focused_inactive #333333 #5f676a #ffffff #484e50 client.unfocused #333333 #222222 #888888 #292d2e -client.urgent #2f343a #900000 #ffffff #900000 +client.urgent #2f343a #900000 #ffffff #900000 #c00000 client.placeholder #000000 #0c0c0c #ffffff #000000 EOT $expected = <<'EOT'; -cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4) -cfg_color(client.focused_inactive, #333333, #5f676a, #ffffff, #484e50) -cfg_color(client.unfocused, #333333, #222222, #888888, #292d2e) -cfg_color(client.urgent, #2f343a, #900000, #ffffff, #900000) -cfg_color(client.placeholder, #000000, #0c0c0c, #ffffff, #000000) +cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4, #b34d4c) +cfg_color(client.focused_inactive, #333333, #5f676a, #ffffff, #484e50, NULL) +cfg_color(client.unfocused, #333333, #222222, #888888, #292d2e, NULL) +cfg_color(client.urgent, #2f343a, #900000, #ffffff, #900000, #c00000) +cfg_color(client.placeholder, #000000, #0c0c0c, #ffffff, #000000, NULL) EOT is(parser_calls($config), @@ -449,7 +449,7 @@ ERROR: CONFIG: (in file ) ERROR: CONFIG: Line 1: hide_edge_border both ERROR: CONFIG: ^^^^^^^^^^^^^^^^^^^^^ ERROR: CONFIG: Line 2: client.focused #4c7899 #285577 #ffffff #2e9ef4 -cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4) +cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4, NULL) EOT $expected = $expected_all_tokens . $expected_end; @@ -469,7 +469,7 @@ ERROR: CONFIG: (in file ) ERROR: CONFIG: Line 1: hide_edge_borders FOOBAR ERROR: CONFIG: ^^^^^^ ERROR: CONFIG: Line 2: client.focused #4c7899 #285577 #ffffff #2e9ef4 -cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4) +cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4, NULL) EOT is(parser_calls($config), @@ -627,7 +627,7 @@ mode "yo" { EOT $expected = <<'EOT'; -cfg_enter_mode(yo) +cfg_enter_mode((null), yo) cfg_mode_binding(bindsym, (null), x, (null), (null), (null), resize shrink left) ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', 'bind', '}' ERROR: CONFIG: (in file ) diff --git a/testcases/t/210-mark-unmark.t b/testcases/t/210-mark-unmark.t index 93b26d94..446d5465 100644 --- a/testcases/t/210-mark-unmark.t +++ b/testcases/t/210-mark-unmark.t @@ -28,7 +28,7 @@ sub get_mark_for_window_on_workspace { my ($ws, $con) = @_; my $current = first { $_->{window} == $con->{id} } @{get_ws_content($ws)}; - return $current->{mark}; + return $current->{marks}; } ############################################################## @@ -41,7 +41,6 @@ cmd 'split h'; is_deeply(get_marks(), [], 'no marks set yet'); - ############################################################## # 2: mark a con, check that it's marked, unmark it, check that ############################################################## @@ -98,7 +97,7 @@ cmd 'mark important'; cmd 'focus left'; cmd 'mark important'; -is(get_mark_for_window_on_workspace($tmp, $first), 'important', 'first container now has the mark'); +is_deeply(get_mark_for_window_on_workspace($tmp, $first), [ 'important' ], 'first container now has the mark'); ok(!get_mark_for_window_on_workspace($tmp, $second), 'second container lost the mark'); ############################################################## @@ -116,7 +115,7 @@ ok(!get_mark_for_window_on_workspace($tmp, $con), 'container no longer has the m $con = open_window; cmd 'mark --toggle important'; -is(get_mark_for_window_on_workspace($tmp, $con), 'important', 'container now has the mark'); +is_deeply(get_mark_for_window_on_workspace($tmp, $con), [ 'important' ], 'container now has the mark'); ############################################################## # 7: mark a con, toggle a different mark, check it is marked @@ -125,8 +124,8 @@ is(get_mark_for_window_on_workspace($tmp, $con), 'important', 'container now has $con = open_window; cmd 'mark boring'; -cmd 'mark --toggle important'; -is(get_mark_for_window_on_workspace($tmp, $con), 'important', 'container has the most recent mark'); +cmd 'mark --replace --toggle important'; +is_deeply(get_mark_for_window_on_workspace($tmp, $con), [ 'important' ], 'container has the most recent mark'); ############################################################## # 8: mark a con, toggle the mark on another con, @@ -140,7 +139,7 @@ cmd 'mark important'; cmd 'focus left'; cmd 'mark --toggle important'; -is(get_mark_for_window_on_workspace($tmp, $first), 'important', 'left container has the mark now'); +is_deeply(get_mark_for_window_on_workspace($tmp, $first), [ 'important' ], 'left container has the mark now'); ok(!get_mark_for_window_on_workspace($tmp, $second), 'second containr no longer has the mark'); ############################################################## diff --git a/testcases/t/216-layout-restore-split-swallows.t b/testcases/t/216-layout-restore-split-swallows.t index 2e2028a2..c064b5d1 100644 --- a/testcases/t/216-layout-restore-split-swallows.t +++ b/testcases/t/216-layout-restore-split-swallows.t @@ -54,9 +54,6 @@ print $fh <<'EOT'; "floating": "auto_off", "layout": "splitv", "percent": 0.883854166666667, - "swallows": [ - {} - ], "type": "con", "nodes": [ { @@ -65,9 +62,6 @@ print $fh <<'EOT'; "floating": "auto_off", "layout": "splitv", "percent": 1, - "swallows": [ - {} - ], "type": "con", "nodes": [ { diff --git a/testcases/t/232-cmd-move-criteria.t b/testcases/t/232-cmd-move-criteria.t index c023bca4..787f72b3 100644 --- a/testcases/t/232-cmd-move-criteria.t +++ b/testcases/t/232-cmd-move-criteria.t @@ -54,7 +54,8 @@ my %window_types = ( 'menu' => '_NET_WM_WINDOW_TYPE_MENU', 'dropdown_menu' => '_NET_WM_WINDOW_TYPE_DROPDOWN_MENU', 'popup_menu' => '_NET_WM_WINDOW_TYPE_POPUP_MENU', - 'tooltip' => '_NET_WM_WINDOW_TYPE_TOOLTIP' + 'tooltip' => '_NET_WM_WINDOW_TYPE_TOOLTIP', + 'notification' => '_NET_WM_WINDOW_TYPE_NOTIFICATION' ); while (my ($window_type, $atom) = each %window_types) { diff --git a/testcases/t/234-layout-restore-output.t b/testcases/t/234-layout-restore-output.t index bc90131d..5a1f3763 100644 --- a/testcases/t/234-layout-restore-output.t +++ b/testcases/t/234-layout-restore-output.t @@ -54,7 +54,7 @@ print $fh <<'EOT'; "percent": 1, "swallows": [ { - // "class": "^URxvt$", + "class": "^URxvt$" // "instance": "^urxvt$", // "title": "^vals\\@w00t\\:\\ \\~$" } @@ -119,7 +119,7 @@ print $fh <<'EOT'; "percent": 1, "swallows": [ { - // "class": "^URxvt$", + "class": "^URxvt$" // "instance": "^urxvt$", // "title": "^vals\\@w00t\\:\\ \\~$" } @@ -165,7 +165,7 @@ print $fh <<'EOT'; "percent": 1, "swallows": [ { - // "class": "^URxvt$", + "class": "^URxvt$" // "instance": "^urxvt$", // "title": "^vals\\@w00t\\:\\ \\~$" } @@ -213,7 +213,7 @@ print $fh <<'EOT'; "percent": 1, "swallows": [ { - // "class": "^URxvt$", + "class": "^URxvt$" // "instance": "^urxvt$", // "title": "^vals\\@w00t\\:\\ \\~$" } diff --git a/testcases/t/235-wm-class-change-handler.t b/testcases/t/235-wm-class-change-handler.t index 3685b30c..ce237b57 100644 --- a/testcases/t/235-wm-class-change-handler.t +++ b/testcases/t/235-wm-class-change-handler.t @@ -63,7 +63,7 @@ is($con->{window_properties}->{instance}, 'special', # The mark `special_class_mark` is added in a `for_window` assignment in the # config for testing purposes -is($con->{mark}, 'special_class_mark', +is_deeply($con->{marks}, [ 'special_class_mark' ], 'A `for_window` assignment should run for a match when the window changes class'); change_window_class($win, "abcdefghijklmnopqrstuv\0abcd", 24); diff --git a/testcases/t/245-move-position-mouse.t b/testcases/t/245-move-position-mouse.t index ae049271..3268e582 100644 --- a/testcases/t/245-move-position-mouse.t +++ b/testcases/t/245-move-position-mouse.t @@ -38,7 +38,7 @@ my $root_rect = $x->root->rect; $config = <atom(name => '_NET_WM_STATE_HIDDEN'); my ($con) = @_; my $cookie = $x->get_property( @@ -34,7 +33,7 @@ sub get_wm_state { my $reply = $x->get_property_reply($cookie->{sequence}); my $len = $reply->{length}; - return 0 if $len == 0; + return undef if $len == 0; my @atoms = unpack("L$len", $reply->{value}); return \@atoms; @@ -65,6 +64,20 @@ cmd 'sticky enable'; cmd 'fullscreen disable'; is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'only _NET_WM_STATE_STICKY is set'); +############################################################################### +# _NET_WM_STATE is removed when the window is withdrawn. +############################################################################### + +fresh_workspace; +$window = open_window; +cmd 'sticky enable'; +is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'sanity check: _NET_WM_STATE_STICKY is set'); + +$window->unmap; +wait_for_unmap($window); + +is(get_wm_state($window), undef, '_NET_WM_STATE is removed'); + ########################################################################## done_testing; diff --git a/testcases/t/254-move-to-output-with-criteria.t b/testcases/t/254-move-to-output-with-criteria.t index fb832184..b27ded3e 100644 --- a/testcases/t/254-move-to-output-with-criteria.t +++ b/testcases/t/254-move-to-output-with-criteria.t @@ -21,7 +21,7 @@ use i3test i3_autostart => 0; my $config = <get_marks->recv; +} + +sub get_mark_for_window_on_workspace { + my ($ws, $con) = @_; + + my $current = first { $_->{window} == $con->{id} } @{get_ws_content($ws)}; + return $current->{marks}; +} + +############################################################################### +# Verify that multiple marks can be set on a window. +############################################################################### + +$ws = fresh_workspace; +$con = open_window; +cmd 'mark --add A'; +cmd 'mark --add B'; + +is_deeply(sort(get_marks()), [ 'A', 'B' ], 'both marks exist'); +is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A', 'B' ], 'both marks are on the same window'); + +cmd 'unmark'; + +############################################################################### +# Verify that toggling a mark can affect only the specified mark. +############################################################################### + +$ws = fresh_workspace; +$con = open_window; +cmd 'mark A'; + +cmd 'mark --add --toggle B'; +is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A', 'B' ], 'both marks are on the same window'); +cmd 'mark --add --toggle B'; +is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A' ], 'only mark B has been removed'); + +cmd 'unmark'; + +############################################################################### +# Verify that unmarking a mark leaves other marks on the same window intact. +############################################################################### + +$ws = fresh_workspace; +$con = open_window; +cmd 'mark --add A'; +cmd 'mark --add B'; +cmd 'mark --add C'; + +cmd 'unmark B'; +is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A', 'C' ], 'only mark B has been removed'); + +cmd 'unmark'; + +############################################################################### +# Verify that matching via mark works on windows with multiple marks. +############################################################################### + +$ws = fresh_workspace; +$con = open_window; +cmd 'mark --add A'; +cmd 'mark --add B'; +open_window; + +cmd '[con_mark=B] mark --add C'; +is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A', 'B', 'C' ], 'matching on a mark works with multiple marks'); + +cmd 'unmark'; + +############################################################################### +# Verify that "unmark" can be matched on the focused window. +############################################################################### + +$ws = fresh_workspace; +$con = open_window; +cmd 'mark --add A'; +cmd 'mark --add B'; +open_window; +cmd 'mark --add C'; +cmd 'mark --add D'; + +is_deeply(sort(get_marks()), [ 'A', 'B', 'C', 'D' ], 'all marks exist'); + +cmd '[con_id=__focused__] unmark'; + +is_deeply(sort(get_marks()), [ 'A', 'B' ], 'marks on the unfocused window still exist'); +is_deeply(get_mark_for_window_on_workspace($ws, $con), [ 'A', 'B' ], 'matching on con_id=__focused__ works for unmark'); + +cmd 'unmark'; + +############################################################################### + +done_testing; diff --git a/testcases/t/256-no-auto-back-and-forth.t b/testcases/t/256-no-auto-back-and-forth.t new file mode 100644 index 00000000..859b76f1 --- /dev/null +++ b/testcases/t/256-no-auto-back-and-forth.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) +# +# Test for the --no-auto-back-and-forth flag. +# Ticket: #2028 +use i3test; + +my ($first, $second, $third, $con); +$first = "1:first"; +$second = "2:second"; +$third = "3:third"; + +############################################################################### +# Switching to another workspace when passing --no-auto-back-and-forth works +# as if the flag wasn't set. +############################################################################### + +cmd qq|workspace "$first"|; +ok(get_ws($first)->{focused}, 'first workspace is focused'); + +cmd qq|workspace --no-auto-back-and-forth "$second"|; +ok(get_ws($second)->{focused}, 'second workspace is focused'); + +cmd qq|workspace --no-auto-back-and-forth number "$third"|; +ok(get_ws($third)->{focused}, 'third workspace is focused'); + +############################################################################### +# Switching to the focused workspace when passing --no-auto-back-and-forth +# is a no-op. +############################################################################### + +cmd qq|workspace "$second"|; +cmd qq|workspace "$first"|; +ok(get_ws($first)->{focused}, 'first workspace is focused'); + +cmd qq|workspace --no-auto-back-and-forth "$first"|; +ok(get_ws($first)->{focused}, 'first workspace is still focused'); + +cmd qq|workspace --no-auto-back-and-forth number "$first"|; +ok(get_ws($first)->{focused}, 'first workspace is still focused'); + +############################################################################### +# Moving a window to another workspace when passing --no-auto-back-and-forth +# works as if the flag wasn't set. +############################################################################### + +cmd qq|workspace "$third"|; +cmd qq|workspace "$second"|; +cmd qq|workspace "$first"|; +$con = open_window; +cmd 'mark mywindow'; + +cmd qq|move --no-auto-back-and-forth window to workspace "$second"|; +is(@{get_ws($second)->{nodes}}, 1, 'window was moved to second workspace'); +cmd qq|[con_mark=mywindow] move window to workspace "$first"|; + +cmd qq|move --no-auto-back-and-forth window to workspace number "$third"|; +is(@{get_ws($third)->{nodes}}, 1, 'window was moved to third workspace'); +cmd qq|[con_mark=mywindow] move window to workspace "$first"|; + +cmd '[con_mark=mywindow] kill'; + +############################################################################### +# Moving a window to the same workspace when passing --no-auto-back-and-forth +# is a no-op. +############################################################################### + +cmd qq|workspace "$second"|; +cmd qq|workspace "$first"|; +$con = open_window; +cmd 'mark mywindow'; + +cmd qq|move --no-auto-back-and-forth window to workspace "$first"|; +is(@{get_ws($first)->{nodes}}, 1, 'window is still on first workspace'); +cmd qq|[con_mark=mywindow] move window to workspace "$first"|; + +cmd qq|move --no-auto-back-and-forth window to workspace number "$first"|; +is(@{get_ws($first)->{nodes}}, 1, 'window is still on first workspace'); +cmd qq|[con_mark=mywindow] move window to workspace "$first"|; + +cmd '[con_mark=mywindow] kill'; + +############################################################################### + +done_testing; diff --git a/testcases/t/257-keypress-group1-fallback.t b/testcases/t/257-keypress-group1-fallback.t new file mode 100644 index 00000000..212dfd15 --- /dev/null +++ b/testcases/t/257-keypress-group1-fallback.t @@ -0,0 +1,96 @@ +#!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 when using multiple keyboard layouts at the same time, bindings +# without a specified XKB group will work in all XKB groups. +# Ticket: #2062 +# Bug still in: 4.11-103-gc8d51b4 +# Bug introduced with commit 0e5180cae9e9295678e3f053042b559e82cb8c98 +use i3test i3_autostart => 0; +use i3test::XTEST; +use ExtUtils::PkgConfig; + +SKIP: { + skip "libxcb-xkb too old (need >= 1.11)", 1 unless + ExtUtils::PkgConfig->atleast_version('xcb-xkb', '1.11'); + skip "setxkbmap not found", 1 if + system(q|setxkbmap -print >/dev/null|) != 0; + +my $config = < 0; +use i3test::XTEST; +use ExtUtils::PkgConfig; + +SKIP: { + skip "libxcb-xkb too old (need >= 1.11)", 1 unless + ExtUtils::PkgConfig->atleast_version('xcb-xkb', '1.11'); + +my $config = < sub { + my ($window) = @_; + + my $atomname = $x->atom(name => '_NET_WM_USER_TIME'); + my $atomtype = $x->atom(name => 'CARDINAL'); + $x->change_property( + PROP_MODE_REPLACE, + $window->id, + $atomname->id, + $atomtype->id, + 32, + 1, + pack('L1', $wm_user_time), + ); + }, + ); + + return $window; +} + +##################################################################### +# 1: if _NET_WM_USER_TIME is set to 0, the window is not focused +# initially. +##################################################################### + +$ws = fresh_workspace; + +open_window; +$other = get_focused($ws); +open_window_with_user_time(0); + +is(get_focused($ws), $other, 'new window is not focused'); + +##################################################################### +# 2: if _NET_WM_USER_TIME is set to something other than 0, the +# window is focused anyway. +##################################################################### + +$ws = fresh_workspace; + +open_window; +$other = get_focused($ws); +open_window_with_user_time(42); + +isnt(get_focused($ws), $other, 'new window is focused'); + +done_testing; diff --git a/testcases/t/260-invalid-criteria.t b/testcases/t/260-invalid-criteria.t new file mode 100644 index 00000000..4c1830f9 --- /dev/null +++ b/testcases/t/260-invalid-criteria.t @@ -0,0 +1,27 @@ +#!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) +# +# Ticket: #2091 +use i3test; + +my $ws = fresh_workspace; +open_window; + +my $result = cmd '[con_id=foobar] kill'; +is($result->[0]->{success}, 0, 'command was unsuccessful'); +is($result->[0]->{error}, 'Invalid match: invalid con_id', 'correct error is returned'); + +done_testing; diff --git a/testcases/t/261-match-con_id-con_mark-combinations.t b/testcases/t/261-match-con_id-con_mark-combinations.t new file mode 100644 index 00000000..61ff1ff0 --- /dev/null +++ b/testcases/t/261-match-con_id-con_mark-combinations.t @@ -0,0 +1,55 @@ +#!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) +# +# Ticket: #2111 +use i3test; + +my ($ws); + +############################################################################### +# Verify that con_id can be combined with other criteria +############################################################################### + +$ws = fresh_workspace; +open_window(wm_class => 'matchme'); + +cmd '[con_id=__focused__ class=doesnotmatch] kill'; +sync_with_i3; +is(@{get_ws($ws)->{nodes}}, 1, 'window was not killed'); + +cmd '[con_id=__focused__ class=matchme] kill'; +sync_with_i3; +is(@{get_ws($ws)->{nodes}}, 0, 'window was killed'); + +############################################################################### +# Verify that con_mark can be combined with other criteria +############################################################################### + +$ws = fresh_workspace; +open_window(wm_class => 'matchme'); +cmd 'mark marked'; + +cmd '[con_mark=marked class=doesnotmatch] kill'; +sync_with_i3; +is(@{get_ws($ws)->{nodes}}, 1, 'window was not killed'); + +cmd '[con_mark=marked class=matchme] kill'; +sync_with_i3; +is(@{get_ws($ws)->{nodes}}, 0, 'window was killed'); + +############################################################################### + +done_testing; diff --git a/testcases/t/262-config-validation.t b/testcases/t/262-config-validation.t new file mode 100644 index 00000000..3a3e42dc --- /dev/null +++ b/testcases/t/262-config-validation.t @@ -0,0 +1,39 @@ +#!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) +# +# Ensures that calling i3 with the -C switch works (smoke test). +# Ticket: #2144 +use i3test i3_autostart => 0; +use POSIX ":sys_wait_h"; +use Time::HiRes qw(sleep); + +my $config = < 1); +isnt($exit_code, 0, 'i3 exited with an error code'); + +my $log = get_i3_log; + +# We don't care so much about the output (there are tests for this), but rather +# that we got correct output at all instead of, e.g., a segfault. +ok($log =~ /Expected one of these tokens/, 'an invalid config token was found'); + +done_testing; diff --git a/testcases/t/262-root-window-mouse-binding.t b/testcases/t/262-root-window-mouse-binding.t new file mode 100644 index 00000000..c8fd89ef --- /dev/null +++ b/testcases/t/262-root-window-mouse-binding.t @@ -0,0 +1,42 @@ +#!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 mouse bindings work on the root window if +# --whole-window is set. +# Ticket: #2115 +use i3test i3_autostart => 0; +use i3test::XTEST; + +my $config = < 0; + +my $config = <connect->recv; + +my $cv = AnyEvent->condvar; +$i3->subscribe({ + mode => sub { + my ($event) = @_; + $cv->send($event->{change} eq 'default'); + } +})->recv; + +cmd 'reload'; + +# Timeout after 0.5s +my $t; +$t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0); }); + +ok($cv->recv, 'Mode event received'); + +exit_gracefully($pid); + +done_testing; diff --git a/testcases/t/263-i3-floating-window-atom.t b/testcases/t/263-i3-floating-window-atom.t new file mode 100644 index 00000000..43b69ccb --- /dev/null +++ b/testcases/t/263-i3-floating-window-atom.t @@ -0,0 +1,70 @@ +#!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 our proprietary atom I3_FLOATING_WINDOW to allow +# identifying floating windows. +# Ticket: #2223 +use i3test; +use X11::XCB qw(:all); + +my ($con); + +sub has_i3_floating_window { + sync_with_i3; + + my ($con) = @_; + my $cookie = $x->get_property( + 0, + $con->{id}, + $x->atom(name => 'I3_FLOATING_WINDOW')->id, + $x->atom(name => 'CARDINAL')->id, + 0, + 1 + ); + + my $reply = $x->get_property_reply($cookie->{sequence}); + return 0 if $reply->{length} != 1; + + return unpack("L", $reply->{value}); +} + +############################################################################### +# Toggling floating on a container adds / removes I3_FLOATING_WINDOW. +############################################################################### + +fresh_workspace; + +$con = open_window; +is(has_i3_floating_window($con), 0, 'I3_FLOATING_WINDOW is not set'); + +cmd 'floating enable'; +is(has_i3_floating_window($con), 1, 'I3_FLOATING_WINDOW is set'); + +cmd 'floating disable'; +is(has_i3_floating_window($con), 0, 'I3_FLOATING_WINDOW is not set'); + +############################################################################### +# A window that is floated when managed has I3_FLOATING_WINDOW set. +############################################################################### +# +fresh_workspace; + +$con = open_floating_window; +is(has_i3_floating_window($con), 1, 'I3_FLOATING_WINDOW is set'); + +############################################################################### + +done_testing; diff --git a/testcases/t/525-i3bar-mouse-bindings.t b/testcases/t/525-i3bar-mouse-bindings.t index eabcad7a..39690291 100644 --- a/testcases/t/525-i3bar-mouse-bindings.t +++ b/testcases/t/525-i3bar-mouse-bindings.t @@ -17,6 +17,7 @@ # Ensures that mouse bindings on the i3bar work correctly. # Ticket: #1695 use i3test i3_autostart => 0; +use i3test::XTEST; my ($cv, $timer); sub reset_test { @@ -41,64 +42,72 @@ bar { } EOT -SKIP: { - qx(command -v xdotool 2> /dev/null); - skip 'xdotool is required for this test', 1 if $?; +my $pid = launch_with_config($config); +my $i3 = i3(get_socket_path()); +$i3->connect()->recv; +my $ws = fresh_workspace; - my $pid = launch_with_config($config); - my $i3 = i3(get_socket_path()); - $i3->connect()->recv; - my $ws = fresh_workspace; - - reset_test; - $i3->subscribe({ +reset_test; +$i3->subscribe({ window => sub { my ($event) = @_; if ($event->{change} eq 'focus') { $cv->send($event->{container}); } + if ($event->{change} eq 'new') { + if (defined($event->{container}->{window_properties}->{class}) && + $event->{container}->{window_properties}->{class} eq 'i3bar') { + $cv->send($event->{container}); + } + } }, })->recv; - my $left = open_window; - my $right = open_window; - sync_with_i3; - my $con = $cv->recv; - is($con->{window}, $right->{id}, 'focus is initially on the right container'); - reset_test; +my $con = $cv->recv; +ok($con, 'i3bar appeared'); - qx(xdotool mousemove 3 3 click 1); - sync_with_i3; - $con = $cv->recv; - is($con->{window}, $left->{id}, 'button 1 moves focus left'); - reset_test; +my $left = open_window; +my $right = open_window; +sync_with_i3; +$con = $cv->recv; +is($con->{window}, $right->{id}, 'focus is initially on the right container'); +reset_test; - qx(xdotool mousemove 3 3 click 2); - sync_with_i3; - $con = $cv->recv; - is($con->{window}, $right->{id}, 'button 2 moves focus right'); - reset_test; +xtest_button_press(1, 3, 3); +xtest_button_release(1, 3, 3); +sync_with_i3; +$con = $cv->recv; +is($con->{window}, $left->{id}, 'button 1 moves focus left'); +reset_test; - qx(xdotool mousemove 3 3 click 3); - sync_with_i3; - $con = $cv->recv; - is($con->{window}, $left->{id}, 'button 3 moves focus left'); - reset_test; +xtest_button_press(2, 3, 3); +xtest_button_release(2, 3, 3); +sync_with_i3; +$con = $cv->recv; +is($con->{window}, $right->{id}, 'button 2 moves focus right'); +reset_test; - qx(xdotool mousemove 3 3 click 4); - sync_with_i3; - $con = $cv->recv; - is($con->{window}, $right->{id}, 'button 4 moves focus right'); - reset_test; +xtest_button_press(3, 3, 3); +xtest_button_release(3, 3, 3); +sync_with_i3; +$con = $cv->recv; +is($con->{window}, $left->{id}, 'button 3 moves focus left'); +reset_test; - qx(xdotool mousemove 3 3 click 5); - sync_with_i3; - $con = $cv->recv; - is($con->{window}, $left->{id}, 'button 5 moves focus left'); - reset_test; +xtest_button_press(4, 3, 3); +xtest_button_release(4, 3, 3); +sync_with_i3; +$con = $cv->recv; +is($con->{window}, $right->{id}, 'button 4 moves focus right'); +reset_test; - exit_gracefully($pid); +xtest_button_press(5, 3, 3); +xtest_button_release(5, 3, 3); +sync_with_i3; +$con = $cv->recv; +is($con->{window}, $left->{id}, 'button 5 moves focus left'); +reset_test; -} +exit_gracefully($pid); done_testing; diff --git a/testcases/t/528-workspace-next-prev.t b/testcases/t/528-workspace-next-prev.t new file mode 100644 index 00000000..79e83a07 --- /dev/null +++ b/testcases/t/528-workspace-next-prev.t @@ -0,0 +1,127 @@ +#!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' works correctly. +# +use List::Util qw(first); +use i3test i3_autostart => 0; + +sub assert_next { + my ($expected) = @_; + + cmd 'workspace next'; + # We need to sync after changing focus to a different output to wait for the + # EnterNotify to be processed, otherwise it will be processed at some point + # later in time and mess up our subsequent tests. + sync_with_i3; + + is(focused_ws, $expected, "workspace $expected focused"); +} + + +my $config = <root->warp_pointer(0, 0); +sync_with_i3; + +cmd 'workspace A'; +# ensure workspace A stays open +open_window; + +cmd 'workspace B'; +# ensure workspace B stays open +open_window; + +cmd 'workspace D'; +# ensure workspace D stays open +open_window; + +cmd 'workspace E'; +# ensure workspace E stays open +open_window; + +cmd 'focus output right'; + +cmd 'workspace 1'; +# ensure workspace 1 stays open +open_window; + +cmd 'workspace 2'; +# ensure workspace 2 stays open +open_window; + +cmd 'workspace 3'; +# ensure workspace 3 stays open +open_window; + +cmd 'workspace 4'; +# ensure workspace 4 stays open +open_window; + +cmd 'workspace 5'; +# ensure workspace 5 stays open +open_window; + +cmd 'workspace C'; +# ensure workspace C stays open +open_window; + +cmd 'workspace F'; +# ensure workspace F stays open +open_window; + +cmd 'focus output right'; + +################################################################################ +# Use workspace next and verify the correct order. +################################################################################ + +# The current order should be: +# output 1: A, B, D, E +# output 2: 1, 2, 3, 4, 5, C, F + +cmd 'workspace A'; +is(focused_ws, 'A', 'back on workspace A'); + +assert_next('B'); +assert_next('D'); +assert_next('E'); +assert_next('C'); +assert_next('F'); +assert_next('1'); +assert_next('2'); +assert_next('3'); +assert_next('4'); +assert_next('5'); +assert_next('A'); +assert_next('B'); + +exit_gracefully($pid); + +done_testing; diff --git a/testcases/t/529-net-wm-desktop.t b/testcases/t/529-net-wm-desktop.t new file mode 100644 index 00000000..f6a3b218 --- /dev/null +++ b/testcases/t/529-net-wm-desktop.t @@ -0,0 +1,350 @@ +#!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 _NET_WM_DESKTOP. +# Ticket: #2153 +use i3test i3_autostart => 0; +use X11::XCB qw(:all); + +############################################################################### + +sub get_net_wm_desktop { + sync_with_i3; + + my ($con) = @_; + my $cookie = $x->get_property( + 0, + $con->{id}, + $x->atom(name => '_NET_WM_DESKTOP')->id, + $x->atom(name => 'CARDINAL')->id, + 0, + 1 + ); + + my $reply = $x->get_property_reply($cookie->{sequence}); + return undef if $reply->{length} != 1; + + return unpack("L", $reply->{value}); +} + +sub send_net_wm_desktop { + my ($con, $idx) = @_; + my $msg = pack "CCSLLLLLL", + X11::XCB::CLIENT_MESSAGE, 32, 0, + $con->{id}, + $x->atom(name => '_NET_WM_DESKTOP')->id, + $idx, 0, 0, 0, 0; + + $x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg); + sync_with_i3; +} + +sub open_window_with_net_wm_desktop { + my $idx = shift; + my $window = open_window( + before_map => sub { + my ($window) = @_; + $x->change_property( + PROP_MODE_REPLACE, + $window->id, + $x->atom(name => '_NET_WM_DESKTOP')->id, + $x->atom(name => 'CARDINAL')->id, + 32, 1, + pack('L', $idx), + ); + }, + ); + + return $window; +} + +# We need to kill all windows in between tests since they survive the i3 restart +# and will interfere with the following tests. +sub kill_windows { + sync_with_i3; + cmd '[title="Window.*"] kill'; +} + +############################################################################### + +my ($config, $config_mm, $pid, $con); + +$config = <{floating_nodes}}, 1, 'The window is floating'); +ok(get_ws('0')->{floating_nodes}->[0]->{nodes}->[0]->{sticky}, 'The window is sticky'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# _NET_WM_DESKTOP is updated when the window is moved to another workspace +# on the same output. +############################################################################### + +$pid = launch_with_config($config); + +cmd 'workspace 0'; +open_window; +cmd 'workspace 1'; +open_window; +cmd 'workspace 0'; +$con = open_window; + +cmd 'move window to workspace 1'; + +is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated when moving the window'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# _NET_WM_DESKTOP is updated when the floating window is moved to another +# workspace on the same output. +############################################################################### + +$pid = launch_with_config($config); + +cmd 'workspace 0'; +open_window; +cmd 'workspace 1'; +open_window; +cmd 'workspace 0'; +$con = open_window; +cmd 'floating enable'; + +cmd 'move window to workspace 1'; + +is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated when moving the window'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# _NET_WM_DESKTOP is updated when the window is moved to another workspace +# on another output. +############################################################################### + +$pid = launch_with_config($config_mm); + +cmd 'workspace 0'; +open_window; +cmd 'workspace 10'; +open_window; +cmd 'workspace 0'; +$con = open_window; + +cmd 'move window to workspace 10'; + +is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated when moving the window'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# _NET_WM_DESKTOP is removed when the window is withdrawn. +############################################################################### + +$pid = launch_with_config($config); + +$con = open_window; +is(get_net_wm_desktop($con), 0, '_NET_WM_DESKTOP is set (sanity check)'); + +$con->unmap; +wait_for_unmap($con); + +is(get_net_wm_desktop($con), undef, '_NET_WM_DESKTOP is removed'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# A _NET_WM_DESKTOP client message sent to the root window moves a window +# to the correct workspace. +############################################################################### + +$pid = launch_with_config($config); + +cmd 'workspace 0'; +open_window; +cmd 'workspace 1'; +open_window; +cmd 'workspace 0'; + +$con = open_window; +is_num_children('0', 2, 'The window is on workspace 0'); + +send_net_wm_desktop($con, 1); + +is_num_children('0', 1, 'The window is no longer on workspace 0'); +is_num_children('1', 2, 'The window is now on workspace 1'); +is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# A _NET_WM_DESKTOP client message sent to the root window can make a window +# sticky. +############################################################################### + +$pid = launch_with_config($config); + +cmd 'workspace 0'; +$con = open_window; + +send_net_wm_desktop($con, 0xFFFFFFFF); + +is(get_net_wm_desktop($con), 0xFFFFFFFF, '_NET_WM_DESKTOP is updated'); +is(@{get_ws('0')->{floating_nodes}}, 1, 'The window is floating'); +ok(get_ws('0')->{floating_nodes}->[0]->{nodes}->[0]->{sticky}, 'The window is sticky'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# _NET_WM_DESKTOP is updated when a new workspace with a lower number is +# opened and closed. +############################################################################### + +$pid = launch_with_config($config); + +cmd 'workspace 1'; +$con = open_window; +is(get_net_wm_desktop($con), 0, '_NET_WM_DESKTOP is set sanity check)'); + +cmd 'workspace 0'; +is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# _NET_WM_DESKTOP is updated when a window is made sticky by command. +############################################################################### + +$pid = launch_with_config($config); + +cmd 'workspace 0'; +$con = open_window; +cmd 'floating enable'; +is(get_net_wm_desktop($con), 0, '_NET_WM_DESKTOP is set sanity check)'); + +cmd 'sticky enable'; +is(get_net_wm_desktop($con), 0xFFFFFFFF, '_NET_WM_DESKTOP is updated'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# _NET_WM_DESKTOP is updated when a window is made sticky by client message. +############################################################################### + +$pid = launch_with_config($config); + +cmd 'workspace 0'; +$con = open_window; +cmd 'floating enable'; +is(get_net_wm_desktop($con), 0, '_NET_WM_DESKTOP is set sanity check)'); + +my $msg = pack "CCSLLLLLL", + X11::XCB::CLIENT_MESSAGE, 32, 0, + $con->{id}, + $x->atom(name => '_NET_WM_STATE')->id, + 1, + $x->atom(name => '_NET_WM_STATE_STICKY')->id, + 0, 0, 0; + +$x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg); +sync_with_i3; + +is(get_net_wm_desktop($con), 0xFFFFFFFF, '_NET_WM_DESKTOP is updated'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### + +done_testing; diff --git a/testcases/t/530-bug-2229.t b/testcases/t/530-bug-2229.t new file mode 100644 index 00000000..cfe61dec --- /dev/null +++ b/testcases/t/530-bug-2229.t @@ -0,0 +1,50 @@ +#!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) +# +# Ticket: #2229 +# Bug still in: 4.11-262-geb631ce +use i3test i3_autostart => 0; + +my $config = <get_workspaces->recv; +my @ws_names = map { $_->{name} } @$get_ws; +ok(!('3' ~~ @ws_names), 'workspace 3 has been closed'); + +exit_gracefully($pid); + +done_testing; diff --git a/travis-build.Dockerfile b/travis-build.Dockerfile new file mode 100644 index 00000000..0861b26a --- /dev/null +++ b/travis-build.Dockerfile @@ -0,0 +1,29 @@ +# vim:ft=Dockerfile +FROM debian:sid + +RUN echo force-unsafe-io > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup +# Paper over occasional network flakiness of some mirrors. +RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry + +# NOTE: I tried exclusively using gce_debian_mirror.storage.googleapis.com +# instead of httpredir.debian.org, but the results (Fetched 123 MB in 36s (3357 +# kB/s)) are not any better than httpredir.debian.org (Fetched 123 MB in 34s +# (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now. + +# Install mk-build-deps (for installing the i3 build dependencies), +# clang and clang-format-3.5 (for checking formatting and building with clang), +# lintian (for checking spelling errors), +# test suite dependencies (for running tests) +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + dpkg-dev devscripts git equivs \ + clang clang-format-3.5 \ + lintian \ + libanyevent-perl libanyevent-i3-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libinline-c-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl-modules libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libipc-run-perl libxcb-xtest0-dev libx11-xcb-perl libanyevent-i3-perl && \ + rm -rf /var/lib/apt/lists/* + +# Install i3 build dependencies. +COPY debian/control /usr/src/i3-debian-packaging/control +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' /usr/src/i3-debian-packaging/control && \ + rm -rf /var/lib/apt/lists/* diff --git a/travis/check-formatting.sh b/travis/check-formatting.sh new file mode 100755 index 00000000..abdb1168 --- /dev/null +++ b/travis/check-formatting.sh @@ -0,0 +1,2 @@ +#!/bin/sh +clang-format-3.5 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false) diff --git a/travis/check-safe-wrappers.sh b/travis/check-safe-wrappers.sh new file mode 100755 index 00000000..f6911ca2 --- /dev/null +++ b/travis/check-safe-wrappers.sh @@ -0,0 +1,19 @@ +#!/bin/sh +funcs='malloc|calloc|realloc|strdup|strndup|asprintf|write' +cstring='"([^"\\]|\\.)*"' +cchar="'[^\\\\]'|'\\\\.[^']*'" +regex="^([^'\"]|${cstring}|${cchar})*\<(${funcs})\>" +detected=0 +while IFS= read -r file; do + if { cpp -w -fpreprocessed "$file" || exit "$?"; } | grep -E -- "$regex"; then + echo "^ $file calls a function that has a safe counterpart." + detected=1 + fi +done << EOF +$(find -name '*.c' -not -name safewrappers.c -not -name strndup.c) +EOF +if [ "$detected" -ne 0 ]; then + echo + echo "Calls of functions that have safe counterparts were detected." + exit 1 +fi diff --git a/travis/check-spelling.pl b/travis/check-spelling.pl new file mode 100755 index 00000000..71feec31 --- /dev/null +++ b/travis/check-spelling.pl @@ -0,0 +1,64 @@ +#!/usr/bin/env perl +# vim:ts=4:sw=4:expandtab +# +# © 2016 Michael Stapelberg +# +# Checks for spelling errors in binaries and manpages (to be run by continuous +# integration to point out spelling errors before accepting contributions). + +use strict; +use warnings; +use v5.10; +use autodie; +use lib 'testcases/lib'; +use i3test::Util qw(slurp); +use Lintian::Check qw(check_spelling); + +# Lintian complains if we don’t set a vendor. +use Lintian::Data; +use Lintian::Profile; +Lintian::Data->set_vendor( + Lintian::Profile->new('debian', ['/usr/share/lintian'], {})); + +my $exitcode = 0; + +# Whitelist for spelling errors in manpages, in case the spell checker has +# false-positives. +my $binary_spelling_exceptions = { + #'exmaple' => 1, # Example for how to add entries to this whitelist. + 'betwen' => 1, # asan_flags.inc contains this spelling error. +}; +my @binaries = qw( + i3 + i3-config-wizard/i3-config-wizard + i3-dump-log/i3-dump-log + i3-input/i3-input + i3-msg/i3-msg + i3-nagbar/i3-nagbar + i3bar/i3bar +); +for my $binary (@binaries) { + check_spelling(slurp($binary), $binary_spelling_exceptions, sub { + my ($current, $fixed) = @_; + say STDERR qq|Binary "$binary" contains a spelling error: "$current" should be "$fixed"|; + $exitcode = 1; + }); +} + +# Whitelist for spelling errors in manpages, in case the spell checker has +# false-positives. +my $manpage_spelling_exceptions = { +}; + +for my $name (glob('man/*.1')) { + for my $line (split(/\n/, slurp($name))) { + next if $line =~ /^\.\\\"/o; + check_spelling($line, $manpage_spelling_exceptions, sub { + my ($current, $fixed) = @_; + say STDERR qq|Manpage "$name" contains a spelling error: "$current" should be "$fixed"|; + $exitcode = 1; + }); + } +} + +exit $exitcode; diff --git a/travis/docker-build-and-push.sh b/travis/docker-build-and-push.sh new file mode 100755 index 00000000..7dfd3392 --- /dev/null +++ b/travis/docker-build-and-push.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +set -e + +# .dockerignore is created on demand so that release.sh and other scripts are +# not influenced by our travis setup. +echo .git > .dockerignore + +docker build --pull --no-cache --rm -t=${BASENAME} -f travis-build.Dockerfile . +docker login -e ${DOCKER_EMAIL} -u ${DOCKER_USER} -p ${DOCKER_PASS} +docker push ${BASENAME} diff --git a/travis/ha.sh b/travis/ha.sh new file mode 100755 index 00000000..688755c3 --- /dev/null +++ b/travis/ha.sh @@ -0,0 +1,7 @@ +#!/bin/sh +# Returns a hash to be used as version number suffix for the i3/travis-base +# docker container. The hash is over all files which influence what gets +# installed in the container, so that any changes in what needs to be installed +# will result in a cache invalidation. + +cat debian/control travis-build.Dockerfile | sha256sum | dd bs=1 count=8 status=none diff --git a/travis/run-tests.sh b/travis/run-tests.sh new file mode 100755 index 00000000..87c5dbb7 --- /dev/null +++ b/travis/run-tests.sh @@ -0,0 +1,8 @@ +#!/bin/sh +cd testcases +# Try running the tests in parallel so that the common case (tests pass) is +# quick, but fall back to running them in sequence to make debugging easier. +if ! xvfb-run ./complete-run.pl +then + xvfb-run ./complete-run.pl --parallel=1 || (cat latest/complete-run.log; false) +fi