]> git.sur5r.net Git - i3/i3/commitdiff
Merge branch 'next' into master
authorMichael Stapelberg <michael@stapelberg.de>
Wed, 30 Sep 2015 06:55:24 +0000 (08:55 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Wed, 30 Sep 2015 06:55:24 +0000 (08:55 +0200)
219 files changed:
.gitignore
.travis.yml
CONTRIBUTING.md
DEPENDS
Makefile
RELEASE-NOTES-4.10.3 [deleted file]
RELEASE-NOTES-4.11 [new file with mode: 0644]
common.mk
debian/changelog
debian/i3-wm.docs
docs/asciidoc-git.conf
docs/debugging
docs/hacking-howto
docs/ipc
docs/stacklimit.png [deleted file]
docs/testsuite
docs/userguide
generate-command-parser.pl
i3-config-wizard/i3-config-wizard.mk
i3-config-wizard/main.c
i3-dmenu-desktop
i3-dump-log/i3-dump-log.mk
i3-dump-log/main.c
i3-input/i3-input.mk
i3-input/main.c
i3-migrate-config-to-v4
i3-msg/i3-msg.mk
i3-msg/main.c
i3-nagbar/i3-nagbar.mk
i3-nagbar/main.c
i3-save-tree
i3-sensible-pager
i3-sensible-terminal
i3.config
i3.config.keycodes
i3bar/i3bar.mk
i3bar/include/child.h
i3bar/include/common.h
i3bar/include/config.h
i3bar/include/ipc.h
i3bar/include/mode.h
i3bar/include/outputs.h
i3bar/include/parse_json_header.h
i3bar/include/trayclients.h
i3bar/include/util.h
i3bar/include/workspaces.h
i3bar/include/xcb.h
i3bar/src/child.c
i3bar/src/config.c
i3bar/src/ipc.c
i3bar/src/main.c
i3bar/src/mode.c
i3bar/src/outputs.c
i3bar/src/parse_json_header.c
i3bar/src/workspaces.c
i3bar/src/xcb.c
include/all.h
include/assignments.h
include/atoms.xmacro
include/bindings.h
include/click.h
include/cmdparse.h
include/commands.h
include/commands_parser.h
include/con.h
include/config.h
include/config_directives.h
include/config_parser.h
include/data.h
include/debug.h
include/display_version.h
include/ewmh.h
include/fake_outputs.h
include/floating.h
include/handlers.h
include/i3.h
include/i3/ipc.h
include/ipc.h
include/key_press.h
include/libi3.h
include/load_layout.h
include/log.h
include/main.h
include/manage.h
include/match.h
include/move.h
include/output.h
include/randr.h
include/regex.h
include/render.h
include/resize.h
include/restore_layout.h
include/scratchpad.h
include/shmlog.h
include/sighandler.h
include/startup.h
include/tree.h
include/util.h
include/window.h
include/workspace.h
include/x.h
include/xcb.h
include/xcb_compat.h
include/xcursor.h
include/xinerama.h
include/yajl_utils.h
libi3/dpi.c
libi3/fake_configure_notify.c
libi3/font.c
libi3/get_colorpixel.c
libi3/get_config_path.c [new file with mode: 0644]
libi3/get_exe_path.c
libi3/get_mod_mask.c
libi3/get_process_filename.c
libi3/get_visualtype.c
libi3/ipc_connect.c
libi3/ipc_recv_message.c
libi3/ipc_send_message.c
libi3/mkdirp.c
libi3/resolve_tilde.c [new file with mode: 0644]
libi3/root_atom_contents.c
libi3/safewrappers.c
libi3/string.c
libi3/strndup.c
libi3/ucs2_conversion.c
man/asciidoc.conf
parser-specs/commands.spec
parser-specs/config.spec
release.sh
src/assignments.c
src/bindings.c
src/click.c
src/commands.c
src/commands_parser.c
src/con.c
src/config.c
src/config_directives.c
src/config_parser.c
src/debug.c
src/display_version.c
src/ewmh.c
src/fake_outputs.c
src/floating.c
src/handlers.c
src/i3.mk
src/ipc.c
src/key_press.c
src/load_layout.c
src/log.c
src/main.c
src/manage.c
src/match.c
src/move.c
src/output.c
src/randr.c
src/regex.c
src/render.c
src/resize.c
src/restore_layout.c
src/scratchpad.c
src/sighandler.c
src/startup.c
src/tree.c
src/util.c
src/version.c [new file with mode: 0644]
src/window.c
src/workspace.c
src/x.c
src/xcb.c
src/xcursor.c
src/xinerama.c
testcases/Makefile.PL
testcases/complete-run.pl
testcases/lib/i3test.pm
testcases/t/101-focus.t
testcases/t/113-urgent.t
testcases/t/116-nestedcons.t
testcases/t/141-resize.t
testcases/t/165-for_window.t
testcases/t/171-config-migrate.t
testcases/t/172-start-on-named-ws.t
testcases/t/175-startup-notification.t
testcases/t/177-bar-config.t
testcases/t/185-scratchpad.t
testcases/t/187-commands-parser.t
testcases/t/189-floating-constraints.t
testcases/t/201-config-parser.t
testcases/t/210-mark-unmark.t
testcases/t/231-ipc-floating-event.t
testcases/t/232-cmd-move-criteria.t
testcases/t/232-ipc-window-urgent.t
testcases/t/235-check-config-no-x.t
testcases/t/235-wm-class-change-handler.t
testcases/t/240-focus-on-window-activation.t [new file with mode: 0644]
testcases/t/240-tabbed-floating-disable-crash.t [new file with mode: 0644]
testcases/t/241-consistent-center.t [new file with mode: 0644]
testcases/t/242-no-focus.t [new file with mode: 0644]
testcases/t/243-move-to-mark.t [new file with mode: 0644]
testcases/t/243-net-wm-state-hidden.t [new file with mode: 0644]
testcases/t/245-move-position-mouse.t [new file with mode: 0644]
testcases/t/246-window-decoration-focus.t [new file with mode: 0644]
testcases/t/247-config-line-continuation.t [new file with mode: 0644]
testcases/t/248-regress-urgency-clear.t [new file with mode: 0644]
testcases/t/249-layout-restore-floating.t [new file with mode: 0644]
testcases/t/250-layout-restore-multiple-criteria.t [new file with mode: 0644]
testcases/t/251-command-criteria-focused.t [new file with mode: 0644]
testcases/t/251-ewmh-visible-name.t [new file with mode: 0644]
testcases/t/251-sticky.t [new file with mode: 0644]
testcases/t/252-floating-size.t [new file with mode: 0644]
testcases/t/253-multiple-net-wm-state-atoms.t [new file with mode: 0644]
testcases/t/254-move-to-output-with-criteria.t [new file with mode: 0644]
testcases/t/523-move-position-center.t [new file with mode: 0644]
testcases/t/524-move.t [new file with mode: 0644]
testcases/t/525-i3bar-mouse-bindings.t [new file with mode: 0644]
testcases/t/526-reconfigure-dock.t [new file with mode: 0644]
testcases/t/527-focus-fallback.t [new file with mode: 0644]
tests/queue.h [deleted file]
tests/swap.c [deleted file]
tests/test_table.c [deleted file]

index 1d4c1678aa4ed2abc89b7e5d856be089a3741022..617421ac1e92f3edc338c7b7c75d868e6696a608 100644 (file)
@@ -33,3 +33,4 @@ docs/*.html
 i3-command-parser.stamp
 i3-config-parser.stamp
 .clang_complete
+LAST_VERSION
index 733daeac39117ae0f04587d31ddf8f4e3522588b..f90e0ebda99bc75ae0207df25082753d53b49ab7 100644 (file)
@@ -32,7 +32,7 @@ before_install:
   - 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
+  - sudo apt-get install --no-install-recommends devscripts equivs xdotool
   - sudo apt-get install -t utopic clang-format-3.5
   - clang-format-3.5 --version
 install:
@@ -45,3 +45,22 @@ 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
index 7a5e0c5fda2b947e1db0c2f11c3a4dab67283d3f..474355ad90f7a98c5325ce584f0ce871bf4c719b 100644 (file)
@@ -1,20 +1,32 @@
-# i3status/i3lock bugreports/feature requests
+# Contributing
 
-Note that i3status and i3lock related bugreports and feature requests should be
-filed in the corresponding repositories, i.e. https://github.com/i3/i3status
-and https://github.com/i3/i3lock
+## i3status/i3lock bug reports and feature requests
 
-# i3 bugreports/feature requests
+Note that bug reports and feature requests for related projects should be filed in the corresponding repositories for [i3status](https://github.com/i3/i3status) and [i3lock](https://github.com/i3/i3lock).
 
-1. Read http://i3wm.org/docs/debugging.html
+## i3 bug reports and feature requests
+
+1. Read the [debugging instructions](http://i3wm.org/docs/debugging.html).
 2. Make sure you include a link to your logfile in your report (section 3).
 3. Make sure you include the i3 version number in your report (section 1).
+4. Please be aware that we cannot support compatibility issues with
+   closed-source software, as digging into compatibility problems without
+   having access to the source code is too time-consuming. Additionally,
+   experience has shown that often, the software in question is responsible for
+   the issue. Please raise an issue with the software in question, not i3.
 
-# Pull requests
+## Pull requests
 
 * Before sending a pull request for new features, please check with us that the
   feature is something we want to see in i3 by opening an issue which has
-  â\80\9cfeature requestâ\80\9d or â\80\9cenhancement” in its title.
+  â\80\9dfeature requestâ\80\9d or â\80\9denhancement” in its title.
 * Use the `next` branch for developing and sending your pull request.
 * Use `clang-format` to format your code.
-* Run the testsuite, see http://i3wm.org/docs/testsuite.html
+* Run the [testsuite](http://i3wm.org/docs/testsuite.html)
+
+## Finding something to do
+
+* Find a [reproducible bug](https://github.com/i3/i3/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3Areproducible+label%3Abug+) from the issue tracker. These issues have been reviewed and confirmed by a project contributor.
+* Find an [accepted enhancement](https://github.com/i3/i3/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3Aaccepted+label%3Aenhancement) from the issue tracker. These have been approved and are ok to start working on.
+
+There's a very good [overview of the codebase](http://i3wm.org/docs/hacking-howto.html) available to get you started.
diff --git a/DEPENDS b/DEPENDS
index d5dae1429d05192a5d0bb56671354d34933b3cb4..0882f1475be4ac96afaefb1611f7b94b42086ddf 100644 (file)
--- a/DEPENDS
+++ b/DEPENDS
@@ -7,29 +7,30 @@
 ┌──────────────┬────────┬────────┬────────────────────────────────────────┐
 │ dependency   │ min.   │ lkgv   │ URL                                    │
 ├──────────────┼────────┼────────┼────────────────────────────────────────┤
-│ pkg-config   │ 0.25   │ 0.26   │ http://pkgconfig.freedesktop.org/      │
-│ libxcb       │ 1.1.93 │ 1.10   │ http://xcb.freedesktop.org/dist/       │
+│ pkg-config   │ 0.25   │ 0.28   │ http://pkgconfig.freedesktop.org/      │
+│ libxcb       │ 1.1.93 │ 1.11   │ http://xcb.freedesktop.org/dist/       │
 │ xcb-util     │ 0.3.3  │ 0.4.1  │ http://xcb.freedesktop.org/dist/       │
-│ xkbcommon    │ 0.4.0  │ 0.4.0  │ http://xkbcommon.org/                  │
-│ xkbcommon-x11│ 0.4.0  │ 0.4.0  │ http://xkbcommon.org/                  │
-│ util-cursor³ │ 0.0.99 │ 0.0.99 │ http://xcb.freedesktop.org/dist/       │
-│ libev        │ 4.0    │ 4.11   │ http://libev.schmorp.de/               │
-│ yajl         │ 2.0.1  │ 2.0.4  │ http://lloyd.github.com/yajl/          │
-│ asciidoc     │ 8.3.0  │ 8.6.4  │ http://www.methods.co.nz/asciidoc/     │
+│ xkbcommon    │ 0.4.0  │ 0.5.0  │ http://xkbcommon.org/                  │
+│ xkbcommon-x11│ 0.4.0  │ 0.5.0  │ http://xkbcommon.org/                  │
+│ util-cursor³⁴│ 0.0.99 │ 0.1.2  │ http://xcb.freedesktop.org/dist/       │
+│ util-wm⁴     │ 0.3.8  │ 0.3.8  │ http://xcb.freedesktop.org/dist/       │
+│ util-keysyms⁴│ 0.3.8  │ 0.4.0  │ http://xcb.freedesktop.org/dist/       │
+│ libev        │ 4.0    │ 4.19   │ http://libev.schmorp.de/               │
+│ yajl         │ 2.0.1  │ 2.1.0  │ http://lloyd.github.com/yajl/          │
+│ asciidoc     │ 8.3.0  │ 8.6.8  │ http://www.methods.co.nz/asciidoc/     │
 │ xmlto        │ 0.0.23 │ 0.0.23 │ http://www.methods.co.nz/asciidoc/     │
 │ Pod::Simple² │ 3.22   │ 3.22   │ http://search.cpan.org/~dwheeler/Pod-Simple-3.23/
 │ docbook-xml  │ 4.5    │ 4.5    │ http://www.methods.co.nz/asciidoc/     │
-│ Xlib         │ 1.3.3  │ 1.4.3  │ http://ftp.x.org/pub/current/src/lib/  │
-│ PCRE         │ 8.12   │ 8.12   │ http://www.pcre.org/                   │
+│ 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.30.0 │ http://www.pango.org/                  │
-│ cairo        │ 1.12.2 │ 1.12.2 │ http://cairographics.org/              │
+│ pango        │ 1.30.0 | 1.36.8 │ http://www.pango.org/                  │
+│ cairo        │ 1.12.2 │ 1.14.0 │ http://cairographics.org/              │
 └──────────────┴────────┴────────┴────────────────────────────────────────┘
  ¹ libsn = libstartup-notification
  ² Pod::Simple is a Perl module required for converting the testsuite
    documentation to HTML. See http://michael.stapelberg.de/cpan/#Pod::Simple
- ³ xcb-util-cursor, to be precise. Might be considered part of xcb-util, or not
  :-).
+ ³ xcb-util-cursor, to be precise.
⁴ Depending on your distribution, this might be considered part of xcb-util.
 
  i3bar, i3-msg, i3-input, i3-nagbar and i3-config-wizard do not introduce any
  new dependencies.
index 1fed4df1ac59185a4540413db3c5b9e436f15344..fd3021017650d11c1daa6cfed7ee327a853b7dc8 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -22,6 +22,12 @@ include i3-dump-log/i3-dump-log.mk
 include docs/docs.mk
 include man/man.mk
 
+# Update $(TOPDIR)/LAST_VERSION if it differs from $I3_VERSION
+CACHED_VERSION := '$(shell [ -f $(TOPDIR)/LAST_VERSION ] && cat $(TOPDIR)/LAST_VERSION)'
+ifneq ($(CACHED_VERSION),$(I3_VERSION))
+$(shell echo -n ${I3_VERSION} > $(TOPDIR)/LAST_VERSION)
+endif
+
 real-all: $(ALL_TARGETS)
 
 install: $(INSTALL_TARGETS)
diff --git a/RELEASE-NOTES-4.10.3 b/RELEASE-NOTES-4.10.3
deleted file mode 100644 (file)
index 524f552..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-
- ┌──────────────────────────────┐
- │ Release notes for i3 v4.10.3 │
- └──────────────────────────────┘
-
-This is i3 v4.10.3. This version is considered stable. All users of i3 are
-strongly encouraged to upgrade.
-
- ┌────────────────────────────┐
- │ Bugfixes                   │
- └────────────────────────────┘
-
-  • serialize con_id with %p in run_binding() (For FreeBSD)
-  • ignore InputHint when not in WM_HINTS (fixes e.g. mupdf focus)
-  • disable physically disconnect RandR outputs
-  • initialize workspace rect to the output's upon creation
-  • userguide: quoted strings need to be used, escaping isn’t possible
-  • mkdirp: do not throw an error if directory exists (fixes layout loss for
-    in-place restarts)
-  • i3bar: fix freeing static strings
-
- ┌────────────────────────────┐
- │ Thanks!                    │
- └────────────────────────────┘
-
-Thanks for testing, bugfixes, discussions and everything I forgot go out to:
-
-  Tony Crisci, Deiz, Theo Buehler, shdown
-
--- Michael Stapelberg, 2015-07-30
diff --git a/RELEASE-NOTES-4.11 b/RELEASE-NOTES-4.11
new file mode 100644 (file)
index 0000000..2cbadda
--- /dev/null
@@ -0,0 +1,115 @@
+
+ ┌────────────────────────────┐
+ │ 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] <mark>”
+  • added “focus_on_window_activation” directive
+  • added “no_focus” directive
+  • added “move [container|window] [to] mark <str>” 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
index b9e15a286fc27d335e4ae8c12a36ec8188dbd972..d28750426bd4864bf9830d7485dadbc3bed4e1b1 100644 (file)
--- a/common.mk
+++ b/common.mk
@@ -1,11 +1,14 @@
 UNAME=$(shell uname)
 DEBUG=1
-COVERAGE=0
 INSTALL=install
 LN=ln
+PKG_CONFIG=pkg-config
 ifndef PREFIX
   PREFIX=/usr
 endif
+ifndef EXEC_PREFIX
+  EXEC_PREFIX=$(PREFIX)
+endif
 ifndef SYSCONFDIR
   ifeq ($(PREFIX),/usr)
     SYSCONFDIR=/etc
@@ -60,7 +63,7 @@ I3_CPPFLAGS += -DI3__FILE__=__FILE__
 
 ## Libraries flags
 
-ifeq ($(shell which pkg-config 2>/dev/null 1>/dev/null || echo 1),1)
+ifeq ($(shell which $(PKG_CONFIG) 2>/dev/null 1>/dev/null || echo 1),1)
 $(error "pkg-config was not found")
 endif
 
@@ -74,15 +77,15 @@ endif
 #
 # We redirect stderr to /dev/null because pkg-config prints an error if support
 # for gnome-config was enabled but gnome-config is not actually installed.
-cflags_for_lib = $(shell pkg-config --silence-errors --cflags $(1) 2>/dev/null)
-ldflags_for_lib = $(shell pkg-config --exists 2>/dev/null $(1) && pkg-config --libs $(1) 2>/dev/null || echo -l$(2))
+cflags_for_lib = $(shell $(PKG_CONFIG) --silence-errors --cflags $(1) 2>/dev/null)
+ldflags_for_lib = $(shell $(PKG_CONFIG) --exists 2>/dev/null $(1) && $(PKG_CONFIG) --libs $(1) 2>/dev/null || echo -l$(2))
 
 # XCB common stuff
 XCB_CFLAGS  := $(call cflags_for_lib, xcb)
 XCB_CFLAGS  += $(call cflags_for_lib, xcb-event)
 XCB_LIBS    := $(call ldflags_for_lib, xcb,xcb)
 XCB_LIBS    += $(call ldflags_for_lib, xcb-event,xcb-event)
-ifeq ($(shell pkg-config --exists xcb-util 2>/dev/null || echo 1),1)
+ifeq ($(shell $(PKG_CONFIG) --exists xcb-util 2>/dev/null || echo 1),1)
 XCB_CFLAGS  += $(call cflags_for_lib, xcb-atom)
 XCB_CFLAGS  += $(call cflags_for_lib, xcb-aux)
 XCB_LIBS    += $(call ldflags_for_lib, xcb-atom,xcb-atom)
@@ -125,7 +128,7 @@ LIBEV_LIBS   := $(call ldflags_for_lib, libev,ev)
 
 # libpcre
 PCRE_CFLAGS := $(call cflags_for_lib, libpcre)
-ifeq ($(shell pkg-config --atleast-version=8.10 libpcre 2>/dev/null && echo 1),1)
+ifeq ($(shell $(PKG_CONFIG) --atleast-version=8.10 libpcre 2>/dev/null && echo 1),1)
 I3_CPPFLAGS += -DPCRE_HAS_UCP=1
 endif
 PCRE_LIBS   := $(call ldflags_for_lib, libpcre,pcre)
@@ -175,6 +178,10 @@ else ifneq ($(UNAME),OpenBSD)
 LIBS += -lrt
 endif
 
+ifeq ($(UNAME),SunOS)
+LIBS += -lsocket -liconv -lgen
+endif
+
 ifneq (,$(filter Linux GNU GNU/%, $(UNAME)))
 I3_CPPFLAGS += -D_GNU_SOURCE
 endif
index cc0e532875100a733545d5415e0a7a2693d63b9c..65b7fc105c34a4b908976e42dcebdfa179b98b54 100644 (file)
@@ -1,8 +1,40 @@
+i3-wm (4.10.5-1) unstable; urgency=medium
+
+  * UNRELEASED
+
+ -- Michael Stapelberg <stapelberg@debian.org>  Tue, 08 Sep 2015 09:25:45 +0200
+
+i3-wm (4.10.4-1) unstable; urgency=medium
+
+  * New upstream release.
+
+ -- Michael Stapelberg <stapelberg@debian.org>  Tue, 08 Sep 2015 09:15:45 +0200
+
+i3-wm (4.10.3-1) unstable; urgency=medium
+
+  * New upstream release.
+
+ -- Michael Stapelberg <stapelberg@debian.org>  Thu, 30 Jul 2015 21:51:27 +0200
+
+i3-wm (4.10.2-2) unstable; urgency=medium
+
+  * New upstream release.
+  * experimental to unstable because i3-wm 4.10.2-1 was only in experimental
+    due to the freeze.
+
+ -- Michael Stapelberg <stapelberg@debian.org>  Fri, 01 May 2015 20:21:18 +0200
+
+i3-wm (4.10.2-1) experimental; urgency=medium
+
+  * New upstream release.
+
+ -- Michael Stapelberg <stapelberg@debian.org>  Thu, 16 Apr 2015 09:02:53 +0200
+
 i3-wm (4.10.1-1) experimental; urgency=medium
 
-  * NOT YET RELEASED.
+  * New upstream release.
 
- -- Michael Stapelberg <stapelberg@debian.org>  Sun, 29 Mar 2015 18:08:13 +0200
+ -- Michael Stapelberg <stapelberg@debian.org>  Sun, 29 Mar 2015 18:54:07 +0200
 
 i3-wm (4.10-1) experimental; urgency=medium
 
index 11a695ba7ea5093f58a54c3d87da812b326cd142..437175b310fef4fe748500dad6d538f367be92d2 100644 (file)
@@ -8,7 +8,6 @@ docs/snapping.png
 docs/two_columns.png
 docs/two_terminals.png
 docs/modes.png
-docs/stacklimit.png
 docs/ipc.html
 docs/multi-monitor.html
 docs/wsbar.html
index 3d42bca8a9999309c3e8b961d9c377de5896585f..36bdb6f5a0cac50ac424537636e140b4799587aa 100644 (file)
@@ -647,7 +647,7 @@ endif::doctype-manpage[]
 </div>\r
 {disable-javascript%<div id="footnotes"><hr /></div>}\r
 <div id="footer" lang="de">\r
-© 2009-2014 Michael Stapelberg, <a href="http://i3wm.org/impress.html">Impressum</a>
+© 2009 Michael Stapelberg, <a href="http://i3wm.org/impress.html">Impressum</a>
 </div>\r
 </body>\r
 </html>\r
index 1253b0c50fc2b405f8279d36211c7c02014174fd..7be7c8e5b37f2d4cd44134c30b9b6f32f4a3ea0a 100644 (file)
@@ -10,10 +10,10 @@ Thank you for being interested in debugging i3. It really means
 something to us to get your bug fixed. If you have any questions about the
 process and/or need further help, do not hesitate to contact us!
 
-== Verify you are using i3 ≥ 4.7
+== Verify you are using i3 ≥ 4.10
 
-Only the latest major version of i3 is supported, i.e. version 4.7 currently.
-To verify which version you are running, use:
+Only the latest major version of i3 is supported. To verify which version
+you are running, use:
 
 ---------------
 $ i3 --moreversion 2>&- || i3 --version
@@ -67,8 +67,44 @@ fly:
 i3-msg 'debuglog on; shmlog on; reload'
 ---------------------------------------
 
+== Reproducing the problem
+
+Before submitting an issue, please make sure to close down on the problem as
+much as you can yourself. Here are some steps you should consider:
+
+* Find a deterministic, reliable way to reproduce the problem and provide it
+  with your bug report.
+* Try using the default i3 config to reproduce the problem. If the issue does
+  not appear with the default config, gradually adapt it to track down what 
+  change(s) to the config introduce the problem.
+* Reproduce the problem with a minimal setup, i.e., only use as few applications,
+  windows and steps as necessary.
+* In addition, try to stick to applications that are common and, even more
+  importantly, free / open source.
+* Before obtaining the log file, restart i3 in-place, execute the steps to
+  reproduce the problem and then save the logs. This keeps the log file as
+  small as possible and necessary.
+
+Please be aware that we cannot support compatibility issues with closed-source
+software, as digging into compatibility problems without having access to the
+source code is too time-consuming. Additionally, experience has shown that
+often, the software in question is responsible for the issue. Please raise an
+issue with the software in question, not i3.
+
 == Obtaining the debug logfile
 
+[CAUTION]
+================================================================================
+Logs may contain sensitive information, so please inspect the log before
+submitting it. Logs may be viewed by anyone, once posted. If you choose to
+redact the log, make an effort not to discard information which may be relevant
+to the issue you are reporting.
+
+The best way to avoid submitting such information is to only run the necessary
+steps to reproduce the behavior when saving the log file. This will also make
+analyzing the log file easier.
+================================================================================
+
 No matter whether i3 misbehaved in some way without crashing or whether it just
 crashed, the logfile provides all information necessary to debug the problem.
 
@@ -121,3 +157,20 @@ When debugging with us in IRC, be prepared to use a so called nopaste service
 such as http://nopaste.info or http://pastebin.com because pasting large
 amounts of text in IRC sometimes leads to incomplete lines (servers have line
 length limitations) or flood kicks.
+
+== Debugging i3bar
+
+To debug i3bar problems, add +verbose yes+ to all +bar {}+ blocks in your i3
+config, reload your config and then restart all i3bar instances like this:
+
+---------------------------------------------------------------------
+$ i3 reload
+$ killall i3bar
+$ for c in $(i3-msg -t get_bar_config | python -c \
+      'import json,sys;print("\n".join(json.load(sys.stdin)))'); do \
+    (i3bar --bar_id=$c >i3bar.$c.log 2>&1) & \
+  done;
+---------------------------------------------------------------------
+
+There will now be +i3bar.*.log+ files in your current directory that you can provide
+in your bug report.
index 12d6b14ee1b2da3406d4b78e7960c66288207749..d6e2b67ebf65dcade1736e60e78c2cb27355f0d5 100644 (file)
@@ -404,10 +404,14 @@ can reconfigure themselves).
 
 == _NET_WM_STATE
 
-Only the _NET_WM_STATE_FULLSCREEN atom is handled. It calls
-``toggle_fullscreen()'' for the specific client which just configures the
-client to use the whole screen on which it currently is. Also, it is set as
-fullscreen_client for the i3Screen.
+Only the _NET_WM_STATE_FULLSCREEN and _NET_WM_STATE_DEMANDS_ATTENTION atoms
+are handled.
+
+The former calls ``toggle_fullscreen()'' for the specific client which just
+configures the client to use the whole screen on which it currently is.
+Also, it is set as fullscreen_client for the i3Screen.
+
+The latter is used to set, read and display urgency hints.
 
 == WM_NAME
 
@@ -629,8 +633,8 @@ unmapped if it should not be visible anymore. +WM_STATE+ will be set to
 +x_draw_decoration+ draws window decorations. It is run for every leaf
 container (representing an actual X11 window) and for every non-leaf container
 which is in a stacked/tabbed container (because stacked/tabbed containers
-display a window decoration for split containers, which at the moment just says
-"another container").
+display a window decoration for split containers, which consists of a representation
+of the child container's names.
 
 Then, parameters are collected to be able to determine whether this decoration
 drawing is actually necessary or was already done. This saves a substantial
@@ -686,9 +690,9 @@ all commands.
 In earlier versions of i3, interpreting these commands was done using lex and
 yacc, but experience has shown that lex and yacc are not well suited for our
 command language. Therefore, starting from version 4.2, we use a custom parser
-for user commands (not yet for the configuration file).
+for user commands and the configuration file.
 The input specification for this parser can be found in the file
-+parser-specs/commands.spec+. Should you happen to use Vim as an editor, use
++parser-specs/*.spec+. Should you happen to use Vim as an editor, use
 :source parser-specs/highlighting.vim to get syntax highlighting for this file
 (highlighting files for other editors are welcome).
 
@@ -729,11 +733,14 @@ features. This is its definition:
 # workspace next|prev|next_on_output|prev_on_output
 # workspace back_and_forth
 # workspace <name>
+# workspace number <number>
 state WORKSPACE:
   direction = 'next_on_output', 'prev_on_output', 'next', 'prev'
       -> call cmd_workspace($direction)
   'back_and_forth'
       -> call cmd_workspace_back_and_forth()
+  'number'
+      -> WORKSPACE_NUMBER
   workspace = string
       -> call cmd_workspace_name($workspace)
 ----------------------------------------------------------------
@@ -772,6 +779,10 @@ workspace <name>::
        single quotes), but just called string. Other possible tokens are word
        (the same as string, but stops matching at a whitespace) and end
        (matches the end of the input).
+workspace number <number>::
+        The workspace command has to be followed by the keyword +number+. It
+        then transitions into the state +WORKSPACE_NUMBER+, where the actual
+        parameter will be read.
 
 === Introducing a new command
 
@@ -952,30 +963,35 @@ http://web.archive.org/web/20121024222556/http://www.spheredev.org/wiki/Git_for_
 or, for more documentation, see http://git-scm.com/documentation
 
 Please talk to us before working on new features to see whether they will be
-accepted. There are a few things which we don’t want to see in i3, e.g. a
-command which will focus windows in an alt+tab like way.
+accepted. A good way for this is to open an issue and asking for opinions on it.
+Even for accepted features, this can be a good way to refine an idea upfront. However,
+we don't want to see certain features in i3, e.g., switching window focus in an
+Alt+Tab like way.
 
 When working on bugfixes, please make sure you mention that you are working on
-it in the corresponding bugreport at https://github.com/i3/i3/issues In case
-there is no bugreport yet, please create one.
+it in the corresponding bug report at https://github.com/i3/i3/issues. In case
+there is no bug report yet, please create one.
 
-After you are done, please submit your work for review at https://github.com/i3/i3
+After you are done, please submit your work for review as a pull request at
+https://github.com/i3/i3.
 
 Do not send emails to the mailing list or any author directly, and don’t submit
 them in the bugtracker, since all reviews should be done in public at
-http://cr.i3wm.org/. In order to make your review go as fast as possible, you
+https://github.com/i3/i3. In order to make your review go as fast as possible, you
 could have a look at previous reviews and see what the common mistakes are.
 
 === Which branch to use?
 
-Work on i3 generally happens in two branches: “master” and “next”. Since
-“master” is what people get when they check out the git repository, its
-contents are always stable. That is, it contains the source code of the latest
-release, plus any bugfixes that were applied since that release.
+Work on i3 generally happens in two branches: “master” and “next” (the latter
+being the default branch, the one that people get when they check out the git
+repository).
+
+The contents of “master” are always stable. That is, it contains the source code
+of the latest release, plus any bugfixes that were applied since that release.
 
 New features are only found in the “next” branch. Therefore, if you are working
-on a new feature, use the “next” branch. If you are working on a bugfix, use
-the “next” branch, too, but make sure your code also works on “master”.
+on a new feature, use the “next” branch. If you are working on a bugfix, use the
+“next” branch, too, but make sure your code also works on “master”.
 
 == Thought experiments
 
index ff7c8aaeb0f185299520dc5f271460060a8d13be..5113d79bf60abc178c798ab1f33ca30873fa7b9b 100644 (file)
--- a/docs/ipc
+++ b/docs/ipc
@@ -520,21 +520,23 @@ statusline::
        Text color to be used for the statusline.
 separator::
        Text color to be used for the separator.
-focused_workspace_text/focused_workspace_bg::
-       Text color/background color for a workspace button when the workspace
+focused_workspace_text/focused_workspace_bg/focused_workspace_border::
+       Text/background/border color for a workspace button when the workspace
        has focus.
-active_workspace_text/active_workspace_bg::
-       Text color/background color for a workspace button when the workspace
+active_workspace_text/active_workspace_bg/active_workspace_border::
+       Text/background/border color for a workspace button when the workspace
        is active (visible) on some output, but the focus is on another one.
        You can only tell this apart from the focused workspace when you are
        using multiple monitors.
-inactive_workspace_text/inactive_workspace_bg::
-       Text color/background color for a workspace button when the workspace
+inactive_workspace_text/inactive_workspace_bg/inactive_workspace_border::
+       Text/background/border color for a workspace button when the workspace
        does not have focus and is not active (visible) on any output. This
        will be the case for most workspaces.
-urgent_workspace_text/urgent_workspace_bar::
-       Text color/background color for workspaces which contain at least one
+urgent_workspace_text/urgent_workspace_bg/urgent_workspace_border::
+       Text/background/border color for workspaces which contain at least one
        window with the urgency hint set.
+binding_mode_text/binding_mode_bg/binding_mode_border::
+        Text/background/border color for the binding mode indicator.
 
 
 *Example of configured bars:*
@@ -580,11 +582,14 @@ human_readable (string)::
        build date and branch name. When you need to display the i3 version to
        your users, use the human-readable version whenever possible (since
        this is what +i3 --version+ displays, too).
+loaded_config_file_name (string)::
+       The current config path.
 
 *Example:*
 -------------------
 {
    "human_readable" : "4.2-169-gf80b877 (2012-08-05, branch \"next\")",
+   "loaded_config_file_name" : "/home/hwangcc23/.i3/config",
    "minor" : 2,
    "patch" : 0,
    "major" : 4
@@ -768,8 +773,8 @@ The +binding (object)+ field contains details about the binding that was run:
 
 command (string)::
        The i3 command that is configured to run for this binding.
-mods (array of strings)::
-       The modifier keys that were configured with this binding.
+event_state_mask (array of strings)::
+       The group and modifier keys that were configured with this binding.
 input_code (integer)::
        If the binding was configured with +bindcode+, this will be the key code
        that was given for the binding. If the binding is a mouse binding, it will be
@@ -787,7 +792,7 @@ input_type (string)::
  "change": "run",
  "binding": {
   "command": "nop",
-  "mods": [
+  "event_state_mask": [
     "shift",
     "ctrl"
   ],
@@ -822,4 +827,7 @@ Python::
        * https://github.com/whitelynx/i3ipc (not maintained)
        * https://github.com/ziberna/i3-py (not maintained)
 Ruby::
-       * http://github.com/badboy/i3-ipc
+       * https://github.com/veelenga/i3ipc-ruby
+       * https://github.com/badboy/i3-ipc (not maintained)
+Rust::
+       * https://github.com/tmerr/i3ipc-rs
diff --git a/docs/stacklimit.png b/docs/stacklimit.png
deleted file mode 100644 (file)
index ab5bed7..0000000
Binary files a/docs/stacklimit.png and /dev/null differ
index 29a352185da2f2f39ec2e24cf47224671fda86d6..8fdb9635b9648fcc6e8e7e1b380a80801033c8e8 100644 (file)
@@ -160,6 +160,27 @@ $ ./complete-run.pl --parallel=1 --keep-xserver-output
 This will show the output of Xephyr, which is the X server implementation we
 use for testing.
 
+==== Coverage testing
+
+Coverage testing is possible with +lcov+, the front-end for GCC's coverage
+testing tool +gcov+. The testcases can generate a nice html report that tells
+you which functions and lines were covered during a run of the tests. You can
+use this tool to judge how effective your tests are.
+
+To use test coverage tools, first compile with coverage enabled.
+
+---------------------------------------------------
+COVERAGE=1 make
+---------------------------------------------------
+
+Then run the tests with the +--coverage-testing+ flag.
+
+---------------------------------------------------
+./complete-run.pl --coverage-testing
+---------------------------------------------------
+
+Then open +latest/i3-coverage/index.html+ in your web browser.
+
 ==== IPC interface
 
 The testsuite makes extensive use of the IPC (Inter-Process Communication)
index 820f539521a53531f756e250459bce8f98886221..13dae4fe087f5798ae05cc49cf4ed6fce60abba1 100644 (file)
@@ -23,6 +23,13 @@ image:keyboard-layer2.png["Keys to use with Shift+$mod",width=600,link="keyboard
 The red keys are the modifiers you need to press (by default), the blue keys
 are your homerow.
 
+Note that when starting i3 without a config file, i3-config-wizard will offer
+you to create a config file in which the key positions (!) match what you see
+in the image above, regardless of the keyboard layout you are using. If you
+prefer to use a config file where the key letters match what you are seeing
+above, just decline i3-config-wizard’s offer and base your config on
++/etc/i3/config+.
+
 == Using i3
 
 Throughout this guide, the keyword +$mod+ will be used to refer to the
@@ -256,8 +263,9 @@ workspace node’s orientation will be changed to +vertical+. The terminal windo
 you moved down is directly attached to the workspace and appears on the bottom
 of the screen. A new (horizontal) container was created to accommodate the
 other two terminal windows. You will notice this when switching to tabbed mode
-(for example). You would end up having one tab called "another container" and
-the other one being the terminal window you moved down.
+(for example). You would end up having one tab with a representation of the split
+container (e.g., "H[urxvt firefox]") and the other one being the terminal window
+you moved down.
 
 [[configuring]]
 == Configuring i3
@@ -319,7 +327,7 @@ and fall back to a working font.
 *Syntax*:
 ------------------------------
 font <X core font description>
-font pango:[family list] [style options] [size]
+font pango:<family list> [<style options>] <size>
 ------------------------------
 
 *Examples*:
@@ -360,8 +368,8 @@ after the keys have been released.
 
 *Syntax*:
 ----------------------------------
-bindsym [--release] [Modifiers+]keysym command
-bindcode [--release] [Modifiers+]keycode command
+bindsym [--release] [<Group>+][<Modifiers>+]<keysym> command
+bindcode [--release] [<Group>+][<Modifiers>+]<keycode> command
 ----------------------------------
 
 *Examples*:
@@ -387,12 +395,13 @@ Available Modifiers:
 Mod1-Mod5, Shift, Control::
 Standard modifiers, see +xmodmap(1)+
 
-Mode_switch::
-Unlike other window managers, i3 can use Mode_switch as a modifier. This allows
-you to remap capslock (for example) to Mode_switch and use it for both: typing
-umlauts or special characters 'and' having some comfortably reachable key
-bindings. For example, when typing, capslock+1 or capslock+2 for switching
-workspaces is totally convenient. Try it :-).
+Group1, Group2, Group3, Group4::
+When using multiple keyboard layouts (e.g. with `setxkbmap -layout us,ru`), you
+can specify in which XKB group (also called “layout”) a keybinding should be
+active. By default, keybindings are translated in Group1 and are active in all
+groups. If you want to override keybindings in one of your layouts, specify the
+corresponding group. For backwards compatibility, the group “Mode_switch” is an
+alias for Group2.
 
 [[mousebindings]]
 
@@ -403,14 +412,17 @@ button in the scope of the clicked container (see <<command_criteria>>). You
 can configure mouse bindings in a similar way to key bindings.
 
 *Syntax*:
-----------------------------------
-bindsym [--release] [--whole-window] [Modifiers+]button[n] command
-----------------------------------
+-------------------------------------------------------------------------------
+bindsym [--release] [--border] [--whole-window] [<Modifiers>+]button<n> command
+-------------------------------------------------------------------------------
 
 By default, the binding will only run when you click on the titlebar of the
-window. If the +--whole-window+ flag is given, it will run when any part of the
-window is clicked. If the +--release+ flag is given, it will run when the mouse
-button is released.
+window. If the +--release+ flag is given, it will run when the mouse button
+is released.
+
+If the +--whole-window+ flag is given, the binding will also run when any part
+of the window is clicked, with the exception of the border. To have a bind run
+when the border is clicked, specify the +--border+ flag.
 
 *Examples*:
 --------------------------------
@@ -447,7 +459,7 @@ ratio will be preserved).
 
 *Syntax*:
 --------------------------------
-floating_modifier <Modifiers>
+floating_modifier <Modifier>
 --------------------------------
 
 *Example*:
@@ -486,9 +498,9 @@ With the +default_orientation+ configuration directive, you can override that
 behavior.
 
 *Syntax*:
-----------------------------------------------
-default_orientation <horizontal|vertical|auto>
-----------------------------------------------
+--------------------------------------------
+default_orientation horizontal|vertical|auto
+--------------------------------------------
 
 *Example*:
 ----------------------------
@@ -499,17 +511,11 @@ default_orientation vertical
 
 This option determines in which mode new containers on workspace level will
 start.
-///////////////////////////////
-See also <<stack-limit>>.
-//////////////////////////////
 
 *Syntax*:
 ---------------------------------------------
-workspace_layout <default|stacking|tabbed>
+workspace_layout default|stacking|tabbed
 ---------------------------------------------
-/////////////////////////////////////////////
-new_container stack-limit <cols|rows> <value>
-/////////////////////////////////////////////
 
 *Example*:
 ---------------------
@@ -519,18 +525,20 @@ workspace_layout tabbed
 === Border style for new windows
 
 This option determines which border style new windows will have. The default is
-"normal". Note that new_float applies only to windows which are starting out as
-floating windows, e.g. dialog windows.
++normal+. Note that new_float applies only to windows which are starting out as
+floating windows, e.g., dialog windows, but not windows that are floated later on.
 
 *Syntax*:
 ---------------------------------------------
-new_window <normal|1pixel|none|pixel>
-new_float <normal|1pixel|none|pixel>
+new_window normal|none|pixel
+new_window normal|pixel <px>
+new_float normal|none|pixel
+new_float normal|pixel <px>
 ---------------------------------------------
 
 *Example*:
 ---------------------
-new_window 1pixel
+new_window pixel
 ---------------------
 
 The "normal" and "pixel" border styles support an optional border width in
@@ -553,9 +561,9 @@ You can hide vertical borders adjacent to the screen edges using
 to waste even two pixels in displayspace. Default is none.
 
 *Syntax*:
-----------------------------
-hide_edge_borders <none|vertical|horizontal|both>
-----------------------------
+-----------------------------------------------
+hide_edge_borders none|vertical|horizontal|both
+-----------------------------------------------
 
 *Example*:
 ----------------------
@@ -564,14 +572,16 @@ hide_edge_borders vertical
 
 === Arbitrary commands for specific windows (for_window)
 
+[[for_window]]
+
 With the +for_window+ command, you can let i3 execute any command when it
 encounters a specific window. This can be used to set windows to floating or to
 change their border style, for example.
 
 *Syntax*:
------------------------------
-for_window <criteria> command
------------------------------
+-------------------------------
+for_window <criteria> <command>
+-------------------------------
 
 *Examples*:
 ------------------------------------------------
@@ -579,7 +589,7 @@ for_window <criteria> command
 for_window [class="XTerm"] floating enable
 
 # Make all urxvts use a 1-pixel border:
-for_window [class="urxvt"] border 1pixel
+for_window [class="urxvt"] border pixel 1
 
 # A less useful, but rather funny example:
 # makes the window floating as soon as I change
@@ -589,6 +599,27 @@ for_window [title="x200: ~/work"] floating enable
 
 The valid criteria are the same as those for commands, see <<command_criteria>>.
 
+=== Don't focus window upon opening
+
+[[no_focus]]
+
+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 <<command_criteria>>.
+
+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
+<<focus_on_window_activation>>.
+
+*Syntax*:
+-------------------
+no_focus <criteria>
+-------------------
+
+*Example*:
+-------------------------------
+no_focus [window_role="pop-up"]
+-------------------------------
+
 === Variables
 
 As you learned in the section about keyboard bindings, you will have
@@ -597,9 +628,9 @@ yourself some typing and be able to change the modifier you use later,
 variables can be handy.
 
 *Syntax*:
---------------
-set $name value
---------------
+-------------------
+set $<name> <value>
+-------------------
 
 *Example*:
 ------------------------
@@ -635,7 +666,7 @@ considered.
 
 *Syntax*:
 ------------------------------------------------------------
-assign <criteria> [→] workspace
+assign <criteria> [→] [workspace] <workspace>
 ------------------------------------------------------------
 
 *Examples*:
@@ -704,10 +735,10 @@ and +,+ (comma): they chain commands together in i3, so you need to use quoted
 strings if they appear in your command.
 
 *Syntax*:
--------------------
-exec [--no-startup-id] command
-exec_always [--no-startup-id] command
--------------------
+---------------------------------------
+exec [--no-startup-id] <command>
+exec_always [--no-startup-id] <command>
+---------------------------------------
 
 *Examples*:
 --------------------------------
@@ -731,9 +762,9 @@ or when starting (e.g., by default it will use 1 for the first screen, 2 for
 the second screen and so on).
 
 *Syntax*:
-----------------------------------
+-------------------------------------
 workspace <workspace> output <output>
-----------------------------------
+-------------------------------------
 
 The 'output' is the name of the RandR output you attach your screen to. On a
 laptop, you might have VGA1 and LVDS1 as output names. You can see the
@@ -753,9 +784,9 @@ workspace "2: vim" output VGA1
 You can change all colors which i3 uses to draw the window decorations.
 
 *Syntax*:
---------------------------------------------
-colorclass border background text indicator
---------------------------------------------
+------------------------------------------------------
+<colorclass> <border> <background> <text> <indicator>
+------------------------------------------------------
 
 Where colorclass can be one of:
 
@@ -771,18 +802,11 @@ client.urgent::
 client.placeholder::
        Background and text color are used to draw placeholder window contents
        (when restoring layouts). Border and indicator are ignored.
-
-You can also specify the color to be used to paint the background of the client
-windows. This color will be used to paint the window on top of which the client
-will be rendered.
-
-*Syntax*:
------------------------
-client.background color
------------------------
-
-Only clients that do not cover the whole area of this window expose the color
-used to paint it.
+client.background::
+        Background color which will be used to paint the background of the
+        client window on top of which the client will be rendered. Only clients
+        which do not cover the whole area of this window expose the color. Note
+        that this colorclass only takes a single color.
 
 Colors are in HTML hex format (#rrggbb), see the following example:
 
@@ -794,6 +818,8 @@ 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
+
+client.background       #ffffff
 ---------------------------------------------------------
 
 Note that for the window decorations, the color around the child window is the
@@ -841,9 +867,9 @@ still be useful inside the currently active window (for example to click on
 links in your browser window).
 
 *Syntax*:
-----------------------------
-focus_follows_mouse <yes|no>
-----------------------------
+--------------------------
+focus_follows_mouse yes|no
+--------------------------
 
 *Example*:
 ----------------------
@@ -861,9 +887,9 @@ be warped. +none+ disables warping entirely, whereas +output+ is the default
 behavior described above.
 
 *Syntax*:
----------------------------
-mouse_warping <output|none>
----------------------------
+-------------------------
+mouse_warping output|none
+-------------------------
 
 *Example*:
 ------------------
@@ -885,9 +911,9 @@ There are three things which are possible to do in this situation:
 3. Leave fullscreen mode.
 
 *Syntax*:
--------------------------------------------------
-popup_during_fullscreen <smart|ignore|leave_fullscreen>
--------------------------------------------------
+-----------------------------------------------------
+popup_during_fullscreen smart|ignore|leave_fullscreen
+-----------------------------------------------------
 
 *Example*:
 ------------------------------
@@ -908,9 +934,9 @@ parent+ to switch to different containers, you can use the
 will always wrap.
 
 *Syntax*:
------------------------------
-force_focus_wrapping <yes|no>
------------------------------
+---------------------------
+force_focus_wrapping yes|no
+---------------------------
 
 *Example*:
 ------------------------
@@ -930,9 +956,9 @@ For people who cannot modify their +~/.xsession+ to add the
 +--force-xinerama+ commandline parameter, a configuration option is provided:
 
 *Syntax*:
------------------------
-force_xinerama <yes|no>
------------------------
+---------------------
+force_xinerama yes|no
+---------------------
 
 *Example*:
 ------------------
@@ -952,9 +978,9 @@ mod+2 because somebody sent you a message. You don’t need to remember where yo
 came from now, you can just press $mod+2 again to switch back to "1: www".
 
 *Syntax*:
---------------------------------------
-workspace_auto_back_and_forth <yes|no>
---------------------------------------
+------------------------------------
+workspace_auto_back_and_forth yes|no
+------------------------------------
 
 *Example*:
 ---------------------------------
@@ -985,6 +1011,65 @@ force_display_urgency_hint <timeout> ms
 force_display_urgency_hint 500 ms
 ---------------------------------
 
+=== Focus on window activation
+
+[[focus_on_window_activation]]
+
+If a window is activated, e.g., via +google-chrome www.google.com+, it may request
+to take focus. Since this may not preferable, different reactions can be configured.
+
+Note that this may not affect windows that are being opened. To prevent new windows
+from being focused, see <<no_focus>>.
+
+*Syntax*:
+--------------------------------------------------
+focus_on_window_activation smart|urgent|focus|none
+--------------------------------------------------
+
+The different modes will act as follows:
+
+smart::
+    This is the default behavior. If the window requesting focus is on an active
+    workspace, it will receive the focus. Otherwise, the urgency hint will be set.
+urgent::
+    The window will always be marked urgent, but the focus will not be stolen.
+focus::
+    The window will always be focused and not be marked urgent.
+none::
+    The window will neither be focused, nor be marked urgent.
+
+=== Drawing marks on window decoration
+
+If activated, marks on windows are drawn in their window decoration. However,
+any mark starting with an underscore in its name (+_+) will not be drawn even if
+this option is activated.
+
+The default for this option is +yes+.
+
+*Syntax*:
+-----------------
+show_marks yes|no
+-----------------
+
+*Example*:
+--------------
+show_marks yes
+--------------
+
+[[line_continuation]]
+
+=== Line continuation
+
+Config files support line continuation, meaning when you end a line in a
+backslash character (`\`), the line-break will be ignored by the parser. This
+feature can be used to create more readable configuration files.
+
+*Examples*:
+-------------------
+bindsym Mod1+f \
+fullscreen toggle
+-------------------
+
 == Configuring i3bar
 
 The bar at the bottom of your monitor is drawn by a separate process called
@@ -1027,9 +1112,9 @@ The specified command will be passed to +sh -c+, so you can use globbing and
 have to have correct quoting etc.
 
 *Syntax*:
-----------------------
-i3bar_command command
-----------------------
+-----------------------
+i3bar_command <command>
+-----------------------
 
 *Example*:
 -------------------------------------------------
@@ -1046,17 +1131,23 @@ right hand side of the bar. This is useful to display system information like
 your current IP address, battery status or date/time.
 
 The specified command will be passed to +sh -c+, so you can use globbing and
-have to have correct quoting etc.
+have to have correct quoting etc. Note that for signal handling, depending on
+your shell (users of dash(1) are known to be affected), you have to use the
+shell’s exec command so that signals are passed to your program, not to the
+shell.
 
 *Syntax*:
-----------------------
-status_command command
-----------------------
+------------------------
+status_command <command>
+------------------------
 
 *Example*:
 -------------------------------------------------
 bar {
     status_command i3status --config ~/.i3status.conf
+
+    # For dash(1) users who want signal handling to work:
+    status_command exec ~/.bin/my_status_command
 }
 -------------------------------------------------
 
@@ -1092,11 +1183,11 @@ The default mode is dock mode; in hide mode, the default modifier is Mod4 (usual
 the windows key). The default value for the hidden_state is hide.
 
 *Syntax*:
-----------------
-mode <dock|hide|invisible>
-hidden_state <hide|show>
+-------------------------
+mode dock|hide|invisible
+hidden_state hide|show
 modifier <Modifier>
-----------------
+------------------------
 
 *Example*:
 ----------------
@@ -1112,23 +1203,41 @@ Available modifiers are Mod1-Mod5, Shift, Control (see +xmodmap(1)+).
 === Mouse button commands
 
 Specifies a command to run when a button was pressed on i3bar to override the
-default behavior. Currently only the mouse wheel buttons are supported. This is
-useful for disabling the scroll wheel action or running scripts that implement
-custom behavior for these buttons.
+default behavior. This is useful, e.g., for disabling the scroll wheel action
+or running scripts that implement custom behavior for these buttons.
+
+A button is always named +button<n>+, where 1 to 5 are default buttons as follows and higher
+numbers can be special buttons on devices offering more buttons:
+
+button1::
+    Left mouse button.
+button2::
+    Middle mouse button.
+button3::
+    Right mouse button.
+button4::
+    Scroll wheel up.
+button5::
+    Scroll wheel down.
+
+Please note that the old +wheel_up_cmd+ and +wheel_down_cmd+ commands are deprecated
+and will be removed in a future release. We strongly recommend using the more general
++bindsym+ with +button4+ and +button5+ instead.
 
 *Syntax*:
----------------------
-wheel_up_cmd <command>
-wheel_down_cmd <command>
----------------------
+----------------------------
+bindsym button<n> <command>
+----------------------------
 
 *Example*:
----------------------
+---------------------------------------------------------
 bar {
-    wheel_up_cmd nop
-    wheel_down_cmd exec ~/.i3/scripts/custom_wheel_down
+    # disable clicking on workspace buttons
+    bindsym button1 nop
+    # execute custom script when scrolling downwards
+    bindsym button5 exec ~/.i3/scripts/custom_wheel_down
 }
----------------------
+---------------------------------------------------------
 
 === Bar ID
 
@@ -1156,9 +1265,9 @@ This option determines in which edge of the screen i3bar should show up.
 The default is bottom.
 
 *Syntax*:
----------------------
-position <top|bottom>
----------------------
+-------------------
+position top|bottom
+-------------------
 
 *Example*:
 ---------------------
@@ -1211,9 +1320,9 @@ You can configure on which output (monitor) the icons should be displayed or
 you can turn off the functionality entirely.
 
 *Syntax*:
--------------------------
-tray_output <none|primary|output>
--------------------------
+---------------------------------
+tray_output none|primary|<output>
+---------------------------------
 
 *Example*:
 -------------------------
@@ -1223,7 +1332,9 @@ bar {
 }
 
 # show tray icons on the primary monitor
-tray_output primary
+bar {
+    tray_output primary
+}
 
 # show tray icons on the big monitor
 bar {
@@ -1236,6 +1347,28 @@ Note that you might not have a primary output configured yet. To do so, run:
 xrandr --output <output> --primary
 -------------------------
 
+Note that when you use multiple bar configuration blocks, either specify
+`tray_output primary` in all of them or explicitly specify `tray_output none`
+in bars which should not display the tray, otherwise the different instances
+might race each other in trying to display tray icons.
+
+=== Tray padding
+
+The tray is shown on the right-hand side of the bar. By default, a padding of 2
+pixels is used for the upper, lower and right-hand side of the tray area and
+between the individual icons.
+
+*Syntax*:
+-------------------------
+tray_padding <px> [px]
+-------------------------
+
+*Example*:
+-------------------------
+# Obey Fitts's law
+tray_padding 0
+-------------------------
+
 === Font
 
 Specifies the font to be used in the bar. See <<fonts>>.
@@ -1278,9 +1411,9 @@ you want to display a statusline-only bar containing additional information.
 The default is to show workspace buttons.
 
 *Syntax*:
---------------------------
-workspace_buttons <yes|no>
---------------------------
+------------------------
+workspace_buttons yes|no
+------------------------
 
 *Example*:
 ------------------------
@@ -1303,9 +1436,9 @@ workspaces to "1:I", "2:II", "3:III", "4:IV", ...
 The default is to display the full name within the workspace button.
 
 *Syntax*:
-----------------------------------
-strip_workspace_numbers <yes|no>
-----------------------------------
+------------------------------
+strip_workspace_numbers yes|no
+------------------------------
 
 *Example*:
 ----------------------------
@@ -1324,9 +1457,9 @@ For an example of a +mode+ definition, see <<resizingconfig>>.
 The default is to show the mode indicator.
 
 *Syntax*:
--------------------------------
-binding_mode_indicator <yes|no>
--------------------------------
+-----------------------------
+binding_mode_indicator yes|no
+-----------------------------
 
 *Example*:
 -----------------------------
@@ -1360,7 +1493,10 @@ inactive_workspace::
        will be the case for most workspaces.
 urgent_workspace::
        Border, background and text color for a workspace button when the workspace
-       contains a window with the urgency hint set. Also applies to +mode+ indicators.
+       contains a window with the urgency hint set.
+binding_mode::
+        Border, background and text color for the binding mode indicator. If not used,
+        the colors will be taken from +urgent_workspace+.
 
 *Syntax*:
 ----------------------------------------
@@ -1369,7 +1505,7 @@ colors {
     statusline <color>
     separator <color>
 
-    colorclass <border> <background> <text>
+    <colorclass> <border> <background> <text>
 }
 ----------------------------------------
 
@@ -1385,6 +1521,7 @@ bar {
         active_workspace   #333333 #5f676a #ffffff
         inactive_workspace #333333 #222222 #888888
         urgent_workspace   #2f343a #900000 #ffffff
+        binding_mode       #2f343a #900000 #ffffff
     }
 }
 --------------------------------------
@@ -1401,6 +1538,8 @@ do this is to use the +i3-msg+ utility:
 i3-msg border none
 --------------------------
 
+[[command_chaining]]
+
 Commands can be chained by using +;+ (a semicolon). So, to move a window to a
 specific workspace and immediately switch to that workspace, you can configure
 the following keybinding:
@@ -1440,27 +1579,43 @@ for_window [class="^evil-app$"] floating enable, move container to workspace 4
 The criteria which are currently implemented are:
 
 class::
-       Compares the window class (the second part of WM_CLASS)
+       Compares the window class (the second part of WM_CLASS). Use the
+       special value +__focused__+ to match all windows having the same window
+       class as the currently focused window.
 instance::
-       Compares the window instance (the first part of WM_CLASS)
+       Compares the window instance (the first part of WM_CLASS). Use the
+       special value +__focused__+ to match all windows having the same window
+       instance as the currently focused window.
 window_role::
-       Compares the window role (WM_WINDOW_ROLE).
+       Compares the window role (WM_WINDOW_ROLE). Use the special value
+       +__focused__+ to match all windows having the same window role as the
+       currently focused window.
+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+.
 id::
        Compares the X11 window ID, which you can get via +xwininfo+ for example.
 title::
        Compares the X11 window title (_NET_WM_NAME or WM_NAME as fallback).
+       Use the special value +__focused__+ to match all windows having the
+       same window title as the currently focused window.
 urgent::
        Compares the urgent state of the window. Can be "latest" or "oldest".
        Matches the latest or oldest urgent window, respectively.
        (The following aliases are also available: newest, last, recent, first)
+workspace::
+       Compares the workspace name of the workspace the window belongs to. Use
+       the special value +__focused__+ to match all windows in the currently
+       focused workspace.
 con_mark::
        Compares the mark set for this container, see <<vim_like_marks>>.
 con_id::
        Compares the i3-internal container ID, which you can get via the IPC
        interface. Handy for scripting.
 
-The criteria +class+, +instance+, +role+, +title+ and +mark+ are actually
-regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for
+The criteria +class+, +instance+, +role+, +title+, +workspace+ and +mark+ are
+actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for
 information on how to use them.
 
 [[exec]]
@@ -1477,9 +1632,9 @@ and +,+ (comma): they chain commands together in i3, so you need to use quoted
 strings if they appear in your command.
 
 *Syntax*:
-------------------------------
-exec [--no-startup-id] command
-------------------------------
+--------------------------------
+exec [--no-startup-id] <command>
+--------------------------------
 
 *Example*:
 ------------------------------
@@ -1512,9 +1667,9 @@ orientation will be changed (if it does not have more than one window). Use
 to splith or vice-versa.
 
 *Syntax*:
----------------------------
-split <vertical|horizontal>
----------------------------
+-------------------------
+split vertical|horizontal
+-------------------------
 
 *Example*:
 ------------------------------
@@ -1537,10 +1692,10 @@ Likewise, to make the current window floating (or tiling again) use +floating
 enable+ respectively +floating disable+ (or +floating toggle+):
 
 *Syntax*:
---------------
-layout <default|tabbed|stacking|splitv|splith>
+--------------------------------------------
+layout default|tabbed|stacking|splitv|splith
 layout toggle [split|all]
---------------
+--------------------------------------------
 
 *Examples*:
 --------------
@@ -1561,15 +1716,17 @@ bindsym $mod+f fullscreen toggle
 bindsym $mod+t floating toggle
 --------------
 
-=== Focusing/Moving containers
+[[_focusing_moving_containers]]
 
-To change the focus, use the focus command: +focus left+, +focus right+, +focus
-down+ and +focus up+.
+=== Focusing containers
 
-There are a few special parameters you can use for the focus command:
+To change focus, you can use the +focus+ command. The following options are
+available:
 
+left|right|up|down::
+        Sets focus to the nearest container in the given direction.
 parent::
-       Sets focus to the +Parent Container+ of the current +Container+.
+       Sets focus to the parent container of the current container.
 child::
        The opposite of +focus parent+, sets the focus to the last focused
        child container.
@@ -1583,23 +1740,16 @@ output::
        Followed by a direction or an output name, this will focus the
        corresponding output.
 
-For moving, use +move left+, +move right+, +move down+ and +move up+.
-
 *Syntax*:
------------------------------------
-focus <left|right|down|up>
-focus <parent|child|floating|tiling|mode_toggle>
-focus output <<left|right|down|up>|output>
-move <left|right|down|up> [<px> px]
-move [absolute] position [[<px> px] [<px> px]|center]
------------------------------------
-
-Note that the amount of pixels you can specify for the +move+ command is only
-relevant for floating containers. The default amount is 10 pixels.
+----------------------------------------------
+focus left|right|down|up
+focus parent|child|floating|tiling|mode_toggle
+focus output left|right|up|down|<output>
+----------------------------------------------
 
 *Examples*:
-----------------------
-# Focus container on the left, bottom, top, right:
+-------------------------------------------------
+# Focus container on the left, bottom, top, right
 bindsym $mod+j focus left
 bindsym $mod+k focus down
 bindsym $mod+l focus up
@@ -1616,8 +1766,33 @@ bindsym $mod+x focus output right
 
 # Focus the big output
 bindsym $mod+x focus output HDMI-2
+-------------------------------------------------
+
+=== Moving containers
+
+Use the +move+ command to move a container.
+
+*Syntax*:
+-----------------------------------------------------
+# Moves the container into the given direction.
+# The optional pixel argument specifies how far the
+# container should be moved if it is floating and
+# defaults to 10 pixels.
+move <left|right|down|up> [<px> px]
 
-# Move container to the left, bottom, top, right:
+# Moves the container either to a specific location
+# or to the center of the screen. If 'absolute' is
+# used, it is moved to the center of all outputs.
+move [absolute] position [[<px> px] [<px> px]|center]
+
+# Moves the container to the current position of the
+# mouse cursor. Only affects floating containers.
+move position mouse
+-----------------------------------------------------
+
+*Examples*:
+-------------------------------------------------------
+# Move container to the left, bottom, top, right
 bindsym $mod+j move left
 bindsym $mod+k move down
 bindsym $mod+l move up
@@ -1627,10 +1802,33 @@ bindsym $mod+semicolon move right
 # move more than the default
 bindsym $mod+j move left 20 px
 
-# Move floating container to the center
-# of all outputs
+# Move floating container to the center of all outputs
 bindsym $mod+c move absolute position center
-----------------------
+
+# Move container to the current position of the cursor
+bindsym $mod+m move position mouse
+-------------------------------------------------------
+
+=== Sticky floating windows
+
+If you want a window to stick to the glass, i.e., have it stay on screen even
+if you switch to another workspace, you can use the +sticky+ command. For
+example, this can be useful for notepads, a media player or a video chat
+window.
+
+Note that while any window can be made sticky through this command, it will
+only take effect if the window is floating.
+
+*Syntax*:
+----------------------------
+sticky enable|disable|toggle
+----------------------------
+
+*Examples*:
+------------------------------------------------------
+# make a terminal sticky that was started as a notepad
+for_window [instance=notepad] sticky enable
+------------------------------------------------------
 
 === Changing (named) workspaces/moving to workspaces
 
@@ -1661,14 +1859,14 @@ workspace using +move container to workspace back_and_forth+.
 
 *Syntax*:
 -----------------------------------
-workspace <next|prev|next_on_output|prev_on_output>
+workspace next|prev|next_on_output|prev_on_output
 workspace back_and_forth
 workspace <name>
 workspace number <name>
 
 move [window|container] [to] workspace <name>
 move [window|container] [to] workspace number <name>
-move [window|container] [to] workspace <prev|next|current>
+move [window|container] [to] workspace prev|next|current
 -----------------------------------
 
 *Examples*:
@@ -1742,7 +1940,7 @@ rename workspace to <new_name>
 i3-msg 'rename workspace 5 to 6'
 i3-msg 'rename workspace 1 to "1: www"'
 i3-msg 'rename workspace "1: www" to "10: www"'
-i3-msg 'rename workspace to "2: mail"
+i3-msg 'rename workspace to "2: mail"'
 bindsym $mod+r exec i3-input -F 'rename workspace to "%s"' -P 'New name: '
 --------------------------------------------------------------------------
 
@@ -1760,10 +1958,10 @@ To move a container to another RandR output (addressed by names like +LVDS1+ or
 +right+, +up+ or +down+), there are two commands:
 
 *Syntax*:
---------------------------------------------------------
-move container to output <<left|right|down|up>|<output>>
-move workspace to output <<left|right|down|up>|<output>>
---------------------------------------------------------
+----------------------------------------------------
+move container to output left|right|down|up|<output>
+move workspace to output left|right|down|up|<output>
+----------------------------------------------------
 
 *Examples*:
 --------------------------------------------------------
@@ -1775,6 +1973,26 @@ bindsym $mod+x move workspace to output right
 bindsym $mod+x move container to output VGA1
 --------------------------------------------------------
 
+=== Moving containers/workspaces to marks
+
+To move a container to another container with a specific mark (see <<vim_like_marks>>),
+you can use the following command.
+
+The window will be moved right after the marked container in the tree, i.e., it ends up
+in the same position as if you had opened a new window when the marked container was
+focused. If the mark is on a split container, the window will appear as a new child
+after the currently focused child within that container.
+
+*Syntax*:
+------------------------------------
+move window|container to mark <mark>
+------------------------------------
+
+*Example*:
+--------------------------------------------------------
+for_window [instance="tabme"] move window to mark target
+--------------------------------------------------------
+
 [[resizingconfig]]
 
 === Resizing containers/windows
@@ -1783,9 +2001,10 @@ If you want to resize containers/windows using your keyboard, you can use the
 +resize+ command:
 
 *Syntax*:
----------------------------------------------------------
-resize <grow|shrink> <direction> [<px> px [or <ppt> ppt]]
----------------------------------------------------------
+-------------------------------------------------------
+resize grow|shrink <direction> [<px> px [or <ppt> ppt]]
+resize set <width> [px] <height> [px]
+-------------------------------------------------------
 
 Direction can either be one of +up+, +down+, +left+ or +right+. Or you can be
 less specific and use +width+ or +height+, in which case i3 will take/give
@@ -1793,7 +2012,8 @@ space from all the other containers. The optional pixel argument specifies by
 how many pixels a *floating container* should be grown or shrunk (the default
 is 10 pixels). The ppt argument means percentage points and specifies by how
 many percentage points a *tiling container* should be grown or shrunk (the
-default is 10 percentage points).
+default is 10 percentage points). Note that +resize set+ will only work for
+floating containers.
 
 I recommend using the resize command inside a so called +mode+:
 
@@ -1826,6 +2046,11 @@ mode "resize" {
 bindsym $mod+r mode "resize"
 ----------------------------------------------------------------------
 
+*Example 2 - setting urxvt size to 640x480:*
+------------------------------------------------
+for_window [class="urxvt"] resize set 640 480
+------------------------------------------------
+
 === Jumping to specific windows
 
 Often when in a multi-monitor environment, you want to quickly jump to a
@@ -1864,11 +2089,17 @@ window, you cannot simply bind it to a key.  +i3-input+ is a tool created
 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.
+
+Refer to +show_marks+ if you don't want marks to be shown in the window decoration.
+
 *Syntax*:
 ------------------------------
-mark identifier
+mark [--toggle] <identifier>
 [con_mark="identifier"] focus
-unmark identifier
+unmark <identifier>
 ------------------------------
 
 *Example (in a terminal)*:
@@ -1893,52 +2124,69 @@ Alternatively, if you do not want to mess with +i3-input+, you could create
 seperate bindings for a specific set of labels and then only use those labels.
 ///////////////////////////////////////////////////////////////////
 
-=== Changing border style
+=== Window title format
 
-To change the border of the current client, you can use +border normal+ to use the normal
-border (including window title), +border 1pixel+ to use a 1-pixel border (no window title)
-and +border none+ to make the client borderless.
+By default, i3 will simply print the X11 window title. Using +title_format+,
+this can be customized by setting the format to the desired output. This
+directive supports
+https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup]
+and the following placeholders which will be replaced:
 
-There is also +border toggle+ which will toggle the different border styles.
++%title+::
+    The X11 window title (_NET_WM_NAME or WM_NAME as fallback).
++%class+:
+    The X11 window class (second part of WM_CLASS). This corresponds to the
+    +class+ criterion, see <<command_criteria>>.
++%instance+:
+    The X11 window instance (first part of WM_CLASS). This corresponds to the
+    +instance+ criterion, see <<command_criteria>>.
+
+Using the <<for_window>> directive, you can set the title format for any window
+based on <<command_criteria>>.
+
+*Syntax*:
+---------------------
+title_format <format>
+---------------------
 
 *Examples*:
-----------------------------
-bindsym $mod+t border normal
-bindsym $mod+y border 1pixel
-bindsym $mod+u border none
-----------------------------
+-------------------------------------------------------------------------------------
+# give the focused window a prefix
+bindsym $mod+p title_format "Important | %title"
 
-[[stack-limit]]
+# print all window titles bold
+for_window [class=".*"] title_format "<b>%title</b>"
 
-///////////////////////////////////////////////////////////////////////////////
-TODO: not yet implemented
-=== Changing the stack-limit of a container
+# print window titles of firefox windows red
+for_window [class="(?i)firefox"] title_format "<span foreground='red'>%title</span>"
+-------------------------------------------------------------------------------------
 
-If you have a single container with a lot of windows inside it (say, more than
-10), the default layout of a stacking container can get a little unhandy.
-Depending on your screen’s size, you might end up with only half of the title
-lines being actually used, wasting a lot of screen space.
+=== Changing border style
 
-Using the +stack-limit+ command, you can limit the number of rows or columns
-in a stacking container. i3 will create columns or rows (depending on what
-you limited) automatically as needed.
+To change the border of the current client, you can use +border normal+ to use the normal
+border (including window title), +border pixel 1+ to use a 1-pixel border (no window title)
+and +border none+ to make the client borderless.
 
-*Syntax*:
---------------------------------
-stack-limit <cols|rows> <value>
---------------------------------
+There is also +border toggle+ which will toggle the different border styles.
 
-*Examples*:
--------------------
-# I always want to have two window titles in one line
-stack-limit cols 2
+*Syntax*:
+-----------------------------------------------
+border normal|pixel [<n>]
+border none|toggle
 
-# Not more than 5 rows in this stacking container
-stack-limit rows 5
--------------------
+# legacy syntax, equivalent to "border pixel 1"
+border 1pixel
+-----------------------------------------------
 
-image:stacklimit.png[Container limited to two columns]
-///////////////////////////////////////////////////////////////////////////////
+*Examples*:
+----------------------------------------------
+# use window title, but no border
+bindsym $mod+t border normal 0
+# use no window title and a thick border
+bindsym $mod+y border pixel 3
+# use neither window title nor border
+bindsym $mod+u border none
+----------------------------------------------
 
 [[shmlog]]
 
@@ -1954,7 +2202,7 @@ discarded and a new one will be started.
 *Syntax*:
 ------------------------------
 shmlog <size_in_bytes>
-shmlog <on|off|toggle>
+shmlog on|off|toggle
 ------------------------------
 
 *Examples*:
@@ -1975,9 +2223,9 @@ command does not activate shared memory logging (shmlog), and as such is most
 likely useful in combination with the above-described <<shmlog>> command.
 
 *Syntax*:
-------------------------
-debuglog <on|off|toggle>
-------------------------
+----------------------
+debuglog on|off|toggle
+----------------------
 
 *Examples*:
 ------------------------
index 9b5ef562941779da30ff5e6386f178bd36c9650d..c0a9a4d42755ea9c11b6c983fa216f53fc849459 100755 (executable)
@@ -2,7 +2,7 @@
 # vim:ts=4:sw=4:expandtab
 #
 # i3 - an improved dynamic tiling window manager
-# © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+# © 2009 Michael Stapelberg and contributors (see also: LICENSE)
 #
 # generate-command-parser.pl: script to generate parts of the command parser
 # from its specification file parser-specs/commands.spec.
@@ -112,7 +112,7 @@ for my $line (@lines) {
 # We sort descendingly by length to be able to replace occurences 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) } keys %states;
+my @keys = sort { (length($b) <=> length($a)) or ($a cmp $b) } keys %states;
 
 open(my $enumfh, '>', "GENERATED_${prefix}_enums.h");
 
index 1dab64526165e73c1642172c459ac4e33f9e8d3a..900893f5e85297824fc0ae1ee0621d9c826b7d5a 100644 (file)
@@ -20,8 +20,8 @@ i3-config-wizard/i3-config-wizard: libi3.a $(i3_config_wizard_OBJECTS)
 
 install-i3-config-wizard: i3-config-wizard/i3-config-wizard
        echo "[i3-config-wizard] Install"
-       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
-       $(INSTALL) -m 0755 i3-config-wizard/i3-config-wizard $(DESTDIR)$(PREFIX)/bin/
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(EXEC_PREFIX)/bin
+       $(INSTALL) -m 0755 i3-config-wizard/i3-config-wizard $(DESTDIR)$(EXEC_PREFIX)/bin/
 
 clean-i3-config-wizard:
        echo "[i3-config-wizard] Clean"
index 35770dc9fa807c38feda80b71c3c97425e5a12f5..bd12cd815af594191df3e2f7c60f958d2a4bfbfc 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * i3-config-wizard: Program to convert configs using keycodes to configs using
  *                   keysyms.
@@ -392,7 +392,7 @@ static char *rewrite_binding(const char *input) {
                     }
                 }
                 if (walk != beginning) {
-                    char *str = scalloc(walk - beginning + 1);
+                    char *str = scalloc(walk - beginning + 1, 1);
                     /* We copy manually to handle escaping of characters. */
                     int inpos, outpos;
                     for (inpos = 0, outpos = 0;
@@ -461,38 +461,6 @@ void errorlog(char *fmt, ...) {
 void debuglog(char *fmt, ...) {
 }
 
-/*
- * This function resolves ~ in pathnames.
- * It may resolve wildcards in the first part of the path, but if no match
- * or multiple matches are found, it just returns a copy of path as given.
- *
- */
-static char *resolve_tilde(const char *path) {
-    static glob_t globbuf;
-    char *head, *tail, *result;
-
-    tail = strchr(path, '/');
-    head = strndup(path, tail ? (size_t)(tail - path) : strlen(path));
-
-    int res = glob(head, GLOB_TILDE, NULL, &globbuf);
-    free(head);
-    /* no match, or many wildcard matches are bad */
-    if (res == GLOB_NOMATCH || globbuf.gl_pathc != 1)
-        result = strdup(path);
-    else if (res != 0) {
-        err(1, "glob() failed");
-    } else {
-        head = globbuf.gl_pathv[0];
-        result = calloc(1, strlen(head) + (tail ? strlen(tail) : 0) + 1);
-        strncpy(result, head, strlen(head));
-        if (tail)
-            strncat(result, tail, strlen(tail));
-    }
-    globfree(&globbuf);
-
-    return result;
-}
-
 /*
  * Handles expose events, that is, draws the window contents.
  *
@@ -514,17 +482,23 @@ static int handle_expose() {
         set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
 
         txt(logical_px(10), 2, "You have not configured i3 yet.");
-        txt(logical_px(10), 3, "Do you want me to generate ~/.i3/config?");
-        txt(logical_px(85), 5, "Yes, generate ~/.i3/config");
-        txt(logical_px(85), 7, "No, I will use the defaults");
+        txt(logical_px(10), 3, "Do you want me to generate a config at");
+
+        char *msg;
+        sasprintf(&msg, "%s?", config_path);
+        txt(logical_px(10), 4, msg);
+        free(msg);
+
+        txt(logical_px(85), 6, "Yes, generate the config");
+        txt(logical_px(85), 8, "No, I will use the defaults");
 
         /* green */
         set_font_colors(pixmap_gc, get_colorpixel("#00FF00"), get_colorpixel("#000000"));
-        txt(logical_px(25), 5, "<Enter>");
+        txt(logical_px(25), 6, "<Enter>");
 
         /* red */
         set_font_colors(pixmap_gc, get_colorpixel("#FF0000"), get_colorpixel("#000000"));
-        txt(logical_px(31), 7, "<ESC>");
+        txt(logical_px(31), 8, "<ESC>");
     }
 
     if (current_step == STEP_GENERATE) {
@@ -534,7 +508,7 @@ static int handle_expose() {
         txt(logical_px(85), 4, "Win as default modifier");
         txt(logical_px(85), 5, "Alt as default modifier");
         txt(logical_px(10), 7, "Afterwards, press");
-        txt(logical_px(85), 9, "to write ~/.i3/config");
+        txt(logical_px(85), 9, "to write the config");
         txt(logical_px(85), 10, "to abort");
 
         /* the not-selected modifier */
@@ -772,7 +746,7 @@ static void finish() {
 }
 
 int main(int argc, char *argv[]) {
-    config_path = resolve_tilde("~/.i3/config");
+    char *xdg_config_home;
     socket_path = getenv("I3SOCK");
     char *pattern = "pango:monospace 8";
     char *patternbold = "pango:monospace bold 8";
@@ -794,7 +768,7 @@ int main(int argc, char *argv[]) {
         switch (o) {
             case 's':
                 FREE(socket_path);
-                socket_path = strdup(optarg);
+                socket_path = sstrdup(optarg);
                 break;
             case 'v':
                 printf("i3-config-wizard " I3_VERSION "\n");
@@ -806,20 +780,29 @@ int main(int argc, char *argv[]) {
         }
     }
 
-    /* Check if the destination config file does not exist but the path is
-     * writable. If not, exit now, this program is not useful in that case. */
-    struct stat stbuf;
-    if (stat(config_path, &stbuf) == 0) {
-        printf("The config file \"%s\" already exists. Exiting.\n", config_path);
+    char *path = get_config_path(NULL, false);
+    if (path != NULL) {
+        printf("The config file \"%s\" already exists. Exiting.\n", path);
+        free(path);
         return 0;
     }
 
-    /* Create ~/.i3 if it does not yet exist */
-    char *config_dir = resolve_tilde("~/.i3");
+    /* Always write to $XDG_CONFIG_HOME/i3/config by default. */
+    if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL)
+        xdg_config_home = "~/.config";
+
+    xdg_config_home = resolve_tilde(xdg_config_home);
+    sasprintf(&config_path, "%s/i3/config", xdg_config_home);
+
+    /* Create $XDG_CONFIG_HOME/i3 if it does not yet exist */
+    char *config_dir;
+    struct stat stbuf;
+    sasprintf(&config_dir, "%s/i3", xdg_config_home);
     if (stat(config_dir, &stbuf) != 0)
-        if (mkdir(config_dir, 0755) == -1)
-            err(1, "mkdir(%s) failed", config_dir);
+        if (mkdirp(config_dir, DEFAULT_DIR_MODE) != 0)
+            err(EXIT_FAILURE, "mkdirp(%s) failed", config_dir);
     free(config_dir);
+    free(xdg_config_home);
 
     int fd;
     if ((fd = open(config_path, O_CREAT | O_RDWR, 0644)) == -1) {
index cc72f1016429780c2e13a2ef8378c119701cb9e0..a0549ead7dbb81c37991cc748f782ef2eace0f33 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 # vim:ts=4:sw=4:expandtab
 #
-# © 2012-2014 Michael Stapelberg
+# © 2012 Michael Stapelberg
 #
 # No dependencies except for perl ≥ v5.10
 
@@ -45,7 +45,7 @@ my $result = GetOptions(
     'dmenu=s' => \$dmenu_cmd,
     'entry-type=s' => \@entry_types,
     'version' => sub {
-        say "dmenu-desktop 1.5 © 2012-2013 Michael Stapelberg";
+        say "dmenu-desktop 1.5 © 2012 Michael Stapelberg";
         exit 0;
     },
     'help' => sub {
index bbce356fddd78eba858f993acb91c77d2724d6d0..7e5d4499fab7dc5d8f3dde55f401437b0d9f8f02 100644 (file)
@@ -20,8 +20,8 @@ i3-dump-log/i3-dump-log: libi3.a $(i3_dump_log_OBJECTS)
 
 install-i3-dump-log: i3-dump-log/i3-dump-log
        echo "[i3-dump-log] Install"
-       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
-       $(INSTALL) -m 0755 i3-dump-log/i3-dump-log $(DESTDIR)$(PREFIX)/bin/
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(EXEC_PREFIX)/bin
+       $(INSTALL) -m 0755 i3-dump-log/i3-dump-log $(DESTDIR)$(EXEC_PREFIX)/bin/
 
 clean-i3-dump-log:
        echo "[i3-dump-log] Clean"
index 1b0d593c5e1c7322c11c210e739adfade5f50030..137554a4f1fefc36539c5ca8c40b5357b32a7e2a 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * i3-dump-log/main.c: Dumps the i3 SHM log to stdout.
  *
index 03f4e0a6e5f53fbe994e102a90b8769c7e902db4..be3e28331d263d02d820be553ae4e0d82508b491 100644 (file)
@@ -20,8 +20,8 @@ i3-input/i3-input: libi3.a $(i3_input_OBJECTS)
 
 install-i3-input: i3-input/i3-input
        echo "[i3-input] Install"
-       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
-       $(INSTALL) -m 0755 i3-input/i3-input $(DESTDIR)$(PREFIX)/bin/
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(EXEC_PREFIX)/bin
+       $(INSTALL) -m 0755 i3-input/i3-input $(DESTDIR)$(EXEC_PREFIX)/bin/
 
 clean-i3-input:
        echo "[i3-input] Clean"
index 996fc0ea9f08958a3b563a359e85ad6c7388b798..cf3884e9b40e9874b697789bbd52d4568b99abd5 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * i3-input/main.c: Utility which lets the user input commands and sends them
  *                  to i3.
@@ -103,7 +103,7 @@ static void restore_input_focus(void) {
  *
  */
 static uint8_t *concat_strings(char **glyphs, int max) {
-    uint8_t *output = calloc(max + 1, 4);
+    uint8_t *output = scalloc(max + 1, 4);
     uint8_t *walk = output;
     for (int c = 0; c < max; c++) {
         printf("at %c\n", glyphs[c][0]);
@@ -187,10 +187,10 @@ static void finish_input() {
 
     /* allocate space for the output */
     int inputlen = strlen(command);
-    char *full = calloc(1,
-                        strlen(format) - (2 * cnt) /* format without all %s */
-                            + (inputlen * cnt)     /* replaced %s */
-                            + 1);                  /* trailing NUL */
+    char *full = scalloc(strlen(format) - (2 * cnt) /* format without all %s */
+                             + (inputlen * cnt)     /* replaced %s */
+                             + 1,                   /* trailing NUL */
+                         1);
     char *dest = full;
     for (c = 0; c < len; c++) {
         /* if this is not % or it is % but without a following 's',
@@ -314,8 +314,52 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
     return 1;
 }
 
+static xcb_rectangle_t get_window_position(void) {
+    xcb_rectangle_t result = (xcb_rectangle_t){logical_px(50), logical_px(50), logical_px(500), font.height + logical_px(8)};
+
+    xcb_get_input_focus_reply_t *input_focus = NULL;
+    xcb_get_geometry_reply_t *geometry = NULL;
+    xcb_translate_coordinates_reply_t *coordinates = NULL;
+
+    /* In rare cases, the window holding the input focus might disappear while we are figuring out its
+     * position. To avoid this, we grab the server in the meantime. */
+    xcb_grab_server(conn);
+
+    input_focus = xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL);
+    if (input_focus == NULL || input_focus->focus == XCB_NONE) {
+        DLOG("Failed to receive the current input focus or no window has the input focus right now.\n");
+        goto free_resources;
+    }
+
+    geometry = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, input_focus->focus), NULL);
+    if (geometry == NULL) {
+        DLOG("Failed to received window geometry.\n");
+        goto free_resources;
+    }
+
+    coordinates = xcb_translate_coordinates_reply(
+        conn, xcb_translate_coordinates(conn, input_focus->focus, root, geometry->x, geometry->y), NULL);
+    if (coordinates == NULL) {
+        DLOG("Failed to translate coordinates.\n");
+        goto free_resources;
+    }
+
+    DLOG("Determined coordinates of window with input focus at x = %i / y = %i.\n", coordinates->dst_x, coordinates->dst_y);
+    result.x += coordinates->dst_x;
+    result.y += coordinates->dst_y;
+
+free_resources:
+    xcb_ungrab_server(conn);
+    xcb_flush(conn);
+
+    FREE(input_focus);
+    FREE(geometry);
+    FREE(coordinates);
+    return result;
+}
+
 int main(int argc, char *argv[]) {
-    format = strdup("%s");
+    format = sstrdup("%s");
     socket_path = getenv("I3SOCK");
     char *pattern = sstrdup("pango:monospace 8");
     int o, option_index = 0;
@@ -337,7 +381,7 @@ int main(int argc, char *argv[]) {
         switch (o) {
             case 's':
                 FREE(socket_path);
-                socket_path = strdup(optarg);
+                socket_path = sstrdup(optarg);
                 break;
             case 'v':
                 printf("i3-input " I3_VERSION);
@@ -357,11 +401,11 @@ int main(int argc, char *argv[]) {
                 break;
             case 'f':
                 FREE(pattern);
-                pattern = strdup(optarg);
+                pattern = sstrdup(optarg);
                 break;
             case 'F':
                 FREE(format);
-                format = strdup(optarg);
+                format = sstrdup(optarg);
                 break;
             case 'h':
                 printf("i3-input " I3_VERSION "\n");
@@ -402,15 +446,17 @@ int main(int argc, char *argv[]) {
     if (prompt != NULL)
         prompt_offset = predict_text_width(prompt);
 
+    const xcb_rectangle_t win_pos = get_window_position();
+
     /* Open an input window */
     win = xcb_generate_id(conn);
     xcb_create_window(
         conn,
         XCB_COPY_FROM_PARENT,
-        win,                                                                          /* the window id */
-        root,                                                                         /* parent == root */
-        logical_px(50), logical_px(50), logical_px(500), font.height + logical_px(8), /* dimensions */
-        0,                                                                            /* X11 border = 0, we draw our own */
+        win,                                                 /* the window id */
+        root,                                                /* parent == root */
+        win_pos.x, win_pos.y, win_pos.width, win_pos.height, /* dimensions */
+        0,                                                   /* X11 border = 0, we draw our own */
         XCB_WINDOW_CLASS_INPUT_OUTPUT,
         XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
         XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK,
index b78378be1c65a68225a38d8ebace7b5be0845a3d..5da4e26ed447d7b30316adec7fd81af1bf6b9f22 100755 (executable)
@@ -117,7 +117,6 @@ for my $line (@lines) {
 
     # new_container changed only the statement name to workspace_layout
     if ($statement eq 'new_container') {
-        # TODO: new_container stack-limit
         print "workspace_layout$parameters\n";
         next;
     }
@@ -184,7 +183,6 @@ sub convert_command {
         restart
         reload
         exit
-        stack-limit
     );
 
     my ($statement, $key, $command) = ($line =~ /([a-zA-Z_-]+)\s+([^\s]+)\s+(.*)/);
index fda56dad003e524c797da9f9e9954319283deca3..277c43e7b788407013dccf69f408330e802cc1e4 100644 (file)
@@ -20,8 +20,8 @@ i3-msg/i3-msg: libi3.a $(i3_msg_OBJECTS)
 
 install-i3-msg: i3-msg/i3-msg
        echo "[i3-msg] Install"
-       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
-       $(INSTALL) -m 0755 i3-msg/i3-msg $(DESTDIR)$(PREFIX)/bin/
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(EXEC_PREFIX)/bin
+       $(INSTALL) -m 0755 i3-msg/i3-msg $(DESTDIR)$(EXEC_PREFIX)/bin/
 
 clean-i3-msg:
        echo "[i3-msg] Clean"
index 238fe15237e01b11a253a9cc10e8069686f7d884..e4a0628c9db985465c5c1f425fa38c5d40d5ac5a 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * i3-msg/main.c: Utility which sends messages to a running i3-instance using
  * IPC via UNIX domain sockets.
@@ -77,7 +77,7 @@ static int reply_boolean_cb(void *params, int val) {
 }
 
 static int reply_string_cb(void *params, const unsigned char *val, size_t len) {
-    char *str = scalloc(len + 1);
+    char *str = scalloc(len + 1, 1);
     strncpy(str, (const char *)val, len);
     if (strcmp(last_key, "error") == 0)
         last_reply.error = str;
@@ -105,7 +105,7 @@ static int reply_end_map_cb(void *params) {
 
 static int reply_map_key_cb(void *params, const unsigned char *keyVal, size_t keyLen) {
     free(last_key);
-    last_key = scalloc(keyLen + 1);
+    last_key = scalloc(keyLen + 1, 1);
     strncpy(last_key, (const char *)keyVal, keyLen);
     return 1;
 }
@@ -191,8 +191,7 @@ int main(int argc, char *argv[]) {
             payload = sstrdup(argv[optind]);
         } else {
             char *both;
-            if (asprintf(&both, "%s %s", payload, argv[optind]) == -1)
-                err(EXIT_FAILURE, "asprintf");
+            sasprintf(&both, "%s %s", payload, argv[optind]);
             free(payload);
             payload = both;
         }
index e54aa6544876739b6289edec0dad9f12acb5c825..aba3c09ab49223fce156f0a61cf5b83d307a5dc4 100644 (file)
@@ -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) $(PANGO_CFLAGS)
-i3_nagbar_LIBS     = $(XCB_LIBS) $(PANGO_LIBS)
+i3_nagbar_CFLAGS   = $(XCB_CFLAGS) $(XCB_WM_CFLAGS) $(PANGO_CFLAGS)
+i3_nagbar_LIBS     = $(XCB_LIBS) $(XCB_WM_LIBS) $(PANGO_LIBS)
 
 i3_nagbar_OBJECTS := $(i3_nagbar_SOURCES:.c=.o)
 
@@ -20,8 +20,8 @@ i3-nagbar/i3-nagbar: libi3.a $(i3_nagbar_OBJECTS)
 
 install-i3-nagbar: i3-nagbar/i3-nagbar
        echo "[i3-nagbar] Install"
-       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
-       $(INSTALL) -m 0755 i3-nagbar/i3-nagbar $(DESTDIR)$(PREFIX)/bin/
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(EXEC_PREFIX)/bin
+       $(INSTALL) -m 0755 i3-nagbar/i3-nagbar $(DESTDIR)$(EXEC_PREFIX)/bin/
 
 clean-i3-nagbar:
        echo "[i3-nagbar] Clean"
index bf6d1cca41f29a6ba39bec468c677ef3571b2734..d86cd69a3f984f15f80012117b868d168f06ef3b 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * i3-nagbar is a utility which displays a nag message, for example in the case
  * when the user has an error in their configuration file.
@@ -27,6 +27,7 @@
 #include <xcb/xcb.h>
 #include <xcb/xcb_aux.h>
 #include <xcb/xcb_event.h>
+#include <xcb/randr.h>
 
 #include "libi3.h"
 #include "i3-nagbar.h"
@@ -288,6 +289,60 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
     return 1;
 }
 
+/**
+ * Return the position and size the i3-nagbar window should use.
+ * This will be the primary output or a fallback if it cannot be determined.
+ */
+static xcb_rectangle_t get_window_position(void) {
+    /* Default values if we cannot determine the primary output or its CRTC info. */
+    xcb_rectangle_t result = (xcb_rectangle_t){50, 50, 500, font.height + logical_px(8) + logical_px(8)};
+
+    xcb_randr_get_screen_resources_current_cookie_t rcookie = xcb_randr_get_screen_resources_current(conn, root);
+    xcb_randr_get_output_primary_cookie_t pcookie = xcb_randr_get_output_primary(conn, root);
+
+    xcb_randr_get_output_primary_reply_t *primary = NULL;
+    xcb_randr_get_screen_resources_current_reply_t *res = NULL;
+
+    if ((primary = xcb_randr_get_output_primary_reply(conn, pcookie, NULL)) == NULL) {
+        DLOG("Could not determine the primary output.\n");
+        goto free_resources;
+    }
+
+    if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) {
+        goto free_resources;
+    }
+
+    xcb_randr_get_output_info_reply_t *output =
+        xcb_randr_get_output_info_reply(conn,
+                                        xcb_randr_get_output_info(conn, primary->output, res->config_timestamp),
+                                        NULL);
+    if (output == NULL || output->crtc == XCB_NONE)
+        goto free_resources;
+
+    xcb_randr_get_crtc_info_reply_t *crtc =
+        xcb_randr_get_crtc_info_reply(conn,
+                                      xcb_randr_get_crtc_info(conn, output->crtc, res->config_timestamp),
+                                      NULL);
+    if (crtc == NULL)
+        goto free_resources;
+
+    DLOG("Found primary output on position x = %i / y = %i / w = %i / h = %i.\n",
+         crtc->x, crtc->y, crtc->width, crtc->height);
+    if (crtc->width == 0 || crtc->height == 0) {
+        DLOG("Primary output is not active, ignoring it.\n");
+        goto free_resources;
+    }
+
+    result.x = crtc->x;
+    result.y = crtc->y;
+    goto free_resources;
+
+free_resources:
+    FREE(res);
+    FREE(primary);
+    return result;
+}
+
 int main(int argc, char *argv[]) {
     /* The following lines are a terribly horrible kludge. Because terminal
      * emulators have different ways of interpreting the -e command line
@@ -316,7 +371,7 @@ int main(int argc, char *argv[]) {
     if (argv0_len > strlen(".nagbar_cmd") &&
         strcmp(argv[0] + argv0_len - strlen(".nagbar_cmd"), ".nagbar_cmd") == 0) {
         unlink(argv[0]);
-        cmd = strdup(argv[0]);
+        cmd = sstrdup(argv[0]);
         *(cmd + argv0_len - strlen(".nagbar_cmd")) = '\0';
         execl("/bin/sh", "/bin/sh", cmd, NULL);
         err(EXIT_FAILURE, "execv(/bin/sh, /bin/sh, %s)", cmd);
@@ -363,7 +418,7 @@ int main(int argc, char *argv[]) {
                 printf("i3-nagbar [-m <message>] [-b <button> <action>] [-t warning|error] [-f <font>] [-v]\n");
                 return 0;
             case 'b':
-                buttons = realloc(buttons, sizeof(button_t) * (buttoncnt + 1));
+                buttons = srealloc(buttons, sizeof(button_t) * (buttoncnt + 1));
                 buttons[buttoncnt].label = i3string_from_utf8(optarg);
                 buttons[buttoncnt].action = argv[optind];
                 printf("button with label *%s* and action *%s*\n",
@@ -410,16 +465,18 @@ int main(int argc, char *argv[]) {
     font = load_font(pattern, true);
     set_font(&font);
 
+    xcb_rectangle_t win_pos = get_window_position();
+
     /* Open an input window */
     win = xcb_generate_id(conn);
 
     xcb_create_window(
         conn,
         XCB_COPY_FROM_PARENT,
-        win,                                                                         /* the window id */
-        root,                                                                        /* parent == root */
-        50, 50, 500, font.height + logical_px(8) + logical_px(8) /* 8 px padding */, /* dimensions */
-        0,                                                                           /* x11 border = 0, we draw our own */
+        win,                                                 /* the window id */
+        root,                                                /* parent == root */
+        win_pos.x, win_pos.y, win_pos.width, win_pos.height, /* dimensions */
+        0,                                                   /* x11 border = 0, we draw our own */
         XCB_WINDOW_CLASS_INPUT_OUTPUT,
         XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
         XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
index 289fd8c6980d4e8aa25cc64d9390947459846ee3..b00e1fe18330926dc638dcc879156e0eabe6eaf3 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env perl
 # vim:ts=4:sw=4:expandtab
 #
-# © 2013-2014 Michael Stapelberg
+# © 2013 Michael Stapelberg
 #
 # Requires perl ≥ v5.10, AnyEvent::I3 and JSON::XS
 
@@ -99,6 +99,7 @@ my %allowed_keys = map { ($_, 1) } qw(
     geometry
     window_properties
     mark
+    rect
 );
 
 sub strip_containers {
@@ -116,6 +117,9 @@ sub strip_containers {
 
     delete $tree->{geometry} if zero_rect($tree->{geometry});
 
+    # Retain the rect for floating containers to keep their positions.
+    delete $tree->{rect} unless $tree->{type} eq 'floating_con';
+
     delete $tree->{current_border_width} if $tree->{current_border_width} == -1;
 
     for my $key (keys %$tree) {
index 5ad786062405a9c4a571ca6a1f54cf60ee366ade..ce71686bd01182ec79651f2d46c078fe7ce36c11 100755 (executable)
@@ -11,7 +11,7 @@
 # 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 i3-sensible-editor; do
+for pager in $PAGER less most w3m pg i3-sensible-editor; do
     if command -v $pager > /dev/null 2>&1; then
         exec $pager "$@"
     fi
index dd4b075ac981740144011de201ebcb5acba232c3..c80e5ee211b785e1f4dc59efc4501c73d5b44bcb 100755 (executable)
@@ -8,7 +8,7 @@
 # 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; do
+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 "$@"
     fi
index f7722d361957c5d48b8aedc42c3d2033bba89d36..b2d7fac8b50d22563e30ab1293404254dd6f58e5 100644 (file)
--- a/i3.config
+++ b/i3.config
@@ -165,6 +165,7 @@ bindsym Mod1+r mode "resize"
 # finds out, if available)
 bar {
         status_command i3status
+        tray_output primary
 }
 
 #######################################################################
index 0c978d0b7c15928009cc75a00f4d79b63d865728..e606d34702c004669ef507ff2ddb4e29fd3c36b8 100644 (file)
@@ -152,4 +152,5 @@ bindcode $mod+27 mode "resize"
 # finds out, if available)
 bar {
         status_command i3status
+        tray_output primary
 }
index 53227a8e10fbb29d0468389bc1bb1a65c2fefa99..737b0b6999db1aed5e9dbe75d95fe4515bad08bf 100644 (file)
@@ -20,8 +20,8 @@ i3bar/i3bar: libi3.a $(i3bar_OBJECTS)
 
 install-i3bar: i3bar/i3bar
        echo "[i3bar] Install"
-       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
-       $(INSTALL) -m 0755 i3bar/i3bar $(DESTDIR)$(PREFIX)/bin/
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(EXEC_PREFIX)/bin
+       $(INSTALL) -m 0755 i3bar/i3bar $(DESTDIR)$(EXEC_PREFIX)/bin/
 
 clean-i3bar:
        echo "[i3bar] Clean"
index b87cfaacacffd8d78ff8679cf71dd8fe7e32c998..873a34651a2a735248a96a549eb29fe327d52b0a 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010 Axel Wagner and contributors (see also: LICENSE)
  *
  * child.c: Getting input for the statusline
  *
index 22b9a28ac81d39e9d7a3a24cf40f2ba900839fe7..90da9388a60b128b34ed2b929df8263c17984e2c 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010 Axel Wagner and contributors (see also: LICENSE)
  *
  */
 #pragma once
index fdc37445ab4944549742ca7c580fa4f488e51de0..1ce5dfa6e4bebaaf930114d61081498a1f77ce55 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010 Axel Wagner and contributors (see also: LICENSE)
  *
  * config.c: Parses the configuration (received from i3).
  *
@@ -22,10 +22,16 @@ typedef enum { M_DOCK = 0,
                M_HIDE = 1,
                M_INVISIBLE = 2 } bar_display_mode_t;
 
+typedef struct binding_t {
+    int input_code;
+    char *command;
+
+    TAILQ_ENTRY(binding_t) bindings;
+} binding_t;
+
 typedef struct config_t {
     int modifier;
-    char *wheel_up_cmd;
-    char *wheel_down_cmd;
+    TAILQ_HEAD(bindings_head, binding_t) bindings;
     position_t position;
     int verbose;
     struct xcb_color_strings_t colors;
@@ -37,6 +43,7 @@ typedef struct config_t {
     char *fontname;
     i3String *separator_symbol;
     char *tray_output;
+    int tray_padding;
     int num_outputs;
     char **outputs;
 
index a60a1ebae1603e44ae0f93847e58ebc6d4fc9f00..686c0322d31c9bd119f9df6627d44dda557df1e4 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010 Axel Wagner and contributors (see also: LICENSE)
  *
  * ipc.c: Communicating with i3
  *
index 37e8e01793d75fea581bd564f58a6155770ddaf0..828d4906a695033aad2fe8fa2132b1b5a48fbfc1 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010 Axel Wagner and contributors (see also: LICENSE)
  *
  * mode.c: Handle "mode" event and show current binding mode in the bar
  *
index 73fbf33f32caf5a9ad674a0f18ebd3e903850797..e6605e1f73da2b7892e6be88b7360c31118f94e4 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010 Axel Wagner and contributors (see also: LICENSE)
  *
  * outputs.c: Maintaining the outputs list
  *
index ef13cf78dd7564c4b686e3ad0e9d958f9f581b81..41e97de2f994ef24a754148e9f676c12446efd60 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010 Axel Wagner and contributors (see also: LICENSE)
  *
  * parse_json_header.c: Parse the JSON protocol header to determine
  *                      protocol version and features.
index 1720ec3b860d68c2f4f5e86bcc7c2ce1ec8919a6..694faa483a7ac5682812094503d888ff2362f055 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010 Axel Wagner and contributors (see also: LICENSE)
  *
  */
 #pragma once
index ac13799851c097d08a4c638cf53eb8dda8a44933..ba08cf763b79e587d9b1f3f4cad95fec4c778592 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
 #pragma once
index 1b8c6c729bd813ba6afb4c26f3e6af1ff5adcea2..5ed84de86e426dc3f9f70dd318a3b4d79dbfb9c2 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010 Axel Wagner and contributors (see also: LICENSE)
  *
  * workspaces.c: Maintaining the workspace lists
  *
index bb37e7d517921066922fca075ff6b591421c7834..835105e7fe7bde2a7315c9bb659cbce66a854697 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010 Axel Wagner and contributors (see also: LICENSE)
  *
  * xcb.c: Communicating with X
  *
@@ -40,6 +40,9 @@ struct xcb_color_strings_t {
     char *urgent_ws_bg;
     char *urgent_ws_fg;
     char *urgent_ws_border;
+    char *binding_mode_bg;
+    char *binding_mode_fg;
+    char *binding_mode_border;
 };
 
 typedef struct xcb_colors_t xcb_colors_t;
index e2638e7bd863225887b41b0e94808ea091af87f8..cfc96d5f1ad2d4841f39ceb39480e235fb93cb26 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010 Axel Wagner and contributors (see also: LICENSE)
  *
  * child.c: Getting input for the statusline
  *
@@ -105,13 +105,13 @@ __attribute__((format(printf, 1, 2))) static void set_statusline_error(const cha
     va_start(args, format);
     (void)vasprintf(&message, format, args);
 
-    struct status_block *err_block = scalloc(sizeof(struct status_block));
+    struct status_block *err_block = scalloc(1, sizeof(struct status_block));
     err_block->full_text = i3string_from_utf8("Error: ");
     err_block->name = sstrdup("error");
     err_block->color = sstrdup("red");
     err_block->no_separator = true;
 
-    struct status_block *message_block = scalloc(sizeof(struct status_block));
+    struct status_block *message_block = scalloc(1, sizeof(struct status_block));
     message_block->full_text = i3string_from_utf8(message);
     message_block->name = sstrdup("error_message");
     message_block->color = sstrdup("red");
@@ -220,21 +220,21 @@ 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 *)malloc(len + 1);
+        char *copy = (char *)smalloc(len + 1);
         strncpy(copy, (const char *)val, len);
         copy[len] = 0;
         ctx->block.min_width_str = copy;
         return 1;
     }
     if (strcasecmp(ctx->last_map_key, "name") == 0) {
-        char *copy = (char *)malloc(len + 1);
+        char *copy = (char *)smalloc(len + 1);
         strncpy(copy, (const char *)val, len);
         copy[len] = 0;
         ctx->block.name = copy;
         return 1;
     }
     if (strcasecmp(ctx->last_map_key, "instance") == 0) {
-        char *copy = (char *)malloc(len + 1);
+        char *copy = (char *)smalloc(len + 1);
         strncpy(copy, (const char *)val, len);
         copy[len] = 0;
         ctx->block.instance = copy;
@@ -433,7 +433,7 @@ void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
     } else {
         /* In case of plaintext, we just add a single block and change its
          * full_text pointer later. */
-        struct status_block *new_block = scalloc(sizeof(struct status_block));
+        struct status_block *new_block = scalloc(1, sizeof(struct status_block));
         TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
         read_flat_input((char *)buffer, rec);
     }
index f7abaee7dff9f9aa65abab5af1fc36a3c4c65cfb..0e2dd05a88d338400291f931558c7f875a47223c 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2011 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010 Axel Wagner and contributors (see also: LICENSE)
  *
  * config.c: Parses the configuration (received from i3).
  *
@@ -20,6 +20,7 @@
 #include "common.h"
 
 static char *cur_key;
+static bool parsing_bindings;
 
 /*
  * Parse a key.
@@ -34,6 +35,14 @@ static int config_map_key_cb(void *params_, const unsigned char *keyVal, size_t
     strncpy(cur_key, (const char *)keyVal, keyLen);
     cur_key[keyLen] = '\0';
 
+    if (strcmp(cur_key, "bindings") == 0)
+        parsing_bindings = true;
+
+    return 1;
+}
+
+static int config_end_array_cb(void *params_) {
+    parsing_bindings = false;
     return 1;
 }
 
@@ -63,6 +72,27 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
     if (!strcmp(cur_key, "id") || !strcmp(cur_key, "socket_path"))
         return 1;
 
+    if (parsing_bindings) {
+        if (strcmp(cur_key, "command") == 0) {
+            binding_t *binding = TAILQ_LAST(&(config.bindings), bindings_head);
+            if (binding == NULL) {
+                ELOG("There is no binding to put the current command onto. This is a bug in i3.\n");
+                return 0;
+            }
+
+            if (binding->command != NULL) {
+                ELOG("The binding for input_code = %d already has a command. This is a bug in i3.\n", binding->input_code);
+                return 0;
+            }
+
+            sasprintf(&(binding->command), "%.*s", len, val);
+            return 1;
+        }
+
+        ELOG("Unknown key \"%s\" while parsing bar bindings.\n", cur_key);
+        return 0;
+    }
+
     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
@@ -112,17 +142,25 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
         return 1;
     }
 
+    /* This key was sent in <= 4.10.2. We keep it around to avoid breakage for
+     * users updating from that version and restarting i3bar before i3. */
     if (!strcmp(cur_key, "wheel_up_cmd")) {
         DLOG("wheel_up_cmd = %.*s\n", len, val);
-        FREE(config.wheel_up_cmd);
-        sasprintf(&config.wheel_up_cmd, "%.*s", len, val);
+        binding_t *binding = scalloc(1, sizeof(binding_t));
+        binding->input_code = 4;
+        sasprintf(&(binding->command), "%.*s", len, val);
+        TAILQ_INSERT_TAIL(&(config.bindings), binding, bindings);
         return 1;
     }
 
+    /* This key was sent in <= 4.10.2. We keep it around to avoid breakage for
+     * users updating from that version and restarting i3bar before i3. */
     if (!strcmp(cur_key, "wheel_down_cmd")) {
         DLOG("wheel_down_cmd = %.*s\n", len, val);
-        FREE(config.wheel_down_cmd);
-        sasprintf(&config.wheel_down_cmd, "%.*s", len, val);
+        binding_t *binding = scalloc(1, sizeof(binding_t));
+        binding->input_code = 5;
+        sasprintf(&(binding->command), "%.*s", len, val);
+        TAILQ_INSERT_TAIL(&(config.bindings), binding, bindings);
         return 1;
     }
 
@@ -191,6 +229,9 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
     COLOR(urgent_workspace_border, urgent_ws_border);
     COLOR(urgent_workspace_bg, urgent_ws_bg);
     COLOR(urgent_workspace_text, urgent_ws_fg);
+    COLOR(binding_mode_border, binding_mode_border);
+    COLOR(binding_mode_bg, binding_mode_bg);
+    COLOR(binding_mode_text, binding_mode_fg);
 
     printf("got unexpected string %.*s for cur_key = %s\n", len, val, cur_key);
 
@@ -229,11 +270,40 @@ static int config_boolean_cb(void *params_, int val) {
     return 0;
 }
 
+/*
+ * Parse an integer value
+ *
+ */
+static int config_integer_cb(void *params_, long long val) {
+    if (parsing_bindings) {
+        if (strcmp(cur_key, "input_code") == 0) {
+            binding_t *binding = scalloc(1, sizeof(binding_t));
+            binding->input_code = val;
+            TAILQ_INSERT_TAIL(&(config.bindings), binding, bindings);
+
+            return 1;
+        }
+
+        ELOG("Unknown key \"%s\" while parsing bar bindings.\n", cur_key);
+        return 0;
+    }
+
+    if (!strcmp(cur_key, "tray_padding")) {
+        DLOG("tray_padding = %lld\n", val);
+        config.tray_padding = val;
+        return 1;
+    }
+
+    return 0;
+}
+
 /* A datastructure to pass all these callbacks to yajl */
 static yajl_callbacks outputs_callbacks = {
     .yajl_null = config_null_cb,
     .yajl_boolean = config_boolean_cb,
+    .yajl_integer = config_integer_cb,
     .yajl_string = config_string_cb,
+    .yajl_end_array = config_end_array_cb,
     .yajl_map_key = config_map_key_cb,
 };
 
@@ -246,6 +316,8 @@ void parse_config_json(char *json) {
     yajl_status state;
     handle = yajl_alloc(&outputs_callbacks, NULL, NULL);
 
+    TAILQ_INIT(&(config.bindings));
+
     state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
 
     /* FIXME: Proper error handling for JSON parsing */
@@ -286,5 +358,8 @@ void free_colors(struct xcb_color_strings_t *colors) {
     FREE_COLOR(focus_ws_fg);
     FREE_COLOR(focus_ws_bg);
     FREE_COLOR(focus_ws_border);
+    FREE_COLOR(binding_mode_fg);
+    FREE_COLOR(binding_mode_bg);
+    FREE_COLOR(binding_mode_border);
 #undef FREE_COLOR
 }
index 15a26d7b4eb42b502fa3ca1a45d01cee90bc42d3..eb48afeaaa2f2432bee7904b52ddda6c6b30fb4b 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010 Axel Wagner and contributors (see also: LICENSE)
  *
  * ipc.c: Communicating with i3
  *
index a9a972dbe6c12a37a5893b830bf7244b5ef34bca..32425319ed3c5afc59a9516d1f434a208ad59fbf 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010 Axel Wagner and contributors (see also: LICENSE)
  *
  */
 #include <stdio.h>
@@ -111,7 +111,7 @@ int main(int argc, char **argv) {
                 socket_path = expand_path(optarg);
                 break;
             case 'v':
-                printf("i3bar version " I3_VERSION " © 2010-2014 Axel Wagner and contributors\n");
+                printf("i3bar version " I3_VERSION " © 2010 Axel Wagner and contributors\n");
                 exit(EXIT_SUCCESS);
                 break;
             case 'b':
index 0ff8ba4f0239fd3fa70d6706e108d40a8144349d..7f7537af7d086e6db8168df6943932fa6e5530f9 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010 Axel Wagner and contributors (see also: LICENSE)
  *
  * mode.c: Handle mode event and show current binding mode in the bar
  *
@@ -32,7 +32,7 @@ static int mode_string_cb(void *params_, const unsigned char *val, size_t len) {
 
     if (!strcmp(params->cur_key, "change")) {
         /* Save the name */
-        params->mode->name = i3string_from_utf8_with_length((const char *)val, len);
+        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);
 
index 8f5c95dc2f16d7e8ffb93b763099a8cc0b65b03e..b49ff53f6892bbb7ee69fb409c0b755d4dfbeb75 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010 Axel Wagner and contributors (see also: LICENSE)
  *
  * outputs.c: Maintaining the outputs list
  *
index f5fb84ab147befeb838296fbbfa153c99ed35bfb..1cd951065c8e379f123d08671b12c769e8468e8f 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010 Axel Wagner and contributors (see also: LICENSE)
  *
  * parse_json_header.c: Parse the JSON protocol header to determine
  *                      protocol version and features.
index e30325265bb36835b2b5bdac5f8a7a76b54d2386..961a41f5da7913c4be7558204f546e1bcc91919f 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010 Axel Wagner and contributors (see also: LICENSE)
  *
  * workspaces.c: Maintaining the workspace lists
  *
@@ -106,7 +106,7 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t
 
     if (!strcmp(params->cur_key, "name")) {
         const char *ws_name = (const char *)val;
-        params->workspaces_walk->canonical_name = strndup(ws_name, len);
+        params->workspaces_walk->canonical_name = sstrndup(ws_name, len);
 
         if (config.strip_ws_numbers && params->workspaces_walk->num >= 0) {
             /* Special case: strip off the workspace number */
index ba57b9fa397d11a8fabc238da2727ba09c4095ab..f90bbceede5e51356204f85d1e9ceb38874606a7 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
+ * © 2010 Axel Wagner and contributors (see also: LICENSE)
  *
  * xcb.c: Communicating with X
  *
@@ -60,6 +60,9 @@ xcb_connection_t *conn;
 /* The font we'll use */
 static i3Font font;
 
+/* Icon size (based on font size) */
+int icon_size;
+
 /* Overall height of the bar (based on font size) */
 int bar_height;
 
@@ -103,6 +106,9 @@ struct xcb_colors_t {
     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;
 };
 struct xcb_colors_t colors;
 
@@ -148,7 +154,7 @@ int get_tray_width(struct tc_head *trayclients) {
     TAILQ_FOREACH_REVERSE(trayclient, trayclients, tc_head, tailq) {
         if (!trayclient->mapped)
             continue;
-        tray_width += font.height + logical_px(2);
+        tray_width += icon_size + logical_px(config.tray_padding);
     }
     if (tray_width > 0)
         tray_width += logical_px(tray_loff_px);
@@ -368,6 +374,18 @@ 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; \
+    } while (0)
+
+    /* For the binding mode indicator colors, we don't hardcode a default.
+     * Instead, we fall back to urgent_ws_* 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);
+#undef PARSE_COLOR_FALLBACK
+
     init_tray_colors();
     xcb_flush(xcb_connection);
 }
@@ -420,7 +438,7 @@ void handle_button(xcb_button_press_event_t *event) {
         int offset = walk->rect.w - statusline_width - tray_width - logical_px(sb_hoff_px);
 
         x = original_x - offset;
-        if (x >= 0) {
+        if (x >= 0 && (size_t)x < statusline_width) {
             struct status_block *block;
             int sep_offset_remainder = 0;
 
@@ -442,25 +460,27 @@ void handle_button(xcb_button_press_event_t *event) {
         x = original_x;
     }
 
+    /* If a custom command was specified for this mouse button, it overrides
+     * the default behavior. */
+    binding_t *binding;
+    TAILQ_FOREACH(binding, &(config.bindings), bindings) {
+        if (binding->input_code != event->detail)
+            continue;
+
+        i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, binding->command);
+        return;
+    }
+
     if (cur_ws == NULL) {
         DLOG("No workspace active?\n");
         return;
     }
-
     switch (event->detail) {
         case 4:
             /* Mouse wheel up. We select the previous ws, if any.
              * If there is no more workspace, don’t even send the workspace
              * command, otherwise (with workspace auto_back_and_forth) we’d end
              * up on the wrong workspace. */
-
-            /* If `wheel_up_cmd [COMMAND]` was specified, it should override
-             * the default behavior */
-            if (config.wheel_up_cmd) {
-                i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, config.wheel_up_cmd);
-                return;
-            }
-
             if (cur_ws == TAILQ_FIRST(walk->workspaces))
                 return;
 
@@ -471,14 +491,6 @@ void handle_button(xcb_button_press_event_t *event) {
              * If there is no more workspace, don’t even send the workspace
              * command, otherwise (with workspace auto_back_and_forth) we’d end
              * up on the wrong workspace. */
-
-            /* if `wheel_down_cmd [COMMAND]` was specified, it should override
-             * the default behavior */
-            if (config.wheel_down_cmd) {
-                i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, config.wheel_down_cmd);
-                return;
-            }
-
             if (cur_ws == TAILQ_LAST(walk->workspaces, ws_head))
                 return;
 
@@ -521,7 +533,7 @@ void handle_button(xcb_button_press_event_t *event) {
     }
 
     const size_t len = namelen + strlen("workspace \"\"") + 1;
-    char *buffer = scalloc(len + num_quotes);
+    char *buffer = scalloc(len + num_quotes, 1);
     strncpy(buffer, "workspace \"", strlen("workspace \""));
     size_t inpos, outpos;
     for (inpos = 0, outpos = strlen("workspace \"");
@@ -591,8 +603,8 @@ static void configure_trayclients(void) {
             clients++;
 
             DLOG("Configuring tray window %08x to x=%d\n",
-                 trayclient->win, output->rect.w - (clients * (font.height + logical_px(2))));
-            uint32_t x = output->rect.w - (clients * (font.height + logical_px(2)));
+                 trayclient->win, output->rect.w - (clients * (icon_size + logical_px(config.tray_padding))));
+            uint32_t x = output->rect.w - (clients * (icon_size + logical_px(config.tray_padding)));
             xcb_configure_window(xcb_connection,
                                  trayclient->win,
                                  XCB_CONFIG_WINDOW_X,
@@ -702,23 +714,23 @@ static void handle_client_message(xcb_client_message_event_t *event) {
             xcb_reparent_window(xcb_connection,
                                 client,
                                 output->bar,
-                                output->rect.w - font.height - 2,
-                                2);
+                                output->rect.w - icon_size - logical_px(config.tray_padding),
+                                logical_px(config.tray_padding));
             /* We reconfigure the window to use a reasonable size. The systray
              * specification explicitly says:
              *   Tray icons may be assigned any size by the system tray, and
              *   should do their best to cope with any size effectively
              */
             mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
-            values[0] = font.height;
-            values[1] = font.height;
+            values[0] = icon_size;
+            values[1] = icon_size;
             xcb_configure_window(xcb_connection,
                                  client,
                                  mask,
                                  values);
 
             /* send the XEMBED_EMBEDDED_NOTIFY message */
-            void *event = scalloc(32);
+            void *event = scalloc(32, 1);
             xcb_client_message_event_t *ev = event;
             ev->response_type = XCB_CLIENT_MESSAGE;
             ev->window = client;
@@ -941,10 +953,10 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
                 continue;
 
             xcb_rectangle_t rect;
-            rect.x = output->rect.w - (clients * (font.height + 2));
-            rect.y = 2;
-            rect.width = font.height;
-            rect.height = font.height;
+            rect.x = output->rect.w - (clients * (icon_size + logical_px(config.tray_padding)));
+            rect.y = logical_px(config.tray_padding);
+            rect.width = icon_size;
+            rect.height = icon_size;
 
             DLOG("This is a tray window. x = %d\n", rect.x);
             fake_configure_notify(xcb_connection, rect, event->window, 0);
@@ -1215,6 +1227,7 @@ void init_xcb_late(char *fontname) {
     set_font(&font);
     DLOG("Calculated font height: %d\n", font.height);
     bar_height = font.height + 2 * logical_px(ws_voff_px);
+    icon_size = bar_height - 2 * logical_px(config.tray_padding);
 
     if (config.separator_symbol)
         separator_symbol_width = predict_text_width(config.separator_symbol);
@@ -1641,8 +1654,7 @@ void reconfig_windows(bool redraw_bars) {
                                                "i3bar\0i3bar\0");
 
             char *name;
-            if (asprintf(&name, "i3bar for output %s", walk->name) == -1)
-                err(EXIT_FAILURE, "asprintf()");
+            sasprintf(&name, "i3bar for output %s", walk->name);
             xcb_void_cookie_t name_cookie;
             name_cookie = xcb_change_property(xcb_connection,
                                               XCB_PROP_MODE_REPLACE,
@@ -1792,6 +1804,8 @@ 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);
 
@@ -1888,11 +1902,11 @@ 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.urgent_ws_fg;
-            uint32_t bg_color = colors.urgent_ws_bg;
+            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;
 
-            uint32_t vals_border[] = {colors.urgent_ws_border, colors.urgent_ws_border};
+            uint32_t vals_border[] = {colors.binding_mode_border, colors.binding_mode_border};
             xcb_change_gc(xcb_connection,
                           outputs_walk->bargc,
                           mask,
@@ -1941,8 +1955,15 @@ void draw_bars(bool unhide) {
             uint32_t max_statusline_width = outputs_walk->rect.w - workspace_width - tray_width - 2 * logical_px(sb_hoff_px);
 
             /* If the statusline is too long, try to use short texts. */
-            if (statusline_width > max_statusline_width)
+            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;
+            }
 
             /* Luckily we already prepared a seperate pixmap containing the rendered
              * statusline, we just have to copy the relevant parts to the relevant
index a355d3d2b65a8e1ef08bb9a97556942a4b8c12bc..85397d59a3d8cad3d6205dc8baee4ed4cdf09e50 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * This header file includes all relevant files of i3 and the most often used
  * system header files. This reduces boilerplate (the amount of code duplicated
index b83ee03faab0c3252e49c1f625048c417b9bb96c..22ebafc345a4a38cf7a54921ef83b1157d46a63d 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * assignments.c: Assignments for specific windows (for_window).
  *
index 59dab6eddd1c1eeeff16b3b5b26d5992b137ad85..f856559cf9367533c2271713ef02af7e2ece527e 100644 (file)
@@ -1,17 +1,25 @@
 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)
index 19345f8c82ad6c6144d8c7bbde53f849bceb86eb..88b4a6cc96ff347fb6b70a907779a1d081c1481d 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2014 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * bindings.h: Functions for configuring, finding, and running bindings.
  *
@@ -24,13 +24,14 @@ const char *DEFAULT_BINDING_MODE;
  *
  */
 Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code,
-                           const char *release, const char *whole_window, const char *command, const char *mode);
+                           const char *release, const char *border, const char *whole_window,
+                           const char *command, const char *mode);
 
 /**
  * Grab the bound keys (tell X to send us keypress events for those keycodes)
  *
  */
-void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch);
+void grab_all_keys(xcb_connection_t *conn);
 
 /**
  * Returns a pointer to the Binding that matches the given xcb event or NULL if
@@ -51,6 +52,21 @@ void translate_keysyms(void);
  */
 void switch_mode(const char *new_mode);
 
+/**
+ * Reorders bindings by event_state_mask descendingly so that get_binding()
+ * correctly matches more specific bindings before more generic bindings. Take
+ * the following binding configuration as an example:
+ *
+ *   bindsym n nop lower-case n pressed
+ *   bindsym Shift+n nop upper-case n pressed
+ *
+ * Without reordering, the first binding’s event_state_mask of 0x0 would match
+ * the actual event_stat_mask of 0x1 and hence trigger instead of the second
+ * keybinding.
+ *
+ */
+void reorder_bindings(void);
+
 /**
  * Checks for duplicate key bindings (the same keycode or keysym is configured
  * more than once). If a duplicate binding is found, a message is printed to
@@ -73,3 +89,9 @@ void binding_free(Binding *bind);
  *
  */
 CommandResult *run_binding(Binding *bind, Con *con);
+
+/**
+ * Loads the XKB keymap from the X11 server and feeds it to xkbcommon.
+ *
+ */
+bool load_keymap(void);
index c63672a7cce954e461ab979350091d71fe62cef2..7ce80d97abcbf50f8edb998e31ce161943c38ed3 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * click.c: Button press (mouse click) events.
  *
index 263801f63d6f125de0d90c7713ec42865326e9d2..2ffa49b011e1b47043dd75bd03a8888f2e6c7e14 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * cmdparse.y: the parser for commands you send to i3 (or bind on keys)
  *
index 780a9e8eb781893dd3fb69c8642783b98c73e676..80b0efb020e91dfe00c278cb1e56116b48b3fb63 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * commands.c: all command functions (see commands_parser.c)
  *
@@ -60,6 +60,12 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name);
  */
 void cmd_move_con_to_workspace_number(I3_CMD, char *which);
 
+/**
+ * Implementation of 'resize set <px> [px] <px> [px]'.
+ *
+ */
+void cmd_size(I3_CMD, char *cwidth, char *cheight);
+
 /**
  * Implementation of 'resize grow|shrink <direction> [<px> px] [or <ppt> ppt]'.
  *
@@ -67,7 +73,7 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which);
 void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resize_ppt);
 
 /**
- * Implementation of 'border normal|none|1pixel|toggle'.
+ * Implementation of 'border normal|pixel [<n>]', 'border none|1pixel|toggle'.
  *
  */
 void cmd_border(I3_CMD, char *border_style_str, char *border_width);
@@ -109,10 +115,10 @@ void cmd_workspace_back_and_forth(I3_CMD);
 void cmd_workspace_name(I3_CMD, char *name);
 
 /**
- * Implementation of 'mark <mark>'
+ * Implementation of 'mark [--toggle] <mark>'
  *
  */
-void cmd_mark(I3_CMD, char *mark);
+void cmd_mark(I3_CMD, char *mark, char *toggle);
 
 /**
  * Implementation of 'unmark [mark]'
@@ -132,6 +138,12 @@ void cmd_mode(I3_CMD, char *mode);
  */
 void cmd_move_con_to_output(I3_CMD, char *name);
 
+/**
+ * Implementation of 'move [window|container] [to] mark <str>'.
+ *
+ */
+void cmd_move_con_to_mark(I3_CMD, char *mark);
+
 /**
  * Implementation of 'floating enable|disable|toggle'
  *
@@ -192,6 +204,12 @@ void cmd_focus(I3_CMD);
  */
 void cmd_fullscreen(I3_CMD, char *action, char *fullscreen_mode);
 
+/**
+ * Implementation of 'sticky enable|disable|toggle'.
+ *
+ */
+void cmd_sticky(I3_CMD, char *action);
+
 /**
  * Implementation of 'move <direction> [<pixels> [px]]'.
  *
@@ -252,6 +270,12 @@ void cmd_move_window_to_position(I3_CMD, char *method, char *x, char *y);
  */
 void cmd_move_window_to_center(I3_CMD, char *method);
 
+/**
+ * Implementation of 'move [window|container] [to] position mouse'
+ *
+ */
+void cmd_move_window_to_mouse(I3_CMD);
+
 /**
  * Implementation of 'move scratchpad'.
  *
@@ -264,6 +288,12 @@ void cmd_move_scratchpad(I3_CMD);
  */
 void cmd_scratchpad_show(I3_CMD);
 
+/**
+ * Implementation of 'title_format <format>'
+ *
+ */
+void cmd_title_format(I3_CMD, char *format);
+
 /**
  * Implementation of 'rename workspace <name> to <name>'
  *
index cfa44dd5a5a03621fd04fd9f081018c297ba0b6d..1acb7faebe791f999db86166b209dfe9b80636a5 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * commands.c: all command functions (see commands_parser.c)
  *
index b025adab16e0349eef40fca2e5972d033bef57c0..16ea6cfabcd471f59534ea2704debff4b3cc7589 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * con.c: Functions which deal with containers directly (creating containers,
  *        searching containers, getting specific properties from containers,
@@ -42,12 +42,25 @@ bool con_is_leaf(Con *con);
  */
 bool con_has_managed_window(Con *con);
 
-/*
+/**
  * Returns true if a container should be considered split.
  *
  */
 bool con_is_split(Con *con);
 
+/**
+ * This will only return true for containers which have some parent with
+ * a tabbed / stacked parent of which they are not the currently focused child.
+ *
+ */
+bool con_is_hidden(Con *con);
+
+/**
+ * Returns whether the container or any of its children is sticky.
+ *
+ */
+bool con_is_sticky(Con *con);
+
 /**
  * Returns true if this node has regular or floating children.
  *
@@ -126,6 +139,34 @@ Con *con_by_window_id(xcb_window_t window);
  */
 Con *con_by_frame_id(xcb_window_t frame);
 
+/**
+ * Returns the container with the given mark or NULL if no such container
+ * exists.
+ *
+ */
+Con *con_by_mark(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);
+
+/**
+ * Assigns a mark to the container.
+ *
+ */
+void con_mark(Con *con, const char *mark);
+
+/**
+ * If mark 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);
+
 /**
  * Returns the first container below 'con' which wants to swallow this window
  * TODO: priority
@@ -198,10 +239,20 @@ void con_disable_fullscreen(Con *con);
  * The dont_warp flag disables pointer warping and will be set when this
  * function is called while dragging a floating window.
  *
+ * If ignore_focus is set, the container will be moved without modifying focus
+ * at all.
+ *
  * TODO: is there a better place for this function?
  *
  */
-void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool dont_warp);
+void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates,
+                           bool dont_warp, bool ignore_focus);
+
+/**
+ * Moves the given container to the given mark.
+ *
+ */
+bool con_move_to_mark(Con *con, const char *mark);
 
 /**
  * Returns the orientation of the given container (for stacked containers,
index 9a0af0e6cd6b8119e1a5ae4a4bd5fcef915cb66e..fd6fe6c092d9511722054bbacc484080b6f1787f 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * include/config.h: Contains all structs/variables for the configurable
  * part of i3 as well as functions handling the configuration file (calling
@@ -167,6 +167,22 @@ struct Config {
      * flag can be delayed using an urgency timer. */
     float workspace_urgency_timer;
 
+    /** Behavior when a window sends a NET_ACTIVE_WINDOW message. */
+    enum {
+        /* Focus if the target workspace is visible, set urgency hint otherwise. */
+        FOWA_SMART,
+        /* Always set the urgency hint. */
+        FOWA_URGENT,
+        /* Always focus the window. */
+        FOWA_FOCUS,
+        /* Ignore the request (no focus, no urgency hint). */
+        FOWA_NONE
+    } focus_on_window_activation;
+
+    /** Specifies whether or not marks should be displayed in the window
+     * decoration. Marks starting with a "_" will be ignored either way. */
+    bool show_marks;
+
     /** The default border style for new windows. */
     border_style_t default_border;
 
@@ -235,6 +251,9 @@ struct Barconfig {
      * disables the tray (it’s enabled by default). */
     char *tray_output;
 
+    /* Padding around the tray icons. */
+    int tray_padding;
+
     /** Path to the i3 IPC socket. This option is discouraged since programs
      * can find out the path by looking for the I3_SOCKET_PATH property on the
      * root window! */
@@ -261,13 +280,7 @@ struct Barconfig {
         M_MOD5 = 7
     } modifier;
 
-    /** Command that should be run when mouse wheel up button is pressed over
-     * i3bar to override the default behavior. */
-    char *wheel_up_cmd;
-
-    /** Command that should be run when mouse wheel down button is pressed over
-     * i3bar to override the default behavior. */
-    char *wheel_down_cmd;
+    TAILQ_HEAD(bar_bindings_head, Barbinding) bar_bindings;
 
     /** Bar position (bottom by default). */
     enum { P_BOTTOM = 0,
@@ -324,11 +337,30 @@ struct Barconfig {
         char *urgent_workspace_border;
         char *urgent_workspace_bg;
         char *urgent_workspace_text;
+
+        char *binding_mode_border;
+        char *binding_mode_bg;
+        char *binding_mode_text;
     } colors;
 
     TAILQ_ENTRY(Barconfig) configs;
 };
 
+/**
+ * Defines a mouse command to be executed instead of the default behavior when
+ * clicking on the non-statusline part of i3bar.
+ *
+ */
+struct Barbinding {
+    /** The button to be used (e.g., 1 for "button1"). */
+    int input_code;
+
+    /** The command which is to be executed for this button. */
+    char *command;
+
+    TAILQ_ENTRY(Barbinding) bindings;
+};
+
 /**
  * Finds the configuration file to use (either the one specified by
  * override_configpath), the user’s one or the system default) and calls
index 6c960b1f8c765771c2fd6d340872a76d7ea4fae6..c0c70bb487fa4ed057163d54e8b1e76482e85a23 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * config_directives.h: all config storing functions (see config_parser.c)
  *
 #include "config_parser.h"
 
 /**
- * A utility function to convert a string of modifiers to the corresponding bit
- * mask.
+ * A utility function to convert a string containing the group and modifiers to
+ * the corresponding bit mask.
  */
-uint32_t modifiers_from_str(const char *str);
+i3_event_state_mask_t event_state_from_str(const char *str);
 
 /** The beginning of the prototype for every cfg_ function. */
 #define I3_CFG Match *current_match, struct ConfigResultIR *result
@@ -51,8 +51,11 @@ CFGFUN(force_focus_wrapping, const char *value);
 CFGFUN(force_xinerama, const char *value);
 CFGFUN(fake_outputs, const char *outputs);
 CFGFUN(force_display_urgency_hint, const long duration_ms);
+CFGFUN(focus_on_window_activation, const char *mode);
+CFGFUN(show_marks, const char *value);
 CFGFUN(hide_edge_borders, const char *borders);
 CFGFUN(assign, const char *workspace);
+CFGFUN(no_focus);
 CFGFUN(ipc_socket, const char *path);
 CFGFUN(restart_state, const char *path);
 CFGFUN(popup_during_fullscreen, const char *value);
@@ -61,10 +64,10 @@ 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 *whole_window, const char *command);
+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(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *whole_window, const char *command);
+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);
 CFGFUN(bar_separator_symbol, const char *separator);
@@ -76,14 +79,17 @@ CFGFUN(bar_verbose, const char *verbose);
 CFGFUN(bar_modifier, const char *modifier);
 CFGFUN(bar_wheel_up_cmd, const char *command);
 CFGFUN(bar_wheel_down_cmd, const char *command);
+CFGFUN(bar_bindsym, const char *button, const char *command);
 CFGFUN(bar_position, const char *position);
 CFGFUN(bar_i3bar_command, const char *i3bar_command);
 CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text);
 CFGFUN(bar_socket_path, const char *socket_path);
 CFGFUN(bar_tray_output, const char *output);
+CFGFUN(bar_tray_padding, const long spacing_px);
 CFGFUN(bar_color_single, const char *colorclass, const char *color);
 CFGFUN(bar_status_command, const char *command);
 CFGFUN(bar_binding_mode_indicator, const char *value);
 CFGFUN(bar_workspace_buttons, const char *value);
 CFGFUN(bar_strip_workspace_numbers, const char *value);
+CFGFUN(bar_start);
 CFGFUN(bar_finish);
index 9fc3bf2ffb5ea63e2b3e22bef34a69f9472dd9d5..28c28e4882e7f2b6fa220cfe98ca4d4b391f1819 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * config_parser.h: config parser-related definitions
  *
index cec571e9df8eecf71f10c32ebb8c2098f8770d16..58e4a00d8a01fc904c70e6cbdcf7a097aada5047 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * include/data.h: This file defines all data structures used by i3
  *
@@ -74,18 +74,6 @@ typedef enum { ADJ_NONE = 0,
                ADJ_UPPER_SCREEN_EDGE = (1 << 2),
                ADJ_LOWER_SCREEN_EDGE = (1 << 4) } adjacent_t;
 
-enum {
-    BIND_NONE = 0,
-    BIND_SHIFT = XCB_MOD_MASK_SHIFT,     /* (1 << 0) */
-    BIND_CONTROL = XCB_MOD_MASK_CONTROL, /* (1 << 2) */
-    BIND_MOD1 = XCB_MOD_MASK_1,          /* (1 << 3) */
-    BIND_MOD2 = XCB_MOD_MASK_2,          /* (1 << 4) */
-    BIND_MOD3 = XCB_MOD_MASK_3,          /* (1 << 5) */
-    BIND_MOD4 = XCB_MOD_MASK_4,          /* (1 << 6) */
-    BIND_MOD5 = XCB_MOD_MASK_5,          /* (1 << 7) */
-    BIND_MODE_SWITCH = (1 << 8)
-};
-
 /**
  * Container layouts. See Con::layout.
  */
@@ -107,6 +95,25 @@ typedef enum {
     B_MOUSE = 1
 } input_type_t;
 
+/**
+ * Bitmask for matching XCB_XKB_GROUP_1 to XCB_XKB_GROUP_4.
+ */
+typedef enum {
+    I3_XKB_GROUP_MASK_ANY = 0,
+    I3_XKB_GROUP_MASK_1 = (1 << 0),
+    I3_XKB_GROUP_MASK_2 = (1 << 1),
+    I3_XKB_GROUP_MASK_3 = (1 << 2),
+    I3_XKB_GROUP_MASK_4 = (1 << 3)
+} i3_xkb_group_mask_t;
+
+/**
+ * The lower 16 bits contain a xcb_key_but_mask_t, the higher 16 bits contain
+ * an i3_xkb_group_mask_t. This type is necessary for the fallback logic to
+ * work when handling XKB groups (see ticket #1775) and makes the code which
+ * locates keybindings upon KeyPress/KeyRelease events simpler.
+ */
+typedef uint32_t i3_event_state_mask_t;
+
 /**
  * Mouse pointer warping modes.
  */
@@ -255,6 +262,10 @@ struct Binding {
         B_UPON_KEYRELEASE_IGNORE_MODS = 2,
     } release;
 
+    /** If this is true for a mouse binding, the binding should be executed
+     * when the button is pressed over the window border. */
+    bool border;
+
     /** If this is true for a mouse binding, the binding should be executed
      * when the button is pressed over any part of the window, not just the
      * title bar (default). */
@@ -265,8 +276,10 @@ struct Binding {
     /** Keycode to bind */
     uint32_t keycode;
 
-    /** Bitmask consisting of BIND_MOD_1, BIND_MODE_SWITCH, … */
-    uint32_t mods;
+    /** Bitmask which is applied against event->state for KeyPress and
+     * KeyRelease events to determine whether this binding applies to the
+     * current state. */
+    i3_event_state_mask_t event_state_mask;
 
     /** Symbol the user specified in configfile, if any. This needs to be
      * stored with the binding to be able to re-convert it into a keycode
@@ -359,6 +372,8 @@ 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
@@ -378,6 +393,9 @@ struct Window {
      * default will be 'accepts focus'. */
     bool doesnt_accept_focus;
 
+    /** The _NET_WM_WINDOW_TYPE for this window. */
+    xcb_atom_t window_type;
+
     /** Whether the window says it is a dock window */
     enum { W_NODOCK = 0,
            W_DOCK_TOP = 1,
@@ -391,6 +409,18 @@ struct Window {
 
     /** Depth of the window */
     uint16_t depth;
+
+    /* the wanted size of the window, used in combination with size
+     * increments (see below). */
+    int base_width;
+    int base_height;
+
+    /* minimum increment size specified for the window (in pixels) */
+    int width_increment;
+    int height_increment;
+
+    /* aspect ratio from WM_NORMAL_HINTS (MPlayer uses this for example) */
+    double aspect_ratio;
 };
 
 /**
@@ -408,6 +438,8 @@ struct Match {
     struct regex *instance;
     struct regex *mark;
     struct regex *window_role;
+    struct regex *workspace;
+    xcb_atom_t window_type;
     enum {
         U_DONTCHECK = -1,
         U_LATEST = 0,
@@ -460,6 +492,7 @@ struct Assignment {
      *
      * A_COMMAND = run the specified command for the matching window
      * A_TO_WORKSPACE = assign the matching window to the specified workspace
+     * A_NO_FOCUS = don't focus matched window when it is managed
      *
      * While the type is a bitmask, only one value can be set at a time. It is
      * a bitmask to allow filtering for multiple types, for example in the
@@ -469,7 +502,8 @@ struct Assignment {
     enum {
         A_ANY = 0,
         A_COMMAND = (1 << 0),
-        A_TO_WORKSPACE = (1 << 1)
+        A_TO_WORKSPACE = (1 << 1),
+        A_NO_FOCUS = (1 << 2)
     } type;
 
     /** the criteria to check if a window matches */
@@ -543,24 +577,15 @@ struct Con {
 
     /* user-definable mark to jump to this container later */
     char *mark;
+    /* cached to decide whether a redraw is needed */
+    bool mark_changed;
 
     double percent;
 
-    /* aspect ratio from WM_NORMAL_HINTS (MPlayer uses this for example) */
-    double aspect_ratio;
-    /* the wanted size of the window, used in combination with size
-     * increments (see below). */
-    int base_width;
-    int base_height;
-
     /* the x11 border pixel attribute */
     int border_width;
     int current_border_width;
 
-    /* minimum increment size specified for the window (in pixels) */
-    int width_increment;
-    int height_increment;
-
     struct Window *window;
 
     /* timer used for disabling urgency */
@@ -578,6 +603,12 @@ struct Con {
     TAILQ_HEAD(swallow_head, Match) swallow_head;
 
     fullscreen_mode_t fullscreen_mode;
+
+    /* Whether this window should stick to the glass. This corresponds to
+     * the _NET_WM_STATE_STICKY atom and will only be respected if the
+     * window is floating. */
+    bool sticky;
+
     /* layout is the layout of this container: one of split[v|h], stacked or
      * tabbed. Special containers in the tree (above workspaces) have special
      * layouts like dockarea or output.
index 3e65c35e0eb01b2c08d67d6011256181ba2f3faf..3875ec6bbdc8d482d71cfad6944858655cae4559 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * debug.c: Debugging functions, especially FormatEvent, which prints unhandled
  *          events.  This code is from xcb-util.
index 6f88ae97f18cb2610590a6712ac5954e9ce8a9ed..b1a5a0e0f3176ccaed3ef222669717164744be57 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * display_version.c: displays the running i3 version, runs as part of
  *                    i3 --moreversion.
index 3b5806285b4f66badec03fd7d2bb513601cb997d..7ed9b544a7b5321296cbe97d1dc9daa90e974a07 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * ewmh.c: Get/set certain EWMH properties easily.
  *
@@ -45,6 +45,12 @@ void ewmh_update_desktop_viewport(void);
  */
 void ewmh_update_active_window(xcb_window_t window);
 
+/**
+ * Updates _NET_WM_VISIBLE_NAME.
+ *
+ */
+void ewmh_update_visible_name(xcb_window_t window, const char *name);
+
 /**
  * Updates the _NET_CLIENT_LIST hint. Used for window listers.
  */
@@ -62,6 +68,12 @@ void ewmh_update_client_list(xcb_window_t *list, int num_windows);
  */
 void ewmh_update_client_list_stacking(xcb_window_t *stack, int num_windows);
 
+/**
+ * Set or remove _NET_WM_STATE_STICKY on the window.
+ *
+ */
+void ewmh_update_sticky(xcb_window_t window, bool sticky);
+
 /**
  * Set up the EWMH hints on the root window.
  *
index 75ef77ba8e71f6ea3ecc01f2dacfce1b9e5c6313..32b07fa97e30be5bf3dcaeb819ec92417a8ce558 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * Faking outputs is useful in pathological situations (like network X servers
  * which don’t support multi-monitor in a useful way) and for our testsuite.
index bea5f7a2885c5474afa5f037e140a4f3c4d15804..466c0e79a4d04c20c4999eaa400014c288a73452 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * floating.c: Floating windows.
  *
@@ -64,6 +64,18 @@ void floating_raise_con(Con *con);
  */
 bool floating_maybe_reassign_ws(Con *con);
 
+/**
+ * Centers a floating con above the specified rect.
+ *
+ */
+void floating_center(Con *con, Rect rect);
+
+/**
+ * Moves the given floating con to the current pointer position.
+ *
+ */
+void floating_move_to_pointer(Con *con);
+
 #if 0
 /**
  * Removes the floating client from its workspace and attaches it to the new
@@ -176,6 +188,15 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event,
  */
 void floating_reposition(Con *con, Rect newrect);
 
+/**
+ * Sets size of the CT_FLOATING_CON to specified dimensions. Might limit the
+ * actual size with regard to size constraints taken from user settings.
+ * Additionally, the dimensions may be upscaled until they're divisible by the
+ * window's size hints.
+ *
+ */
+void floating_resize(Con *floating_con, int x, int y);
+
 /**
  * Fixes the coordinates of the floating window whenever the window gets
  * reassigned to a different output (or when the output’s rect changes).
index 82f6b982fc43474c5717e5c48748a04409e8102c..d80a24f8fdbbda1d1dc7fd98bee473cc562f3a90 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * handlers.c: Small handlers for various events (keypresses, focus changes,
  *             …).
index 5ca875417bb163c386e5da8d462ffcc6d1a2c486..2a43148602bb0cc44b624fead17bc04e42598b98 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * i3.h: global variables that are used all over i3.
  *
@@ -24,6 +24,9 @@
 #include "data.h"
 #include "xcb.h"
 
+/** Git commit identifier, from version.c */
+extern const char *i3_version;
+
 /** The original value of RLIMIT_CORE when i3 was started. We need to restore
  * this before starting any other process, since we set RLIMIT_CORE to
  * RLIM_INFINITY for i3 debugging versions. */
@@ -34,6 +37,15 @@ extern bool debug_build;
 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
+ * window manager is present. This window is created when i3 starts and
+ * kept alive until i3 exits.
+ * We also use this window as the focused window if no other window is
+ * available to be focused on the active workspace in order to prevent
+ * keyboard focus issues (see #1378).
+ */
+extern xcb_window_t ewmh_window;
 /** The last timestamp we got from X11 (timestamps are included in some events
  * and are used for some things, like determining a unique ID in startup
  * notification). */
index f1b50dec011b1e07548628e35b713013eb3d48dd..8912bf130a5daf877ccb2445821524599a581ad9 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * This public header defines the different constants and message types to use
  * for the IPC interface to i3 (see docs/ipc for more information).
index 4eed319a21e86c7a95765c96c03974e9d6c6b7c5..e1c18816715e1fccddc199a19f736ed184e1b6e2 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * ipc.c: UNIX domain socket IPC (initialization, client handling, protocol).
  *
index 86cc6836f1300402ad29a26c3cddcc62968c4ff2..bbfec83b4808d725451820e492d1896807963324 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * key_press.c: key press handler
  *
index d6a2e98097c5baa7fd2dc6d337a91db06efbce61..9e7ef133fb4ca3ce49d253fa2956adcec57186b7 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * libi3: contains functions which are used by i3 *and* accompanying tools such
  * as i3-msg, i3-config-wizard, …
@@ -21,6 +21,8 @@
 #include <pango/pango.h>
 #endif
 
+#define DEFAULT_DIR_MODE (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)
+
 /**
  * Opaque data structure for storing strings.
  *
@@ -111,7 +113,7 @@ void *smalloc(size_t size);
  * there is no more memory available)
  *
  */
-void *scalloc(size_t size);
+void *scalloc(size_t num, size_t size);
 
 /**
  * Safe-wrapper around realloc which exits if realloc returns NULL (meaning
@@ -127,6 +129,13 @@ void *srealloc(void *ptr, size_t size);
  */
 char *sstrdup(const char *str);
 
+/**
+ * Safe-wrapper around strndup which exits if strndup returns NULL (meaning that
+ * there is no more memory available)
+ *
+ */
+char *sstrndup(const char *str, size_t size);
+
 /**
  * Safe-wrapper around asprintf which exits if it returns -1 (meaning that
  * there is no more memory available)
@@ -236,6 +245,11 @@ bool i3string_is_markup(i3String *str);
  */
 void i3string_set_markup(i3String *str, bool is_markup);
 
+/**
+ * Escape pango markup characters in the given string.
+ */
+i3String *i3string_escape_markup(i3String *str);
+
 /**
  * Returns the number of glyphs in an i3String.
  *
@@ -374,6 +388,12 @@ xcb_char2b_t *convert_utf8_to_ucs2(char *input, size_t *real_strlen);
  */
 void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background);
 
+/**
+ * Returns true if and only if the current font is a pango font.
+ *
+ */
+bool font_is_pango(void);
+
 /**
  * Draws text onto the specified X drawable (normally a pixmap) at the
  * specified coordinates (from the top left corner of the leftmost, uppermost
@@ -435,8 +455,28 @@ char *get_exe_path(const char *argv0);
  */
 int logical_px(const int logical);
 
+/**
+ * This function resolves ~ in pathnames.
+ * It may resolve wildcards in the first part of the path, but if no match
+ * or multiple matches are found, it just returns a copy of path as given.
+ *
+ */
+char *resolve_tilde(const char *path);
+
+/**
+ * Get the path of the first configuration file found. If override_configpath
+ * is specified, that path is returned and saved for further calls. Otherwise,
+ * checks the home directory first, then the system directory first, always
+ * taking into account the XDG Base Directory Specification ($XDG_CONFIG_HOME,
+ * $XDG_CONFIG_DIRS)
+ *
+ */
+char *get_config_path(const char *override_configpath, bool use_system_paths);
+
+#if !defined(__sun)
 /**
  * Emulates mkdir -p (creates any missing folders)
  *
  */
-bool mkdirp(const char *path);
+int mkdirp(const char *path, mode_t mode);
+#endif
index 8736a50cf46559dead850d0edb66a8e3717eab2a..f8b7da20a6f7ac58144f8cc53e0a891351a015bc 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2014 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * load_layout.c: Restore (parts of) the layout, for example after an inplace
  *                restart.
index a5086dbee5ae78fa3327017763882d3bbdac39e5..e7fc8acba1341c182078a83a9657f789d0354ef8 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * log.c: Logging functions.
  *
index 18c6e374472f6b199588b966b95580743894fa6a..1ce539540be033a0eadbf3c5fd1c00fa09311a79 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * main.c: Initialization, main loop
  *
index bd1a14f76ec4184ad559356bcf6ade3a113e80d3..b0c4238155969e206f9874c6dc41f410f04636a7 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * manage.c: Initially managing new windows (or existing ones on restart).
  *
index 09975cac20fe644162930e08ea25742e1d12644f..dbd9bb795b95975d173de9bfcc4f5745ec3f766b 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * A "match" is a data structure which acts like a mask or expression to match
  * certain windows or not. For example, when using commands, you can specify a
index 939665ec41f09b73cb2be7cce20dc598a5dfb5fe..7debcf28e397c8b06877853de7f9e9cff51ed9ba 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * move.c: Moving containers into some direction.
  *
index 6514c477fc7f5ca01b5e9e6daec629eb2877743d..8299a19bc1ddefd6fb7f37cec5f3bfbd4635e499 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * output.c: Output (monitor) related functions.
  *
@@ -21,3 +21,10 @@ Con *output_get_content(Con *output);
  *
  */
 Output *get_output_from_string(Output *current_output, const char *output_str);
+
+/**
+ * Iterates over all outputs and pushes sticky windows to the currently visible
+ * workspace on that output.
+ *
+ */
+void output_push_sticky_windows(Con *to_focus);
index 43f7efe826ae26820842aa5a67fc10ea67a614cc..998f0d597e8839237da1885375fec363a898abbc 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * For more information on RandR, please see the X.org RandR specification at
  * http://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt
@@ -29,13 +29,6 @@ typedef enum {
  */
 void randr_init(int *event_base);
 
-/**
- * Disables RandR support by creating exactly one output with the size of the
- * X11 screen.
- *
- */
-void disable_randr(xcb_connection_t *conn);
-
 /**
  * Initializes a CT_OUTPUT Con (searches existing ones from inplace restart
  * before) to use for the given Output.
@@ -120,3 +113,9 @@ Output *get_output_next(direction_t direction, Output *current, output_close_far
  *
  */
 Output *get_output_next_wrap(direction_t direction, Output *current);
+
+/*
+ * Creates an output covering the root window.
+ *
+ */
+Output *create_root_output(xcb_connection_t *conn);
index e11d377e453ae7d8ddb7db83ce383d62414de226..2e2f22fd45f142e87af6079f1d2d9afbb00cdaad 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * regex.c: Interface to libPCRE (perl compatible regular expressions).
  *
index 1794b513894ea0c8583ff15400e1a66f679f39db..717459e98268dd8d743134e11820f62b33c78d5e 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * render.c: Renders (determines position/sizes) the layout tree, updating the
  *           various rects. Needs to be pushed to X11 (see x.c) to be visible.
index 5c795046c5fdd27c3269c1eab3aa01555145a6d5..945678d98b6d518c31e2ab597d7e2bcc35255645 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * resize.c: Interactive resizing.
  *
index 3f0229d3d8eee7ed9216662d8c9ac5cfea335159..f952834c564939c557752e11f813effc5f32e5dc 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * restore_layout.c: Everything for restored containers that is not pure state
  *                   parsing (which can be found in load_layout.c).
index 1aca73bda56ce466932628b0a5466b363fa3a41d..b385550cdbaf144274d3e4557445e9eaa9a550e2 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * scratchpad.c: Scratchpad functions (TODO: more description)
  *
index 5af697e708ff90283585893f5a81df418666282d..231681cab591d012bef2ca9075eb206c7bb6f923 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * The format of the shmlog data structure which i3 development versions use by
  * default (ringbuffer for storing the debug log).
index 184db73bc7d7d2890668aed32eba46510f252d96..56680bc48a2aaa84e3b252517ff415c2eb7ad5ce 100644 (file)
@@ -2,8 +2,8 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
- * © 2009-2010 Jan-Erik Rediger
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Jan-Erik Rediger
  *
  * sighandler.c: Interactive crash dialog upon SIGSEGV/SIGABRT/SIGFPE (offers
  *               to restart inplace).
index cb784913b303efc859a6894750673b4a596288ee..7d5d2a39644abf350b8c3191b48a9490654bfe8c 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * startup.c: Startup notification code. Ensures a startup notification context
  *            is setup when launching applications. We store the current
index b3c2a515131eef95ef964ab94ffce7a3057ee803..af3309e9092295c4aec4d55d59308970c98d8e07 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * tree.c: Everything that primarily modifies the layout tree data structure.
  *
index dec681168b49ee851995dd3d88444405d598e257..01f732caed9aab3eae9571ecfc34fb58d8921ea8 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * util.c: Utility functions, which can be useful everywhere within i3 (see
  *         also libi3).
@@ -106,14 +106,6 @@ void exec_i3_utility(char *name, char *argv[]);
 void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie,
                  char *err_message);
 
-/**
- * This function resolves ~ in pathnames.
- * It may resolve wildcards in the first part of the path, but if no match
- * or multiple matches are found, it just returns a copy of path as given.
- *
- */
-char *resolve_tilde(const char *path);
-
 /**
  * Checks if the given path exists by calling stat().
  *
index 480cee18844362adb707ea3a447828bca216419a..395c9883d702c983a5c79bb5ea9f4407b38b1572 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * window.c: Updates window attributes (X11 hints/properties).
  *
@@ -56,6 +56,12 @@ void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop);
  */
 void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
 
+/**
+ * Updates the _NET_WM_WINDOW_TYPE property.
+ *
+ */
+void window_update_type(i3Window *window, xcb_get_property_reply_t *reply);
+
 /**
  * Updates the WM_HINTS (we only care about the input focus handling part).
  *
@@ -75,3 +81,10 @@ 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);
index d0f801e0039240195496c6d3245f0ff9540e43d9..21ab18d69d5043e663312ce1141495410db0a88c 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * workspace.c: Modifying workspaces, accessing them, moving containers to
  *              workspaces.
  */
 Con *workspace_get(const char *num, bool *created);
 
-/*
+/**
+ * Extracts workspace names from keybindings (e.g. “web” from “bindsym $mod+1
+ * workspace web”), so that when an output needs a workspace, i3 can start with
+ * the first configured one. Needs to be called before reorder_bindings() so
+ * that the config-file order is used, not the i3-internal order.
+ *
+ */
+void extract_workspace_names_from_bindings(void);
+
+/**
  * Returns a pointer to a new workspace in the given output. The workspace
  * is created attached to the tree hierarchy through the given content
  * container.
index 07f8a725cfb3a8e3c52eefbfd94246b28951bd6d..b6855cb328ccc67a52265b7c6dfd95fa200d0466 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * x.c: Interface to X11, transfers our in-memory state to X11 (see also
  *      render.c). Basically a big state machine.
index 9f4ea91fd8f29733ff4549ad2d8fcaa9af386ece..2c87a19c688499aaa977eadbb41efe3e3c210b7f 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * xcb.c: Helper functions for easier usage of XCB
  *
@@ -108,6 +108,16 @@ void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window);
  */
 void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r);
 
+/**
+ * Returns the first supported _NET_WM_WINDOW_TYPE atom.
+ *
+ */
+xcb_atom_t xcb_get_preferred_window_type(xcb_get_property_reply_t *reply);
+
+/**
+ * Returns true if the given reply contains the given data.
+ *
+ */
 bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom);
 
 /**
@@ -135,3 +145,18 @@ uint16_t get_visual_depth(xcb_visualid_t visual_id);
  *
  */
 xcb_visualid_t get_visualid_by_depth(uint16_t depth);
+
+/**
+ * Add an atom to a list of atoms the given property defines.
+ * This is useful, for example, for manipulating _NET_WM_STATE.
+ *
+ */
+void xcb_add_property_atom(xcb_connection_t *conn, xcb_window_t window, xcb_atom_t property, xcb_atom_t atom);
+
+/**
+ * Remove an atom from a list of atoms the given property defines without
+ * removing any other potentially set atoms.  This is useful, for example, for
+ * manipulating _NET_WM_STATE.
+ *
+ */
+void xcb_remove_property_atom(xcb_connection_t *conn, xcb_window_t window, xcb_atom_t property, xcb_atom_t atom);
index 89c2c457887ed0d660cc6518f5a0efae405fbac9..066d834aa4b39e3cd9f78d14da385e698d19c09d 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * xcb_compat.h: uses #define to create aliases for xcb functions which got
  *               renamed. Makes the code work with >= 0.3.8 xcb-util and
index bb329e4c88ca06cfb90ed1da5c63a3d0a560fb52..e0ee98138cfb2c1855037ab03b155683f13a1368 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * xcursor.c: libXcursor support for themed cursors.
  *
index 46c2a63558ccc12715ead04d1e91d0732b94e9d6..936b9653b98ae6b3eb2b2220f24657de22505dad 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * This is LEGACY code (we support RandR, which can do much more than
  * Xinerama), but necessary for the poor users of the nVidia binary
index e8422aabd2d051294d3fedcd007b94658d0722b5..93c63d8f7b641a14da94d61eb10841454721a97a 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * yajl_utils.h
  *
index 6f58d57a409c8eaa87728513c77bbd6a745bf674..a347b08f089f47ca758d02b526fc8a2d5c88b905 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2014 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
 #include "libi3.h"
index 2c0f5771c1a9db75b5eb40027929c68f47fe58d3..2c4d4559848ffd23d10632ebea4219459117ef69 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
 #include <stdlib.h>
@@ -23,7 +23,7 @@ void fake_configure_notify(xcb_connection_t *conn, xcb_rectangle_t r, xcb_window
     /* Every X11 event is 32 bytes long. Therefore, XCB will copy 32 bytes.
      * In order to properly initialize these bytes, we allocate 32 bytes even
      * though we only need less for an xcb_configure_notify_event_t */
-    void *event = scalloc(32);
+    void *event = scalloc(32, 1);
     xcb_configure_notify_event_t *generated_event = event;
 
     generated_event->event = window;
index 847bc61beb30c2d474ef4ee5c96639305f4e91e3..b502b52c6390c57dc5e9774ba269f33bce738716 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
 #include <assert.h>
@@ -340,6 +340,18 @@ void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background
     }
 }
 
+/*
+ * Returns true if and only if the current font is a pango font.
+ *
+ */
+bool font_is_pango(void) {
+#if PANGO_SUPPORT
+    return savedFont->type == FONT_TYPE_PANGO;
+#else
+    return false;
+#endif
+}
+
 static int predict_text_width_xcb(const xcb_char2b_t *text, size_t text_len);
 
 static void draw_text_xcb(const xcb_char2b_t *text, size_t text_len, xcb_drawable_t drawable,
index b093594e10484d74677752756798062b5178719b..44ad295d5fc48378a3651dba78bfaea3949f5780 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
 #include <stdlib.h>
diff --git a/libi3/get_config_path.c b/libi3/get_config_path.c
new file mode 100644 (file)
index 0000000..bad75c4
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include "libi3.h"
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+/*
+ * Checks if the given path exists by calling stat().
+ *
+ */
+static bool path_exists(const char *path) {
+    struct stat buf;
+    return (stat(path, &buf) == 0);
+}
+
+/*
+ * Get the path of the first configuration file found. If override_configpath
+ * is specified, that path is returned and saved for further calls. Otherwise,
+ * checks the home directory first, then the system directory first, always
+ * taking into account the XDG Base Directory Specification ($XDG_CONFIG_HOME,
+ * $XDG_CONFIG_DIRS)
+ *
+ */
+char *get_config_path(const char *override_configpath, bool use_system_paths) {
+    char *xdg_config_home, *xdg_config_dirs, *config_path;
+
+    static const char *saved_configpath = NULL;
+
+    if (override_configpath != NULL) {
+        saved_configpath = override_configpath;
+        return sstrdup(saved_configpath);
+    }
+
+    if (saved_configpath != NULL)
+        return sstrdup(saved_configpath);
+
+    /* 1: check the traditional path under the home directory */
+    config_path = resolve_tilde("~/.i3/config");
+    if (path_exists(config_path))
+        return config_path;
+    free(config_path);
+
+    /* 2: check for $XDG_CONFIG_HOME/i3/config */
+    if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL)
+        xdg_config_home = "~/.config";
+
+    xdg_config_home = resolve_tilde(xdg_config_home);
+    sasprintf(&config_path, "%s/i3/config", xdg_config_home);
+    free(xdg_config_home);
+
+    if (path_exists(config_path))
+        return config_path;
+    free(config_path);
+
+    /* The below paths are considered system-level, and can be skipped if the
+     * caller only wants user-level configs. */
+    if (!use_system_paths)
+        return NULL;
+
+    /* 3: check the traditional path under /etc */
+    config_path = SYSCONFDIR "/i3/config";
+    if (path_exists(config_path))
+        return sstrdup(config_path);
+
+    /* 4: check for $XDG_CONFIG_DIRS/i3/config */
+    if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL)
+        xdg_config_dirs = "/etc/xdg";
+
+    char *buf = sstrdup(xdg_config_dirs);
+    char *tok = strtok(buf, ":");
+    while (tok != NULL) {
+        tok = resolve_tilde(tok);
+        sasprintf(&config_path, "%s/i3/config", tok);
+        free(tok);
+        if (path_exists(config_path)) {
+            free(buf);
+            return config_path;
+        }
+        free(config_path);
+        tok = strtok(NULL, ":");
+    }
+    free(buf);
+
+    return NULL;
+}
index fc9b3014d0f1a98c6d57349f744287efa8c474e3..ef8f23bc25a1915385647f96656a9d4586cb6494 100644 (file)
@@ -73,7 +73,7 @@ char *get_exe_path(const char *argv0) {
         }
         sasprintf(&path, ":%s", tmp);
     } else {
-        path = strdup(path);
+        path = sstrdup(path);
     }
     const char *component;
     char *str = path;
index cf8cbe3863654c150a0b74800e3fcbd1f71e9d14..3b6976ad6b0d1669149fed67533aa53d1b1d4b7c 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
 #include <stdint.h>
index 941d4439e75177161b09d3fdca06597a6aa8c24c..a3dee9cd3ef84dddae60ac6d44cd2494ea3a4a93 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
 #include <assert.h>
index d11722f000ec54fbebc6d34b8a52d0d2c8ac98c6..ccf266db641aa673276e83b785b331d9c7f4525c 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
 #include "libi3.h"
index 69629daa6a657b6d98abcf58824935d0dc1264fa..f493b4f11e6a6d118951afc058b9632c6621cd5b 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
 #include <sys/types.h>
index cb4edd6732b956f91e24c1738b796d5263a47c97..0ef4fced4f27b1fd1bf415727efb6f1bbdd14796 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
 #include <string.h>
index 80709ed3b255c638933e0644841accecc7eb4318..a9ba31653e07cacc1b86ed030075eccccc509a52 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
 #include <string.h>
index 95c31b59d5634bc6a1026195aaabad7d1b68942d..c64b80a3352b97de1ac5e1ec6f21558812e668b7 100644 (file)
@@ -8,24 +8,26 @@
  * Emulates mkdir -p (creates any missing folders)
  *
  */
-bool mkdirp(const char *path) {
-    if (mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0)
-        return true;
+
+#if !defined(__sun)
+int mkdirp(const char *path, mode_t mode) {
+    if (mkdir(path, mode) == 0)
+        return 0;
     if (errno == EEXIST) {
         struct stat st;
         /* Check that the named file actually is a directory. */
         if (stat(path, &st)) {
             ELOG("stat(%s) failed: %s\n", path, strerror(errno));
-            return false;
+            return -1;
         }
         if (!S_ISDIR(st.st_mode)) {
             ELOG("mkdir(%s) failed: %s\n", path, strerror(ENOTDIR));
-            return false;
+            return -1;
         }
-        return true;
+        return 0;
     } else if (errno != ENOENT) {
         ELOG("mkdir(%s) failed: %s\n", path, strerror(errno));
-        return false;
+        return -1;
     }
     char *copy = sstrdup(path);
     /* strip trailing slashes, if any */
@@ -38,13 +40,14 @@ bool mkdirp(const char *path) {
             free(copy);
             copy = NULL;
         }
-        return false;
+        return -1;
     }
     *sep = '\0';
-    bool result = false;
-    if (mkdirp(copy))
-        result = mkdirp(path);
+    int result = -1;
+    if (mkdirp(copy, mode) == 0)
+        result = mkdirp(path, mode);
     free(copy);
 
     return result;
 }
+#endif
diff --git a/libi3/resolve_tilde.c b/libi3/resolve_tilde.c
new file mode 100644 (file)
index 0000000..7a4fb79
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+
+#include "libi3.h"
+#include <err.h>
+#include <glob.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * This function resolves ~ in pathnames.
+ * It may resolve wildcards in the first part of the path, but if no match
+ * or multiple matches are found, it just returns a copy of path as given.
+ *
+ */
+char *resolve_tilde(const char *path) {
+    static glob_t globbuf;
+    char *head, *tail, *result;
+
+    tail = strchr(path, '/');
+    head = sstrndup(path, tail ? (size_t)(tail - path) : strlen(path));
+
+    int res = glob(head, GLOB_TILDE, NULL, &globbuf);
+    free(head);
+    /* no match, or many wildcard matches are bad */
+    if (res == GLOB_NOMATCH || globbuf.gl_pathc != 1)
+        result = sstrdup(path);
+    else if (res != 0) {
+        err(EXIT_FAILURE, "glob() failed");
+    } else {
+        head = globbuf.gl_pathv[0];
+        result = scalloc(strlen(head) + (tail ? strlen(tail) : 0) + 1, 1);
+        strncpy(result, head, strlen(head));
+        if (tail)
+            strncat(result, tail, strlen(tail));
+    }
+    globfree(&globbuf);
+
+    return result;
+}
index f70c31a78fd5c7fed10a3ff556e8dc1a6cc9b90e..d91f1e15b4ba10f21cc122c3798746354bae9476 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
 #include <stdio.h>
@@ -80,18 +80,10 @@ char *root_atom_contents(const char *atomname, xcb_connection_t *provided_conn,
     if (prop_reply->type == XCB_ATOM_CARDINAL) {
         /* We treat a CARDINAL as a >= 32-bit unsigned int. The only CARDINAL
          * we query is I3_PID, which is 32-bit. */
-        if (asprintf(&content, "%u", *((unsigned int *)xcb_get_property_value(prop_reply))) == -1) {
-            free(atom_reply);
-            free(prop_reply);
-            return NULL;
-        }
+        sasprintf(&content, "%u", *((unsigned int *)xcb_get_property_value(prop_reply)));
     } else {
-        if (asprintf(&content, "%.*s", xcb_get_property_value_length(prop_reply),
-                     (char *)xcb_get_property_value(prop_reply)) == -1) {
-            free(atom_reply);
-            free(prop_reply);
-            return NULL;
-        }
+        sasprintf(&content, "%.*s", xcb_get_property_value_length(prop_reply),
+                  (char *)xcb_get_property_value(prop_reply));
     }
     if (provided_conn == NULL)
         xcb_disconnect(conn);
index db9b6b4a42efbf287e06466295ad0e2d2e30e2e5..0a233a1271f996272a50751c964e5ecf94b17015 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
 #include <string.h>
@@ -27,10 +27,10 @@ void *smalloc(size_t size) {
     return result;
 }
 
-void *scalloc(size_t size) {
-    void *result = calloc(size, 1);
+void *scalloc(size_t num, size_t size) {
+    void *result = calloc(num, size);
     if (result == NULL)
-        err(EXIT_FAILURE, "calloc(%zd)", size);
+        err(EXIT_FAILURE, "calloc(%zd, %zd)", num, size);
     return result;
 }
 
@@ -48,6 +48,13 @@ char *sstrdup(const char *str) {
     return result;
 }
 
+char *sstrndup(const char *str, size_t size) {
+    char *result = strndup(str, size);
+    if (result == NULL)
+        err(EXIT_FAILURE, "strndup()");
+    return result;
+}
+
 int sasprintf(char **strp, const char *fmt, ...) {
     va_list args;
     int result;
index e6297f9eed89b0282a2b104fab360921eb723b07..7741fde064f0ade5ab81583d802c3e593f7cdcc1 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * string.c: Define an i3String type to automagically handle UTF-8/UCS-2
  *           conversions. Some font backends need UCS-2 (X core fonts),
 #include <stdlib.h>
 #include <string.h>
 
+#if PANGO_SUPPORT
+#include <glib.h>
+#endif
+
 #include "libi3.h"
 
 struct _i3String {
@@ -29,7 +33,7 @@ struct _i3String {
  *
  */
 i3String *i3string_from_utf8(const char *from_utf8) {
-    i3String *str = scalloc(sizeof(i3String));
+    i3String *str = scalloc(1, sizeof(i3String));
 
     /* Get the text */
     str->utf8 = sstrdup(from_utf8);
@@ -60,10 +64,10 @@ i3String *i3string_from_markup(const char *from_markup) {
  *
  */
 i3String *i3string_from_utf8_with_length(const char *from_utf8, size_t num_bytes) {
-    i3String *str = scalloc(sizeof(i3String));
+    i3String *str = scalloc(1, sizeof(i3String));
 
     /* Copy the actual text to our i3String */
-    str->utf8 = scalloc(sizeof(char) * (num_bytes + 1));
+    str->utf8 = scalloc(num_bytes + 1, 1);
     strncpy(str->utf8, from_utf8, num_bytes);
     str->utf8[num_bytes] = '\0';
 
@@ -93,12 +97,11 @@ i3String *i3string_from_markup_with_length(const char *from_markup, size_t num_b
  *
  */
 i3String *i3string_from_ucs2(const xcb_char2b_t *from_ucs2, size_t num_glyphs) {
-    i3String *str = scalloc(sizeof(i3String));
+    i3String *str = scalloc(1, sizeof(i3String));
 
     /* Copy the actual text to our i3String */
-    size_t num_bytes = num_glyphs * sizeof(xcb_char2b_t);
-    str->ucs2 = scalloc(num_bytes);
-    memcpy(str->ucs2, from_ucs2, num_bytes);
+    str->ucs2 = scalloc(num_glyphs, sizeof(xcb_char2b_t));
+    memcpy(str->ucs2, from_ucs2, num_glyphs * sizeof(xcb_char2b_t));
 
     /* Store the length */
     str->num_glyphs = num_glyphs;
@@ -185,6 +188,21 @@ void i3string_set_markup(i3String *str, bool is_markup) {
     str->is_markup = is_markup;
 }
 
+/*
+ * Escape pango markup characters in the given string.
+ */
+i3String *i3string_escape_markup(i3String *str) {
+#if PANGO_SUPPORT
+    const char *text = i3string_as_utf8(str);
+    char *escaped = g_markup_escape_text(text, -1);
+    i3String *result = i3string_from_utf8(escaped);
+    free(escaped);
+    return result;
+#else
+    return str;
+#endif
+}
+
 /*
  * Returns the number of glyphs in an i3String.
  *
index eec1a0edcfd9527bc39445e6a21c1acb8a8c1222..c4032dc5816130b01dd1689b819a7914219fc1fa 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
 #include <sys/types.h>
index 9a8c478f0c7213d3f1c19ba4c370623e5015e389..b583e32cd6a4f9ba667748d5bd423d994bb595c9 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
 #include <err.h>
@@ -23,8 +23,8 @@ static iconv_t ucs2_conversion_descriptor = (iconv_t)-1;
  */
 char *convert_ucs2_to_utf8(xcb_char2b_t *text, size_t num_glyphs) {
     /* Allocate the output buffer (UTF-8 is at most 4 bytes per glyph) */
-    size_t buffer_size = num_glyphs * 4 * sizeof(char) + 1;
-    char *buffer = scalloc(buffer_size);
+    size_t buffer_size = num_glyphs * 4 + 1;
+    char *buffer = scalloc(buffer_size, 1);
 
     /* We need to use an additional pointer, because iconv() modifies it */
     char *output = buffer;
index eda71d6e2c1e1041c9019dc1f3d5033529416efe..6cbcebe8d7f34d5d90a537359a01f2fdd9fdeefa 100644 (file)
@@ -7,7 +7,7 @@ template::[header-declarations]
 <refentrytitle>{mantitle}</refentrytitle>
 <manvolnum>{manvolnum}</manvolnum>
 <refmiscinfo class="source">i3</refmiscinfo>
-<refmiscinfo class="version">4.10.3</refmiscinfo>
+<refmiscinfo class="version">4.11</refmiscinfo>
 <refmiscinfo class="manual">i3 Manual</refmiscinfo>
 </refmeta>
 <refnamediv>
index 315a9218dd5930695e540506ce11e0cd30cabbf4..5e2bfd8f0b0296373c6be9bba040e1f3a469669a 100644 (file)
@@ -1,7 +1,7 @@
 # vim:ts=2:sw=2:expandtab
 #
 # i3 - an improved dynamic tiling window manager
-# © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+# © 2009 Michael Stapelberg and contributors (see also: LICENSE)
 #
 # parser-specs/commands.spec: Specification file for generate-command-parser.pl
 # which will generate the appropriate header files for our C parser.
@@ -29,6 +29,7 @@ state INITIAL:
   'kill' -> KILL
   'open' -> call cmd_open()
   'fullscreen' -> FULLSCREEN
+  'sticky' -> STICKY
   'split' -> SPLIT
   'floating' -> FLOATING
   'mark' -> MARK
@@ -37,18 +38,21 @@ state INITIAL:
   'rename' -> RENAME
   'nop' -> NOP
   'scratchpad' -> SCRATCHPAD
+  'title_format' -> TITLE_FORMAT
   'mode' -> MODE
   'bar' -> BAR
 
 state CRITERIA:
-  ctype = 'class' -> CRITERION
-  ctype = 'instance' -> CRITERION
+  ctype = 'class'       -> CRITERION
+  ctype = 'instance'    -> CRITERION
   ctype = 'window_role' -> CRITERION
-  ctype = 'con_id' -> CRITERION
-  ctype = 'id' -> CRITERION
-  ctype = 'con_mark' -> CRITERION
-  ctype = 'title' -> CRITERION
-  ctype = 'urgent' -> CRITERION
+  ctype = 'con_id'      -> CRITERION
+  ctype = 'id'          -> CRITERION
+  ctype = 'window_type' -> CRITERION
+  ctype = 'con_mark'    -> CRITERION
+  ctype = 'title'       -> CRITERION
+  ctype = 'urgent'      -> CRITERION
+  ctype = 'workspace'   -> CRITERION
   ']' -> call cmd_criteria_match_windows(); INITIAL
 
 state CRITERION:
@@ -76,7 +80,8 @@ state DEBUGLOG:
   argument = 'toggle', 'on', 'off'
     -> call cmd_debuglog($argument)
 
-# border normal|none|1pixel|toggle|1pixel
+# border normal|pixel [<n>]
+# border none|1pixel|toggle
 state BORDER:
   border_style = 'normal', 'pixel'
     -> BORDER_WIDTH
@@ -179,6 +184,11 @@ state FULLSCREEN_COMPAT:
   end
       -> call cmd_fullscreen("toggle", "output")
 
+# sticky enable|disable|toggle
+state STICKY:
+  action = 'enable', 'disable', 'toggle'
+      -> call cmd_sticky($action)
+
 # split v|h|vertical|horizontal
 state SPLIT:
   direction = 'horizontal', 'vertical', 'v', 'h'
@@ -189,10 +199,12 @@ state FLOATING:
   floating = 'enable', 'disable', 'toggle'
       -> call cmd_floating($floating)
 
-# mark <mark>
+# mark [--toggle] <mark>
 state MARK:
+  toggle = '--toggle'
+      ->
   mark = string
-      -> call cmd_mark($mark)
+      -> call cmd_mark($mark, $toggle)
 
 # unmark [mark]
 state UNMARK:
@@ -205,6 +217,8 @@ state UNMARK:
 state RESIZE:
   way = 'grow', 'shrink'
       -> RESIZE_DIRECTION
+  set = 'set'
+      -> RESIZE_SET
 
 state RESIZE_DIRECTION:
   direction = 'up', 'down', 'left', 'right', 'width', 'height'
@@ -232,6 +246,20 @@ state RESIZE_TILING_FINAL:
   'ppt', end
       -> call cmd_resize($way, $direction, $resize_px, $resize_ppt)
 
+state RESIZE_SET:
+  width = word
+      -> RESIZE_WIDTH
+
+state RESIZE_WIDTH:
+  'px'
+      ->
+  height = word
+      -> RESIZE_HEIGHT
+
+state RESIZE_HEIGHT:
+  'px', end
+      -> call cmd_size($width, $height)
+
 # rename workspace <name> to <name>
 # rename workspace to <name>
 state RENAME:
@@ -263,10 +291,12 @@ state RENAME_WORKSPACE_NEW_NAME:
 # move <direction> [<pixels> [px]]
 # move [window|container] [to] workspace [<str>|next|prev|next_on_output|prev_on_output|current]
 # move [window|container] [to] output <str>
+# move [window|container] [to] mark <str>
 # move [window|container] [to] scratchpad
 # move workspace to [output] <str>
 # move scratchpad
 # move [window|container] [to] [absolute] position [ [<pixels> [px] <pixels> [px]] | center ]
+# move [window|container] [to] position mouse|cursor|pointer
 state MOVE:
   'window'
       ->
@@ -278,6 +308,8 @@ state MOVE:
       -> MOVE_WORKSPACE
   'output'
       -> MOVE_TO_OUTPUT
+  'mark'
+      -> MOVE_TO_MARK
   'scratchpad'
       -> call cmd_move_scratchpad()
   direction = 'left', 'right', 'up', 'down'
@@ -319,6 +351,10 @@ state MOVE_TO_OUTPUT:
   output = string
       -> call cmd_move_con_to_output($output)
 
+state MOVE_TO_MARK:
+  mark = string
+      -> call cmd_move_con_to_mark($mark)
+
 state MOVE_WORKSPACE_TO_OUTPUT:
   'output'
       ->
@@ -332,6 +368,8 @@ state MOVE_TO_ABSOLUTE_POSITION:
 state MOVE_TO_POSITION:
   'center'
       -> call cmd_move_window_to_center($method)
+  'mouse', 'cursor', 'pointer'
+      -> call cmd_move_window_to_mouse()
   coord_x = word
       -> MOVE_TO_POSITION_X
 
@@ -360,6 +398,10 @@ state SCRATCHPAD:
   'show'
       -> call cmd_scratchpad_show()
 
+state TITLE_FORMAT:
+  format = string
+      -> call cmd_title_format($format)
+
 # bar (hidden_state hide|show|toggle)|(mode dock|hide|invisible|toggle) [<bar_id>]
 state BAR:
   bar_type = 'hidden_state'
index 25be5cf107e2bef2eebdba243980ee04085c9efa..b9542c8c3c9e14cee6c8fa690b43bc8905ca6e3e 100644 (file)
@@ -1,7 +1,7 @@
 # vim:ts=2:sw=2:expandtab
 #
 # i3 - an improved dynamic tiling window manager
-# © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+# © 2009 Michael Stapelberg and contributors (see also: LICENSE)
 #
 # parser-specs/config.spec: Specification file for generate-command-parser.pl
 # which will generate the appropriate header files for our C parser.
@@ -31,6 +31,7 @@ state INITIAL:
   'hide_edge_borders'                      -> HIDE_EDGE_BORDERS
   'for_window'                             -> FOR_WINDOW
   'assign'                                 -> ASSIGN
+  'no_focus'                               -> NO_FOCUS
   'focus_follows_mouse'                    -> FOCUS_FOLLOWS_MOUSE
   'mouse_warping'                          -> MOUSE_WARPING
   'force_focus_wrapping'                   -> FORCE_FOCUS_WRAPPING
@@ -38,6 +39,8 @@ state INITIAL:
   'workspace_auto_back_and_forth'          -> WORKSPACE_BACK_AND_FORTH
   'fake_outputs', 'fake-outputs'           -> FAKE_OUTPUTS
   'force_display_urgency_hint'             -> FORCE_DISPLAY_URGENCY_HINT
+  'focus_on_window_activation'             -> FOCUS_ON_WINDOW_ACTIVATION
+  'show_marks'                             -> SHOW_MARKS
   'workspace'                              -> WORKSPACE
   'ipc_socket', 'ipc-socket'               -> IPC_SOCKET
   'restart_state'                          -> RESTART_STATE
@@ -100,8 +103,6 @@ state WORKSPACE_LAYOUT:
 
 # new_window <normal|1pixel|none>
 # new_float <normal|1pixel|none>
-# TODO: new_float is not in the userguide yet
-# TODO: pixel is not in the userguide yet
 state NEW_WINDOW:
   border = 'normal', 'pixel'
       -> NEW_WINDOW_PIXELS
@@ -148,6 +149,15 @@ state ASSIGN_WORKSPACE:
   workspace = string
       -> call cfg_assign($workspace)
 
+# no_focus <criteria>
+state NO_FOCUS:
+  '['
+      -> call cfg_criteria_init(NO_FOCUS_END); CRITERIA
+
+state NO_FOCUS_END:
+  end
+      -> call cfg_no_focus()
+
 # Criteria: Used by for_window and assign.
 state CRITERIA:
   ctype = 'class'       -> CRITERION
@@ -155,9 +165,11 @@ state CRITERIA:
   ctype = 'window_role' -> CRITERION
   ctype = 'con_id'      -> CRITERION
   ctype = 'id'          -> CRITERION
+  ctype = 'window_type' -> CRITERION
   ctype = 'con_mark'    -> CRITERION
   ctype = 'title'       -> CRITERION
   ctype = 'urgent'      -> CRITERION
+  ctype = 'workspace'   -> CRITERION
   ']'
       -> call cfg_criteria_pop_state()
 
@@ -204,12 +216,22 @@ state FORCE_DISPLAY_URGENCY_HINT:
   duration_ms = number
       -> FORCE_DISPLAY_URGENCY_HINT_MS
 
+# show_marks
+state SHOW_MARKS:
+  value = word
+      -> call cfg_show_marks($value)
+
 state FORCE_DISPLAY_URGENCY_HINT_MS:
   'ms'
       ->
   end
       -> call cfg_force_display_urgency_hint(&duration_ms)
 
+# focus_on_window_activation <smart|urgent|focus|none>
+state FOCUS_ON_WINDOW_ACTIVATION:
+  mode = word
+      -> call cfg_focus_on_window_activation($mode)
+
 # workspace <workspace> output <output>
 state WORKSPACE:
   workspace = word
@@ -278,9 +300,11 @@ state FONT:
 state BINDING:
   release = '--release'
       ->
+  border = '--border'
+      ->
   whole_window = '--whole-window'
       ->
-  modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', '$mod'
+  modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', 'Group1', 'Group2', 'Group3', 'Group4', '$mod'
       ->
   '+'
       ->
@@ -290,10 +314,12 @@ state BINDING:
 state BINDCOMMAND:
   release = '--release'
       ->
+  border = '--border'
+      ->
   whole_window = '--whole-window'
       ->
   command = string
-      -> call cfg_binding($bindtype, $modifiers, $key, $release, $whole_window, $command)
+      -> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $command)
 
 ################################################################################
 # Mode configuration
@@ -327,7 +353,11 @@ state MODE_IGNORE_LINE:
 state MODE_BINDING:
   release = '--release'
       ->
-  modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', '$mod'
+  border = '--border'
+      ->
+  whole_window = '--whole-window'
+      ->
+  modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', 'Group1', 'Group2', 'Group3', 'Group4', '$mod'
       ->
   '+'
       ->
@@ -337,10 +367,12 @@ state MODE_BINDING:
 state MODE_BINDCOMMAND:
   release = '--release'
       ->
+  border = '--border'
+      ->
   whole_window = '--whole-window'
       ->
   command = string
-      -> call cfg_mode_binding($bindtype, $modifiers, $key, $release, $whole_window, $command); MODE
+      -> call cfg_mode_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $command); MODE
 
 ################################################################################
 # Bar configuration (i3bar)
@@ -350,7 +382,7 @@ state BARBRACE:
   end
       ->
   '{'
-      -> BAR
+      -> call cfg_bar_start(); BAR
 
 state BAR:
   end ->
@@ -366,9 +398,11 @@ state BAR:
   'modifier'               -> BAR_MODIFIER
   'wheel_up_cmd'           -> BAR_WHEEL_UP_CMD
   'wheel_down_cmd'         -> BAR_WHEEL_DOWN_CMD
+  'bindsym'                -> BAR_BINDSYM
   'position'               -> BAR_POSITION
   'output'                 -> BAR_OUTPUT
   'tray_output'            -> BAR_TRAY_OUTPUT
+  'tray_padding'           -> BAR_TRAY_PADDING
   'font'                   -> BAR_FONT
   'separator_symbol'       -> BAR_SEPARATOR_SYMBOL
   'binding_mode_indicator' -> BAR_BINDING_MODE_INDICATOR
@@ -420,6 +454,14 @@ state BAR_WHEEL_DOWN_CMD:
   command = string
       -> call cfg_bar_wheel_down_cmd($command); BAR
 
+state BAR_BINDSYM:
+  button = word
+      -> BAR_BINDSYM_COMMAND
+
+state BAR_BINDSYM_COMMAND:
+  command = string
+      -> call cfg_bar_bindsym($button, $command); BAR
+
 state BAR_POSITION:
   position = 'top', 'bottom'
       -> call cfg_bar_position($position); BAR
@@ -432,6 +474,16 @@ state BAR_TRAY_OUTPUT:
   output = word
       -> call cfg_bar_tray_output($output); BAR
 
+state BAR_TRAY_PADDING:
+  padding_px = number
+      -> BAR_TRAY_PADDING_PX
+
+state BAR_TRAY_PADDING_PX:
+  'px'
+      ->
+  end
+      -> call cfg_bar_tray_padding(&padding_px); BAR
+
 state BAR_FONT:
   font = string
       -> call cfg_bar_font($font); BAR
@@ -468,7 +520,7 @@ state BAR_COLORS:
   'set' -> BAR_COLORS_IGNORE_LINE
   colorclass = 'background', 'statusline', 'separator'
       -> BAR_COLORS_SINGLE
-  colorclass = 'focused_workspace', 'active_workspace', 'inactive_workspace', 'urgent_workspace'
+  colorclass = 'focused_workspace', 'active_workspace', 'inactive_workspace', 'urgent_workspace', 'binding_mode'
       -> BAR_COLORS_BORDER
   '}'
       -> BAR
index 646034a69e1b920256b7ea280282a3c05b08b516..33b8dcd63f3a799ba6203a330d553bb6779be0a3 100755 (executable)
@@ -1,9 +1,9 @@
 #!/bin/zsh
 # This script is used to prepare a new release of i3.
 
-export RELEASE_VERSION="4.10.1"
-export PREVIOUS_VERSION="4.10"
-export RELEASE_BRANCH="next"
+export RELEASE_VERSION="4.10.4"
+export PREVIOUS_VERSION="4.10.3"
+export RELEASE_BRANCH="master"
 
 if [ ! -e "../i3.github.io" ]
 then
@@ -12,6 +12,12 @@ then
        exit 1
 fi
 
+if ! (cd ../i3.github.io && git pull)
+then
+       echo "Could not update ../i3.github.io repository"
+       exit 1
+fi
+
 if [ ! -e "RELEASE-NOTES-${RELEASE_VERSION}" ]
 then
        echo "RELEASE-NOTES-${RELEASE_VERSION} not found."
@@ -74,14 +80,20 @@ if [ "${RELEASE_BRANCH}" = "master" ]; then
        git checkout master
        git merge --no-ff release-${RELEASE_VERSION} -m "Merge branch 'release-${RELEASE_VERSION}'"
        git checkout next
-       git merge --no-ff master -m "Merge branch 'master' into next"
+       git merge --no-ff -X ours master -m "Merge branch 'master' into next"
 else
        git checkout next
        git merge --no-ff release-${RELEASE_VERSION} -m "Merge branch 'release-${RELEASE_VERSION}'"
        git checkout master
-       git merge --no-ff next -m "Merge branch 'next' into master"
+       git merge --no-ff -X theirs next -m "Merge branch 'next' into master"
 fi
 
+git remote remove origin
+git remote add origin git@github.com:i3/i3.git
+git config --add remote.origin.push "+refs/tags/*:refs/tags/*"
+git config --add remote.origin.push "+refs/heads/next:refs/heads/next"
+git config --add remote.origin.push "+refs/heads/master:refs/heads/master"
+
 ################################################################################
 # Section 2: Debian packaging
 ################################################################################
@@ -92,6 +104,7 @@ mkdir debian
 # Copy over the changelog because we expect it to be locally modified in the
 # start directory.
 cp "${STARTDIR}/debian/changelog" i3/debian/changelog
+(cd i3 && git add debian/changelog && git commit -m 'Update debian/changelog')
 
 cat > ${TMPDIR}/Dockerfile <<EOT
 FROM debian:sid
@@ -129,6 +142,10 @@ debsign -k4AC8EE1D ${TMPDIR}/debian/*.changes
 # Section 3: website
 ################################################################################
 
+# Ensure we are in the correct branch for copying the docs.
+cd ${TMPDIR}/i3
+git checkout ${RELEASE_BRANCH}
+
 cd ${TMPDIR}
 git clone --quiet ${STARTDIR}/../i3.github.io
 cd i3.github.io
@@ -151,21 +168,45 @@ git commit -a -m "save docs for ${PREVIOUS_VERSION}"
 for i in $(find _docs -maxdepth 1 -and -type f -and \! -regex ".*\.\(html\|man\)$" -and \! -name "Makefile")
 do
        base="$(basename $i)"
-       [ -e "${STARTDIR}/docs/${base}" ] && cp "${STARTDIR}/docs/${base}" "_docs/${base}"
+       [ -e "${TMPDIR}/i3/docs/${base}" ] && cp "${TMPDIR}/i3/docs/${base}" "_docs/${base}"
 done
 
+sed -i "s,Verify you are using i3 ≥ .*,Verify you are using i3 ≥ ${RELEASE_VERSION},g" _docs/debugging
+
 (cd _docs && make)
 
 for i in $(find _docs -maxdepth 1 -and -type f -and \! -regex ".*\.\(html\|man\)$" -and \! -name "Makefile")
 do
        base="$(basename $i)"
-       [ -e "${STARTDIR}/docs/${base}" ] && cp "_docs/${base}.html" docs/
+       [ -e "${TMPDIR}/i3/docs/${base}" ] && cp "_docs/${base}.html" docs/
 done
 
 git commit -a -m "update docs for ${RELEASE_VERSION}"
 
+git remote remove origin
+git remote add origin git@github.com:i3/i3.github.io.git
+git config --add remote.origin.push "+refs/heads/master:refs/heads/master"
+
 ################################################################################
-# Section 4: final push instructions
+# Section 4: prepare release announcement email
+################################################################################
+
+cd ${TMPDIR}
+cat >email.txt <<EOT
+From: Michael Stapelberg <michael@i3wm.org>
+To: i3-announce@i3.zekjur.net
+Subject: i3 v${RELEASE_VERSION} released
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: 8bit
+
+Hi,
+
+I just released i3 v${RELEASE_VERSION}. Release notes follow:
+EOT
+cat ${TMPDIR}/i3/RELEASE-NOTES-${RELEASE_VERSION} >>email.txt
+
+################################################################################
+# Section 5: final push instructions
 ################################################################################
 
 echo "As a final sanity check, install the debian package and see whether i3 works."
@@ -174,20 +215,19 @@ echo "When satisfied, run:"
 echo "  cd ${TMPDIR}/i3"
 echo "  git checkout next"
 echo "  vi debian/changelog"
-# TODO: can we just set up the remote spec properly?
-echo "  git push git@github.com:i3/i3 next"
-echo "  git push git@github.com:i3/i3 master"
-echo "  git push git@github.com:i3/i3 --tags"
+echo "  git commit -a -m \"debian: update changelog\""
+echo "  git push"
 echo ""
 echo "  cd ${TMPDIR}/i3.github.io"
-# TODO: can we just set up the remote spec properly?
-echo "  git push git@github.com:i3/i3.github.io master"
+echo "  git push"
 echo ""
 echo "  cd ${TMPDIR}/debian"
 echo "  dput *.changes"
 echo ""
+echo "  cd ${TMPDIR}"
+echo "  sendmail -t < email.txt"
+echo ""
 echo "Announce on:"
 echo "  twitter"
 echo "  google+"
-echo "  mailing list"
 echo "  #i3 topic"
index 96834f648539d4bcb06c1778a4b6be65117845f4..babe890e9250c439419b45278405d9d6e0a8ed93 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * assignments.c: Assignments for specific windows (for_window).
  *
index 9a38ba9132ec4ce8fb47529e6b2d8f1d48688dc7..32aac05af3d605e5c9c0f6fa4b7442e6db726e23 100644 (file)
@@ -2,13 +2,17 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2014 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * bindings.c: Functions for configuring, finding and, running bindings.
  */
 #include "all.h"
 
 #include <xkbcommon/xkbcommon.h>
+#include <xkbcommon/xkbcommon-x11.h>
+
+static struct xkb_context *xkb_context;
+static struct xkb_keymap *xkb_keymap;
 
 pid_t command_error_nagbar_pid = -1;
 
@@ -33,9 +37,9 @@ static struct Mode *mode_from_name(const char *name) {
     }
 
     /* If the mode was not found, create a new one */
-    mode = scalloc(sizeof(struct Mode));
+    mode = scalloc(1, sizeof(struct Mode));
     mode->name = sstrdup(name);
-    mode->bindings = scalloc(sizeof(struct bindings_head));
+    mode->bindings = scalloc(1, sizeof(struct bindings_head));
     TAILQ_INIT(mode->bindings);
     SLIST_INSERT_HEAD(&modes, mode, modes);
 
@@ -49,10 +53,12 @@ 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 *whole_window, const char *command, const char *modename) {
-    Binding *new_binding = scalloc(sizeof(Binding));
+                           const char *release, const char *border, const char *whole_window,
+                           const char *command, const char *modename) {
+    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);
+    new_binding->border = (border != NULL);
     new_binding->whole_window = (whole_window != NULL);
     if (strcmp(bindtype, "bindsym") == 0) {
         new_binding->input_type = (strncasecmp(input_code, "button", (sizeof("button") - 1)) == 0
@@ -61,17 +67,29 @@ Binding *configure_binding(const char *bindtype, const char *modifiers, const ch
 
         new_binding->symbol = sstrdup(input_code);
     } else {
-        // TODO: strtol with proper error handling
-        new_binding->keycode = atoi(input_code);
+        char *endptr;
+        long keycode = strtol(input_code, &endptr, 10);
+        new_binding->keycode = keycode;
         new_binding->input_type = B_KEYBOARD;
-        if (new_binding->keycode == 0) {
+        if (keycode == LONG_MAX || keycode == LONG_MIN || keycode < 0 || *endptr != '\0' || endptr == input_code) {
             ELOG("Could not parse \"%s\" as an input code, ignoring this binding.\n", input_code);
             FREE(new_binding);
             return NULL;
         }
     }
-    new_binding->mods = modifiers_from_str(modifiers);
     new_binding->command = sstrdup(command);
+    new_binding->event_state_mask = event_state_from_str(modifiers);
+    int group_bits_set = 0;
+    if ((new_binding->event_state_mask >> 16) & I3_XKB_GROUP_MASK_1)
+        group_bits_set++;
+    if ((new_binding->event_state_mask >> 16) & I3_XKB_GROUP_MASK_2)
+        group_bits_set++;
+    if ((new_binding->event_state_mask >> 16) & I3_XKB_GROUP_MASK_3)
+        group_bits_set++;
+    if ((new_binding->event_state_mask >> 16) & I3_XKB_GROUP_MASK_4)
+        group_bits_set++;
+    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);
     TAILQ_INSERT_TAIL(mode->bindings, new_binding, bindings);
@@ -83,18 +101,23 @@ static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint
     if (bind->input_type != B_KEYBOARD)
         return;
 
-    DLOG("Grabbing %d with modifiers %d (with mod_mask_lock %d)\n", keycode, bind->mods, bind->mods | XCB_MOD_MASK_LOCK);
 /* Grab the key in all combinations */
 #define GRAB_KEY(modifier)                                                                       \
     do {                                                                                         \
         xcb_grab_key(conn, 0, root, modifier, keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); \
     } while (0)
-    int mods = bind->mods;
-    if ((bind->mods & BIND_MODE_SWITCH) != 0) {
-        mods &= ~BIND_MODE_SWITCH;
-        if (mods == 0)
-            mods = XCB_MOD_MASK_ANY;
-    }
+    int mods = bind->event_state_mask;
+    if (((mods >> 16) & I3_XKB_GROUP_MASK_1) && xkb_current_group != XCB_XKB_GROUP_1)
+        return;
+    if (((mods >> 16) & I3_XKB_GROUP_MASK_2) && xkb_current_group != XCB_XKB_GROUP_2)
+        return;
+    if (((mods >> 16) & I3_XKB_GROUP_MASK_3) && xkb_current_group != XCB_XKB_GROUP_3)
+        return;
+    if (((mods >> 16) & I3_XKB_GROUP_MASK_4) && xkb_current_group != XCB_XKB_GROUP_4)
+        return;
+    mods &= 0xFFFF;
+    DLOG("Grabbing keycode %d with event state mask 0x%x (mods 0x%x)\n",
+         keycode, bind->event_state_mask, mods);
     GRAB_KEY(mods);
     GRAB_KEY(mods | xcb_numlock_mask);
     GRAB_KEY(mods | XCB_MOD_MASK_LOCK);
@@ -105,12 +128,10 @@ static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint
  * Grab the bound keys (tell X to send us keypress events for those keycodes)
  *
  */
-void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) {
+void grab_all_keys(xcb_connection_t *conn) {
     Binding *bind;
     TAILQ_FOREACH(bind, bindings, bindings) {
-        if (bind->input_type != B_KEYBOARD ||
-            (bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) == 0) ||
-            (!bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) != 0))
+        if (bind->input_type != B_KEYBOARD)
             continue;
 
         /* The easy case: the user specified a keycode directly. */
@@ -119,9 +140,8 @@ void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) {
             continue;
         }
 
-        xcb_keycode_t *walk = bind->translated_to;
         for (uint32_t i = 0; i < bind->number_keycodes; i++)
-            grab_keycode_for_binding(conn, bind, *walk++);
+            grab_keycode_for_binding(conn, bind, bind->translated_to[i]);
     }
 }
 
@@ -130,7 +150,7 @@ void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) {
  * keycode or NULL if no such binding exists.
  *
  */
-static Binding *get_binding(uint16_t modifiers, bool is_release, uint16_t input_code, input_type_t input_type) {
+static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_release, uint16_t input_code, input_type_t input_type) {
     Binding *bind;
 
     if (!is_release) {
@@ -145,12 +165,15 @@ static Binding *get_binding(uint16_t modifiers, bool is_release, uint16_t input_
     }
 
     TAILQ_FOREACH(bind, bindings, bindings) {
-        /* First compare the modifiers (unless this is a
+        DLOG("binding with event_state_mask 0x%x, state_filtered 0x%x, match: %s\n",
+             bind->event_state_mask, state_filtered,
+             ((state_filtered & bind->event_state_mask) == bind->event_state_mask) ? "yes" : "no");
+        /* First compare the state_filtered (unless this is a
          * B_UPON_KEYRELEASE_IGNORE_MODS binding and this is a KeyRelease
          * event) */
         if (bind->input_type != input_type)
             continue;
-        if (bind->mods != modifiers &&
+        if ((state_filtered & bind->event_state_mask) != bind->event_state_mask &&
             (bind->release != B_UPON_KEYRELEASE_IGNORE_MODS ||
              !is_release))
             continue;
@@ -194,49 +217,89 @@ static Binding *get_binding(uint16_t modifiers, bool is_release, uint16_t input_
  *
  */
 Binding *get_binding_from_xcb_event(xcb_generic_event_t *event) {
-    bool is_release = (event->response_type == XCB_KEY_RELEASE || event->response_type == XCB_BUTTON_RELEASE);
-
-    input_type_t input_type = ((event->response_type == XCB_BUTTON_RELEASE || event->response_type == XCB_BUTTON_PRESS)
-                                   ? B_MOUSE
-                                   : B_KEYBOARD);
-
-    uint16_t event_state = ((xcb_key_press_event_t *)event)->state;
-    uint16_t event_detail = ((xcb_key_press_event_t *)event)->detail;
+    const bool is_release = (event->response_type == XCB_KEY_RELEASE ||
+                             event->response_type == XCB_BUTTON_RELEASE);
+
+    const input_type_t input_type = ((event->response_type == XCB_BUTTON_RELEASE ||
+                                      event->response_type == XCB_BUTTON_PRESS)
+                                         ? B_MOUSE
+                                         : B_KEYBOARD);
+
+    const uint16_t event_state = ((xcb_key_press_event_t *)event)->state;
+    const uint16_t event_detail = ((xcb_key_press_event_t *)event)->detail;
+
+    /* Remove the numlock bit */
+    i3_event_state_mask_t state_filtered = event_state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK);
+    DLOG("(removed numlock, state = 0x%x)\n", state_filtered);
+    /* Transform the keyboard_group from bit 13 and bit 14 into an
+     * i3_xkb_group_mask_t, so that get_binding() can just bitwise AND the
+     * configured bindings against |state_filtered|.
+     *
+     * These bits are only set because we set the XKB client flags
+     * XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE and
+     * XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED. See also doc/kbproto
+     * section 2.2.2:
+     * http://www.x.org/releases/X11R7.7/doc/kbproto/xkbproto.html#Computing_A_State_Field_from_an_XKB_State */
+    switch ((event_state & 0x6000) >> 13) {
+        case XCB_XKB_GROUP_1:
+            state_filtered |= (I3_XKB_GROUP_MASK_1 << 16);
+            break;
+        case XCB_XKB_GROUP_2:
+            state_filtered |= (I3_XKB_GROUP_MASK_2 << 16);
+            break;
+        case XCB_XKB_GROUP_3:
+            state_filtered |= (I3_XKB_GROUP_MASK_3 << 16);
+            break;
+        case XCB_XKB_GROUP_4:
+            state_filtered |= (I3_XKB_GROUP_MASK_4 << 16);
+            break;
+    }
+    state_filtered &= ~0x6000;
+    DLOG("(transformed keyboard group, state = 0x%x)\n", state_filtered);
+    return get_binding(state_filtered, is_release, event_detail, input_type);
+}
 
-    /* Remove the numlock bit, all other bits are modifiers we can bind to */
-    uint16_t state_filtered = event_state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK);
-    DLOG("(removed numlock, state = %d)\n", state_filtered);
-    /* Only use the lower 8 bits of the state (modifier masks) so that mouse
-     * button masks are filtered out */
-    state_filtered &= 0xFF;
-    DLOG("(removed upper 8 bits, state = %d)\n", state_filtered);
+struct resolve {
+    /* The binding which we are resolving. */
+    Binding *bind;
 
-    if (xkb_current_group == XCB_XKB_GROUP_2)
-        state_filtered |= BIND_MODE_SWITCH;
+    /* |bind|’s keysym (translated to xkb_keysym_t), e.g. XKB_KEY_R. */
+    xkb_keysym_t keysym;
 
-    DLOG("(checked mode_switch, state %d)\n", state_filtered);
+    /* The xkb state built from the user-provided modifiers and group. */
+    struct xkb_state *xkb_state;
 
-    /* Find the binding */
-    Binding *bind = get_binding(state_filtered, is_release, event_detail, input_type);
+    /* Like |xkb_state|, just without the shift modifier, if shift was specified. */
+    struct xkb_state *xkb_state_no_shift;
+};
 
-    /* No match? Then the user has Mode_switch enabled but does not have a
-     * specific keybinding. Fall back to the default keybindings (without
-     * Mode_switch). Makes it much more convenient for users of a hybrid
-     * layout (like ru). */
-    if (bind == NULL) {
-        state_filtered &= ~(BIND_MODE_SWITCH);
-        DLOG("no match, new state_filtered = %d\n", state_filtered);
-        if ((bind = get_binding(state_filtered, is_release, event_detail, input_type)) == NULL) {
-            /* This is not a real error since we can have release and
-             * non-release bindings. On a press event for which there is only a
-             * !release-binding, but no release-binding, the corresponding
-             * release event will trigger this. No problem, though. */
-            DLOG("Could not lookup key binding (modifiers %d, keycode %d)\n",
-                 state_filtered, event_detail);
-        }
+/*
+ * add_keycode_if_matches is called for each keycode in the keymap and will add
+ * the keycode to |data->bind| if the keycode can result in the keysym
+ * |data->resolving|.
+ *
+ */
+static void add_keycode_if_matches(struct xkb_keymap *keymap, xkb_keycode_t key, void *data) {
+    const struct resolve *resolving = data;
+    xkb_keysym_t sym = xkb_state_key_get_one_sym(resolving->xkb_state, key);
+    if (sym != resolving->keysym) {
+        /* Check if Shift was specified, and try resolving the symbol without
+         * shift, so that “bindsym $mod+Shift+a nop” actually works. */
+        const xkb_layout_index_t layout = xkb_state_key_get_layout(resolving->xkb_state, key);
+        if (layout == XKB_LAYOUT_INVALID)
+            return;
+        if (xkb_state_key_get_level(resolving->xkb_state, key, layout) > 1)
+            return;
+        sym = xkb_state_key_get_one_sym(resolving->xkb_state_no_shift, key);
+        if (sym != resolving->keysym)
+            return;
     }
-
-    return bind;
+    Binding *bind = resolving->bind;
+    bind->number_keycodes++;
+    bind->translated_to = srealloc(bind->translated_to,
+                                   (sizeof(xcb_keycode_t) *
+                                    bind->number_keycodes));
+    bind->translated_to[bind->number_keycodes - 1] = key;
 }
 
 /*
@@ -244,20 +307,26 @@ Binding *get_binding_from_xcb_event(xcb_generic_event_t *event) {
  *
  */
 void translate_keysyms(void) {
-    Binding *bind;
-    xcb_keysym_t keysym;
-    int col;
-    xcb_keycode_t i, min_keycode, max_keycode;
+    struct xkb_state *dummy_state = xkb_state_new(xkb_keymap);
+    if (dummy_state == NULL) {
+        ELOG("Could not create XKB state, cannot translate keysyms.\n");
+        return;
+    }
 
-    min_keycode = xcb_get_setup(conn)->min_keycode;
-    max_keycode = xcb_get_setup(conn)->max_keycode;
+    struct xkb_state *dummy_state_no_shift = xkb_state_new(xkb_keymap);
+    if (dummy_state_no_shift == NULL) {
+        ELOG("Could not create XKB state, cannot translate keysyms.\n");
+        return;
+    }
 
+    Binding *bind;
     TAILQ_FOREACH(bind, bindings, bindings) {
         if (bind->input_type == B_MOUSE) {
-            int button = atoi(bind->symbol + (sizeof("button") - 1));
+            char *endptr;
+            long button = strtol(bind->symbol + (sizeof("button") - 1), &endptr, 10);
             bind->keycode = button;
 
-            if (button < 1)
+            if (button == LONG_MAX || button == LONG_MIN || button < 0 || *endptr != '\0' || endptr == bind->symbol)
                 ELOG("Could not translate string to button: \"%s\"\n", bind->symbol);
 
             continue;
@@ -267,36 +336,66 @@ void translate_keysyms(void) {
             continue;
 
         /* We need to translate the symbol to a keycode */
-        keysym = xkb_keysym_from_name(bind->symbol, XKB_KEYSYM_NO_FLAGS);
+        const xkb_keysym_t keysym = xkb_keysym_from_name(bind->symbol, XKB_KEYSYM_NO_FLAGS);
         if (keysym == XKB_KEY_NoSymbol) {
             ELOG("Could not translate string to key symbol: \"%s\"\n",
                  bind->symbol);
             continue;
         }
 
-        /* Base column we use for looking up key symbols. We always consider
-         * the base column and the corresponding shift column, so without
-         * mode_switch, we look in 0 and 1, with mode_switch we look in 2 and
-         * 3. */
-        col = (bind->mods & BIND_MODE_SWITCH ? 2 : 0);
-
+        xkb_layout_index_t group = XCB_XKB_GROUP_1;
+        if ((bind->event_state_mask >> 16) & I3_XKB_GROUP_MASK_2)
+            group = XCB_XKB_GROUP_2;
+        else if ((bind->event_state_mask >> 16) & I3_XKB_GROUP_MASK_3)
+            group = XCB_XKB_GROUP_3;
+        else if ((bind->event_state_mask >> 16) & I3_XKB_GROUP_MASK_4)
+            group = XCB_XKB_GROUP_4;
+
+        DLOG("group = %d, event_state_mask = %d, &2 = %s, &3 = %s, &4 = %s\n", group,
+             bind->event_state_mask,
+             (bind->event_state_mask & I3_XKB_GROUP_MASK_2) ? "yes" : "no",
+             (bind->event_state_mask & I3_XKB_GROUP_MASK_3) ? "yes" : "no",
+             (bind->event_state_mask & I3_XKB_GROUP_MASK_4) ? "yes" : "no");
+        (void)xkb_state_update_mask(
+            dummy_state,
+            (bind->event_state_mask & 0x1FFF) /* xkb_mod_mask_t base_mods, */,
+            0 /* xkb_mod_mask_t latched_mods, */,
+            0 /* xkb_mod_mask_t locked_mods, */,
+            0 /* xkb_layout_index_t base_group, */,
+            0 /* xkb_layout_index_t latched_group, */,
+            group /* xkb_layout_index_t locked_group, */);
+
+        (void)xkb_state_update_mask(
+            dummy_state_no_shift,
+            (bind->event_state_mask & 0x1FFF) ^ XCB_KEY_BUT_MASK_SHIFT /* xkb_mod_mask_t base_mods, */,
+            0 /* xkb_mod_mask_t latched_mods, */,
+            0 /* xkb_mod_mask_t locked_mods, */,
+            0 /* xkb_layout_index_t base_group, */,
+            0 /* xkb_layout_index_t latched_group, */,
+            group /* xkb_layout_index_t locked_group, */);
+
+        struct resolve resolving = {
+            .bind = bind,
+            .keysym = keysym,
+            .xkb_state = dummy_state,
+            .xkb_state_no_shift = dummy_state_no_shift,
+        };
         FREE(bind->translated_to);
         bind->number_keycodes = 0;
-
-        for (i = min_keycode; i && i <= max_keycode; i++) {
-            if ((xcb_key_symbols_get_keysym(keysyms, i, col) != keysym) &&
-                (xcb_key_symbols_get_keysym(keysyms, i, col + 1) != keysym))
-                continue;
-            bind->number_keycodes++;
-            bind->translated_to = srealloc(bind->translated_to,
-                                           (sizeof(xcb_keycode_t) *
-                                            bind->number_keycodes));
-            bind->translated_to[bind->number_keycodes - 1] = i;
+        xkb_keymap_key_for_each(xkb_keymap, add_keycode_if_matches, &resolving);
+        char *keycodes = sstrdup("");
+        for (uint32_t n = 0; n < bind->number_keycodes; n++) {
+            char *tmp;
+            sasprintf(&tmp, "%s %d", keycodes, bind->translated_to[n]);
+            free(keycodes);
+            keycodes = tmp;
         }
-
-        DLOG("Translated symbol \"%s\" to %d keycode (mods %d)\n", bind->symbol,
-             bind->number_keycodes, bind->mods);
+        DLOG("state=0x%x, cfg=\"%s\", sym=0x%x → keycodes%s (%d)\n",
+             bind->event_state_mask, bind->symbol, keysym, keycodes, bind->number_keycodes);
+        free(keycodes);
     }
+
+    xkb_state_unref(dummy_state);
 }
 
 /*
@@ -315,7 +414,7 @@ void switch_mode(const char *new_mode) {
         ungrab_all_keys(conn);
         bindings = mode->bindings;
         translate_keysyms();
-        grab_all_keys(conn, false);
+        grab_all_keys(conn);
 
         char *event_msg;
         sasprintf(&event_msg, "{\"change\":\"%s\"}", mode->name);
@@ -329,6 +428,70 @@ void switch_mode(const char *new_mode) {
     ELOG("ERROR: Mode not found\n");
 }
 
+static int reorder_binding_cmp(const void *a, const void *b) {
+    Binding *first = *((Binding **)a);
+    Binding *second = *((Binding **)b);
+    if (first->event_state_mask < second->event_state_mask) {
+        return 1;
+    } else if (first->event_state_mask == second->event_state_mask) {
+        return 0;
+    } else {
+        return -1;
+    }
+}
+
+static void reorder_bindings_of_mode(struct Mode *mode) {
+    /* Copy the bindings into an array, so that we can use qsort(3). */
+    int n = 0;
+    Binding *current;
+    TAILQ_FOREACH(current, mode->bindings, bindings) {
+        n++;
+    }
+    Binding **tmp = scalloc(n, sizeof(Binding *));
+    n = 0;
+    TAILQ_FOREACH(current, mode->bindings, bindings) {
+        tmp[n++] = current;
+    }
+
+    qsort(tmp, n, sizeof(Binding *), reorder_binding_cmp);
+
+    struct bindings_head *reordered = scalloc(1, sizeof(struct bindings_head));
+    TAILQ_INIT(reordered);
+    for (int i = 0; i < n; i++) {
+        current = tmp[i];
+        TAILQ_REMOVE(mode->bindings, current, bindings);
+        TAILQ_INSERT_TAIL(reordered, current, bindings);
+    }
+    free(tmp);
+    assert(TAILQ_EMPTY(mode->bindings));
+    /* Free the old bindings_head, which is now empty. */
+    free(mode->bindings);
+    mode->bindings = reordered;
+}
+
+/*
+ * Reorders bindings by event_state_mask descendingly so that get_binding()
+ * correctly matches more specific bindings before more generic bindings. Take
+ * the following binding configuration as an example:
+ *
+ *   bindsym n nop lower-case n pressed
+ *   bindsym Shift+n nop upper-case n pressed
+ *
+ * Without reordering, the first binding’s event_state_mask of 0x0 would match
+ * the actual event_stat_mask of 0x1 and hence trigger instead of the second
+ * keybinding.
+ *
+ */
+void reorder_bindings(void) {
+    struct Mode *mode;
+    SLIST_FOREACH(mode, &modes, modes) {
+        const bool current_mode = (mode->bindings == bindings);
+        reorder_bindings_of_mode(mode);
+        if (current_mode)
+            bindings = mode->bindings;
+    }
+}
+
 /*
  * Checks for duplicate key bindings (the same keycode or keysym is configured
  * more than once). If a duplicate binding is found, a message is printed to
@@ -366,17 +529,17 @@ void check_for_duplicate_bindings(struct context *context) {
             /* Check if the keycodes or modifiers are different. If so, they
              * can't be duplicate */
             if (bind->keycode != current->keycode ||
-                bind->mods != current->mods ||
+                bind->event_state_mask != current->event_state_mask ||
                 bind->release != current->release)
                 continue;
 
             context->has_errors = true;
             if (current->keycode != 0) {
-                ELOG("Duplicate keybinding in config file:\n  modmask %d with keycode %d, command \"%s\"\n",
-                     current->mods, current->keycode, current->command);
+                ELOG("Duplicate keybinding in config file:\n  state mask 0x%x with keycode %d, command \"%s\"\n",
+                     current->event_state_mask, current->keycode, current->command);
             } else {
-                ELOG("Duplicate keybinding in config file:\n  modmask %d with keysym %s, command \"%s\"\n",
-                     current->mods, current->symbol, current->command);
+                ELOG("Duplicate keybinding in config file:\n  state mask 0x%x with keysym %s, command \"%s\"\n",
+                     current->event_state_mask, current->symbol, current->command);
             }
         }
     }
@@ -389,9 +552,9 @@ static Binding *binding_copy(Binding *bind) {
     Binding *ret = smalloc(sizeof(Binding));
     *ret = *bind;
     if (bind->symbol != NULL)
-        ret->symbol = strdup(bind->symbol);
+        ret->symbol = sstrdup(bind->symbol);
     if (bind->command != NULL)
-        ret->command = strdup(bind->command);
+        ret->command = sstrdup(bind->command);
     if (bind->translated_to != NULL) {
         ret->translated_to = smalloc(sizeof(xcb_keycode_t) * bind->number_keycodes);
         memcpy(ret->translated_to, bind->translated_to, sizeof(xcb_keycode_t) * bind->number_keycodes);
@@ -462,3 +625,28 @@ CommandResult *run_binding(Binding *bind, Con *con) {
 
     return result;
 }
+
+/*
+ * Loads the XKB keymap from the X11 server and feeds it to xkbcommon.
+ *
+ */
+bool load_keymap(void) {
+    if (xkb_context == NULL) {
+        if ((xkb_context = xkb_context_new(0)) == NULL) {
+            ELOG("Could not create xkbcommon context\n");
+            return false;
+        }
+    }
+
+    struct xkb_keymap *new_keymap;
+    const int32_t device_id = xkb_x11_get_core_keyboard_device_id(conn);
+    DLOG("device_id = %d\n", device_id);
+    if ((new_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL) {
+        ELOG("xkb_x11_keymap_new_from_device failed\n");
+        return false;
+    }
+    xkb_keymap_unref(xkb_keymap);
+    xkb_keymap = new_keymap;
+
+    return true;
+}
index 10abc0570c3140201a1d46cae53421f3f243d16d..92a1a1fa20fe26bf301d7712cd63dde3a2f542c6 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * click.c: Button press (mouse click) events.
  *
@@ -180,14 +180,18 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
     if (con->parent->type == CT_DOCKAREA)
         goto done;
 
+    const bool is_left_or_right_click = (event->detail == XCB_BUTTON_INDEX_1 ||
+                                         event->detail == XCB_BUTTON_INDEX_3);
+
     /* if the user has bound an action to this click, it should override the
      * default behavior. */
-    if (dest == CLICK_DECORATION || dest == CLICK_INSIDE) {
+    if (dest == CLICK_DECORATION || dest == CLICK_INSIDE || dest == CLICK_BORDER) {
         Binding *bind = get_binding_from_xcb_event((xcb_generic_event_t *)event);
         /* clicks over a window decoration will always trigger the binding and
          * clicks on the inside of the window will only trigger a binding if
          * the --whole-window flag was given for the binding. */
-        if (bind && (dest == CLICK_DECORATION || bind->whole_window)) {
+        if (bind && ((dest == CLICK_DECORATION || bind->whole_window) ||
+                     (dest == CLICK_BORDER && bind->border))) {
             CommandResult *result = run_binding(bind, con);
 
             /* ASYNC_POINTER eats the event */
@@ -225,7 +229,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
 
     /* get the floating con */
     Con *floatingcon = con_inside_floating(con);
-    const bool proportional = (event->state & BIND_SHIFT);
+    const bool proportional = (event->state & XCB_KEY_BUT_MASK_SHIFT) == XCB_KEY_BUT_MASK_SHIFT;
     const bool in_stacked = (con->parent->layout == L_STACKED || con->parent->layout == L_TABBED);
 
     /* 1: see if the user scrolled on the decoration of a stacked/tabbed con */
@@ -278,7 +282,8 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
             return 1;
         }
 
-        if (!in_stacked && dest == CLICK_DECORATION) {
+        if (!in_stacked && dest == CLICK_DECORATION &&
+            is_left_or_right_click) {
             /* try tiling resize, but continue if it doesn’t work */
             DLOG("tiling resize with fallback\n");
             if (tiling_resize(con, event, dest))
@@ -291,7 +296,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
             return 1;
         }
 
-        if (dest == CLICK_BORDER) {
+        if (dest == CLICK_BORDER && is_left_or_right_click) {
             DLOG("floating resize due to border click\n");
             floating_resize_window(floatingcon, proportional, event);
             return 1;
@@ -299,7 +304,8 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
 
         /* 6: dragging, if this was a click on a decoration (which did not lead
          * to a resize) */
-        if (!in_stacked && dest == CLICK_DECORATION) {
+        if (!in_stacked && dest == CLICK_DECORATION &&
+            (event->detail == XCB_BUTTON_INDEX_1)) {
             floating_drag_window(floatingcon, event);
             return 1;
         }
@@ -320,8 +326,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
     }
     /* 8: otherwise, check for border/decoration clicks and resize */
     else if ((dest == CLICK_BORDER || dest == CLICK_DECORATION) &&
-             (event->detail == XCB_BUTTON_INDEX_1 ||
-              event->detail == XCB_BUTTON_INDEX_3)) {
+             is_left_or_right_click) {
         DLOG("Trying to resize (tiling)\n");
         tiling_resize(con, event, dest);
     }
@@ -344,8 +349,8 @@ done:
  */
 int handle_button_press(xcb_button_press_event_t *event) {
     Con *con;
-    DLOG("Button %d %s on window 0x%08x (child 0x%08x) at (%d, %d) (root %d, %d)\n",
-         event->state, (event->response_type == XCB_BUTTON_PRESS ? "press" : "release"),
+    DLOG("Button %d (state %d) %s on window 0x%08x (child 0x%08x) at (%d, %d) (root %d, %d)\n",
+         event->detail, event->state, (event->response_type == XCB_BUTTON_PRESS ? "press" : "release"),
          event->event, event->child, event->event_x, event->event_y, event->root_x,
          event->root_y);
 
index 9d5275dc3d57dadb4d70f7bc74b30052107a8151..a696ad34a083b7258ee2e26cae1e45ba7eb0061e 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * commands.c: all command functions (see commands_parser.c)
  *
@@ -363,6 +363,31 @@ void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue) {
         return;
     }
 
+    if (strcmp(ctype, "window_type") == 0) {
+        if (strcasecmp(cvalue, "normal") == 0)
+            current_match->window_type = A__NET_WM_WINDOW_TYPE_NORMAL;
+        else if (strcasecmp(cvalue, "dialog") == 0)
+            current_match->window_type = A__NET_WM_WINDOW_TYPE_DIALOG;
+        else if (strcasecmp(cvalue, "utility") == 0)
+            current_match->window_type = A__NET_WM_WINDOW_TYPE_UTILITY;
+        else if (strcasecmp(cvalue, "toolbar") == 0)
+            current_match->window_type = A__NET_WM_WINDOW_TYPE_TOOLBAR;
+        else if (strcasecmp(cvalue, "splash") == 0)
+            current_match->window_type = A__NET_WM_WINDOW_TYPE_SPLASH;
+        else if (strcasecmp(cvalue, "menu") == 0)
+            current_match->window_type = A__NET_WM_WINDOW_TYPE_MENU;
+        else if (strcasecmp(cvalue, "dropdown_menu") == 0)
+            current_match->window_type = A__NET_WM_WINDOW_TYPE_DROPDOWN_MENU;
+        else if (strcasecmp(cvalue, "popup_menu") == 0)
+            current_match->window_type = A__NET_WM_WINDOW_TYPE_POPUP_MENU;
+        else if (strcasecmp(cvalue, "tooltip") == 0)
+            current_match->window_type = A__NET_WM_WINDOW_TYPE_TOOLTIP;
+        else
+            ELOG("unknown window_type value \"%s\"\n", cvalue);
+
+        return;
+    }
+
     if (strcmp(ctype, "con_mark") == 0) {
         current_match->mark = regex_new(cvalue);
         return;
@@ -386,6 +411,11 @@ void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue) {
         return;
     }
 
+    if (strcmp(ctype, "workspace") == 0) {
+        current_match->workspace = regex_new(cvalue);
+        return;
+    }
+
     ELOG("Unknown criterion: %s\n", ctype);
 }
 
@@ -431,7 +461,7 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) {
 
     TAILQ_FOREACH(current, &owindows, owindows) {
         DLOG("matching: %p / %s\n", current->con, current->con->name);
-        con_move_to_workspace(current->con, ws, true, false);
+        con_move_to_workspace(current->con, ws, true, false, false);
     }
 
     cmd_output->needs_tree_render = true;
@@ -458,7 +488,7 @@ void cmd_move_con_to_workspace_back_and_forth(I3_CMD) {
 
     TAILQ_FOREACH(current, &owindows, owindows) {
         DLOG("matching: %p / %s\n", current->con, current->con->name);
-        con_move_to_workspace(current->con, ws, true, false);
+        con_move_to_workspace(current->con, ws, true, false, false);
     }
 
     cmd_output->needs_tree_render = true;
@@ -502,7 +532,7 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) {
 
     TAILQ_FOREACH(current, &owindows, owindows) {
         DLOG("matching: %p / %s\n", current->con, current->con->name);
-        con_move_to_workspace(current->con, ws, true, false);
+        con_move_to_workspace(current->con, ws, true, false, false);
     }
 
     cmd_output->needs_tree_render = true;
@@ -553,7 +583,7 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
 
     TAILQ_FOREACH(current, &owindows, owindows) {
         DLOG("matching: %p / %s\n", current->con, current->con->name);
-        con_move_to_workspace(current->con, workspace, true, false);
+        con_move_to_workspace(current->con, workspace, true, false, false);
     }
 
     cmd_output->needs_tree_render = true;
@@ -569,17 +599,20 @@ static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floatin
     /* ensure that resize will take place even if pixel increment is smaller than
      * height increment or width increment.
      * fixes #1011 */
-    if (strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0 ||
-        strcmp(direction, "height") == 0) {
-        if (px < 0)
-            px = (-px < focused_con->height_increment) ? -focused_con->height_increment : px;
-        else
-            px = (px < focused_con->height_increment) ? focused_con->height_increment : px;
-    } else if (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0) {
-        if (px < 0)
-            px = (-px < focused_con->width_increment) ? -focused_con->width_increment : px;
-        else
-            px = (px < focused_con->width_increment) ? focused_con->width_increment : px;
+    const i3Window *window = focused_con->window;
+    if (window != NULL) {
+        if (strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0 ||
+            strcmp(direction, "height") == 0) {
+            if (px < 0)
+                px = (-px < window->height_increment) ? -window->height_increment : px;
+            else
+                px = (px < window->height_increment) ? window->height_increment : px;
+        } else if (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0) {
+            if (px < 0)
+                px = (-px < window->width_increment) ? -window->width_increment : px;
+            else
+                px = (px < window->width_increment) ? window->width_increment : px;
+        }
     }
 
     if (strcmp(direction, "up") == 0) {
@@ -791,7 +824,38 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz
 }
 
 /*
- * Implementation of 'border normal|none|1pixel|toggle|pixel'.
+ * Implementation of 'resize set <px> [px] <px> [px]'.
+ *
+ */
+void cmd_size(I3_CMD, char *cwidth, char *cheight) {
+    DLOG("resizing to %sx%s px\n", cwidth, cheight);
+    // TODO: We could either handle this in the parser itself as a separate token (and make the stack typed) or we need a better way to convert a string to a number with error checking
+    int x = atoi(cwidth);
+    int y = atoi(cheight);
+    if (x <= 0 || y <= 0) {
+        ELOG("Resize failed: dimensions cannot be negative (was %sx%s)\n", cwidth, cheight);
+        return;
+    }
+
+    HANDLE_EMPTY_MATCH;
+
+    owindow *current;
+    TAILQ_FOREACH(current, &owindows, owindows) {
+        Con *floating_con;
+        if ((floating_con = con_inside_floating(current->con))) {
+            floating_resize(floating_con, x, y);
+        } else {
+            ELOG("Resize failed: %p not a floating container\n", current->con);
+        }
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    ysuccess(true);
+}
+
+/*
+ * Implementation of 'border normal|pixel [<n>]', 'border none|1pixel|toggle'.
  *
  */
 void cmd_border(I3_CMD, char *border_style_str, char *border_width) {
@@ -1037,26 +1101,29 @@ void cmd_workspace_name(I3_CMD, char *name) {
 }
 
 /*
- * Implementation of 'mark <mark>'
+ * Implementation of 'mark [--toggle] <mark>'
  *
  */
-void cmd_mark(I3_CMD, char *mark) {
-    DLOG("Clearing all windows which have that mark first\n");
+void cmd_mark(I3_CMD, char *mark, char *toggle) {
+    HANDLE_EMPTY_MATCH;
 
-    Con *con;
-    TAILQ_FOREACH(con, &all_cons, all_cons) {
-        if (con->mark && strcmp(con->mark, mark) == 0)
-            FREE(con->mark);
+    owindow *current = TAILQ_FIRST(&owindows);
+    if (current == NULL) {
+        ysuccess(false);
+        return;
     }
 
-    DLOG("marking window with str %s\n", mark);
-    owindow *current;
-
-    HANDLE_EMPTY_MATCH;
+    /* Marks must be unique, i.e., no two windows must have the same mark. */
+    if (current != TAILQ_LAST(&owindows, owindows_head)) {
+        yerror("A mark must not be put onto more than one window");
+        return;
+    }
 
-    TAILQ_FOREACH(current, &owindows, owindows) {
-        DLOG("matching: %p / %s\n", current->con, current->con->name);
-        current->con->mark = sstrdup(mark);
+    DLOG("matching: %p / %s\n", current->con, current->con->name);
+    if (toggle != NULL) {
+        con_mark_toggle(current->con, mark);
+    } else {
+        con_mark(current->con, mark);
     }
 
     cmd_output->needs_tree_render = true;
@@ -1069,20 +1136,7 @@ void cmd_mark(I3_CMD, char *mark) {
  *
  */
 void cmd_unmark(I3_CMD, char *mark) {
-    if (mark == NULL) {
-        Con *con;
-        TAILQ_FOREACH(con, &all_cons, all_cons) {
-            FREE(con->mark);
-        }
-        DLOG("removed all window marks");
-    } else {
-        Con *con;
-        TAILQ_FOREACH(con, &all_cons, all_cons) {
-            if (con->mark && strcmp(con->mark, mark) == 0)
-                FREE(con->mark);
-        }
-        DLOG("removed window mark %s\n", mark);
-    }
+    con_unmark(mark);
 
     cmd_output->needs_tree_render = true;
     // XXX: default reply for now, make this a better reply
@@ -1106,41 +1160,57 @@ void cmd_mode(I3_CMD, char *mode) {
  *
  */
 void cmd_move_con_to_output(I3_CMD, char *name) {
+    DLOG("Should move window to output \"%s\".\n", name);
+    HANDLE_EMPTY_MATCH;
+
     owindow *current;
+    bool had_error = false;
+    TAILQ_FOREACH(current, &owindows, owindows) {
+        DLOG("matching: %p / %s\n", current->con, current->con->name);
 
-    DLOG("should move window to output %s\n", name);
+        Output *current_output = get_output_of_con(current->con);
+        assert(current_output != NULL);
 
-    HANDLE_EMPTY_MATCH;
+        Output *output = get_output_from_string(current_output, name);
+        if (output == NULL) {
+            ELOG("Could not find output \"%s\", skipping.\n", name);
+            had_error = true;
+            continue;
+        }
 
-    Output *current_output = NULL;
-    // TODO: fix the handling of criteria
-    TAILQ_FOREACH(current, &owindows, owindows)
-    current_output = get_output_of_con(current->con);
-    assert(current_output != NULL);
+        Con *ws = NULL;
+        GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
+        if (ws == NULL) {
+            ELOG("Could not find a visible workspace on output %p.\n", output);
+            had_error = true;
+            continue;
+        }
 
-    Output *output = get_output_from_string(current_output, name);
-    if (!output) {
-        LOG("No such output found.\n");
-        ysuccess(false);
-        return;
+        con_move_to_workspace(current->con, ws, true, false, false);
     }
 
-    /* get visible workspace on output */
-    Con *ws = NULL;
-    GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
-    if (!ws) {
-        ysuccess(false);
-        return;
-    }
+    cmd_output->needs_tree_render = true;
+    ysuccess(!had_error);
+}
+
+/*
+ * Implementation of 'move [container|window] [to] mark <str>'.
+ *
+ */
+void cmd_move_con_to_mark(I3_CMD, char *mark) {
+    DLOG("moving window to mark \"%s\"\n", mark);
+
+    HANDLE_EMPTY_MATCH;
 
+    bool result = true;
+    owindow *current;
     TAILQ_FOREACH(current, &owindows, owindows) {
-        DLOG("matching: %p / %s\n", current->con, current->con->name);
-        con_move_to_workspace(current->con, ws, true, false);
+        DLOG("moving matched window %p / %s to mark \"%s\"\n", current->con, current->con->name, mark);
+        result &= con_move_to_mark(current->con, mark);
     }
 
     cmd_output->needs_tree_render = true;
-    // XXX: default reply for now, make this a better reply
-    ysuccess(true);
+    ysuccess(result);
 }
 
 /*
@@ -1306,15 +1376,14 @@ void cmd_focus_window_mode(I3_CMD, char *window_mode) {
     DLOG("window_mode = %s\n", window_mode);
 
     Con *ws = con_get_workspace(focused);
-    Con *current;
     if (ws != NULL) {
         if (strcmp(window_mode, "mode_toggle") == 0) {
-            current = TAILQ_FIRST(&(ws->focus_head));
-            if (current != NULL && current->type == CT_FLOATING_CON)
+            if (con_inside_floating(focused))
                 window_mode = "tiling";
             else
                 window_mode = "floating";
         }
+        Con *current;
         TAILQ_FOREACH(current, &(ws->focus_head), focused) {
             if ((strcmp(window_mode, "floating") == 0 && current->type != CT_FLOATING_CON) ||
                 (strcmp(window_mode, "tiling") == 0 && current->type == CT_FLOATING_CON))
@@ -1430,8 +1499,7 @@ void cmd_focus(I3_CMD) {
             count);
 
     cmd_output->needs_tree_render = true;
-    // XXX: default reply for now, make this a better reply
-    ysuccess(true);
+    ysuccess(count > 0);
 }
 
 /*
@@ -1462,6 +1530,42 @@ void cmd_fullscreen(I3_CMD, char *action, char *fullscreen_mode) {
     ysuccess(true);
 }
 
+/*
+ * Implementation of 'sticky enable|disable|toggle'.
+ *
+ */
+void cmd_sticky(I3_CMD, char *action) {
+    DLOG("%s sticky on window\n", action);
+    HANDLE_EMPTY_MATCH;
+
+    owindow *current;
+    TAILQ_FOREACH(current, &owindows, owindows) {
+        if (current->con->window == NULL) {
+            ELOG("only containers holding a window can be made sticky, skipping con = %p\n", current->con);
+            continue;
+        }
+        DLOG("setting sticky for container = %p / %s\n", current->con, current->con->name);
+
+        bool sticky = false;
+        if (strcmp(action, "enable") == 0)
+            sticky = true;
+        else if (strcmp(action, "disable") == 0)
+            sticky = false;
+        else if (strcmp(action, "toggle") == 0)
+            sticky = !current->con->sticky;
+
+        current->con->sticky = sticky;
+        ewmh_update_sticky(current->con->window->id, sticky);
+    }
+
+    /* A window we made sticky might not be on a visible workspace right now, so we need to make
+     * sure it gets pushed to the front now. */
+    output_push_sticky_windows(focused);
+
+    cmd_output->needs_tree_render = true;
+    ysuccess(true);
+}
+
 /*
  * Implementation of 'move <direction> [<pixels> [px]]'.
  *
@@ -1746,31 +1850,48 @@ void cmd_move_window_to_center(I3_CMD, char *method) {
     }
 
     if (strcmp(method, "absolute") == 0) {
-        Rect *rect = &focused->parent->rect;
-
         DLOG("moving to absolute center\n");
-        rect->x = croot->rect.width / 2 - rect->width / 2;
-        rect->y = croot->rect.height / 2 - rect->height / 2;
+        floating_center(focused->parent, croot->rect);
 
         floating_maybe_reassign_ws(focused->parent);
         cmd_output->needs_tree_render = true;
     }
 
     if (strcmp(method, "position") == 0) {
-        Rect *wsrect = &con_get_workspace(focused)->rect;
-        Rect newrect = focused->parent->rect;
-
         DLOG("moving to center\n");
-        newrect.x = wsrect->width / 2 - newrect.width / 2;
-        newrect.y = wsrect->height / 2 - newrect.height / 2;
+        floating_center(focused->parent, con_get_workspace(focused)->rect);
 
-        floating_reposition(focused->parent, newrect);
+        cmd_output->needs_tree_render = true;
     }
 
     // XXX: default reply for now, make this a better reply
     ysuccess(true);
 }
 
+/*
+ * Implementation of 'move [window|container] [to] position mouse'
+ *
+ */
+void cmd_move_window_to_mouse(I3_CMD) {
+    HANDLE_EMPTY_MATCH;
+
+    owindow *current;
+    TAILQ_FOREACH(current, &owindows, owindows) {
+        Con *floating_con = con_inside_floating(current->con);
+        if (floating_con == NULL) {
+            DLOG("con %p / %s is not floating, cannot move it to the mouse position.\n",
+                 current->con, current->con->name);
+            continue;
+        }
+
+        DLOG("moving floating container %p / %s to cursor position\n", floating_con, floating_con->name);
+        floating_move_to_pointer(floating_con);
+    }
+
+    cmd_output->needs_tree_render = true;
+    ysuccess(true);
+}
+
 /*
  * Implementation of 'move scratchpad'.
  *
@@ -1813,13 +1934,50 @@ void cmd_scratchpad_show(I3_CMD) {
     ysuccess(true);
 }
 
+/*
+ * Implementation of 'title_format <format>'
+ *
+ */
+void cmd_title_format(I3_CMD, char *format) {
+    DLOG("setting title_format to \"%s\"\n", format);
+    HANDLE_EMPTY_MATCH;
+
+    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);
+
+        /* 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);
+
+            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);
+        } 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);
+        }
+
+        /* Make sure the window title is redrawn immediately. */
+        current->con->window->name_x_changed = true;
+    }
+
+    cmd_output->needs_tree_render = true;
+    ysuccess(true);
+}
+
 /*
  * Implementation of 'rename workspace [<name>] to <name>'
  *
  */
 void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) {
     if (strncasecmp(new_name, "__", strlen("__")) == 0) {
-        LOG("Cannot rename workspace to \"%s\": names starting with __ are i3-internal.", new_name);
+        LOG("Cannot rename workspace to \"%s\": names starting with __ are i3-internal.\n", new_name);
         ysuccess(false);
         return;
     }
index fa4c2360101513e4796490c9720ff9b63533915f..ffe416f04dd1c3659ff0aae5687f02269ee63700 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * commands_parser.c: hand-written parser to parse commands (commands are what
  * you bind on keys and what you can send to i3 using the IPC interface, like
@@ -148,9 +148,9 @@ static TAILQ_HEAD(criteria_head, criterion) criteria =
  */
 static void push_criterion(void *unused_criteria, const char *type,
                            const char *value) {
-    struct criterion *criterion = malloc(sizeof(struct criterion));
-    criterion->type = strdup(type);
-    criterion->value = strdup(value);
+    struct criterion *criterion = smalloc(sizeof(struct criterion));
+    criterion->type = sstrdup(type);
+    criterion->value = sstrdup(value);
     TAILQ_INSERT_TAIL(&criteria, criterion, criteria);
 }
 
@@ -243,7 +243,7 @@ char *parse_string(const char **walk, bool as_word) {
     if (*walk == beginning)
         return NULL;
 
-    char *str = scalloc(*walk - beginning + 1);
+    char *str = scalloc(*walk - beginning + 1, 1);
     /* We copy manually to handle escaping of characters. */
     int inpos, outpos;
     for (inpos = 0, outpos = 0;
@@ -270,7 +270,7 @@ char *parse_string(const char **walk, bool as_word) {
 CommandResult *parse_command(const char *input, yajl_gen gen) {
     DLOG("COMMAND: *%s*\n", input);
     state = INITIAL;
-    CommandResult *result = scalloc(sizeof(CommandResult));
+    CommandResult *result = scalloc(1, sizeof(CommandResult));
 
     /* A YAJL JSON generator used for formatting replies. */
     command_output.json_gen = gen;
index b610b0c60c1c9cfb792d107a7ff6d05acdc27487..d1148301f5d5bb9d37e8f69a00112493b67a718c 100644 (file)
--- a/src/con.c
+++ b/src/con.c
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * con.c: Functions which deal with containers directly (creating containers,
  *        searching containers, getting specific properties from containers,
@@ -36,18 +36,19 @@ void con_force_split_parents_redraw(Con *con) {
  *
  */
 Con *con_new_skeleton(Con *parent, i3Window *window) {
-    Con *new = scalloc(sizeof(Con));
+    Con *new = scalloc(1, sizeof(Con));
     new->on_remove_child = con_on_remove_child;
     TAILQ_INSERT_TAIL(&all_cons, new, all_cons);
-    new->aspect_ratio = 0.0;
     new->type = CT_CON;
     new->window = window;
     new->border_style = config.default_border;
     new->current_border_width = -1;
-    if (window)
+    if (window) {
         new->depth = window->depth;
-    else
+        new->window->aspect_ratio = 0.0;
+    } else {
         new->depth = XCB_COPY_FROM_PARENT;
+    }
     DLOG("opening window\n");
 
     TAILQ_INIT(&(new->floating_head));
@@ -70,20 +71,10 @@ Con *con_new(Con *parent, i3Window *window) {
     return new;
 }
 
-/*
- * Attaches the given container to the given parent. This happens when moving
- * a container or when inserting a new container at a specific place in the
- * tree.
- *
- * ignore_focus is to just insert the Con at the end (useful when creating a
- * new split container *around* some containers, that is, detaching and
- * attaching them in order without wanting to mess with the focus in between).
- *
- */
-void con_attach(Con *con, Con *parent, bool ignore_focus) {
+static void _con_attach(Con *con, Con *parent, Con *previous, bool ignore_focus) {
     con->parent = parent;
     Con *loop;
-    Con *current = NULL;
+    Con *current = previous;
     struct nodes_head *nodes_head = &(parent->nodes_head);
     struct focus_head *focus_head = &(parent->focus_head);
 
@@ -155,8 +146,7 @@ void con_attach(Con *con, Con *parent, bool ignore_focus) {
         /* Insert the container after the tiling container, if found.
          * When adding to a CT_OUTPUT, just append one after another. */
         if (current && parent->type != CT_OUTPUT) {
-            DLOG("Inserting con = %p after last focused tiling con %p\n",
-                 con, current);
+            DLOG("Inserting con = %p after con %p\n", con, current);
             TAILQ_INSERT_AFTER(nodes_head, current, con, nodes);
         } else
             TAILQ_INSERT_TAIL(nodes_head, con, nodes);
@@ -170,6 +160,20 @@ add_to_focus_head:
     con_force_split_parents_redraw(con);
 }
 
+/*
+ * Attaches the given container to the given parent. This happens when moving
+ * a container or when inserting a new container at a specific place in the
+ * tree.
+ *
+ * ignore_focus is to just insert the Con at the end (useful when creating a
+ * new split container *around* some containers, that is, detaching and
+ * attaching them in order without wanting to mess with the focus in between).
+ *
+ */
+void con_attach(Con *con, Con *parent, bool ignore_focus) {
+    _con_attach(con, parent, NULL, ignore_focus);
+}
+
 /*
  * Detaches the given container from its current parent
  *
@@ -208,7 +212,7 @@ void con_focus(Con *con) {
      * checks before resetting the urgency.
      */
     if (con->urgent && con_is_leaf(con)) {
-        con->urgent = false;
+        con_set_urgency(con, false);
         con_update_parents_urgency(con);
         workspace_update_urgent_flag(con_get_workspace(con));
         ipc_send_window_event("urgent", con);
@@ -257,6 +261,46 @@ bool con_is_split(Con *con) {
     }
 }
 
+/*
+ * This will only return true for containers which have some parent with
+ * a tabbed / stacked parent of which they are not the currently focused child.
+ *
+ */
+bool con_is_hidden(Con *con) {
+    Con *current = con;
+
+    /* ascend to the workspace level and memorize the highest-up container
+     * which is stacked or tabbed. */
+    while (current != NULL && current->type != CT_WORKSPACE) {
+        Con *parent = current->parent;
+        if (parent != NULL && (parent->layout == L_TABBED || parent->layout == L_STACKED)) {
+            if (TAILQ_FIRST(&(parent->focus_head)) != current)
+                return true;
+        }
+
+        current = parent;
+    }
+
+    return false;
+}
+
+/*
+ * Returns whether the container or any of its children is sticky.
+ *
+ */
+bool con_is_sticky(Con *con) {
+    if (con->sticky)
+        return true;
+
+    Con *child;
+    TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+        if (con_is_sticky(child))
+            return true;
+    }
+
+    return false;
+}
+
 /*
  * Returns true if this node accepts a window (if the node swallows windows,
  * it might already have swallowed enough and cannot hold any more).
@@ -460,6 +504,94 @@ Con *con_by_frame_id(xcb_window_t frame) {
     return NULL;
 }
 
+/*
+ * Returns the container with the given mark or NULL if no such container
+ * exists.
+ *
+ */
+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)
+            return con;
+    }
+
+    return NULL;
+}
+
+/*
+ * 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) {
+    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);
+    } else {
+        con_mark(con, mark);
+    }
+}
+
+/*
+ * Assigns a mark to the container.
+ *
+ */
+void con_mark(Con *con, const char *mark) {
+    assert(con != NULL);
+    DLOG("Setting mark \"%s\" on con = %p.\n", mark, con);
+
+    FREE(con->mark);
+    con->mark = sstrdup(mark);
+    con->mark_changed = true;
+
+    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;
+        }
+    }
+}
+
+/*
+ * If mark 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) {
+        DLOG("Unmarking all containers.\n");
+        TAILQ_FOREACH(con, &all_cons, all_cons) {
+            if (con->mark == NULL)
+                continue;
+
+            FREE(con->mark);
+            con->mark_changed = true;
+        }
+    } else {
+        DLOG("Removing mark \"%s\".\n", mark);
+        con = con_by_mark(mark);
+        if (con == 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;
+    }
+}
+
 /*
  * Returns the first container below 'con' which wants to swallow this window
  * TODO: priority
@@ -599,14 +731,13 @@ static void con_set_fullscreen_mode(Con *con, fullscreen_mode_t fullscreen_mode)
     if (con->window == NULL)
         return;
 
-    uint32_t values[1];
-    unsigned int num = 0;
-
-    if (con->fullscreen_mode != CF_NONE)
-        values[num++] = A__NET_WM_STATE_FULLSCREEN;
-
-    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id,
-                        A__NET_WM_STATE, XCB_ATOM_ATOM, 32, num, values);
+    if (con->fullscreen_mode != CF_NONE) {
+        DLOG("Setting _NET_WM_STATE_FULLSCREEN for con = %p / window = %d.\n", con, con->window->id);
+        xcb_add_property_atom(conn, con->window->id, A__NET_WM_STATE, A__NET_WM_STATE_FULLSCREEN);
+    } else {
+        DLOG("Removing _NET_WM_STATE_FULLSCREEN for con = %p / window = %d.\n", con, con->window->id);
+        xcb_remove_property_atom(conn, con->window->id, A__NET_WM_STATE, A__NET_WM_STATE_FULLSCREEN);
+    }
 }
 
 /*
@@ -682,93 +813,75 @@ void con_disable_fullscreen(Con *con) {
     con_set_fullscreen_mode(con, CF_NONE);
 }
 
-/*
- * Moves the given container to the currently focused container on the given
- * workspace.
- *
- * The fix_coordinates flag will translate the current coordinates (offset from
- * the monitor position basically) to appropriate coordinates on the
- * destination workspace.
- * Not enabling this behaviour comes in handy when this function gets called by
- * floating_maybe_reassign_ws, which will only "move" a floating window when it
- * *already* changed its coordinates to a different output.
- *
- * The dont_warp flag disables pointer warping and will be set when this
- * function is called while dragging a floating window.
- *
- * TODO: is there a better place for this function?
- *
- */
-void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool dont_warp) {
+static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fix_coordinates, bool dont_warp, bool ignore_focus) {
+    Con *orig_target = target;
+
     /* Prevent moving if this would violate the fullscreen focus restrictions. */
-    if (!con_fullscreen_permits_focusing(workspace)) {
-        LOG("Cannot move out of a fullscreen container");
-        return;
+    Con *target_ws = con_get_workspace(target);
+    if (!con_fullscreen_permits_focusing(target_ws)) {
+        LOG("Cannot move out of a fullscreen container.\n");
+        return false;
     }
 
     if (con_is_floating(con)) {
-        DLOG("Using FLOATINGCON instead\n");
+        DLOG("Container is floating, using parent instead.\n");
         con = con->parent;
     }
 
     Con *source_ws = con_get_workspace(con);
-    if (workspace == source_ws) {
-        DLOG("Not moving, already there\n");
-        return;
-    }
 
     if (con->type == CT_WORKSPACE) {
         /* Re-parent all of the old workspace's floating windows. */
         Con *child;
         while (!TAILQ_EMPTY(&(source_ws->floating_head))) {
             child = TAILQ_FIRST(&(source_ws->floating_head));
-            con_move_to_workspace(child, workspace, true, true);
+            con_move_to_workspace(child, target_ws, true, true, false);
         }
 
         /* If there are no non-floating children, ignore the workspace. */
         if (con_is_leaf(con))
-            return;
+            return false;
 
         con = workspace_encapsulate(con);
         if (con == NULL) {
             ELOG("Workspace failed to move its contents into a container!\n");
-            return;
+            return false;
         }
     }
 
+    /* Save the urgency state so that we can restore it. */
+    bool urgent = con->urgent;
+
     /* Save the current workspace. So we can call workspace_show() by the end
      * of this function. */
     Con *current_ws = con_get_workspace(focused);
 
     Con *source_output = con_get_output(con),
-        *dest_output = con_get_output(workspace);
+        *dest_output = con_get_output(target_ws);
 
     /* 1: save the container which is going to be focused after the current
      * container is moved away */
     Con *focus_next = con_next_focused(con);
 
-    /* 2: get the focused container of this workspace */
-    Con *next = con_descend_focused(workspace);
-
-    /* 3: we go up one level, but only when next is a normal container */
-    if (next->type != CT_WORKSPACE) {
-        DLOG("next originally = %p / %s / type %d\n", next, next->name, next->type);
-        next = next->parent;
+    /* 2: we go up one level, but only when target is a normal container */
+    if (target->type != CT_WORKSPACE) {
+        DLOG("target originally = %p / %s / type %d\n", target, target->name, target->type);
+        target = target->parent;
     }
 
-    /* 4: if the target container is floating, we get the workspace instead.
+    /* 3: if the target container is floating, we get the workspace instead.
      * Only tiling windows need to get inserted next to the current container.
      * */
-    Con *floatingcon = con_inside_floating(next);
+    Con *floatingcon = con_inside_floating(target);
     if (floatingcon != NULL) {
         DLOG("floatingcon, going up even further\n");
-        next = floatingcon->parent;
+        target = floatingcon->parent;
     }
 
     if (con->type == CT_FLOATING_CON) {
-        Con *ws = con_get_workspace(next);
+        Con *ws = con_get_workspace(target);
         DLOG("This is a floating window, using workspace %p / %s\n", ws, ws->name);
-        next = ws;
+        target = ws;
     }
 
     if (source_output != dest_output) {
@@ -782,8 +895,8 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
         /* If moving to a visible workspace, call show so it can be considered
          * focused. Must do before attaching because workspace_show checks to see
          * if focused container is in its area. */
-        if (workspace_is_visible(workspace)) {
-            workspace_show(workspace);
+        if (!ignore_focus && workspace_is_visible(target_ws)) {
+            workspace_show(target_ws);
 
             /* Don’t warp if told so (when dragging floating windows with the
              * mouse for example) */
@@ -796,29 +909,30 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
 
     /* If moving a fullscreen container and the destination already has a
      * fullscreen window on it, un-fullscreen the target's fullscreen con. */
-    Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
+    Con *fullscreen = con_get_fullscreen_con(target_ws, CF_OUTPUT);
     if (con->fullscreen_mode != CF_NONE && fullscreen != NULL) {
         con_toggle_fullscreen(fullscreen, CF_OUTPUT);
         fullscreen = NULL;
     }
 
-    DLOG("Re-attaching container to %p / %s\n", next, next->name);
-    /* 5: re-attach the con to the parent of this focused container */
+    DLOG("Re-attaching container to %p / %s\n", target, target->name);
+    /* 4: re-attach the con to the parent of this focused container */
     Con *parent = con->parent;
     con_detach(con);
-    con_attach(con, next, false);
+    _con_attach(con, target, behind_focused ? NULL : orig_target, !behind_focused);
 
-    /* 6: fix the percentages */
+    /* 5: fix the percentages */
     con_fix_percent(parent);
     con->percent = 0.0;
-    con_fix_percent(next);
+    con_fix_percent(target);
 
-    /* 7: focus the con on the target workspace, but only within that
+    /* 6: focus the con on the target workspace, but only within that
      * workspace, that is, don’t move focus away if the target workspace is
      * invisible.
      * We don’t focus the con for i3 pseudo workspaces like __i3_scratch and
-     * we don’t focus when there is a fullscreen con on that workspace. */
-    if (!con_is_internal(workspace) && !fullscreen) {
+     * we don’t focus when there is a fullscreen con on that workspace. We
+     * also don't do it if the caller requested to ignore focus. */
+    if (!ignore_focus && !con_is_internal(target_ws) && !fullscreen) {
         /* We need to save the focused workspace on the output in case the
          * new workspace is hidden and it's necessary to immediately switch
          * back to the originally-focused workspace. */
@@ -830,20 +944,21 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
             con_focus(old_focus);
     }
 
-    /* 8: when moving to another workspace, we leave the focus on the current
+    /* 7: when moving to another workspace, we leave the focus on the current
      * workspace. (see also #809) */
 
     /* Descend focus stack in case focus_next is a workspace which can
      * occur if we move to the same workspace.  Also show current workspace
      * to ensure it is focused. */
-    workspace_show(current_ws);
+    if (!ignore_focus)
+        workspace_show(current_ws);
 
     /* Set focus only if con was on current workspace before moving.
      * Otherwise we would give focus to some window on different workspace. */
-    if (source_ws == current_ws)
+    if (!ignore_focus && source_ws == current_ws)
         con_focus(con_descend_focused(focus_next));
 
-    /* If anything within the container is associated with a startup sequence,
+    /* 8. If anything within the container is associated with a startup sequence,
      * delete it so child windows won't be created on the old workspace. */
     struct Startup_Sequence *sequence;
     xcb_get_property_cookie_t cookie;
@@ -877,7 +992,81 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
 
     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);
+    }
+
     ipc_send_window_event("move", con);
+    return true;
+}
+
+/*
+ * Moves the given container to the given mark.
+ *
+ */
+bool con_move_to_mark(Con *con, const char *mark) {
+    Con *target = con_by_mark(mark);
+    if (target == NULL) {
+        DLOG("found no container with mark \"%s\"\n", mark);
+        return false;
+    }
+
+    /* For floating target containers, we just send the window to the same workspace. */
+    if (con_is_floating(target)) {
+        DLOG("target container is floating, moving container to target's workspace.\n");
+        con_move_to_workspace(con, con_get_workspace(target), true, false, false);
+        return true;
+    }
+
+    /* For split containers, we use the currently focused container within it.
+     * This allows setting marks on, e.g., tabbed containers which will move
+     * con to a new tab behind the focused tab. */
+    if (con_is_split(target)) {
+        DLOG("target is a split container, descending to the currently focused child.\n");
+        target = TAILQ_FIRST(&(target->focus_head));
+    }
+
+    if (con == target || con == target->parent) {
+        DLOG("cannot move the container to or inside itself, aborting.\n");
+        return false;
+    }
+
+    return _con_move_to_con(con, target, false, true, false, false);
+}
+
+/*
+ * Moves the given container to the currently focused container on the given
+ * workspace.
+ *
+ * The fix_coordinates flag will translate the current coordinates (offset from
+ * the monitor position basically) to appropriate coordinates on the
+ * destination workspace.
+ * Not enabling this behaviour comes in handy when this function gets called by
+ * floating_maybe_reassign_ws, which will only "move" a floating window when it
+ * *already* changed its coordinates to a different output.
+ *
+ * The dont_warp flag disables pointer warping and will be set when this
+ * function is called while dragging a floating window.
+ *
+ * If ignore_focus is set, the container will be moved without modifying focus
+ * at all.
+ *
+ * TODO: is there a better place for this function?
+ *
+ */
+void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool dont_warp, bool ignore_focus) {
+    assert(workspace->type == CT_WORKSPACE);
+
+    Con *source_ws = con_get_workspace(con);
+    if (workspace == source_ws) {
+        DLOG("Not moving, already there\n");
+        return;
+    }
+
+    Con *target = con_descend_focused(workspace);
+    _con_move_to_con(con, target, true, fix_coordinates, dont_warp, ignore_focus);
 }
 
 /*
@@ -1150,18 +1339,13 @@ Rect con_border_style_rect(Con *con) {
     int border_style = con_border_style(con);
     if (border_style == BS_NONE)
         return (Rect){0, 0, 0, 0};
-    borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
     if (border_style == BS_NORMAL) {
         result = (Rect){border_width, 0, -(2 * border_width), -(border_width)};
     } else {
         result = (Rect){border_width, border_width, -(2 * border_width), -(2 * border_width)};
     }
 
-    /* Floating windows are never adjacent to any other window, so
-       don’t hide their border(s). This prevents bug #998. */
-    if (con_is_floating(con))
-        return result;
-
+    borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
     if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) {
         result.x -= border_width;
         result.width += border_width;
@@ -1185,6 +1369,11 @@ Rect con_border_style_rect(Con *con) {
  */
 adjacent_t con_adjacent_borders(Con *con) {
     adjacent_t result = ADJ_NONE;
+    /* Floating windows are never adjacent to any other window, so
+       don’t hide their border(s). This prevents bug #998. */
+    if (con_is_floating(con))
+        return result;
+
     Con *workspace = con_get_workspace(con);
     if (con->rect.x == workspace->rect.x)
         result |= ADJ_LEFT_SCREEN_EDGE;
@@ -1346,11 +1535,7 @@ void con_set_layout(Con *con, layout_t layout) {
          * with an orientation). Since we switched to splith/splitv layouts,
          * using the "default" layout (which "only" should happen when using
          * legacy configs) is using the last split layout (either splith or
-         * splitv) in order to still do the same thing.
-         *
-         * Starting from v4.6 though, we will nag users about using "layout
-         * default", and in v4.9 we will remove it entirely (with an
-         * appropriate i3-migrate-config mechanism). */
+         * splitv) in order to still do the same thing. */
         con->layout = con->last_split_layout;
         /* In case last_split_layout was not initialized… */
         if (con->layout == L_DEFAULT)
@@ -1632,13 +1817,13 @@ void con_update_parents_urgency(Con *con) {
  *
  */
 void con_set_urgency(Con *con, bool urgent) {
-    if (focused == con) {
+    if (urgent && focused == con) {
         DLOG("Ignoring urgency flag for current client\n");
-        con->window->urgent.tv_sec = 0;
-        con->window->urgent.tv_usec = 0;
         return;
     }
 
+    const bool old_urgent = con->urgent;
+
     if (con->urgency_timer == NULL) {
         con->urgent = urgent;
     } else
@@ -1662,7 +1847,7 @@ void con_set_urgency(Con *con, bool urgent) {
     if ((ws = con_get_workspace(con)) != NULL)
         workspace_update_urgent_flag(ws);
 
-    if (con->urgent == urgent) {
+    if (con->urgent != old_urgent) {
         LOG("Urgency flag changed to %d\n", con->urgent);
         ipc_send_window_event("urgent", con);
     }
index 6f906b8ce02e7218828ae2f449bf3223a82dd205..d8db85e63c0c630aa433f780a3ade29045bfc9ee 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * config.c: Configuration file (calling the parser (src/config_parser.c) with
  *           the correct path, switching key bindings mode).
@@ -39,73 +39,6 @@ void update_barconfig() {
     }
 }
 
-/*
- * Get the path of the first configuration file found. If override_configpath
- * is specified, that path is returned and saved for further calls. Otherwise,
- * checks the home directory first, then the system directory first, always
- * taking into account the XDG Base Directory Specification ($XDG_CONFIG_HOME,
- * $XDG_CONFIG_DIRS)
- *
- */
-static char *get_config_path(const char *override_configpath) {
-    char *xdg_config_home, *xdg_config_dirs, *config_path;
-
-    static const char *saved_configpath = NULL;
-
-    if (override_configpath != NULL) {
-        saved_configpath = override_configpath;
-        return sstrdup(saved_configpath);
-    }
-
-    if (saved_configpath != NULL)
-        return sstrdup(saved_configpath);
-
-    /* 1: check the traditional path under the home directory */
-    config_path = resolve_tilde("~/.i3/config");
-    if (path_exists(config_path))
-        return config_path;
-    free(config_path);
-
-    /* 2: check for $XDG_CONFIG_HOME/i3/config */
-    if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL)
-        xdg_config_home = "~/.config";
-
-    xdg_config_home = resolve_tilde(xdg_config_home);
-    sasprintf(&config_path, "%s/i3/config", xdg_config_home);
-    free(xdg_config_home);
-
-    if (path_exists(config_path))
-        return config_path;
-    free(config_path);
-
-    /* 3: check the traditional path under /etc */
-    config_path = SYSCONFDIR "/i3/config";
-    if (path_exists(config_path))
-        return sstrdup(config_path);
-
-    /* 4: check for $XDG_CONFIG_DIRS/i3/config */
-    if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL)
-        xdg_config_dirs = "/etc/xdg";
-
-    char *buf = sstrdup(xdg_config_dirs);
-    char *tok = strtok(buf, ":");
-    while (tok != NULL) {
-        tok = resolve_tilde(tok);
-        sasprintf(&config_path, "%s/i3/config", tok);
-        free(tok);
-        if (path_exists(config_path)) {
-            free(buf);
-            return config_path;
-        }
-        free(config_path);
-        tok = strtok(NULL, ":");
-    }
-    free(buf);
-
-    die("Unable to find the configuration file (looked at "
-        "~/.i3/config, $XDG_CONFIG_HOME/i3/config, " SYSCONFDIR "/i3/config and $XDG_CONFIG_DIRS/i3/config)");
-}
-
 /*
  * Finds the configuration file to use (either the one specified by
  * override_configpath), the user’s one or the system default) and calls
@@ -113,14 +46,19 @@ static char *get_config_path(const char *override_configpath) {
  *
  */
 bool parse_configuration(const char *override_configpath, bool use_nagbar) {
-    char *path = get_config_path(override_configpath);
+    char *path = get_config_path(override_configpath, true);
+    if (path == NULL) {
+        die("Unable to find the configuration file (looked at "
+            "~/.i3/config, $XDG_CONFIG_HOME/i3/config, " SYSCONFDIR "/i3/config and $XDG_CONFIG_DIRS/i3/config)");
+    }
+
     LOG("Parsing configfile %s\n", path);
     FREE(current_configpath);
     current_configpath = path;
 
     /* initialize default bindings if we're just validating the config file */
     if (!use_nagbar && bindings == NULL) {
-        bindings = scalloc(sizeof(struct bindings_head));
+        bindings = scalloc(1, sizeof(struct bindings_head));
         TAILQ_INIT(bindings);
     }
 
@@ -192,6 +130,9 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
             FREE(barconfig->colors.urgent_workspace_border);
             FREE(barconfig->colors.urgent_workspace_bg);
             FREE(barconfig->colors.urgent_workspace_text);
+            FREE(barconfig->colors.binding_mode_border);
+            FREE(barconfig->colors.binding_mode_bg);
+            FREE(barconfig->colors.binding_mode_text);
             TAILQ_REMOVE(&barconfigs, barconfig, configs);
             FREE(barconfig);
         }
@@ -214,9 +155,9 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
 
     SLIST_INIT(&modes);
 
-    struct Mode *default_mode = scalloc(sizeof(struct Mode));
+    struct Mode *default_mode = scalloc(1, sizeof(struct Mode));
     default_mode->name = sstrdup("default");
-    default_mode->bindings = scalloc(sizeof(struct bindings_head));
+    default_mode->bindings = scalloc(1, sizeof(struct bindings_head));
     TAILQ_INIT(default_mode->bindings);
     SLIST_INSERT_HEAD(&modes, default_mode, modes);
 
@@ -252,6 +193,8 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
     INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888", "#000000");
     INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff", "#000000");
 
+    config.show_marks = true;
+
     config.default_border = BS_NORMAL;
     config.default_floating_border = BS_NORMAL;
     config.default_border_width = logical_px(2);
@@ -267,7 +210,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
 
     if (reload) {
         translate_keysyms();
-        grab_all_keys(conn, false);
+        grab_all_keys(conn);
     }
 
     if (config.font.type == FONT_TYPE_NONE) {
index c8b25c7645f6cfc7e8c2d8ab210101138719957d..d772387dc7e61260b0af01950e9532568fd3642e 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * config_directives.c: all config storing functions (see config_parser.c)
  *
@@ -89,6 +89,31 @@ CFGFUN(criteria_add, const char *ctype, const char *cvalue) {
         return;
     }
 
+    if (strcmp(ctype, "window_type") == 0) {
+        if (strcasecmp(cvalue, "normal") == 0)
+            current_match->window_type = A__NET_WM_WINDOW_TYPE_NORMAL;
+        else if (strcasecmp(cvalue, "dialog") == 0)
+            current_match->window_type = A__NET_WM_WINDOW_TYPE_DIALOG;
+        else if (strcasecmp(cvalue, "utility") == 0)
+            current_match->window_type = A__NET_WM_WINDOW_TYPE_UTILITY;
+        else if (strcasecmp(cvalue, "toolbar") == 0)
+            current_match->window_type = A__NET_WM_WINDOW_TYPE_TOOLBAR;
+        else if (strcasecmp(cvalue, "splash") == 0)
+            current_match->window_type = A__NET_WM_WINDOW_TYPE_SPLASH;
+        else if (strcasecmp(cvalue, "menu") == 0)
+            current_match->window_type = A__NET_WM_WINDOW_TYPE_MENU;
+        else if (strcasecmp(cvalue, "dropdown_menu") == 0)
+            current_match->window_type = A__NET_WM_WINDOW_TYPE_DROPDOWN_MENU;
+        else if (strcasecmp(cvalue, "popup_menu") == 0)
+            current_match->window_type = A__NET_WM_WINDOW_TYPE_POPUP_MENU;
+        else if (strcasecmp(cvalue, "tooltip") == 0)
+            current_match->window_type = A__NET_WM_WINDOW_TYPE_TOOLTIP;
+        else
+            ELOG("unknown window_type value \"%s\"\n", cvalue);
+
+        return;
+    }
+
     if (strcmp(ctype, "con_mark") == 0) {
         current_match->mark = regex_new(cvalue);
         return;
@@ -112,6 +137,11 @@ CFGFUN(criteria_add, const char *ctype, const char *cvalue) {
         return;
     }
 
+    if (strcmp(ctype, "workspace") == 0) {
+        current_match->workspace = regex_new(cvalue);
+        return;
+    }
+
     ELOG("Unknown criterion: %s\n", ctype);
 }
 
@@ -131,32 +161,40 @@ static bool eval_boolstr(const char *str) {
 }
 
 /*
- * A utility function to convert a string of modifiers to the corresponding bit
- * mask.
+ * A utility function to convert a string containing the group and modifiers to
+ * the corresponding bit mask.
  */
-uint32_t modifiers_from_str(const char *str) {
+i3_event_state_mask_t event_state_from_str(const char *str) {
     /* It might be better to use strtok() here, but the simpler strstr() should
      * do for now. */
-    uint32_t result = 0;
+    i3_event_state_mask_t result = 0;
     if (str == NULL)
         return result;
     if (strstr(str, "Mod1") != NULL)
-        result |= BIND_MOD1;
+        result |= XCB_KEY_BUT_MASK_MOD_1;
     if (strstr(str, "Mod2") != NULL)
-        result |= BIND_MOD2;
+        result |= XCB_KEY_BUT_MASK_MOD_2;
     if (strstr(str, "Mod3") != NULL)
-        result |= BIND_MOD3;
+        result |= XCB_KEY_BUT_MASK_MOD_3;
     if (strstr(str, "Mod4") != NULL)
-        result |= BIND_MOD4;
+        result |= XCB_KEY_BUT_MASK_MOD_4;
     if (strstr(str, "Mod5") != NULL)
-        result |= BIND_MOD5;
+        result |= XCB_KEY_BUT_MASK_MOD_5;
     if (strstr(str, "Control") != NULL ||
         strstr(str, "Ctrl") != NULL)
-        result |= BIND_CONTROL;
+        result |= XCB_KEY_BUT_MASK_CONTROL;
     if (strstr(str, "Shift") != NULL)
-        result |= BIND_SHIFT;
-    if (strstr(str, "Mode_switch") != NULL)
-        result |= BIND_MODE_SWITCH;
+        result |= XCB_KEY_BUT_MASK_SHIFT;
+
+    if (strstr(str, "Group1") != NULL)
+        result |= (I3_XKB_GROUP_MASK_1 << 16);
+    if (strstr(str, "Group2") != NULL ||
+        strstr(str, "Mode_switch") != NULL)
+        result |= (I3_XKB_GROUP_MASK_2 << 16);
+    if (strstr(str, "Group3") != NULL)
+        result |= (I3_XKB_GROUP_MASK_3 << 16);
+    if (strstr(str, "Group4") != NULL)
+        result |= (I3_XKB_GROUP_MASK_4 << 16);
     return result;
 }
 
@@ -171,8 +209,8 @@ CFGFUN(font, const char *font) {
     font_pattern = sstrdup(font);
 }
 
-CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *whole_window, const char *command) {
-    configure_binding(bindtype, modifiers, key, release, whole_window, command, DEFAULT_BINDING_MODE);
+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);
 }
 
 /*******************************************************************************
@@ -181,8 +219,8 @@ CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, co
 
 static char *current_mode;
 
-CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *whole_window, const char *command) {
-    configure_binding(bindtype, modifiers, key, release, whole_window, command, current_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) {
+    configure_binding(bindtype, modifiers, key, release, border, whole_window, command, current_mode);
 }
 
 CFGFUN(enter_mode, const char *modename) {
@@ -212,7 +250,7 @@ CFGFUN(for_window, const char *command) {
         return;
     }
     DLOG("\t should execute command %s for the criteria mentioned above\n", command);
-    Assignment *assignment = scalloc(sizeof(Assignment));
+    Assignment *assignment = scalloc(1, sizeof(Assignment));
     assignment->type = A_COMMAND;
     match_copy(&(assignment->match), current_match);
     assignment->dest.command = sstrdup(command);
@@ -230,7 +268,7 @@ CFGFUN(floating_maximum_size, const long width, const long height) {
 }
 
 CFGFUN(floating_modifier, const char *modifiers) {
-    config.floating_modifier = modifiers_from_str(modifiers);
+    config.floating_modifier = event_state_from_str(modifiers);
 }
 
 CFGFUN(default_orientation, const char *orientation) {
@@ -329,6 +367,27 @@ CFGFUN(force_display_urgency_hint, const long duration_ms) {
     config.workspace_urgency_timer = duration_ms / 1000.0;
 }
 
+CFGFUN(focus_on_window_activation, const char *mode) {
+    if (strcmp(mode, "smart") == 0)
+        config.focus_on_window_activation = FOWA_SMART;
+    else if (strcmp(mode, "urgent") == 0)
+        config.focus_on_window_activation = FOWA_URGENT;
+    else if (strcmp(mode, "focus") == 0)
+        config.focus_on_window_activation = FOWA_FOCUS;
+    else if (strcmp(mode, "none") == 0)
+        config.focus_on_window_activation = FOWA_NONE;
+    else {
+        ELOG("Unknown focus_on_window_activation mode \"%s\", ignoring it.\n", mode);
+        return;
+    }
+
+    DLOG("Set new focus_on_window_activation mode = %i.\n", config.focus_on_window_activation);
+}
+
+CFGFUN(show_marks, const char *value) {
+    config.show_marks = eval_boolstr(value);
+}
+
 CFGFUN(workspace, const char *workspace, const char *output) {
     DLOG("Assigning workspace \"%s\" to output \"%s\"\n", workspace, output);
     /* Check for earlier assignments of the same workspace so that we
@@ -345,7 +404,7 @@ CFGFUN(workspace, const char *workspace, const char *output) {
         }
     }
     if (!duplicate) {
-        assignment = scalloc(sizeof(struct Workspace_Assignment));
+        assignment = scalloc(1, sizeof(struct Workspace_Assignment));
         assignment->name = sstrdup(workspace);
         assignment->output = sstrdup(output);
         TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
@@ -402,14 +461,27 @@ CFGFUN(assign, const char *workspace) {
         ELOG("Match is empty, ignoring this assignment\n");
         return;
     }
-    DLOG("new assignment, using above criteria, to workspace %s\n", workspace);
-    Assignment *assignment = scalloc(sizeof(Assignment));
+    DLOG("New assignment, using above criteria, to workspace \"%s\".\n", workspace);
+    Assignment *assignment = scalloc(1, sizeof(Assignment));
     match_copy(&(assignment->match), current_match);
     assignment->type = A_TO_WORKSPACE;
     assignment->dest.workspace = sstrdup(workspace);
     TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
 }
 
+CFGFUN(no_focus) {
+    if (match_is_empty(current_match)) {
+        ELOG("Match is empty, ignoring this assignment\n");
+        return;
+    }
+
+    DLOG("New assignment, using above criteria, to ignore focus on manage.\n");
+    Assignment *assignment = scalloc(1, sizeof(Assignment));
+    match_copy(&(assignment->match), current_match);
+    assignment->type = A_NO_FOCUS;
+    TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
+}
+
 /*******************************************************************************
  * Bar configuration (i3bar)
  ******************************************************************************/
@@ -467,14 +539,44 @@ CFGFUN(bar_modifier, const char *modifier) {
         current_bar.modifier = M_SHIFT;
 }
 
+static void bar_configure_binding(const char *button, const char *command) {
+    if (strncasecmp(button, "button", strlen("button")) != 0) {
+        ELOG("Bindings for a bar can only be mouse bindings, not \"%s\", ignoring.\n", button);
+        return;
+    }
+
+    int input_code = atoi(button + strlen("button"));
+    if (input_code < 1) {
+        ELOG("Button \"%s\" does not seem to be in format 'buttonX'.\n", button);
+        return;
+    }
+
+    struct Barbinding *current;
+    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;
+        }
+    }
+
+    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);
+}
+
 CFGFUN(bar_wheel_up_cmd, const char *command) {
-    FREE(current_bar.wheel_up_cmd);
-    current_bar.wheel_up_cmd = sstrdup(command);
+    ELOG("'wheel_up_cmd' is deprecated. Please us 'bindsym button4 %s' instead.\n", command);
+    bar_configure_binding("button4", command);
 }
 
 CFGFUN(bar_wheel_down_cmd, const char *command) {
-    FREE(current_bar.wheel_down_cmd);
-    current_bar.wheel_down_cmd = sstrdup(command);
+    ELOG("'wheel_down_cmd' is deprecated. Please us 'bindsym button5 %s' instead.\n", command);
+    bar_configure_binding("button5", command);
+}
+
+CFGFUN(bar_bindsym, const char *button, const char *command) {
+    bar_configure_binding(button, command);
 }
 
 CFGFUN(bar_position, const char *position) {
@@ -507,6 +609,7 @@ CFGFUN(bar_color, const char *colorclass, const char *border, const char *backgr
     APPLY_COLORS(active_workspace);
     APPLY_COLORS(inactive_workspace);
     APPLY_COLORS(urgent_workspace);
+    APPLY_COLORS(binding_mode);
 
 #undef APPLY_COLORS
 }
@@ -521,6 +624,10 @@ CFGFUN(bar_tray_output, const char *output) {
     current_bar.tray_output = sstrdup(output);
 }
 
+CFGFUN(bar_tray_padding, const long 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);
@@ -547,6 +654,11 @@ CFGFUN(bar_strip_workspace_numbers, const char *value) {
     current_bar.strip_workspace_numbers = eval_boolstr(value);
 }
 
+CFGFUN(bar_start) {
+    TAILQ_INIT(&(current_bar.bar_bindings));
+    current_bar.tray_padding = 2;
+}
+
 CFGFUN(bar_finish) {
     DLOG("\t new bar configuration finished, saving.\n");
     /* Generate a unique ID for this bar if not already configured */
@@ -561,7 +673,7 @@ CFGFUN(bar_finish) {
 
     /* Copy the current (static) structure into a dynamically allocated
      * one, then cleanup our static one. */
-    Barconfig *bar_config = scalloc(sizeof(Barconfig));
+    Barconfig *bar_config = scalloc(1, sizeof(Barconfig));
     memcpy(bar_config, &current_bar, sizeof(Barconfig));
     TAILQ_INSERT_TAIL(&barconfigs, bar_config, configs);
 
index eef03cae238e0cf7455fd64ca1b18658e47d977f..ea00412d78b2d2533c5dd0b87486aaa3da7e9f4e 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * config_parser.c: hand-written parser to parse configuration directives.
  *
@@ -202,9 +202,9 @@ static TAILQ_HEAD(criteria_head, criterion) criteria =
  */
 static void push_criterion(void *unused_criteria, const char *type,
                            const char *value) {
-    struct criterion *criterion = malloc(sizeof(struct criterion));
-    criterion->type = strdup(type);
-    criterion->value = strdup(value);
+    struct criterion *criterion = smalloc(sizeof(struct criterion));
+    criterion->type = sstrdup(type);
+    criterion->value = sstrdup(value);
     TAILQ_INSERT_TAIL(&criteria, criterion, criteria);
 }
 
@@ -414,7 +414,7 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
                     }
                 }
                 if (walk != beginning) {
-                    char *str = scalloc(walk - beginning + 1);
+                    char *str = scalloc(walk - beginning + 1, 1);
                     /* We copy manually to handle escaping of characters. */
                     int inpos, outpos;
                     for (inpos = 0, outpos = 0;
@@ -519,7 +519,7 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
 
             /* Contains the same amount of characters as 'input' has, but with
              * the unparseable part highlighted using ^ characters. */
-            char *position = scalloc(strlen(error_line) + 1);
+            char *position = scalloc(strlen(error_line) + 1, 1);
             const char *copywalk;
             for (copywalk = error_line;
                  *copywalk != '\n' && *copywalk != '\r' && *copywalk != '\0';
@@ -789,12 +789,12 @@ static char *migrate_config(char *input, off_t size) {
 
     /* read the script’s output */
     int conv_size = 65535;
-    char *converted = malloc(conv_size);
+    char *converted = smalloc(conv_size);
     int read_bytes = 0, ret;
     do {
         if (read_bytes == conv_size) {
             conv_size += 65535;
-            converted = realloc(converted, conv_size);
+            converted = srealloc(converted, conv_size);
         }
         ret = read(readpipe[0], converted + read_bytes, conv_size - read_bytes);
         if (ret == -1) {
@@ -837,11 +837,11 @@ static char *migrate_config(char *input, off_t size) {
  */
 bool parse_file(const char *f, bool use_nagbar) {
     SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
-    int fd, ret, read_bytes = 0;
+    int fd;
     struct stat stbuf;
     char *buf;
     FILE *fstr;
-    char buffer[1026], key[512], value[512];
+    char buffer[4096], key[512], value[512], *continuation = NULL;
 
     if ((fd = open(f, O_RDONLY)) == -1)
         die("Could not open configuration file: %s\n", strerror(errno));
@@ -849,28 +849,31 @@ bool parse_file(const char *f, bool use_nagbar) {
     if (fstat(fd, &stbuf) == -1)
         die("Could not fstat file: %s\n", strerror(errno));
 
-    buf = scalloc((stbuf.st_size + 1) * sizeof(char));
-    while (read_bytes < stbuf.st_size) {
-        if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0)
-            die("Could not read(): %s\n", strerror(errno));
-        read_bytes += ret;
-    }
-
-    if (lseek(fd, 0, SEEK_SET) == (off_t)-1)
-        die("Could not lseek: %s\n", strerror(errno));
+    buf = scalloc(stbuf.st_size + 1, 1);
 
     if ((fstr = fdopen(fd, "r")) == NULL)
         die("Could not fdopen: %s\n", strerror(errno));
 
     while (!feof(fstr)) {
-        if (fgets(buffer, 1024, fstr) == NULL) {
+        if (!continuation)
+            continuation = buffer;
+        if (fgets(continuation, sizeof(buffer) - (continuation - buffer), fstr) == NULL) {
             if (feof(fstr))
                 break;
             die("Could not read configuration file\n");
         }
+        if (buffer[strlen(buffer) - 1] != '\n') {
+            ELOG("Your line continuation is too long, it exceeds %zd bytes\n", sizeof(buffer));
+        }
+        continuation = strstr(buffer, "\\\n");
+        if (continuation) {
+            continue;
+        }
+
+        strncpy(buf + strlen(buf), buffer, strlen(buffer) + 1);
 
         /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */
-        if (sscanf(buffer, "%s %[^\n]", key, value) < 1 ||
+        if (sscanf(buffer, "%511s %511[^\n]", key, value) < 1 ||
             key[0] == '#' || strlen(key) < 3)
             continue;
 
@@ -894,7 +897,7 @@ bool parse_file(const char *f, bool use_nagbar) {
             while (*v_value == '\t' || *v_value == ' ')
                 v_value++;
 
-            struct Variable *new = scalloc(sizeof(struct Variable));
+            struct Variable *new = scalloc(1, sizeof(struct Variable));
             new->key = sstrdup(v_key);
             new->value = sstrdup(v_value);
             SLIST_INSERT_HEAD(&variables, new, variables);
@@ -928,7 +931,7 @@ bool parse_file(const char *f, bool use_nagbar) {
     /* Then, allocate a new buffer and copy the file over to the new one,
      * but replace occurences of our variables */
     char *walk = buf, *destwalk;
-    char *new = smalloc((stbuf.st_size + extra_bytes + 1) * sizeof(char));
+    char *new = smalloc(stbuf.st_size + extra_bytes + 1);
     destwalk = new;
     while (walk < (buf + stbuf.st_size)) {
         /* Find the next variable */
@@ -987,16 +990,18 @@ bool parse_file(const char *f, bool use_nagbar) {
         }
     }
 
-    context = scalloc(sizeof(struct context));
+    context = scalloc(1, sizeof(struct context));
     context->filename = f;
 
     struct ConfigResultIR *config_output = parse_config(new, context);
     yajl_gen_free(config_output->json_gen);
 
+    extract_workspace_names_from_bindings();
     check_for_duplicate_bindings(context);
+    reorder_bindings();
 
     if (use_nagbar && (context->has_errors || context->has_warnings)) {
-        ELOG("FYI: You are using i3 version " I3_VERSION "\n");
+        ELOG("FYI: You are using i3 version %s\n", i3_version);
         if (version == 3)
             ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n");
 
index f3bc106de210681639a1125ba42634e52409e11a..bb880b2f52e1ff67b7fe6c4e82510c51afdb6c75 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * debug.c: Debugging functions, especially FormatEvent, which prints unhandled
  *          events.  This code is from xcb-util.
index a539dad366adf3b447ccb61f817cfdf08cf35abc..c82610ed38ff2856975a8ae37838b947a4f23e1b 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * display_version.c: displays the running i3 version, runs as part of
  *                    i3 --moreversion.
 #include <sys/socket.h>
 #include <sys/un.h>
 #include <fcntl.h>
+#include <time.h>
 #include "all.h"
 
-static bool human_readable_key;
-static char *human_readable_version;
+static bool human_readable_key, loaded_config_file_name_key;
+static char *human_readable_version, *loaded_config_file_name;
 
 static int version_string(void *ctx, const unsigned char *val, size_t len) {
     if (human_readable_key)
         sasprintf(&human_readable_version, "%.*s", (int)len, val);
+    if (loaded_config_file_name_key)
+        sasprintf(&loaded_config_file_name, "%.*s", (int)len, val);
     return 1;
 }
 
 static int version_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) {
     human_readable_key = (stringlen == strlen("human_readable") &&
                           strncmp((const char *)stringval, "human_readable", strlen("human_readable")) == 0);
+    loaded_config_file_name_key = (stringlen == strlen("loaded_config_file_name") &&
+                                   strncmp((const char *)stringval, "loaded_config_file_name", strlen("loaded_config_file_name")) == 0);
     return 1;
 }
 
@@ -104,6 +109,21 @@ void display_running_version(void) {
 
     printf("\rRunning i3 version: %s (pid %s)\n", human_readable_version, pid_from_atom);
 
+    if (loaded_config_file_name) {
+        struct stat sb;
+        time_t now;
+        char mtime[64];
+        printf("Loaded i3 config: %s", loaded_config_file_name);
+        if (stat(loaded_config_file_name, &sb) == -1) {
+            printf("\n");
+            ELOG("Cannot stat config file \"%s\"\n", loaded_config_file_name);
+        } else {
+            strftime(mtime, sizeof(mtime), "%c", localtime(&(sb.st_mtime)));
+            time(&now);
+            printf(" (Last modified: %s, %.f seconds ago)\n", mtime, difftime(now, sb.st_mtime));
+        }
+    }
+
 #ifdef __linux__
     size_t destpath_size = 1024;
     ssize_t linksize;
index 844a0db921ada15d78cea84818ef82b282da19ac..b2260d64301e45e330f9950d289506b1c35ce5e2 100644 (file)
@@ -4,13 +4,15 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * ewmh.c: Get/set certain EWMH properties easily.
  *
  */
 #include "all.h"
 
+xcb_window_t ewmh_window;
+
 /*
  * Updates _NET_CURRENT_DESKTOP with the current desktop number.
  *
@@ -148,6 +150,18 @@ void ewmh_update_active_window(xcb_window_t window) {
                         A__NET_ACTIVE_WINDOW, XCB_ATOM_WINDOW, 32, 1, &window);
 }
 
+/*
+ * Updates _NET_WM_VISIBLE_NAME.
+ *
+ */
+void ewmh_update_visible_name(xcb_window_t window, const char *name) {
+    if (name != NULL) {
+        xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window, A__NET_WM_VISIBLE_NAME, A_UTF8_STRING, 8, strlen(name), name);
+    } else {
+        xcb_delete_property(conn, window, A__NET_WM_VISIBLE_NAME);
+    }
+}
+
 /*
  * i3 currently does not support _NET_WORKAREA, because it does not correspond
  * to i3’s concept of workspaces. See also:
@@ -199,6 +213,20 @@ void ewmh_update_client_list_stacking(xcb_window_t *stack, int num_windows) {
         stack);
 }
 
+/*
+ * Set or remove _NET_WM_STATE_STICKY on the window.
+ *
+ */
+void ewmh_update_sticky(xcb_window_t window, bool sticky) {
+    if (sticky) {
+        DLOG("Setting _NET_WM_STATE_STICKY for window = %d.\n", window);
+        xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_STICKY);
+    } else {
+        DLOG("Removing _NET_WM_STATE_STICKY for window = %d.\n", window);
+        xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_STICKY);
+    }
+}
+
 /*
  * Set up the EWMH hints on the root window.
  *
@@ -215,25 +243,30 @@ void ewmh_setup_hints(void) {
      * present, a child window has to be created (and kept alive as long as the
      * window manager is running) which has the _NET_SUPPORTING_WM_CHECK and
      * _NET_WM_ATOMS. */
-    xcb_window_t child_window = xcb_generate_id(conn);
+    ewmh_window = xcb_generate_id(conn);
+    /* We create the window and put it at (-1, -1) so that it is off-screen. */
     xcb_create_window(
         conn,
         XCB_COPY_FROM_PARENT,        /* depth */
-        child_window,                /* window id */
+        ewmh_window,                 /* window id */
         root,                        /* parent */
-        0, 0, 1, 1,                  /* dimensions (x, y, w, h) */
+        -1, -1, 1, 1,                /* dimensions (x, y, w, h) */
         0,                           /* border */
         XCB_WINDOW_CLASS_INPUT_ONLY, /* window class */
         XCB_COPY_FROM_PARENT,        /* visual */
-        0,
-        NULL);
-    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, child_window, A__NET_SUPPORTING_WM_CHECK, XCB_ATOM_WINDOW, 32, 1, &child_window);
-    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, child_window, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3");
-    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTING_WM_CHECK, XCB_ATOM_WINDOW, 32, 1, &child_window);
+        XCB_CW_OVERRIDE_REDIRECT,
+        (uint32_t[]){1});
+    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, ewmh_window, A__NET_SUPPORTING_WM_CHECK, XCB_ATOM_WINDOW, 32, 1, &ewmh_window);
+    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, ewmh_window, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3");
+    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTING_WM_CHECK, XCB_ATOM_WINDOW, 32, 1, &ewmh_window);
 
     /* 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 24 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, 24, supported_atoms);
+    /* 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);
+
+    /* 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});
 }
index 4f2740992b9540639b0fd5009a1a95382487129e..3df0e246a28661a0d0a0d29504928442bbeff3ef 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * Faking outputs is useful in pathological situations (like network X servers
  * which don’t support multi-monitor in a useful way) and for our testsuite.
@@ -49,7 +49,7 @@ void fake_outputs_init(const char *output_spec) {
             new_output->rect.width = min(new_output->rect.width, width);
             new_output->rect.height = min(new_output->rect.height, height);
         } else {
-            new_output = scalloc(sizeof(Output));
+            new_output = scalloc(1, sizeof(Output));
             sasprintf(&(new_output->name), "fake-%d", num_screens);
             DLOG("Created new fake output %s (%p)\n", new_output->name, new_output);
             new_output->active = true;
index e6ca2d7188d6392c172aa09bf17d6eb0588f6544..d7a330678d1d2d8227e3037c9a690c3d30bbb861 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * floating.c: Floating windows.
  *
@@ -18,6 +18,9 @@ extern xcb_connection_t *conn;
  *
  */
 static Rect total_outputs_dimensions(void) {
+    if (TAILQ_EMPTY(&outputs))
+        return (Rect){0, 0, root_screen->width_in_pixels, root_screen->height_in_pixels};
+
     Output *output;
     /* Use Rect to encapsulate dimensions, ignoring x/y */
     Rect outputs_dimensions = {0, 0, 0, 0};
@@ -42,7 +45,7 @@ void floating_check_size(Con *floating_con) {
     Con *focused_con = con_descend_focused(floating_con);
 
     /* obey size increments */
-    if (focused_con->height_increment || focused_con->width_increment) {
+    if (focused_con->window != NULL && (focused_con->window->height_increment || focused_con->window->width_increment)) {
         Rect border_rect = con_border_style_rect(focused_con);
 
         /* We have to do the opposite calculations that render_con() do
@@ -54,18 +57,18 @@ void floating_check_size(Con *floating_con) {
         if (con_border_style(focused_con) == BS_NORMAL)
             border_rect.height += render_deco_height();
 
-        if (focused_con->height_increment &&
-            floating_con->rect.height >= focused_con->base_height + border_rect.height) {
-            floating_con->rect.height -= focused_con->base_height + border_rect.height;
-            floating_con->rect.height -= floating_con->rect.height % focused_con->height_increment;
-            floating_con->rect.height += focused_con->base_height + border_rect.height;
+        if (focused_con->window->height_increment &&
+            floating_con->rect.height >= focused_con->window->base_height + border_rect.height) {
+            floating_con->rect.height -= focused_con->window->base_height + border_rect.height;
+            floating_con->rect.height -= floating_con->rect.height % focused_con->window->height_increment;
+            floating_con->rect.height += focused_con->window->base_height + border_rect.height;
         }
 
-        if (focused_con->width_increment &&
-            floating_con->rect.width >= focused_con->base_width + border_rect.width) {
-            floating_con->rect.width -= focused_con->base_width + border_rect.width;
-            floating_con->rect.width -= floating_con->rect.width % focused_con->width_increment;
-            floating_con->rect.width += focused_con->base_width + border_rect.width;
+        if (focused_con->window->width_increment &&
+            floating_con->rect.width >= focused_con->window->base_width + border_rect.width) {
+            floating_con->rect.width -= focused_con->window->base_width + border_rect.width;
+            floating_con->rect.width -= floating_con->rect.width % focused_con->window->width_increment;
+            floating_con->rect.width += focused_con->window->base_width + border_rect.width;
         }
     }
 
@@ -246,12 +249,10 @@ void floating_enable(Con *con, bool automatic) {
         if (con->window && con->window->leader != XCB_NONE &&
             (leader = con_by_window_id(con->window->leader)) != NULL) {
             DLOG("Centering above leader\n");
-            nc->rect.x = leader->rect.x + (leader->rect.width / 2) - (nc->rect.width / 2);
-            nc->rect.y = leader->rect.y + (leader->rect.height / 2) - (nc->rect.height / 2);
+            floating_center(nc, leader->rect);
         } else {
             /* center the window on workspace as fallback */
-            nc->rect.x = ws->rect.x + (ws->rect.width / 2) - (nc->rect.width / 2);
-            nc->rect.y = ws->rect.y + (ws->rect.height / 2) - (nc->rect.height / 2);
+            floating_center(nc, ws->rect);
         }
     }
 
@@ -310,8 +311,7 @@ void floating_enable(Con *con, bool automatic) {
     }
 
     ELOG("No output found at destination coordinates, centering floating window on current ws\n");
-    nc->rect.x = ws->rect.x + (ws->rect.width / 2) - (nc->rect.width / 2);
-    nc->rect.y = ws->rect.y + (ws->rect.height / 2) - (nc->rect.height / 2);
+    floating_center(nc, ws->rect);
 
     ipc_send_window_event("floating", con);
 }
@@ -369,6 +369,12 @@ void floating_disable(Con *con, bool automatic) {
  *
  */
 void toggle_floating_mode(Con *con, bool automatic) {
+    /* forbid the command to toggle floating on a CT_FLOATING_CON */
+    if (con->type == CT_FLOATING_CON) {
+        ELOG("Cannot toggle floating mode on con = %p because it is of type CT_FLOATING_CON.\n", con);
+        return;
+    }
+
     /* see if the client is already floating */
     if (con_is_floating(con)) {
         LOG("already floating, re-setting to tiling\n");
@@ -415,11 +421,56 @@ bool floating_maybe_reassign_ws(Con *con) {
     Con *content = output_get_content(output->con);
     Con *ws = TAILQ_FIRST(&(content->focus_head));
     DLOG("Moving con %p / %s to workspace %p / %s\n", con, con->name, ws, ws->name);
-    con_move_to_workspace(con, ws, false, true);
+    con_move_to_workspace(con, ws, false, true, false);
     con_focus(con_descend_focused(con));
     return true;
 }
 
+/*
+ * Centers a floating con above the specified rect.
+ *
+ */
+void floating_center(Con *con, Rect rect) {
+    con->rect.x = rect.x + (rect.width / 2) - (con->rect.width / 2);
+    con->rect.y = rect.y + (rect.height / 2) - (con->rect.height / 2);
+}
+
+/*
+ * Moves the given floating con to the current pointer position.
+ *
+ */
+void floating_move_to_pointer(Con *con) {
+    assert(con->type == CT_FLOATING_CON);
+
+    xcb_query_pointer_reply_t *reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL);
+    if (reply == NULL) {
+        ELOG("could not query pointer position, not moving this container\n");
+        return;
+    }
+
+    Output *output = get_output_containing(reply->root_x, reply->root_y);
+    if (output == NULL) {
+        ELOG("The pointer is not on any output, cannot move the container here.\n");
+        return;
+    }
+
+    /* Determine where to put the window. */
+    int32_t x = reply->root_x - con->rect.width / 2;
+    int32_t y = reply->root_y - con->rect.height / 2;
+    FREE(reply);
+
+    /* Correct target coordinates to be in-bounds. */
+    x = MAX(x, (int32_t)output->rect.x);
+    y = MAX(y, (int32_t)output->rect.y);
+    if (x + con->rect.width > output->rect.x + output->rect.width)
+        x = output->rect.x + output->rect.width - con->rect.width;
+    if (y + con->rect.height > output->rect.y + output->rect.height)
+        y = output->rect.y + output->rect.height - con->rect.height;
+
+    /* Update container's coordinates to position it correctly. */
+    floating_reposition(con, (Rect){x, y, con->rect.width, con->rect.height});
+}
+
 DRAGGING_CB(drag_window_callback) {
     const struct xcb_button_press_event_t *event = extra;
 
@@ -621,7 +672,7 @@ static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) {
                 break;
 
             case XCB_KEY_PRESS:
-                DLOG("A key was pressed during drag, reverting changes.");
+                DLOG("A key was pressed during drag, reverting changes.\n");
                 dragloop->result = DRAG_REVERT;
                 handle_event(type, event);
                 break;
@@ -783,6 +834,37 @@ void floating_reposition(Con *con, Rect newrect) {
     tree_render();
 }
 
+/*
+ * Sets size of the CT_FLOATING_CON to specified dimensions. Might limit the
+ * actual size with regard to size constraints taken from user settings.
+ * Additionally, the dimensions may be upscaled until they're divisible by the
+ * window's size hints.
+ *
+ */
+void floating_resize(Con *floating_con, int x, int y) {
+    DLOG("floating resize to %dx%d px\n", x, y);
+    Rect *rect = &floating_con->rect;
+    Con *focused_con = con_descend_focused(floating_con);
+    if (focused_con->window == NULL) {
+        DLOG("No window is focused. Not resizing.\n");
+        return;
+    }
+    int wi = focused_con->window->width_increment;
+    int hi = focused_con->window->height_increment;
+    rect->width = x;
+    rect->height = y;
+    if (wi)
+        rect->width += (wi - 1 - rect->width) % wi;
+    if (hi)
+        rect->height += (hi - 1 - rect->height) % hi;
+
+    floating_check_size(floating_con);
+
+    /* If this is a scratchpad window, don't auto center it from now on. */
+    if (floating_con->scratchpad_state == SCRATCHPAD_FRESH)
+        floating_con->scratchpad_state = SCRATCHPAD_CHANGED;
+}
+
 /*
  * Fixes the coordinates of the floating window whenever the window gets
  * reassigned to a different output (or when the output’s rect changes).
index 041f7e366f76a96da1620c1ab950ffacc084edc5..e3ddf701161886c1ea6bbff68e5d3615d37c1342 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * handlers.c: Small handlers for various events (keypresses, focus changes,
  *             …).
@@ -16,7 +16,6 @@
 #include <float.h>
 #include <sys/time.h>
 #include <xcb/randr.h>
-#include <X11/XKBlib.h>
 #define SN_API_NOT_YET_FROZEN 1
 #include <libsn/sn-monitor.h>
 
@@ -215,7 +214,7 @@ static void handle_motion_notify(xcb_motion_notify_event_t *event) {
 
     /* Skip events where the pointer was over a child window, we are only
      * interested in events on the root window. */
-    if (event->child != 0)
+    if (event->child != XCB_NONE)
         return;
 
     Con *con;
@@ -228,7 +227,7 @@ static void handle_motion_notify(xcb_motion_notify_event_t *event) {
     if (config.disable_focus_follows_mouse)
         return;
 
-    if (con->layout != L_DEFAULT)
+    if (con->layout != L_DEFAULT && con->layout != L_SPLITV && con->layout != L_SPLITH)
         return;
 
     /* see over which rect the user is */
@@ -245,8 +244,6 @@ static void handle_motion_notify(xcb_motion_notify_event_t *event) {
         x_push_changes(croot);
         return;
     }
-
-    return;
 }
 
 /*
@@ -266,7 +263,7 @@ static void handle_mapping_notify(xcb_mapping_notify_event_t *event) {
 
     ungrab_all_keys(conn);
     translate_keysyms();
-    grab_all_keys(conn, false);
+    grab_all_keys(conn);
 
     return;
 }
@@ -389,15 +386,35 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
         return;
     }
 
-    /* Dock windows can be reconfigured in their height */
+    /* Dock windows can be reconfigured in their height and moved to another output. */
     if (con->parent && con->parent->type == CT_DOCKAREA) {
-        DLOG("Dock window, only height reconfiguration allowed\n");
+        DLOG("Reconfiguring dock window (con = %p).\n", con);
         if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT) {
-            DLOG("Height given, changing\n");
+            DLOG("Dock client wants to change height to %d, we can do that.\n", event->height);
 
             con->geometry.height = event->height;
             tree_render();
         }
+
+        if (event->value_mask & XCB_CONFIG_WINDOW_X || event->value_mask & XCB_CONFIG_WINDOW_Y) {
+            int16_t x = event->value_mask & XCB_CONFIG_WINDOW_X ? event->x : (int16_t)con->geometry.x;
+            int16_t y = event->value_mask & XCB_CONFIG_WINDOW_Y ? event->y : (int16_t)con->geometry.y;
+
+            Con *current_output = con_get_output(con);
+            Output *target = get_output_containing(x, y);
+            if (target != NULL && current_output != target->con) {
+                DLOG("Dock client is requested to be moved to output %s, moving it there.\n", target->name);
+                Match *match;
+                Con *nc = con_for_window(target->con, con->window, &match);
+                DLOG("Dock client will be moved to container %p.\n", nc);
+                con_detach(con);
+                con_attach(con, nc, false);
+
+                tree_render();
+            } else {
+                DLOG("Dock client will not be moved, we only support moving it to another output.\n");
+            }
+        }
     }
 
     fake_absolute_configure_notify(con);
@@ -678,7 +695,8 @@ static void handle_client_message(xcb_client_message_event_t *event) {
     if (event->type == A__NET_WM_STATE) {
         if (event->format != 32 ||
             (event->data.data32[1] != A__NET_WM_STATE_FULLSCREEN &&
-             event->data.data32[1] != A__NET_WM_STATE_DEMANDS_ATTENTION)) {
+             event->data.data32[1] != A__NET_WM_STATE_DEMANDS_ATTENTION &&
+             event->data.data32[1] != A__NET_WM_STATE_STICKY)) {
             DLOG("Unknown atom in clientmessage of type %d\n", event->data.data32[1]);
             return;
         }
@@ -708,6 +726,17 @@ static void handle_client_message(xcb_client_message_event_t *event) {
                 con_set_urgency(con, false);
             else if (event->data.data32[0] == _NET_WM_STATE_TOGGLE)
                 con_set_urgency(con, !con->urgent);
+        } else if (event->data.data32[1] == A__NET_WM_STATE_STICKY) {
+            DLOG("Received a client message to modify _NET_WM_STATE_STICKY.\n");
+            if (event->data.data32[0] == _NET_WM_STATE_ADD)
+                con->sticky = true;
+            else if (event->data.data32[0] == _NET_WM_STATE_REMOVE)
+                con->sticky = false;
+            else if (event->data.data32[0] == _NET_WM_STATE_TOGGLE)
+                con->sticky = !con->sticky;
+
+            DLOG("New sticky status for con = %p is %i.\n", con, con->sticky);
+            output_push_sticky_windows(focused);
         }
 
         tree_render();
@@ -743,16 +772,17 @@ static void handle_client_message(xcb_client_message_event_t *event) {
             workspace_show(ws);
             con_focus(con);
         } else {
-            /* If the request is from an application, only focus if the
-             * workspace is visible. Otherwise set the urgency hint. */
-            if (workspace_is_visible(ws)) {
-                DLOG("Request to focus con on a visible workspace. Focusing con = %p\n", con);
+            /* Request is from an application. */
+
+            if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) {
+                DLOG("Focusing con = %p\n", con);
                 workspace_show(ws);
                 con_focus(con);
-            } else {
-                DLOG("Request to focus con on a hidden workspace. Setting urgent con = %p\n", con);
+            } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) {
+                DLOG("Marking con = %p urgent\n", con);
                 con_set_urgency(con, true);
-            }
+            } else
+                DLOG("Ignoring request for con = %p.\n", con);
         }
 
         tree_render();
@@ -761,7 +791,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
         uint32_t rnd = event->data.data32[1];
         DLOG("[i3 sync protocol] Sending random value %d back to X11 window 0x%08x\n", rnd, window);
 
-        void *reply = scalloc(32);
+        void *reply = scalloc(32, 1);
         xcb_client_message_event_t *ev = reply;
 
         ev->response_type = XCB_CLIENT_MESSAGE;
@@ -879,7 +909,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
                 floating_drag_window(con->parent, &fake);
                 break;
             case _NET_WM_MOVERESIZE_SIZE_TOPLEFT... _NET_WM_MOVERESIZE_SIZE_LEFT:
-                floating_resize_window(con->parent, FALSE, &fake);
+                floating_resize_window(con->parent, false, &fake);
                 break;
             default:
                 DLOG("_NET_WM_MOVERESIZE direction %d not implemented\n", direction);
@@ -891,15 +921,15 @@ static void handle_client_message(xcb_client_message_event_t *event) {
     }
 }
 
-#if 0
-int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
-                        xcb_atom_t atom, xcb_get_property_reply_t *property) {
-        /* TODO: Implement this one. To do this, implement a little test program which sleep(1)s
-         before changing this property. */
-        ELOG("_NET_WM_WINDOW_TYPE changed, this is not yet implemented.\n");
-        return 0;
+bool handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
+                        xcb_atom_t atom, xcb_get_property_reply_t *reply) {
+    Con *con;
+    if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
+        return false;
+
+    window_update_type(con->window, reply);
+    return true;
 }
-#endif
 
 /*
  * Handles the size hints set by a window, but currently only the part necessary for displaying
@@ -934,13 +964,13 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
     bool changed = false;
     if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC)) {
         if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF)
-            if (con->width_increment != size_hints.width_inc) {
-                con->width_increment = size_hints.width_inc;
+            if (con->window->width_increment != size_hints.width_inc) {
+                con->window->width_increment = size_hints.width_inc;
                 changed = true;
             }
         if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF)
-            if (con->height_increment != size_hints.height_inc) {
-                con->height_increment = size_hints.height_inc;
+            if (con->window->height_increment != size_hints.height_inc) {
+                con->window->height_increment = size_hints.height_inc;
                 changed = true;
             }
 
@@ -962,10 +992,10 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
         base_height = size_hints.min_height;
     }
 
-    if (base_width != con->base_width ||
-        base_height != con->base_height) {
-        con->base_width = base_width;
-        con->base_height = base_height;
+    if (base_width != con->window->base_width ||
+        base_height != con->window->base_height) {
+        con->window->base_width = base_width;
+        con->window->base_height = base_height;
         DLOG("client's base_height changed to %d\n", base_height);
         DLOG("client's base_width changed to %d\n", base_width);
         changed = true;
@@ -1001,8 +1031,8 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
     } else
         goto render_and_return;
 
-    if (fabs(con->aspect_ratio - aspect_ratio) > DBL_EPSILON) {
-        con->aspect_ratio = aspect_ratio;
+    if (fabs(con->window->aspect_ratio - aspect_ratio) > DBL_EPSILON) {
+        con->window->aspect_ratio = aspect_ratio;
         changed = true;
     }
 
@@ -1263,7 +1293,8 @@ static struct property_handler_t property_handlers[] = {
     {0, UINT_MAX, handle_transient_for},
     {0, 128, handle_windowrole_change},
     {0, 128, handle_class_change},
-    {0, UINT_MAX, handle_strut_partial_change}};
+    {0, UINT_MAX, handle_strut_partial_change},
+    {0, UINT_MAX, handle_window_type}};
 #define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t))
 
 /*
@@ -1283,6 +1314,7 @@ void property_handlers_init(void) {
     property_handlers[6].atom = A_WM_WINDOW_ROLE;
     property_handlers[7].atom = XCB_ATOM_WM_CLASS;
     property_handlers[8].atom = A__NET_WM_STRUT_PARTIAL;
+    property_handlers[9].atom = A__NET_WM_WINDOW_TYPE;
 }
 
 static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) {
@@ -1318,7 +1350,9 @@ static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom)
  *
  */
 void handle_event(int type, xcb_generic_event_t *event) {
-    DLOG("event type %d, xkb_base %d\n", type, xkb_base);
+    if (type != XCB_MOTION_NOTIFY)
+        DLOG("event type %d, xkb_base %d\n", type, xkb_base);
+
     if (randr_base > -1 &&
         type == randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY) {
         handle_screen_change(event);
@@ -1333,9 +1367,11 @@ void handle_event(int type, xcb_generic_event_t *event) {
             DLOG("xkb new keyboard notify, sequence %d, time %d\n", state->sequence, state->time);
             xcb_key_symbols_free(keysyms);
             keysyms = xcb_key_symbols_alloc(conn);
+            if (((xcb_xkb_new_keyboard_notify_event_t *)event)->changed & XCB_XKB_NKN_DETAIL_KEYCODES)
+                (void)load_keymap();
             ungrab_all_keys(conn);
             translate_keysyms();
-            grab_all_keys(conn, false);
+            grab_all_keys(conn);
         } else if (state->xkbType == XCB_XKB_MAP_NOTIFY) {
             if (event_is_ignored(event->sequence, type)) {
                 DLOG("Ignoring map notify event for sequence %d.\n", state->sequence);
@@ -1346,26 +1382,16 @@ void handle_event(int type, xcb_generic_event_t *event) {
                 keysyms = xcb_key_symbols_alloc(conn);
                 ungrab_all_keys(conn);
                 translate_keysyms();
-                grab_all_keys(conn, false);
+                grab_all_keys(conn);
+                (void)load_keymap();
             }
         } else if (state->xkbType == XCB_XKB_STATE_NOTIFY) {
             DLOG("xkb state group = %d\n", state->group);
-
-            /* See The XKB Extension: Library Specification, section 14.1 */
-            /* We check if the current group (each group contains
-             * two levels) has been changed. Mode_switch activates
-             * group XCB_XKB_GROUP_2 */
             if (xkb_current_group == state->group)
                 return;
             xkb_current_group = state->group;
-            if (state->group == XCB_XKB_GROUP_1) {
-                DLOG("Mode_switch disabled\n");
-                ungrab_all_keys(conn);
-                grab_all_keys(conn, false);
-            } else {
-                DLOG("Mode_switch enabled\n");
-                grab_all_keys(conn, true);
-            }
+            ungrab_all_keys(conn);
+            grab_all_keys(conn);
         }
 
         return;
index 8c7634643ab7cc10292abda8869e24dfd435556d..8472106ec5328f18fa82843e9927d25801a54618 100644 (file)
--- 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) $(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) $(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) $(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
 
 # When using clang, we use pre-compiled headers to speed up the build. With
 # gcc, this actually makes the build slower.
@@ -32,6 +32,10 @@ include/all.h.pch: $(i3_HEADERS)
        echo "[i3] PCH all.h"
        $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -x c-header include/all.h -o include/all.h.pch
 
+src/version.o: src/version.c LAST_VERSION $(i3_HEADERS_DEP)
+       echo "[i3] CC $<"
+       $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(PCH_FLAGS) -c -o $@ ${canonical_path}/$<
+
 src/%.o: src/%.c $(i3_HEADERS_DEP)
        echo "[i3] CC $<"
        $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(PCH_FLAGS) -c -o $@ ${canonical_path}/$<
@@ -70,26 +74,26 @@ i3: libi3.a $(i3_OBJECTS)
 
 install-i3: i3
        echo "[i3] Install"
-       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(EXEC_PREFIX)/bin
        $(INSTALL) -d -m 0755 $(DESTDIR)$(SYSCONFDIR)/i3
-       $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/include/i3
+       $(INSTALL) -d -m 0755 $(DESTDIR)$(EXEC_PREFIX)/include/i3
        $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/xsessions
        $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/applications
-       $(INSTALL) -m 0755 i3 $(DESTDIR)$(PREFIX)/bin/
-       $(LN) -sf i3 $(DESTDIR)$(PREFIX)/bin/i3-with-shmlog
-       $(INSTALL) -m 0755 i3-migrate-config-to-v4 $(DESTDIR)$(PREFIX)/bin/
-       $(INSTALL) -m 0755 i3-sensible-editor $(DESTDIR)$(PREFIX)/bin/
-       $(INSTALL) -m 0755 i3-sensible-pager $(DESTDIR)$(PREFIX)/bin/
-       $(INSTALL) -m 0755 i3-sensible-terminal $(DESTDIR)$(PREFIX)/bin/
-       $(INSTALL) -m 0755 i3-save-tree $(DESTDIR)$(PREFIX)/bin/
-       $(INSTALL) -m 0755 i3-dmenu-desktop $(DESTDIR)$(PREFIX)/bin/
+       $(INSTALL) -m 0755 i3 $(DESTDIR)$(EXEC_PREFIX)/bin/
+       $(LN) -sf i3 $(DESTDIR)$(EXEC_PREFIX)/bin/i3-with-shmlog
+       $(INSTALL) -m 0755 i3-migrate-config-to-v4 $(DESTDIR)$(EXEC_PREFIX)/bin/
+       $(INSTALL) -m 0755 i3-sensible-editor $(DESTDIR)$(EXEC_PREFIX)/bin/
+       $(INSTALL) -m 0755 i3-sensible-pager $(DESTDIR)$(EXEC_PREFIX)/bin/
+       $(INSTALL) -m 0755 i3-sensible-terminal $(DESTDIR)$(EXEC_PREFIX)/bin/
+       $(INSTALL) -m 0755 i3-save-tree $(DESTDIR)$(EXEC_PREFIX)/bin/
+       $(INSTALL) -m 0755 i3-dmenu-desktop $(DESTDIR)$(EXEC_PREFIX)/bin/
        test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config
        test -e $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes || $(INSTALL) -m 0644 i3.config.keycodes $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes
        $(INSTALL) -m 0644 i3.xsession.desktop $(DESTDIR)$(PREFIX)/share/xsessions/i3.desktop
        $(INSTALL) -m 0644 i3-with-shmlog.xsession.desktop $(DESTDIR)$(PREFIX)/share/xsessions/i3-with-shmlog.desktop
        $(INSTALL) -m 0644 i3.applications.desktop $(DESTDIR)$(PREFIX)/share/applications/i3.desktop
-       $(INSTALL) -m 0644 include/i3/ipc.h $(DESTDIR)$(PREFIX)/include/i3/
+       $(INSTALL) -m 0644 include/i3/ipc.h $(DESTDIR)$(EXEC_PREFIX)/include/i3/
 
 clean-i3:
        echo "[i3] Clean"
-       rm -f $(i3_OBJECTS) $(i3_SOURCES_GENERATED) $(i3_HEADERS_CMDPARSER) include/loglevels.h loglevels.tmp include/all.h.pch i3-command-parser.stamp i3-config-parser.stamp i3 test.config_parser test.commands_parser src/*.gcno src/cfgparse.* src/cmdparse.*
+       rm -f $(i3_OBJECTS) $(i3_SOURCES_GENERATED) $(i3_HEADERS_CMDPARSER) include/loglevels.h loglevels.tmp include/all.h.pch i3-command-parser.stamp i3-config-parser.stamp i3 test.config_parser test.commands_parser src/*.gcno src/cfgparse.* src/cmdparse.* LAST_VERSION
index 8fed75f1b13dbf3993d7ac4c7dbbfa80bf4fbd58..1a8d28ed218189c073be4c40e9113cf8a56d9414 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * ipc.c: UNIX domain socket IPC (initialization, client handling, protocol).
  *
@@ -84,7 +84,7 @@ void ipc_shutdown(void) {
 IPC_HANDLER(command) {
     /* To get a properly terminated buffer, we copy
      * message_size bytes out of the buffer */
-    char *command = scalloc(message_size + 1);
+    char *command = scalloc(message_size + 1, 1);
     strncpy(command, (const char *)message, message_size);
     LOG("IPC: received: *%s*\n", command);
     yajl_gen gen = yajl_gen_alloc(NULL);
@@ -121,56 +121,92 @@ static void dump_rect(yajl_gen gen, const char *name, Rect r) {
     y(map_close);
 }
 
-static void dump_binding(yajl_gen gen, Binding *bind) {
-    y(map_open);
-    ystr("input_code");
-    y(integer, bind->keycode);
-
-    ystr("input_type");
-    ystr((const char *)(bind->input_type == B_KEYBOARD ? "keyboard" : "mouse"));
-
-    ystr("symbol");
-    if (bind->symbol == NULL)
-        y(null);
-    else
-        ystr(bind->symbol);
-
-    ystr("command");
-    ystr(bind->command);
-
-    ystr("mods");
+static void dump_event_state_mask(yajl_gen gen, Binding *bind) {
     y(array_open);
-    for (int i = 0; i < 8; i++) {
-        if (bind->mods & (1 << i)) {
+    for (int i = 0; i < 20; i++) {
+        if (bind->event_state_mask & (1 << i)) {
             switch (1 << i) {
-                case XCB_MOD_MASK_SHIFT:
+                case XCB_KEY_BUT_MASK_SHIFT:
                     ystr("shift");
                     break;
-                case XCB_MOD_MASK_LOCK:
+                case XCB_KEY_BUT_MASK_LOCK:
                     ystr("lock");
                     break;
-                case XCB_MOD_MASK_CONTROL:
+                case XCB_KEY_BUT_MASK_CONTROL:
                     ystr("ctrl");
                     break;
-                case XCB_MOD_MASK_1:
+                case XCB_KEY_BUT_MASK_MOD_1:
                     ystr("Mod1");
                     break;
-                case XCB_MOD_MASK_2:
+                case XCB_KEY_BUT_MASK_MOD_2:
                     ystr("Mod2");
                     break;
-                case XCB_MOD_MASK_3:
+                case XCB_KEY_BUT_MASK_MOD_3:
                     ystr("Mod3");
                     break;
-                case XCB_MOD_MASK_4:
+                case XCB_KEY_BUT_MASK_MOD_4:
                     ystr("Mod4");
                     break;
-                case XCB_MOD_MASK_5:
+                case XCB_KEY_BUT_MASK_MOD_5:
                     ystr("Mod5");
                     break;
+                case XCB_KEY_BUT_MASK_BUTTON_1:
+                    ystr("Button1");
+                    break;
+                case XCB_KEY_BUT_MASK_BUTTON_2:
+                    ystr("Button2");
+                    break;
+                case XCB_KEY_BUT_MASK_BUTTON_3:
+                    ystr("Button3");
+                    break;
+                case XCB_KEY_BUT_MASK_BUTTON_4:
+                    ystr("Button4");
+                    break;
+                case XCB_KEY_BUT_MASK_BUTTON_5:
+                    ystr("Button5");
+                    break;
+                case (I3_XKB_GROUP_MASK_1 << 16):
+                    ystr("Group1");
+                    break;
+                case (I3_XKB_GROUP_MASK_2 << 16):
+                    ystr("Group2");
+                    break;
+                case (I3_XKB_GROUP_MASK_3 << 16):
+                    ystr("Group3");
+                    break;
+                case (I3_XKB_GROUP_MASK_4 << 16):
+                    ystr("Group4");
+                    break;
             }
         }
     }
     y(array_close);
+}
+
+static void dump_binding(yajl_gen gen, Binding *bind) {
+    y(map_open);
+    ystr("input_code");
+    y(integer, bind->keycode);
+
+    ystr("input_type");
+    ystr((const char *)(bind->input_type == B_KEYBOARD ? "keyboard" : "mouse"));
+
+    ystr("symbol");
+    if (bind->symbol == NULL)
+        y(null);
+    else
+        ystr(bind->symbol);
+
+    ystr("command");
+    ystr(bind->command);
+
+    // This key is only provided for compatibility, new programs should use
+    // event_state_mask instead.
+    ystr("mods");
+    dump_event_state_mask(gen, bind);
+
+    ystr("event_state_mask");
+    dump_event_state_mask(gen, bind);
 
     y(map_close);
 }
@@ -400,6 +436,9 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
     ystr("fullscreen_mode");
     y(integer, con->fullscreen_mode);
 
+    ystr("sticky");
+    y(bool, con->sticky);
+
     ystr("floating");
     switch (con->floating) {
         case FLOATING_AUTO_OFF:
@@ -469,6 +508,28 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
     y(map_close);
 }
 
+static void dump_bar_bindings(yajl_gen gen, Barconfig *config) {
+    if (TAILQ_EMPTY(&(config->bar_bindings)))
+        return;
+
+    ystr("bindings");
+    y(array_open);
+
+    struct Barbinding *current;
+    TAILQ_FOREACH(current, &(config->bar_bindings), bindings) {
+        y(map_open);
+
+        ystr("input_code");
+        y(integer, current->input_code);
+        ystr("command");
+        ystr(current->command);
+
+        y(map_close);
+    }
+
+    y(array_close);
+}
+
 static void dump_bar_config(yajl_gen gen, Barconfig *config) {
     y(map_open);
 
@@ -492,6 +553,10 @@ 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);
+
     YSTR_IF_SET(socket_path);
 
     ystr("mode");
@@ -549,15 +614,7 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
             break;
     }
 
-    if (config->wheel_up_cmd) {
-        ystr("wheel_up_cmd");
-        ystr(config->wheel_up_cmd);
-    }
-
-    if (config->wheel_down_cmd) {
-        ystr("wheel_down_cmd");
-        ystr(config->wheel_down_cmd);
-    }
+    dump_bar_bindings(gen, config);
 
     ystr("position");
     if (config->position == P_BOTTOM)
@@ -611,6 +668,9 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
     YSTR_IF_SET(urgent_workspace_border);
     YSTR_IF_SET(urgent_workspace_bg);
     YSTR_IF_SET(urgent_workspace_text);
+    YSTR_IF_SET(binding_mode_border);
+    YSTR_IF_SET(binding_mode_bg);
+    YSTR_IF_SET(binding_mode_text);
     y(map_close);
 
     y(map_close);
@@ -791,7 +851,10 @@ IPC_HANDLER(get_version) {
     y(integer, PATCH_VERSION);
 
     ystr("human_readable");
-    ystr(I3_VERSION);
+    ystr(i3_version);
+
+    ystr("loaded_config_file_name");
+    ystr(current_configpath);
 
     y(map_close);
 
@@ -831,7 +894,7 @@ IPC_HANDLER(get_bar_config) {
 
     /* To get a properly terminated buffer, we copy
      * message_size bytes out of the buffer */
-    char *bar_id = scalloc(message_size + 1);
+    char *bar_id = scalloc(message_size + 1, 1);
     strncpy(bar_id, (const char *)message, message_size);
     LOG("IPC: looking for config for bar ID \"%s\"\n", bar_id);
     Barconfig *current, *config = NULL;
@@ -876,10 +939,10 @@ static int add_subscription(void *extra, const unsigned char *s,
     int event = client->num_events;
 
     client->num_events++;
-    client->events = realloc(client->events, client->num_events * sizeof(char *));
+    client->events = srealloc(client->events, client->num_events * sizeof(char *));
     /* We copy the string because it is not null-terminated and strndup()
      * is missing on some BSD systems */
-    client->events[event] = scalloc(len + 1);
+    client->events[event] = scalloc(len + 1, 1);
     memcpy(client->events[event], s, len);
 
     DLOG("client is now subscribed to:\n");
@@ -1036,13 +1099,13 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) {
 
     set_nonblock(client);
 
-    struct ev_io *package = scalloc(sizeof(struct ev_io));
+    struct ev_io *package = scalloc(1, sizeof(struct ev_io));
     ev_io_init(package, ipc_receive_message, client, EV_READ);
     ev_io_start(EV_A_ package);
 
     DLOG("IPC: new client connected on fd %d\n", w->fd);
 
-    ipc_client *new = scalloc(sizeof(ipc_client));
+    ipc_client *new = scalloc(1, sizeof(ipc_client));
     new->fd = client;
 
     TAILQ_INSERT_TAIL(&all_clients, new, clients);
@@ -1063,7 +1126,7 @@ int ipc_create_socket(const char *filename) {
     char *copy = sstrdup(resolved);
     const char *dir = dirname(copy);
     if (!path_exists(dir))
-        mkdirp(dir);
+        mkdirp(dir, DEFAULT_DIR_MODE);
     free(copy);
 
     /* Unlink the unix domain socket before */
index 95e5079eef44101e8a5cf97c1b88a00a4b4d84fe..aa6d815066beaf423142edb1a456664fbde12e3f 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * key_press.c: key press handler
  *
  *
  */
 void handle_key_press(xcb_key_press_event_t *event) {
-    bool key_release = (event->response_type == XCB_KEY_RELEASE);
+    const bool key_release = (event->response_type == XCB_KEY_RELEASE);
 
     last_timestamp = event->time;
 
-    DLOG("%s %d, state raw = %d\n", (key_release ? "KeyRelease" : "KeyPress"), event->detail, event->state);
+    DLOG("%s %d, state raw = 0x%x\n", (key_release ? "KeyRelease" : "KeyPress"), event->detail, event->state);
 
     Binding *bind = get_binding_from_xcb_event((xcb_generic_event_t *)event);
 
index c4d39fcee57fa569be1ac07fced152b57b56b55f..4a67e6b1fe1828883329004597186c3ca921ae9f 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * load_layout.c: Restore (parts of) the layout, for example after an inplace
  *                restart.
@@ -116,6 +116,33 @@ static int json_end_map(void *ctx) {
             json_node->num = ws_name_to_number(json_node->name);
         }
 
+        // When appending JSON layout files that only contain the workspace
+        // _contents_, we might not have an upfront signal that the
+        // container we’re currently parsing is a floating container (like
+        // the “floating_nodes” key of the workspace container itself).
+        // That’s why we make sure the con is attached at the right place
+        // in the hierarchy in case it’s floating.
+        if (json_node->type == CT_FLOATING_CON) {
+            DLOG("fixing parent which currently is %p / %s\n", json_node->parent, json_node->parent->name);
+            json_node->parent = con_get_workspace(json_node->parent);
+
+            // Also set a size if none was supplied, otherwise the placeholder
+            // window cannot be created as X11 requests with width=0 or
+            // height=0 are invalid.
+            const Rect zero = {0, 0, 0, 0};
+            if (memcmp(&(json_node->rect), &zero, sizeof(Rect)) == 0) {
+                DLOG("Geometry not set, combining children\n");
+                Con *child;
+                TAILQ_FOREACH(child, &(json_node->nodes_head), nodes) {
+                    DLOG("child geometry: %d x %d\n", child->geometry.width, child->geometry.height);
+                    json_node->rect.width += child->geometry.width;
+                    json_node->rect.height = max(json_node->rect.height, child->geometry.height);
+                }
+            }
+
+            floating_check_size(json_node);
+        }
+
         LOG("attaching\n");
         con_attach(json_node, json_node->parent, true);
         LOG("Creating window\n");
@@ -167,7 +194,7 @@ static int json_end_array(void *ctx) {
 static int json_key(void *ctx, const unsigned char *val, size_t len) {
     LOG("key: %.*s\n", (int)len, val);
     FREE(last_key);
-    last_key = scalloc((len + 1) * sizeof(char));
+    last_key = scalloc(len + 1, 1);
     memcpy(last_key, val, len);
     if (strcasecmp(last_key, "swallows") == 0)
         parsing_swallows = true;
@@ -209,10 +236,10 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) {
         free(sval);
     } else {
         if (strcasecmp(last_key, "name") == 0) {
-            json_node->name = scalloc((len + 1) * sizeof(char));
+            json_node->name = scalloc(len + 1, 1);
             memcpy(json_node->name, val, len);
         } else if (strcasecmp(last_key, "sticky_group") == 0) {
-            json_node->sticky_group = scalloc((len + 1) * sizeof(char));
+            json_node->sticky_group = scalloc(len + 1, 1);
             memcpy(json_node->sticky_group, val, len);
             LOG("sticky_group of this container is %s\n", json_node->sticky_group);
         } else if (strcasecmp(last_key, "orientation") == 0) {
@@ -311,6 +338,10 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) {
         } else if (strcasecmp(last_key, "mark") == 0) {
             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;
         } else if (strcasecmp(last_key, "floating") == 0) {
             char *buf = NULL;
@@ -361,7 +392,7 @@ static int json_int(void *ctx, long long val) {
         json_node->old_id = val;
 
     if (parsing_focus) {
-        struct focus_mapping *focus_mapping = scalloc(sizeof(struct focus_mapping));
+        struct focus_mapping *focus_mapping = scalloc(1, sizeof(struct focus_mapping));
         focus_mapping->old_id = val;
         TAILQ_INSERT_TAIL(&focus_mappings, focus_mapping, focus_mappings);
     }
@@ -408,6 +439,9 @@ static int json_bool(void *ctx, int val) {
         to_focus = json_node;
     }
 
+    if (strcasecmp(last_key, "sticky") == 0)
+        json_node->sticky = val;
+
     if (parsing_swallows) {
         if (strcasecmp(last_key, "restart_mode") == 0)
             current_swallow->restart_mode = val;
index 6f44fa96e37f79fcfc848e377ba657a8e0df73a8..856330b6d4a8ea4ebe0c353d5aecd906296fd921 100644 (file)
--- a/src/log.c
+++ b/src/log.c
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * log.c: Logging functions.
  *
@@ -246,6 +246,15 @@ static void vlog(const bool print, const char *fmt, va_list args) {
         len += vsnprintf(message + len, sizeof(message) - len, fmt, args);
         if (len >= sizeof(message)) {
             fprintf(stderr, "BUG: single log message > 4k\n");
+
+            /* vsnprintf returns the number of bytes that *would have been written*,
+             * not the actual amount written. Thus, limit len to sizeof(message) to avoid
+             * memory corruption and outputting garbage later.  */
+            len = sizeof(message);
+
+            /* Punch in a newline so the next log message is not dangling at
+             * the end of the truncated message. */
+            message[len - 2] = '\n';
         }
 
         /* If there is no space for the current message in the ringbuffer, we
index 8b514178bbbf3283fc9391f11ef5c6f42643ba1a..0dc259363163e7bb89998387acb27827cd73e572 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * main.c: Initialization, main loop
  *
@@ -188,7 +188,7 @@ static void handle_signal(int sig, siginfo_t *info, void *data) {
 int main(int argc, char *argv[]) {
     /* Keep a symbol pointing to the I3_VERSION string constant so that we have
      * it in gdb backtraces. */
-    const char *i3_version __attribute__((unused)) = I3_VERSION;
+    const char *_i3_version __attribute__((unused)) = i3_version;
     char *override_configpath = NULL;
     bool autostart = true;
     char *layout_path = NULL;
@@ -261,11 +261,11 @@ int main(int argc, char *argv[]) {
                 only_check_config = true;
                 break;
             case 'v':
-                printf("i3 version " I3_VERSION " © 2009-2014 Michael Stapelberg and contributors\n");
+                printf("i3 version %s © 2009 Michael Stapelberg and contributors\n", i3_version);
                 exit(EXIT_SUCCESS);
                 break;
             case 'm':
-                printf("Binary i3 version:  " I3_VERSION " © 2009-2014 Michael Stapelberg and contributors\n");
+                printf("Binary i3 version:  %s © 2009 Michael Stapelberg and contributors\n", i3_version);
                 display_running_version();
                 exit(EXIT_SUCCESS);
                 break;
@@ -450,13 +450,13 @@ int main(int argc, char *argv[]) {
             memset(cwd, '\0', cwd_size);
             if (read(patternfd, cwd, cwd_size) > 0)
                 /* a trailing newline is included in cwd */
-                LOG("CORE DUMPS: Your core_pattern is: %s", cwd);
+                LOG("CORE DUMPS: Your core_pattern is: \"%s\".\n", cwd);
             close(patternfd);
         }
         free(cwd);
     }
 
-    LOG("i3 " I3_VERSION " starting\n");
+    LOG("i3 %s starting\n", i3_version);
 
     conn = xcb_connect(NULL, &conn_screen);
     if (xcb_connection_has_error(conn))
@@ -474,6 +474,12 @@ int main(int argc, char *argv[]) {
     root_screen = xcb_aux_get_screen(conn, conn_screen);
     root = root_screen->root;
 
+/* Place requests for the atoms we need as soon as possible */
+#define xmacro(atom) \
+    xcb_intern_atom_cookie_t atom##_cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom);
+#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
@@ -491,6 +497,20 @@ int main(int argc, char *argv[]) {
     xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(conn, root);
     xcb_query_pointer_cookie_t pointercookie = xcb_query_pointer(conn, root);
 
+/* Setup NetWM atoms */
+#define xmacro(name)                                                                       \
+    do {                                                                                   \
+        xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, name##_cookie, NULL); \
+        if (!reply) {                                                                      \
+            ELOG("Could not get atom " #name "\n");                                        \
+            exit(-1);                                                                      \
+        }                                                                                  \
+        A_##name = reply->atom;                                                            \
+        free(reply);                                                                       \
+    } while (0);
+#include "atoms.xmacro"
+#undef xmacro
+
     load_configuration(conn, override_configpath, false);
 
     if (config.ipc_socket_path == NULL) {
@@ -512,12 +532,6 @@ int main(int argc, char *argv[]) {
     }
     DLOG("root geometry reply: (%d, %d) %d x %d\n", greply->x, greply->y, greply->width, greply->height);
 
-/* Place requests for the atoms we need as soon as possible */
-#define xmacro(atom) \
-    xcb_intern_atom_cookie_t atom##_cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom);
-#include "atoms.xmacro"
-#undef xmacro
-
     xcursor_load_cursors();
 
     /* Set a cursor for the root window (otherwise the root window will show no
@@ -542,25 +556,42 @@ int main(int argc, char *argv[]) {
                               0xff,
                               0xff,
                               NULL);
+
+        /* Setting both, XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE and
+         * XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED, will lead to the
+         * X server sending us the full XKB state in KeyPress and KeyRelease:
+         * https://sources.debian.net/src/xorg-server/2:1.17.2-1.1/xkb/xkbEvents.c/?hl=927#L927
+         */
+        xcb_xkb_per_client_flags_reply_t *pcf_reply;
+        /* The last three parameters are unset because they are only relevant
+         * when using a feature called “automatic reset of boolean controls”:
+         * http://www.x.org/releases/X11R7.7/doc/kbproto/xkbproto.html#Automatic_Reset_of_Boolean_Controls
+         * */
+        pcf_reply = xcb_xkb_per_client_flags_reply(
+            conn,
+            xcb_xkb_per_client_flags(
+                conn,
+                XCB_XKB_ID_USE_CORE_KBD,
+                XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE | XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED,
+                XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE | XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED,
+                0 /* uint32_t ctrlsToChange */,
+                0 /* uint32_t autoCtrls */,
+                0 /* uint32_t autoCtrlsValues */),
+            NULL);
+        if (pcf_reply == NULL ||
+            !(pcf_reply->value & XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE)) {
+            ELOG("Could not set XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE\n");
+        }
+        if (pcf_reply == NULL ||
+            !(pcf_reply->value & XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED)) {
+            ELOG("Could not set XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED\n");
+        }
+        free(pcf_reply);
         xkb_base = extreply->first_event;
     }
 
     restore_connect();
 
-/* Setup NetWM atoms */
-#define xmacro(name)                                                                       \
-    do {                                                                                   \
-        xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, name##_cookie, NULL); \
-        if (!reply) {                                                                      \
-            ELOG("Could not get atom " #name "\n");                                        \
-            exit(-1);                                                                      \
-        }                                                                                  \
-        A_##name = reply->atom;                                                            \
-        free(reply);                                                                       \
-    } while (0);
-#include "atoms.xmacro"
-#undef xmacro
-
     property_handlers_init();
 
     ewmh_setup_hints();
@@ -569,12 +600,15 @@ int main(int argc, char *argv[]) {
 
     xcb_numlock_mask = aio_get_mod_mask_for(XCB_NUM_LOCK, keysyms);
 
+    if (!load_keymap())
+        die("Could not load keymap\n");
+
     translate_keysyms();
-    grab_all_keys(conn, false);
+    grab_all_keys(conn);
 
     bool needs_tree_init = true;
     if (layout_path) {
-        LOG("Trying to restore the layout from %s...", layout_path);
+        LOG("Trying to restore the layout from \"%s\".\n", layout_path);
         needs_tree_init = !tree_restore(layout_path, greply);
         if (delete_layout_path) {
             unlink(layout_path);
@@ -633,7 +667,7 @@ int main(int argc, char *argv[]) {
     if (ipc_socket == -1) {
         ELOG("Could not create the IPC socket, IPC disabled\n");
     } else {
-        struct ev_io *ipc_io = scalloc(sizeof(struct ev_io));
+        struct ev_io *ipc_io = scalloc(1, sizeof(struct ev_io));
         ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ);
         ev_io_start(main_loop, ipc_io);
     }
@@ -661,7 +695,7 @@ int main(int argc, char *argv[]) {
                 ELOG("Could not disable FD_CLOEXEC on fd %d\n", fd);
             }
 
-            struct ev_io *ipc_io = scalloc(sizeof(struct ev_io));
+            struct ev_io *ipc_io = scalloc(1, sizeof(struct ev_io));
             ev_io_init(ipc_io, ipc_new_client, fd, EV_READ);
             ev_io_start(main_loop, ipc_io);
         }
@@ -677,9 +711,9 @@ int main(int argc, char *argv[]) {
     ewmh_update_desktop_names();
     ewmh_update_desktop_viewport();
 
-    struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io));
-    xcb_check = scalloc(sizeof(struct ev_check));
-    struct ev_prepare *xcb_prepare = scalloc(sizeof(struct ev_prepare));
+    struct ev_io *xcb_watcher = scalloc(1, sizeof(struct ev_io));
+    xcb_check = scalloc(1, sizeof(struct ev_check));
+    struct ev_prepare *xcb_prepare = scalloc(1, sizeof(struct ev_prepare));
 
     ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
     ev_io_start(main_loop, xcb_watcher);
@@ -766,7 +800,7 @@ int main(int argc, char *argv[]) {
             sigaction(SIGABRT, &action, NULL) == -1 ||
             sigaction(SIGFPE, &action, NULL) == -1 ||
             sigaction(SIGSEGV, &action, NULL) == -1)
-            ELOG("Could not setup signal handler");
+            ELOG("Could not setup signal handler.\n");
     }
 
     /* Catch all signals with default action "Term", see signal(7) */
@@ -775,7 +809,7 @@ int main(int argc, char *argv[]) {
         sigaction(SIGALRM, &action, NULL) == -1 ||
         sigaction(SIGUSR1, &action, NULL) == -1 ||
         sigaction(SIGUSR2, &action, NULL) == -1)
-        ELOG("Could not setup signal handler");
+        ELOG("Could not setup signal handler.\n");
 
     /* Ignore SIGPIPE to survive errors when an IPC client disconnects
      * while we are sending them a message */
index 2b3c6743a20df8b8b1c4525c9719fa11cdf6f01a..e3769670fd0644684d3412cd8af2a3679f0f7f52 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * manage.c: Initially managing new windows (or existing ones on restart).
  *
@@ -164,24 +164,15 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
 
     DLOG("Managing window 0x%08x\n", window);
 
-    i3Window *cwindow = scalloc(sizeof(i3Window));
+    i3Window *cwindow = scalloc(1, sizeof(i3Window));
     cwindow->id = window;
     cwindow->depth = get_visual_depth(attr->visual);
 
-    /* We need to grab the mouse buttons for click to focus */
+    /* 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,
-                    1 /* left mouse button */,
-                    XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
-
-    xcb_grab_button(conn, false, window, XCB_EVENT_MASK_BUTTON_PRESS,
-                    XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
-                    2 /* middle mouse button */,
-                    XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
-
-    xcb_grab_button(conn, false, window, XCB_EVENT_MASK_BUTTON_PRESS,
-                    XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
-                    3 /* right mouse button */,
+                    XCB_BUTTON_INDEX_ANY,
                     XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
 
     /* update as much information as possible so far (some replies may be NULL) */
@@ -210,6 +201,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     /* check if the window needs WM_TAKE_FOCUS */
     cwindow->needs_take_focus = window_supports_protocol(cwindow->id, A_WM_TAKE_FOCUS);
 
+    /* read the preferred _NET_WM_WINDOW_TYPE atom */
+    cwindow->window_type = xcb_get_preferred_window_type(type_reply);
+
     /* Where to start searching for a container that swallows the new one? */
     Con *search_at = croot;
 
@@ -307,6 +301,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     if (nc->window != NULL && nc->window != cwindow) {
         if (!restore_kill_placeholder(nc->window->id)) {
             DLOG("Uh?! Container without a placeholder, but with a window, has swallowed this to-be-managed window?!\n");
+        } else {
+            /* Remove remaining criteria, the first swallowed window wins. */
+            while (!TAILQ_EMPTY(&(nc->swallow_head))) {
+                Match *first = TAILQ_FIRST(&(nc->swallow_head));
+                TAILQ_REMOVE(&(nc->swallow_head), first, matches);
+                match_free(first);
+            }
         }
     }
     nc->window = cwindow;
@@ -383,6 +384,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
         want_floating = true;
     }
 
+    if (xcb_reply_contains_atom(state_reply, A__NET_WM_STATE_STICKY))
+        nc->sticky = true;
+
     FREE(state_reply);
     FREE(type_reply);
 
@@ -425,6 +429,17 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     if (cwindow->dock)
         want_floating = false;
 
+    /* Plasma windows set their geometry in WM_SIZE_HINTS. */
+    if ((wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_US_POSITION || wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_POSITION) &&
+        (wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_US_SIZE || wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_SIZE)) {
+        DLOG("We are setting geometry according to wm_size_hints x=%d y=%d w=%d h=%d\n",
+             wm_size_hints.x, wm_size_hints.y, wm_size_hints.width, wm_size_hints.height);
+        geom->x = wm_size_hints.x;
+        geom->y = wm_size_hints.y;
+        geom->width = wm_size_hints.width;
+        geom->height = wm_size_hints.height;
+    }
+
     /* Store the requested geometry. The width/height gets raised to at least
      * 75x50 when entering floating mode, which is the minimum size for a
      * window to be useful (smaller windows are usually overlays/toolbars/…
@@ -512,8 +527,10 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     /* 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) {
-        DLOG("Now setting focus.\n");
-        con_focus(nc);
+        if (assignment_for(cwindow, A_NO_FOCUS) == NULL) {
+            DLOG("Now setting focus.\n");
+            con_focus(nc);
+        }
     }
 
     tree_render();
index dc4d422f27550d9754ea791977d00c899148c0c6..20af38f61b3c765cbf6ada30fab725a3979c3b9c 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * A "match" is a data structure which acts like a mask or expression to match
  * certain windows or not. For example, when using commands, you can specify a
  */
 void match_init(Match *match) {
     memset(match, 0, sizeof(Match));
-    match->dock = -1;
+    match->dock = M_DONTCHECK;
     match->urgent = U_DONTCHECK;
+    /* we use this as the placeholder value for "not set". */
+    match->window_type = UINT32_MAX;
 }
 
 /*
@@ -46,8 +48,10 @@ bool match_is_empty(Match *match) {
             match->class == NULL &&
             match->instance == NULL &&
             match->window_role == NULL &&
+            match->workspace == NULL &&
             match->urgent == U_DONTCHECK &&
             match->id == XCB_NONE &&
+            match->window_type == UINT32_MAX &&
             match->con_id == NULL &&
             match->dock == -1 &&
             match->floating == M_ANY);
@@ -75,6 +79,7 @@ void match_copy(Match *dest, Match *src) {
     DUPLICATE_REGEX(class);
     DUPLICATE_REGEX(instance);
     DUPLICATE_REGEX(window_role);
+    DUPLICATE_REGEX(workspace);
 }
 
 /*
@@ -85,8 +90,12 @@ bool match_matches_window(Match *match, i3Window *window) {
     LOG("Checking window 0x%08x (class %s)\n", window->id, window->class_class);
 
     if (match->class != NULL) {
-        if (window->class_class != NULL &&
-            regex_matches(match->class, window->class_class)) {
+        if (window->class_class == NULL)
+            return false;
+        if (strcmp(match->class->pattern, "__focused__") == 0 &&
+            strcmp(window->class_class, focused->window->class_class) == 0) {
+            LOG("window class matches focused window\n");
+        } else if (regex_matches(match->class, window->class_class)) {
             LOG("window class matches (%s)\n", window->class_class);
         } else {
             return false;
@@ -94,8 +103,12 @@ bool match_matches_window(Match *match, i3Window *window) {
     }
 
     if (match->instance != NULL) {
-        if (window->class_instance != NULL &&
-            regex_matches(match->instance, window->class_instance)) {
+        if (window->class_instance == NULL)
+            return false;
+        if (strcmp(match->instance->pattern, "__focused__") == 0 &&
+            strcmp(window->class_instance, focused->window->class_instance) == 0) {
+            LOG("window instance matches focused window\n");
+        } else if (regex_matches(match->instance, window->class_instance)) {
             LOG("window instance matches (%s)\n", window->class_instance);
         } else {
             return false;
@@ -112,23 +125,41 @@ bool match_matches_window(Match *match, i3Window *window) {
     }
 
     if (match->title != NULL) {
-        if (window->name != NULL &&
-            regex_matches(match->title, i3string_as_utf8(window->name))) {
-            LOG("title matches (%s)\n", i3string_as_utf8(window->name));
+        if (window->name == NULL)
+            return false;
+
+        const char *title = i3string_as_utf8(window->name);
+        if (strcmp(match->title->pattern, "__focused__") == 0 &&
+            strcmp(title, i3string_as_utf8(focused->window->name)) == 0) {
+            LOG("window title matches focused window\n");
+        } else if (regex_matches(match->title, title)) {
+            LOG("title matches (%s)\n", title);
         } else {
             return false;
         }
     }
 
     if (match->window_role != NULL) {
-        if (window->role != NULL &&
-            regex_matches(match->window_role, window->role)) {
+        if (window->role == NULL)
+            return false;
+        if (strcmp(match->window_role->pattern, "__focused__") == 0 &&
+            strcmp(window->role, focused->window->role) == 0) {
+            LOG("window role matches focused window\n");
+        } else if (regex_matches(match->window_role, window->role)) {
             LOG("window_role matches (%s)\n", window->role);
         } else {
             return false;
         }
     }
 
+    if (match->window_type != UINT32_MAX) {
+        if (window->window_type == match->window_type) {
+            LOG("window_type matches (%i)\n", match->window_type);
+        } else {
+            return false;
+        }
+    }
+
     Con *con = NULL;
     if (match->urgent == U_LATEST) {
         /* if the window isn't urgent, no sense in searching */
@@ -161,7 +192,25 @@ bool match_matches_window(Match *match, i3Window *window) {
         LOG("urgent matches oldest\n");
     }
 
-    if (match->dock != -1) {
+    if (match->workspace != NULL) {
+        if ((con = con_by_window_id(window->id)) == NULL)
+            return false;
+
+        Con *ws = con_get_workspace(con);
+        if (ws == NULL)
+            return false;
+
+        if (strcmp(match->workspace->pattern, "__focused__") == 0 &&
+            strcmp(ws->name, con_get_workspace(focused)->name) == 0) {
+            LOG("workspace matches focused workspace\n");
+        } else if (regex_matches(match->workspace, ws->name)) {
+            LOG("workspace matches (%s)\n", ws->name);
+        } else {
+            return false;
+        }
+    }
+
+    if (match->dock != M_DONTCHECK) {
         if ((window->dock == W_DOCK_TOP && match->dock == M_DOCK_TOP) ||
             (window->dock == W_DOCK_BOTTOM && match->dock == M_DOCK_BOTTOM) ||
             ((window->dock == W_DOCK_TOP || window->dock == W_DOCK_BOTTOM) &&
index 1999a1fe901853d06924df49f5c026cc4f7977e2..bd228a1cc19a19692f48e3f3dd7fb9b6c920f39a 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * move.c: Moving containers into some direction.
  *
@@ -249,6 +249,14 @@ void tree_move(Con *con, int direction) {
                         ? AFTER
                         : BEFORE);
         insert_con_into(con, target, position);
+    } else if (con->parent->parent->type == CT_WORKSPACE &&
+               con->parent->layout != L_DEFAULT &&
+               con_num_children(con->parent) == 1) {
+        /* Con is the lone child of a non-default layout container at the edge
+         * of the workspace. Treat it as though the workspace is its parent
+         * and move it to the next output. */
+        DLOG("Grandparent is workspace\n");
+        move_to_output_directed(con, direction);
     } else {
         DLOG("Moving into container above\n");
         position = (direction == D_UP || direction == D_LEFT ? BEFORE : AFTER);
index 822a0f8891004b5c8c8b14cc4c8f68deace51f3b..62b146a74150fa65e094e09a6b4e4570552700b1 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * output.c: Output (monitor) related functions.
  *
@@ -46,3 +46,41 @@ Output *get_output_from_string(Output *current_output, const char *output_str) {
 
     return output;
 }
+
+/*
+ * Iterates over all outputs and pushes sticky windows to the currently visible
+ * workspace on that output.
+ *
+ */
+void output_push_sticky_windows(Con *to_focus) {
+    Con *output;
+    TAILQ_FOREACH(output, &(croot->focus_head), focused) {
+        Con *workspace, *visible_ws = NULL;
+        GREP_FIRST(visible_ws, output_get_content(output), workspace_is_visible(child));
+
+        /* We use this loop instead of TAILQ_FOREACH to avoid problems if the
+         * sticky window was the last window on that workspace as moving it in
+         * this case will close the workspace. */
+        for (workspace = TAILQ_FIRST(&(output_get_content(output)->focus_head));
+             workspace != TAILQ_END(&(output_get_content(output)->focus_head));) {
+            Con *current_ws = workspace;
+            workspace = TAILQ_NEXT(workspace, focused);
+
+            /* Since moving the windows actually removes them from the list of
+             * floating windows on this workspace, here too we need to use
+             * another loop than TAILQ_FOREACH. */
+            Con *child;
+            for (child = TAILQ_FIRST(&(current_ws->focus_head));
+                 child != TAILQ_END(&(current_ws->focus_head));) {
+                Con *current = child;
+                child = TAILQ_NEXT(child, focused);
+                if (current->type != CT_FLOATING_CON)
+                    continue;
+
+                if (con_is_sticky(current)) {
+                    con_move_to_workspace(current, visible_ws, true, false, current != to_focus->parent);
+                }
+            }
+        }
+    }
+}
index a4a0f6fd6d36fe7c626d836e7bc23d8c34573cdb..e4522c4de1009a33ccdabae73062e363a8c76934 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * For more information on RandR, please see the X.org RandR specification at
  * http://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt
@@ -27,7 +27,8 @@ xcb_randr_get_output_primary_reply_t *primary;
 /* Stores all outputs available in your current session. */
 struct outputs_head outputs = TAILQ_HEAD_INITIALIZER(outputs);
 
-static bool randr_disabled = false;
+/* This is the output covering the root window */
+static Output *root_output;
 
 /*
  * Get a specific output by its internal X11 id. Used by randr_query_outputs
@@ -72,6 +73,21 @@ Output *get_first_output(void) {
     die("No usable outputs available.\n");
 }
 
+/*
+ * Check whether there are any active outputs (excluding the root output).
+ *
+ */
+static bool any_randr_output_active(void) {
+    Output *output;
+
+    TAILQ_FOREACH(output, &outputs, outputs) {
+        if (output != root_output && !output->to_be_disabled && output->active)
+            return true;
+    }
+
+    return false;
+}
+
 /*
  * Returns the active (!) output which contains the coordinates x, y or NULL
  * if there is no output which contains these coordinates.
@@ -220,27 +236,20 @@ Output *get_output_next(direction_t direction, Output *current, output_close_far
 }
 
 /*
- * Disables RandR support by creating exactly one output with the size of the
- * X11 screen.
+ * Creates an output covering the root window.
  *
  */
-void disable_randr(xcb_connection_t *conn) {
-    DLOG("RandR extension unusable, disabling.\n");
-
-    Output *s = scalloc(sizeof(Output));
+Output *create_root_output(xcb_connection_t *conn) {
+    Output *s = scalloc(1, sizeof(Output));
 
-    s->active = true;
+    s->active = false;
     s->rect.x = 0;
     s->rect.y = 0;
     s->rect.width = root_screen->width_in_pixels;
     s->rect.height = root_screen->height_in_pixels;
     s->name = "xroot-0";
-    output_init_con(s);
-    init_ws_for_output(s, output_get_content(s->con));
-
-    TAILQ_INSERT_TAIL(&outputs, s, outputs);
 
-    randr_disabled = true;
+    return s;
 }
 
 /*
@@ -292,7 +301,7 @@ void output_init_con(Output *output) {
     topdock->type = CT_DOCKAREA;
     topdock->layout = L_DOCKAREA;
     /* this container swallows dock clients */
-    Match *match = scalloc(sizeof(Match));
+    Match *match = scalloc(1, sizeof(Match));
     match_init(match);
     match->dock = M_DOCK_TOP;
     match->insert_where = M_BELOW;
@@ -326,7 +335,7 @@ void output_init_con(Output *output) {
     bottomdock->type = CT_DOCKAREA;
     bottomdock->layout = L_DOCKAREA;
     /* this container swallows dock clients */
-    match = scalloc(sizeof(Match));
+    match = scalloc(1, sizeof(Match));
     match_init(match);
     match->dock = M_DOCK_BOTTOM;
     match->insert_where = M_BELOW;
@@ -522,7 +531,7 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id,
     Output *new = get_output_by_id(id);
     bool existing = (new != NULL);
     if (!existing)
-        new = scalloc(sizeof(Output));
+        new = scalloc(1, sizeof(Output));
     new->id = id;
     new->primary = (primary && primary->output == id);
     FREE(new->name);
@@ -588,6 +597,8 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id,
 /*
  * (Re-)queries the outputs via RandR and stores them in the list of outputs.
  *
+ * If no outputs are found use the root window.
+ *
  */
 void randr_query_outputs(void) {
     Output *output, *other, *first;
@@ -602,9 +613,6 @@ void randr_query_outputs(void) {
     /* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */
     xcb_randr_output_t *randr_outputs;
 
-    if (randr_disabled)
-        return;
-
     /* Get screen resources (primary output, crtcs, outputs, modes) */
     rcookie = xcb_randr_get_screen_resources_current(conn, root);
     pcookie = xcb_randr_get_output_primary(conn, root);
@@ -613,10 +621,9 @@ void randr_query_outputs(void) {
         ELOG("Could not get RandR primary output\n");
     else
         DLOG("primary output is %08x\n", primary->output);
-    if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) {
-        disable_randr(conn);
+    if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL)
         return;
-    }
+
     cts = res->config_timestamp;
 
     int len = xcb_randr_get_screen_resources_current_outputs_length(res);
@@ -638,6 +645,16 @@ void randr_query_outputs(void) {
         free(output);
     }
 
+    /* If there's no randr output, enable the output covering the root window. */
+    if (any_randr_output_active()) {
+        DLOG("Active RandR output found. Disabling root output.\n");
+        if (root_output->active)
+            root_output->to_be_disabled = true;
+    } else {
+        DLOG("No active RandR output found. Enabling root output.\n");
+        root_output->active = true;
+    }
+
     /* Check for clones, disable the clones and reduce the mode to the
      * lowest common mode */
     TAILQ_FOREACH(output, &outputs, outputs) {
@@ -779,14 +796,6 @@ void randr_query_outputs(void) {
         }
     }
 
-    if (TAILQ_EMPTY(&outputs)) {
-        ELOG("No outputs found via RandR, disabling\n");
-        disable_randr(conn);
-    }
-
-    /* Verifies that there is at least one active output as a side-effect. */
-    get_first_output();
-
     /* Just go through each active output and assign one workspace */
     TAILQ_FOREACH(output, &outputs, outputs) {
         if (!output->active)
@@ -822,12 +831,20 @@ void randr_query_outputs(void) {
 void randr_init(int *event_base) {
     const xcb_query_extension_reply_t *extreply;
 
+    root_output = create_root_output(conn);
+    TAILQ_INSERT_TAIL(&outputs, root_output, outputs);
+
     extreply = xcb_get_extension_data(conn, &xcb_randr_id);
     if (!extreply->present) {
-        disable_randr(conn);
+        DLOG("RandR is not present, activating root output.\n");
+        root_output->active = true;
+        output_init_con(root_output);
+        init_ws_for_output(root_output, output_get_content(root_output->con));
+
         return;
-    } else
-        randr_query_outputs();
+    }
+
+    randr_query_outputs();
 
     if (event_base != NULL)
         *event_base = extreply->first_event;
index 60dee5cc92a24d57e42eddefb78a7436a3ed728b..913519bede31d826144bdb7e5272e9c92598d876 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * regex.c: Interface to libPCRE (perl compatible regular expressions).
  *
@@ -25,7 +25,7 @@ struct regex *regex_new(const char *pattern) {
     const char *error;
     int errorcode, offset;
 
-    struct regex *re = scalloc(sizeof(struct regex));
+    struct regex *re = scalloc(1, sizeof(struct regex));
     re->pattern = sstrdup(pattern);
     int options = PCRE_UTF8;
 #ifdef PCRE_HAS_UCP
index d7d576673e4b65bb13dcf9ddfbf1e5e20e2bf070..7ada19ebdc37d76546c4156eef839fe4a35e8919 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * render.c: Renders (determines position/sizes) the layout tree, updating the
  *           various rects. Needs to be pushed to X11 (see x.c) to be visible.
@@ -172,14 +172,14 @@ void render_con(Con *con, bool render_fullscreen) {
          * Ignoring aspect ratio during fullscreen was necessary to fix MPlayer
          * subtitle rendering, see http://bugs.i3wm.org/594 */
         if (!render_fullscreen &&
-            con->aspect_ratio > 0.0) {
+            con->window->aspect_ratio > 0.0) {
             DLOG("aspect_ratio = %f, current width/height are %d/%d\n",
-                 con->aspect_ratio, inset->width, inset->height);
+                 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->aspect_ratio) * new_width;
+                new_height = (1.0 / con->window->aspect_ratio) * new_width;
 
                 if (new_height > inset->height)
                     new_width--;
index 4ed835d57ac37c681cdbe78c1d2b059c7a467be0..05fe50558dba737352efc15333b362cf4ce69e20 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * resize.c: Interactive resizing.
  *
@@ -88,7 +88,7 @@ bool resize_find_tiling_participants(Con **current, Con **other, direction_t dir
         }
     }
 
-    DLOG("Found participants: first=%p and second=%p.", first, second);
+    DLOG("Found participants: first=%p and second=%p.\n", first, second);
     *current = first;
     *other = second;
     if (first == NULL || second == NULL) {
index 53a70d69afe7345c8ed792951303e98aa7c4b18d..439d23ccf43abbb33c47220724fc53a801c626eb 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * restore_layout.c: Everything for restored containers that is not pure state
  *                   parsing (which can be found in load_layout.c).
@@ -109,9 +109,9 @@ void restore_connect(void) {
     if (restore_conn == NULL || xcb_connection_has_error(restore_conn))
         errx(EXIT_FAILURE, "Cannot open display\n");
 
-    xcb_watcher = scalloc(sizeof(struct ev_io));
-    xcb_check = scalloc(sizeof(struct ev_check));
-    xcb_prepare = scalloc(sizeof(struct ev_prepare));
+    xcb_watcher = scalloc(1, sizeof(struct ev_io));
+    xcb_check = scalloc(1, sizeof(struct ev_check));
+    xcb_prepare = scalloc(1, sizeof(struct ev_prepare));
 
     ev_io_init(xcb_watcher, restore_xcb_got_event, xcb_get_file_descriptor(restore_conn), EV_READ);
     ev_io_start(main_loop, xcb_watcher);
@@ -210,7 +210,7 @@ static void open_placeholder_window(Con *con) {
         DLOG("Created placeholder window 0x%08x for leaf container %p / %s\n",
              placeholder, con, con->name);
 
-        placeholder_state *state = scalloc(sizeof(placeholder_state));
+        placeholder_state *state = scalloc(1, sizeof(placeholder_state));
         state->window = placeholder;
         state->con = con;
         state->rect = con->rect;
index 3b7c2a72b009131e616cd57cf0665af3499c46bc..6d83a558ccdc738e42941588536b45806ff82714 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * scratchpad.c: Moving windows to the scratchpad and making them visible again.
  *
@@ -60,7 +60,7 @@ void scratchpad_move(Con *con) {
 
     /* 2: Send the window to the __i3_scratch workspace, mainting its
      * coordinates and not warping the pointer. */
-    con_move_to_workspace(con, __i3_scratch, true, true);
+    con_move_to_workspace(con, __i3_scratch, true, true, false);
 
     /* 3: If this is the first time this window is used as a scratchpad, we set
      * the scratchpad_state to SCRATCHPAD_FRESH. The window will then be
@@ -142,7 +142,7 @@ void scratchpad_show(Con *con) {
             floating->scratchpad_state != SCRATCHPAD_NONE) {
             DLOG("Found a visible scratchpad window on another workspace,\n");
             DLOG("moving it to this workspace: con = %p\n", walk_con);
-            con_move_to_workspace(walk_con, focused_ws, true, false);
+            con_move_to_workspace(walk_con, focused_ws, true, false, false);
             return;
         }
     }
@@ -189,7 +189,7 @@ void scratchpad_show(Con *con) {
     }
 
     /* 1: Move the window from __i3_scratch to the current workspace. */
-    con_move_to_workspace(con, active, true, false);
+    con_move_to_workspace(con, active, true, false, false);
 
     /* 2: Adjust the size if this window was not adjusted yet. */
     if (con->scratchpad_state == SCRATCHPAD_FRESH) {
@@ -198,10 +198,7 @@ void scratchpad_show(Con *con) {
         con->rect.width = output->rect.width * 0.5;
         con->rect.height = output->rect.height * 0.75;
         floating_check_size(con);
-        con->rect.x = output->rect.x +
-                      ((output->rect.width / 2.0) - (con->rect.width / 2.0));
-        con->rect.y = output->rect.y +
-                      ((output->rect.height / 2.0) - (con->rect.height / 2.0));
+        floating_center(con, con_get_workspace(con)->rect);
     }
 
     /* Activate active workspace if window is from another workspace to ensure
index e971f6bdd18c8f8f3b2e38b0aeb165d50a6bb574..ceaa4842286ec14f27567fdf56c29188853719f9 100644 (file)
@@ -4,8 +4,8 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
- * © 2009-2010 Jan-Erik Rediger
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Jan-Erik Rediger
  *
  * sighandler.c: Interactive crash dialog upon SIGSEGV/SIGABRT/SIGFPE (offers
  *               to restart inplace).
@@ -327,5 +327,5 @@ void setup_signal_handler(void) {
         sigaction(SIGABRT, &action, NULL) == -1 ||
         sigaction(SIGFPE, &action, NULL) == -1 ||
         sigaction(SIGSEGV, &action, NULL) == -1)
-        ELOG("Could not setup signal handler");
+        ELOG("Could not setup signal handler.\n");
 }
index aa347bd75fe86096096b7bc0b6148f7859fe8a91..400d319239ec6149df3c4d8046cd544c22faf9de 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * startup.c: Startup notification code. Ensures a startup notification context
  *            is setup when launching applications. We store the current
@@ -131,7 +131,7 @@ void startup_sequence_delete(struct Startup_Sequence *sequence) {
  *
  */
 void start_application(const char *command, bool no_startup_id) {
-    SnLauncherContext *context;
+    SnLauncherContext *context = NULL;
 
     if (!no_startup_id) {
         /* Create a startup notification context to monitor the progress of this
@@ -149,7 +149,7 @@ void start_application(const char *command, bool no_startup_id) {
         free(first_word);
 
         /* Trigger a timeout after 60 seconds */
-        struct ev_timer *timeout = scalloc(sizeof(struct ev_timer));
+        struct ev_timer *timeout = scalloc(1, sizeof(struct ev_timer));
         ev_timer_init(timeout, startup_timeout, 60.0, 0.);
         timeout->data = context;
         ev_timer_start(main_loop, timeout);
@@ -159,7 +159,7 @@ void start_application(const char *command, bool no_startup_id) {
         /* Save the ID and current workspace in our internal list of startup
          * sequences */
         Con *ws = con_get_workspace(focused);
-        struct Startup_Sequence *sequence = scalloc(sizeof(struct Startup_Sequence));
+        struct Startup_Sequence *sequence = scalloc(1, sizeof(struct Startup_Sequence));
         sequence->id = sstrdup(sn_launcher_context_get_startup_id(context));
         sequence->workspace = sstrdup(ws->name);
         sequence->context = context;
@@ -316,14 +316,8 @@ struct Startup_Sequence *startup_sequence_get(i3Window *cwindow,
     }
 
     char *startup_id;
-    if (asprintf(&startup_id, "%.*s", xcb_get_property_value_length(startup_id_reply),
-                 (char *)xcb_get_property_value(startup_id_reply)) == -1) {
-        perror("asprintf()");
-        DLOG("Could not get _NET_STARTUP_ID\n");
-        free(startup_id_reply);
-        return NULL;
-    }
-
+    sasprintf(&startup_id, "%.*s", xcb_get_property_value_length(startup_id_reply),
+              (char *)xcb_get_property_value(startup_id_reply));
     struct Startup_Sequence *current, *sequence = NULL;
     TAILQ_FOREACH(current, &startup_sequences, sequences) {
         if (strcmp(current->id, startup_id) != 0)
index 753b02fcd35734073f7afa4e241045323bae054f..1d06d87481bc42b53cecb9748db111d6f0348369 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * tree.c: Everything that primarily modifies the layout tree data structure.
  *
@@ -202,7 +202,7 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
 
     /* remove the urgency hint of the workspace (if set) */
     if (con->urgent) {
-        con->urgent = false;
+        con_set_urgency(con, false);
         con_update_parents_urgency(con);
         workspace_update_urgent_flag(con_get_workspace(con));
     }
index 5760ae72588f52afc5cd6caf617a4b184e344ab9..7a73011d050d879dc5aecd205e777f0779f16e95 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * util.c: Utility functions, which can be useful everywhere within i3 (see
  *         also libi3).
@@ -122,7 +122,7 @@ void exec_i3_utility(char *name, char *argv[]) {
     /* if the script is not in path, maybe the user installed to a strange
      * location and runs the i3 binary with an absolute path. We use
      * argv[0]’s dirname */
-    char *pathbuf = strdup(start_argv[0]);
+    char *pathbuf = sstrdup(start_argv[0]);
     char *dir = dirname(pathbuf);
     sasprintf(&migratepath, "%s/%s", dir, name);
     argv[0] = migratepath;
@@ -159,38 +159,6 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_mes
     }
 }
 
-/*
- * This function resolves ~ in pathnames.
- * It may resolve wildcards in the first part of the path, but if no match
- * or multiple matches are found, it just returns a copy of path as given.
- *
- */
-char *resolve_tilde(const char *path) {
-    static glob_t globbuf;
-    char *head, *tail, *result;
-
-    tail = strchr(path, '/');
-    head = strndup(path, tail ? (size_t)(tail - path) : strlen(path));
-
-    int res = glob(head, GLOB_TILDE, NULL, &globbuf);
-    free(head);
-    /* no match, or many wildcard matches are bad */
-    if (res == GLOB_NOMATCH || globbuf.gl_pathc != 1)
-        result = sstrdup(path);
-    else if (res != 0) {
-        die("glob() failed");
-    } else {
-        head = globbuf.gl_pathv[0];
-        result = scalloc(strlen(head) + (tail ? strlen(tail) : 0) + 1);
-        strncpy(result, head, strlen(head));
-        if (tail)
-            strncat(result, tail, strlen(tail));
-    }
-    globfree(&globbuf);
-
-    return result;
-}
-
 /*
  * Checks if the given path exists by calling stat().
  *
@@ -201,24 +169,35 @@ bool path_exists(const char *path) {
 }
 
 /*
- * Goes through the list of arguments (for exec()) and checks if the given argument
- * is present. If not, it copies the arguments (because we cannot realloc it) and
- * appends the given argument.
- *
+ * Goes through the list of arguments (for exec()) and add/replace the given option,
+ * including the option name, its argument, and the option character.
  */
-static char **append_argument(char **original, char *argument) {
+static char **add_argument(char **original, char *opt_char, char *opt_arg, char *opt_name) {
     int num_args;
-    for (num_args = 0; original[num_args] != NULL; num_args++) {
-        DLOG("original argument: \"%s\"\n", original[num_args]);
-        /* If the argument is already present we return the original pointer */
-        if (strcmp(original[num_args], argument) == 0)
-            return original;
+    for (num_args = 0; original[num_args] != NULL; num_args++)
+        ;
+    char **result = scalloc(num_args + 3, sizeof(char *));
+
+    /* copy the arguments, but skip the ones we'll replace */
+    int write_index = 0;
+    bool skip_next = false;
+    for (int i = 0; i < num_args; ++i) {
+        if (skip_next) {
+            skip_next = false;
+            continue;
+        }
+        if (!strcmp(original[i], opt_char) ||
+            (opt_name && !strcmp(original[i], opt_name))) {
+            if (opt_arg)
+                skip_next = true;
+            continue;
+        }
+        result[write_index++] = original[i];
     }
-    /* Copy the original array */
-    char **result = smalloc((num_args + 2) * sizeof(char *));
-    memcpy(result, original, num_args * sizeof(char *));
-    result[num_args] = argument;
-    result[num_args + 1] = NULL;
+
+    /* add the arguments we'll replace */
+    result[write_index++] = opt_char;
+    result[write_index] = opt_arg;
 
     return result;
 }
@@ -254,7 +233,7 @@ char *store_restart_layout(void) {
     char *filenamecopy = sstrdup(filename);
     char *base = dirname(filenamecopy);
     DLOG("Creating \"%s\" for storing the restart layout\n", base);
-    if (!mkdirp(base))
+    if (mkdirp(base, DEFAULT_DIR_MODE) != 0)
         ELOG("Could not create \"%s\" for storing the restart layout, layout will be lost.\n", base);
     free(filenamecopy);
 
@@ -299,39 +278,21 @@ void i3_restart(bool forget_layout) {
     ipc_shutdown();
 
     LOG("restarting \"%s\"...\n", start_argv[0]);
-    /* make sure -a is in the argument list or append it */
-    start_argv = append_argument(start_argv, "-a");
+    /* make sure -a is in the argument list or add it */
+    start_argv = add_argument(start_argv, "-a", NULL, NULL);
+
+    /* make debuglog-on persist */
+    if (get_debug_logging()) {
+        start_argv = add_argument(start_argv, "-d", "all", NULL);
+    }
 
     /* replace -r <file> so that the layout is restored */
     if (restart_filename != NULL) {
-        /* create the new argv */
-        int num_args;
-        for (num_args = 0; start_argv[num_args] != NULL; num_args++)
-            ;
-        char **new_argv = scalloc((num_args + 3) * sizeof(char *));
-
-        /* copy the arguments, but skip the ones we'll replace */
-        int write_index = 0;
-        bool skip_next = false;
-        for (int i = 0; i < num_args; ++i) {
-            if (skip_next)
-                skip_next = false;
-            else if (!strcmp(start_argv[i], "-r") ||
-                     !strcmp(start_argv[i], "--restart"))
-                skip_next = true;
-            else
-                new_argv[write_index++] = start_argv[i];
-        }
-
-        /* add the arguments we'll replace */
-        new_argv[write_index++] = "--restart";
-        new_argv[write_index] = restart_filename;
-
-        /* swap the argvs */
-        start_argv = new_argv;
+        start_argv = add_argument(start_argv, "--restart", restart_filename, "-r");
     }
 
     execvp(start_argv[0], start_argv);
+
     /* not reached */
 }
 
diff --git a/src/version.c b/src/version.c
new file mode 100644 (file)
index 0000000..6e385ad
--- /dev/null
@@ -0,0 +1,11 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * Stores the latest Git commit identifier so that it can be linked into i3
+ * and used dynamically without recompiling every object file.
+ *
+ */
+const char *i3_version = I3_VERSION;
index 36737943fe92cc9a0b2c564c4f4300cb22d7c32b..5898333f3d6490f9074f53b50df9511208b29c22 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * window.c: Updates window attributes (X11 hints/properties).
  *
@@ -27,30 +27,27 @@ void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool bef
      * null-terminated strings (for compatibility reasons). Instead, we
      * use strdup() on both strings */
     const size_t prop_length = xcb_get_property_value_length(prop);
-    char *new_class = smalloc(prop_length + 1);
-    memcpy(new_class, xcb_get_property_value(prop), prop_length);
-    new_class[prop_length] = '\0';
+    char *new_class = xcb_get_property_value(prop);
+    const size_t class_class_index = strnlen(new_class, prop_length) + 1;
 
     FREE(win->class_instance);
     FREE(win->class_class);
 
-    win->class_instance = sstrdup(new_class);
-    if ((strlen(new_class) + 1) < prop_length)
-        win->class_class = sstrdup(new_class + strlen(new_class) + 1);
+    win->class_instance = sstrndup(new_class, prop_length);
+    if (class_class_index < prop_length)
+        win->class_class = sstrndup(new_class + class_class_index, prop_length - class_class_index);
     else
         win->class_class = NULL;
     LOG("WM_CLASS changed to %s (instance), %s (class)\n",
         win->class_instance, win->class_class);
 
     if (before_mgmt) {
-        free(new_class);
         free(prop);
         return;
     }
 
     run_assignments(win);
 
-    free(new_class);
     free(prop);
 }
 
@@ -69,6 +66,8 @@ 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)));
     win->name_x_changed = true;
     LOG("_NET_WM_NAME changed to \"%s\"\n", i3string_as_utf8(win->name));
 
@@ -107,6 +106,8 @@ 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)));
 
     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 "
@@ -211,13 +212,8 @@ void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool befo
     }
 
     char *new_role;
-    if (asprintf(&new_role, "%.*s", xcb_get_property_value_length(prop),
-                 (char *)xcb_get_property_value(prop)) == -1) {
-        perror("asprintf()");
-        DLOG("Could not get WM_WINDOW_ROLE\n");
-        free(prop);
-        return;
-    }
+    sasprintf(&new_role, "%.*s", xcb_get_property_value_length(prop),
+              (char *)xcb_get_property_value(prop));
     FREE(win->role);
     win->role = new_role;
     LOG("WM_WINDOW_ROLE changed to \"%s\"\n", win->role);
@@ -232,6 +228,23 @@ void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool befo
     free(prop);
 }
 
+/*
+ * Updates the _NET_WM_WINDOW_TYPE property.
+ *
+ */
+void window_update_type(i3Window *window, xcb_get_property_reply_t *reply) {
+    xcb_atom_t new_type = xcb_get_preferred_window_type(reply);
+    if (new_type == XCB_NONE) {
+        DLOG("cannot read _NET_WM_WINDOW_TYPE from window.\n");
+        return;
+    }
+
+    window->window_type = new_type;
+    LOG("_NET_WM_WINDOW_TYPE changed to %i.\n", window->window_type);
+
+    run_assignments(window);
+}
+
 /*
  * Updates the WM_HINTS (we only care about the input focus handling part).
  *
@@ -320,3 +333,76 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo
 #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;
+}
index 8bd4b5ea2fa466078e83e3d0062656edfd2de96b..d3a97453187ee63efefb66f92db78f8e8a722100 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * workspace.c: Modifying workspaces, accessing them, moving containers to
  *              workspaces.
  * back-and-forth switching. */
 static char *previous_workspace_name = NULL;
 
+/* NULL-terminated list of workspace names (in order) extracted from
+ * keybindings. */
+static char **binding_workspace_names = NULL;
+
 /*
  * Sets ws->layout to splith/splitv if default_orientation was specified in the
  * configfile. Otherwise, it uses splith/splitv depending on whether the output
@@ -107,21 +111,21 @@ Con *workspace_get(const char *num, bool *created) {
 }
 
 /*
- * Returns a pointer to a new workspace in the given output. The workspace
- * is created attached to the tree hierarchy through the given content
- * container.
+ * Extracts workspace names from keybindings (e.g. “web” from “bindsym $mod+1
+ * workspace web”), so that when an output needs a workspace, i3 can start with
+ * the first configured one. Needs to be called before reorder_bindings() so
+ * that the config-file order is used, not the i3-internal order.
  *
  */
-Con *create_workspace_on_output(Output *output, Con *content) {
-    /* add a workspace to this output */
-    Con *out, *current;
-    char *name;
-    bool exists = true;
-    Con *ws = con_new(NULL, NULL);
-    ws->type = CT_WORKSPACE;
-
-    /* try the configured workspace bindings first to find a free name */
+void extract_workspace_names_from_bindings(void) {
     Binding *bind;
+    int n = 0;
+    if (binding_workspace_names != NULL) {
+        for (int i = 0; binding_workspace_names[i] != NULL; i++) {
+            free(binding_workspace_names[i]);
+        }
+        FREE(binding_workspace_names);
+    }
     TAILQ_FOREACH(bind, bindings, bindings) {
         DLOG("binding with command %s\n", bind->command);
         if (strlen(bind->command) < strlen("workspace ") ||
@@ -129,7 +133,7 @@ Con *create_workspace_on_output(Output *output, Con *content) {
             continue;
         DLOG("relevant command = %s\n", bind->command);
         const char *target = bind->command + strlen("workspace ");
-        while ((*target == ' ' || *target == '\t') && target != '\0')
+        while (*target == ' ' || *target == '\t')
             target++;
         /* We check if this is the workspace
          * next/prev/next_on_output/prev_on_output/back_and_forth/number command.
@@ -152,17 +156,39 @@ Con *create_workspace_on_output(Output *output, Con *content) {
             free(target_name);
             continue;
         }
-        FREE(ws->name);
-        ws->name = target_name;
-        DLOG("trying name *%s*\n", ws->name);
+        DLOG("Saving workspace name \"%s\"\n", target_name);
 
+        binding_workspace_names = srealloc(binding_workspace_names, ++n * sizeof(char *));
+        binding_workspace_names[n - 1] = target_name;
+    }
+    binding_workspace_names = srealloc(binding_workspace_names, ++n * sizeof(char *));
+    binding_workspace_names[n - 1] = NULL;
+}
+
+/*
+ * Returns a pointer to a new workspace in the given output. The workspace
+ * is created attached to the tree hierarchy through the given content
+ * container.
+ *
+ */
+Con *create_workspace_on_output(Output *output, Con *content) {
+    /* add a workspace to this output */
+    Con *out, *current;
+    char *name;
+    bool exists = true;
+    Con *ws = con_new(NULL, NULL);
+    ws->type = CT_WORKSPACE;
+
+    /* try the configured workspace bindings first to find a free name */
+    for (int n = 0; binding_workspace_names[n] != NULL; n++) {
+        char *target_name = binding_workspace_names[n];
         /* Ensure that this workspace is not assigned to a different output —
          * otherwise we would create it, then move it over to its output, then
          * find a new workspace, etc… */
         bool assigned = false;
         struct Workspace_Assignment *assignment;
         TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
-            if (strcmp(assignment->name, ws->name) != 0 ||
+            if (strcmp(assignment->name, target_name) != 0 ||
                 strcmp(assignment->output, output->name) == 0)
                 continue;
 
@@ -175,10 +201,10 @@ Con *create_workspace_on_output(Output *output, Con *content) {
 
         current = NULL;
         TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
-        GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name));
-
+        GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, target_name));
         exists = (current != NULL);
         if (!exists) {
+            ws->name = sstrdup(target_name);
             /* Set ->num to the number of the workspace, if the name actually
              * is a number or starts with a number */
             ws->num = ws_name_to_number(ws->name);
@@ -322,21 +348,22 @@ static void workspace_reassign_sticky(Con *con) {
 static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents) {
     Con *con = w->data;
 
+    ev_timer_stop(main_loop, con->urgency_timer);
+    FREE(con->urgency_timer);
+
     if (con->urgent) {
         DLOG("Resetting urgency flag of con %p by timer\n", con);
-        con->urgent = false;
+        con_set_urgency(con, false);
         con_update_parents_urgency(con);
         workspace_update_urgent_flag(con_get_workspace(con));
         ipc_send_window_event("urgent", con);
         tree_render();
     }
-
-    ev_timer_stop(main_loop, con->urgency_timer);
-    FREE(con->urgency_timer);
 }
 
 static void _workspace_show(Con *workspace) {
     Con *current, *old = NULL;
+    Con *old_focus = focused;
 
     /* safe-guard against showing i3-internal workspaces like __i3_scratch */
     if (con_is_internal(workspace))
@@ -397,7 +424,7 @@ static void _workspace_show(Con *workspace) {
         if (focused->urgency_timer == NULL) {
             DLOG("Deferring reset of urgency flag of con %p on newly shown workspace %p\n",
                  focused, workspace);
-            focused->urgency_timer = scalloc(sizeof(struct ev_timer));
+            focused->urgency_timer = scalloc(1, sizeof(struct ev_timer));
             /* use a repeating timer to allow for easy resets */
             ev_timer_init(focused->urgency_timer, workspace_defer_update_urgent_hint_cb,
                           config.workspace_urgency_timer, config.workspace_urgency_timer);
@@ -450,6 +477,9 @@ static void _workspace_show(Con *workspace) {
 
     /* Update the EWMH hints */
     ewmh_update_current_desktop();
+
+    /* Push any sticky windows to the now visible workspace. */
+    output_push_sticky_windows(old_focus);
 }
 
 /*
@@ -729,7 +759,7 @@ workspace_prev_on_output_end:
  */
 void workspace_back_and_forth(void) {
     if (!previous_workspace_name) {
-        DLOG("No previous workspace name set. Not switching.");
+        DLOG("No previous workspace name set. Not switching.\n");
         return;
     }
 
@@ -742,7 +772,7 @@ void workspace_back_and_forth(void) {
  */
 Con *workspace_back_and_forth_get(void) {
     if (!previous_workspace_name) {
-        DLOG("no previous workspace name set.");
+        DLOG("No previous workspace name set.\n");
         return NULL;
     }
 
@@ -847,6 +877,9 @@ Con *workspace_attach_to(Con *ws) {
     DLOG("Attaching new split %p to workspace %p\n", new, ws);
     con_attach(new, ws, false);
 
+    /* 5: fix the percentages */
+    con_fix_percent(ws);
+
     return new;
 }
 
diff --git a/src/x.c b/src/x.c
index 9dd09117cf72eaeb234b2f1c10b7c49a9e89b17b..9b9ba6aa75482a338a4f0dc2db73bb84c0e92de6 100644 (file)
--- a/src/x.c
+++ b/src/x.c
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * x.c: Interface to X11, transfers our in-memory state to X11 (see also
  *      render.c). Basically a big state machine.
@@ -12,6 +12,8 @@
  */
 #include "all.h"
 
+xcb_window_t ewmh_window;
+
 /* Stores the X11 window ID of the currently focused window */
 xcb_window_t focused_id = XCB_NONE;
 
@@ -36,6 +38,7 @@ typedef struct con_state {
     bool mapped;
     bool unmap_now;
     bool child_mapped;
+    bool is_hidden;
 
     /** The con for which this state is. */
     Con *con;
@@ -142,11 +145,19 @@ void x_con_init(Con *con, uint16_t depth) {
 
     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_change_property(conn,
+                        XCB_PROP_MODE_REPLACE,
+                        con->frame,
+                        XCB_ATOM_WM_CLASS,
+                        XCB_ATOM_STRING,
+                        8,
+                        (strlen("i3-frame") + 1) * 2,
+                        "i3-frame\0i3-frame\0");
 
     if (win_colormap != XCB_NONE)
         xcb_free_colormap(conn, win_colormap);
 
-    struct con_state *state = scalloc(sizeof(struct con_state));
+    struct con_state *state = scalloc(1, sizeof(struct con_state));
     state->id = con->frame;
     state->mapped = false;
     state->initial = true;
@@ -285,7 +296,7 @@ void x_window_kill(xcb_window_t window, kill_window_t kill_window) {
     /* Every X11 event is 32 bytes long. Therefore, XCB will copy 32 bytes.
      * In order to properly initialize these bytes, we allocate 32 bytes even
      * though we only need less for an xcb_configure_notify_event_t */
-    void *event = scalloc(32);
+    void *event = scalloc(32, 1);
     xcb_client_message_event_t *ev = event;
 
     ev->response_type = XCB_CLIENT_MESSAGE;
@@ -336,7 +347,7 @@ void x_draw_decoration(Con *con) {
         return;
 
     /* 1: build deco_params and compare with cache */
-    struct deco_render_params *p = scalloc(sizeof(struct deco_render_params));
+    struct deco_render_params *p = scalloc(1, sizeof(struct deco_render_params));
 
     /* find out which colors to use */
     if (con->urgent)
@@ -363,6 +374,7 @@ void x_draw_decoration(Con *con) {
         (con->window == NULL || !con->window->name_x_changed) &&
         !parent->pixmap_recreated &&
         !con->pixmap_recreated &&
+        !con->mark_changed &&
         memcmp(p, con->deco_render_params, sizeof(struct deco_render_params)) == 0) {
         free(p);
         goto copy_pixmaps;
@@ -381,6 +393,7 @@ void x_draw_decoration(Con *con) {
 
     parent->pixmap_recreated = false;
     con->pixmap_recreated = false;
+    con->mark_changed = false;
 
     /* 2: draw the client.background, but only for the parts around the client_rect */
     if (con->window != NULL) {
@@ -531,10 +544,28 @@ void x_draw_decoration(Con *con) {
     //DLOG("indent_level = %d, indent_mult = %d\n", indent_level, indent_mult);
     int indent_px = (indent_level * 5) * indent_mult;
 
-    draw_text(win->name,
+    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);
+        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 + 2 + indent_px, con->deco_rect.y + text_offset_y,
-              con->deco_rect.width - 2 - indent_px);
+              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_FREE(title);
 
 after_title:
     /* Since we don’t clip the text at all, it might in some cases be painted
@@ -594,6 +625,31 @@ void x_deco_recurse(Con *con) {
         x_draw_decoration(con);
 }
 
+/*
+ * Sets or removes the _NET_WM_STATE_HIDDEN property on con if necessary.
+ *
+ */
+static void set_hidden_state(Con *con) {
+    if (con->window == NULL) {
+        return;
+    }
+
+    con_state *state = state_for_frame(con->frame);
+    bool should_be_hidden = con_is_hidden(con);
+    if (should_be_hidden == state->is_hidden)
+        return;
+
+    if (should_be_hidden) {
+        DLOG("setting _NET_WM_STATE_HIDDEN for con = %p\n", con);
+        xcb_add_property_atom(conn, con->window->id, A__NET_WM_STATE, A__NET_WM_STATE_HIDDEN);
+    } else {
+        DLOG("removing _NET_WM_STATE_HIDDEN for con = %p\n", con);
+        xcb_remove_property_atom(conn, con->window->id, A__NET_WM_STATE, A__NET_WM_STATE_HIDDEN);
+    }
+
+    state->is_hidden = should_be_hidden;
+}
+
 /*
  * This function pushes the properties of each node of the layout tree to
  * X11 if they have changed (like the map state, position of the window, …).
@@ -797,6 +853,8 @@ void x_push_node(Con *con) {
         fake_absolute_configure_notify(con);
     }
 
+    set_hidden_state(con);
+
     /* Handle all children and floating windows of this node. We recurse
      * in focus order to display the focused client in a stack first when
      * switching workspaces (reduces flickering). */
@@ -1040,10 +1098,12 @@ void x_push_changes(Con *con) {
     }
 
     if (focused_id == XCB_NONE) {
-        DLOG("Still no window focused, better set focus to the root window\n");
-        xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
+        /* If we still have no window to focus, we focus the EWMH window instead. We use this rather than the
+         * root window in order to avoid an X11 fallback mechanism causing a ghosting effect (see #1378). */
+        DLOG("Still no window focused, better set focus to the EWMH support window (%d)\n", ewmh_window);
+        xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, ewmh_window, XCB_CURRENT_TIME);
         ewmh_update_active_window(XCB_WINDOW_NONE);
-        focused_id = root;
+        focused_id = ewmh_window;
     }
 
     xcb_flush(conn);
index 5dda5ccee1bc6d940842af8100fb04209923ff03..f98115f5c80ce25049e081d9ea303636acc180bd 100644 (file)
--- a/src/xcb.c
+++ b/src/xcb.c
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * xcb.c: Helper functions for easier usage of XCB
  *
@@ -114,7 +114,7 @@ void send_take_focus(xcb_window_t window, xcb_timestamp_t timestamp) {
     /* Every X11 event is 32 bytes long. Therefore, XCB will copy 32 bytes.
      * In order to properly initialize these bytes, we allocate 32 bytes even
      * though we only need less for an xcb_configure_notify_event_t */
-    void *event = scalloc(32);
+    void *event = scalloc(32, 1);
     xcb_client_message_event_t *ev = event;
 
     ev->response_type = XCB_CLIENT_MESSAGE;
@@ -154,6 +154,35 @@ void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r) {
     add_ignore_event(cookie.sequence, -1);
 }
 
+/*
+ * Returns the first supported _NET_WM_WINDOW_TYPE atom.
+ *
+ */
+xcb_atom_t xcb_get_preferred_window_type(xcb_get_property_reply_t *reply) {
+    if (reply == NULL || xcb_get_property_value_length(reply) == 0)
+        return XCB_NONE;
+
+    xcb_atom_t *atoms;
+    if ((atoms = xcb_get_property_value(reply)) == NULL)
+        return XCB_NONE;
+
+    for (int i = 0; i < xcb_get_property_value_length(reply) / (reply->format / 8); i++) {
+        if (atoms[i] == A__NET_WM_WINDOW_TYPE_NORMAL ||
+            atoms[i] == A__NET_WM_WINDOW_TYPE_DIALOG ||
+            atoms[i] == A__NET_WM_WINDOW_TYPE_UTILITY ||
+            atoms[i] == A__NET_WM_WINDOW_TYPE_TOOLBAR ||
+            atoms[i] == A__NET_WM_WINDOW_TYPE_SPLASH ||
+            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) {
+            return atoms[i];
+        }
+    }
+
+    return XCB_NONE;
+}
+
 /*
  * Returns true if the given reply contains the given atom.
  *
@@ -245,3 +274,48 @@ xcb_visualid_t get_visualid_by_depth(uint16_t depth) {
     }
     return 0;
 }
+
+/*
+ * Add an atom to a list of atoms the given property defines.
+ * This is useful, for example, for manipulating _NET_WM_STATE.
+ *
+ */
+void xcb_add_property_atom(xcb_connection_t *conn, xcb_window_t window, xcb_atom_t property, xcb_atom_t atom) {
+    xcb_change_property(conn, XCB_PROP_MODE_APPEND, window, property, XCB_ATOM_ATOM, 32, 1, (uint32_t[]){atom});
+}
+
+/*
+ * Remove an atom from a list of atoms the given property defines without
+ * removing any other potentially set atoms.  This is useful, for example, for
+ * manipulating _NET_WM_STATE.
+ *
+ */
+void xcb_remove_property_atom(xcb_connection_t *conn, xcb_window_t window, xcb_atom_t property, xcb_atom_t atom) {
+    xcb_grab_server(conn);
+
+    xcb_get_property_reply_t *reply =
+        xcb_get_property_reply(conn,
+                               xcb_get_property(conn, false, window, property, XCB_GET_PROPERTY_TYPE_ANY, 0, 4096), NULL);
+    if (reply == NULL || xcb_get_property_value_length(reply) == 0)
+        goto release_grab;
+    xcb_atom_t *atoms = xcb_get_property_value(reply);
+    if (atoms == NULL) {
+        goto release_grab;
+    }
+
+    {
+        int num = 0;
+        const int current_size = xcb_get_property_value_length(reply) / (reply->format / 8);
+        xcb_atom_t values[current_size];
+        for (int i = 0; i < current_size; i++) {
+            if (atoms[i] != atom)
+                values[num++] = atoms[i];
+        }
+
+        xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window, property, XCB_ATOM_ATOM, 32, num, values);
+    }
+
+release_grab:
+    FREE(reply);
+    xcb_ungrab_server(conn);
+}
index 0a8da42545c5384f405b7de67a60675e7b419ccc..1e1e23b0dedadd2d6ae342e71a8edded2d541088 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * xcursor.c: xcursor support for themed cursors.
  *
index 9e412e0330db13a788068c1bcb3c34534047fb40..fb3b8603b768561e9a0fcdae6d8736c24c6868a1 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * This is LEGACY code (we support RandR, which can do much more than
  * Xinerama), but necessary for the poor users of the nVidia binary
@@ -56,7 +56,7 @@ static void query_screens(xcb_connection_t *conn) {
             s->rect.width = min(s->rect.width, screen_info[screen].width);
             s->rect.height = min(s->rect.height, screen_info[screen].height);
         } else {
-            s = scalloc(sizeof(Output));
+            s = scalloc(1, sizeof(Output));
             sasprintf(&(s->name), "xinerama-%d", num_screens);
             DLOG("Created new Xinerama screen %s (%p)\n", s->name, s);
             s->active = true;
@@ -87,6 +87,19 @@ static void query_screens(xcb_connection_t *conn) {
     }
 }
 
+/*
+ * This creates the root_output (borrowed from randr.c) and uses it
+ * as the sole output for this session.
+ *
+ */
+static void use_root_output(xcb_connection_t *conn) {
+    Output *s = create_root_output(conn);
+    s->active = true;
+    TAILQ_INSERT_TAIL(&outputs, s, outputs);
+    output_init_con(s);
+    init_ws_for_output(s, output_get_content(s->con));
+}
+
 /*
  * We have just established a connection to the X server and need the initial Xinerama
  * information to setup workspaces for each screen.
@@ -94,15 +107,15 @@ static void query_screens(xcb_connection_t *conn) {
  */
 void xinerama_init(void) {
     if (!xcb_get_extension_data(conn, &xcb_xinerama_id)->present) {
-        DLOG("Xinerama extension not found, disabling.\n");
-        disable_randr(conn);
+        DLOG("Xinerama extension not found, using root output.\n");
+        use_root_output(conn);
     } else {
         xcb_xinerama_is_active_reply_t *reply;
         reply = xcb_xinerama_is_active_reply(conn, xcb_xinerama_is_active(conn), NULL);
 
         if (reply == NULL || !reply->state) {
-            DLOG("Xinerama is not active (in your X-Server), disabling.\n");
-            disable_randr(conn);
+            DLOG("Xinerama is not active (in your X-Server), using root output.\n");
+            use_root_output(conn);
         } else
             query_screens(conn);
 
index f2c15013f5246b7e9346819d60365818d4fd68dd..3c2a26f97255c09161e13c03f48ee4658aaa8b0e 100755 (executable)
@@ -8,8 +8,8 @@ WriteMakefile(
     MIN_PERL_VERSION => '5.010000', # 5.10.0
     PREREQ_PM => {
         'AnyEvent'     => 0,
-        'AnyEvent::I3' => '0.15',
-        'X11::XCB'     => '0.09',
+        'AnyEvent::I3' => '0.16',
+        'X11::XCB'     => '0.12',
         'Inline'       => 0,
         'Inline::C'    => 0,
         'ExtUtils::PkgConfig' => 0,
index eaf57bde1620495c3ef168018a76189c39c32bf5..c1244e08745bfff08211a741d0a833d0acbc79e3 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env perl
 # vim:ts=4:sw=4:expandtab
-# © 2010-2012 Michael Stapelberg and contributors
+# © 2010 Michael Stapelberg and contributors
 package complete_run;
 use strict;
 use warnings;
@@ -8,7 +8,6 @@ use v5.10;
 use utf8;
 # the following are modules which ship with Perl (>= 5.10):
 use Pod::Usage;
-use Cwd qw(abs_path);
 use File::Temp qw(tempfile tempdir);
 use Getopt::Long;
 use POSIX ();
@@ -17,8 +16,20 @@ use TAP::Parser;
 use TAP::Parser::Aggregator;
 use Time::HiRes qw(time);
 use IO::Handle;
+
+my $dirname;
+
+BEGIN {
+    use File::Basename;
+    use Cwd qw(abs_path);
+
+    # fileparse()[1] contains the directory portion of the specified path.
+    # See File::Basename(3p) for more details.
+    $dirname = (fileparse(abs_path($0)))[1];
+}
+
 # these are shipped with the testsuite
-use lib qw(lib);
+use lib $dirname . 'lib';
 use StartXServer;
 use StatusLine;
 use TestWorker;
@@ -70,6 +81,8 @@ my $result = GetOptions(
 
 pod2usage(-verbose => 2, -exitcode => 0) if $help;
 
+chdir $dirname or die "Could not chdir into $dirname";
+
 # Check for missing executables
 my @binaries = qw(
                    ../i3
@@ -86,6 +99,16 @@ foreach my $binary (@binaries) {
     die "$binary is not an executable" unless -x $binary;
 }
 
+if ($options{coverage}) {
+    qx(command -v lcov &> /dev/null);
+    die "Cannot find lcov needed for coverage testing." if $?;
+    qx(command -v genhtml &> /dev/null);
+    die "Cannot find genhtml needed for coverage testing." if $?;
+
+    # clean out the counters that may be left over from previous tests.
+    qx(lcov -d ../ --zerocounters &> /dev/null);
+}
+
 qx(Xephyr -help 2>&1);
 die "Xephyr was not found in your path. Please install Xephyr (xserver-xephyr on Debian)." if $?;
 
@@ -227,6 +250,17 @@ if ($numtests == 1) {
 
 END { cleanup() }
 
+if ($options{coverage}) {
+    print("\nGenerating test coverage report...\n");
+    qx(lcov -d ../ -b ../ --capture -o latest/i3-coverage.info);
+    qx(genhtml -o latest/i3-coverage latest/i3-coverage.info);
+    if ($?) {
+        print("Could not generate test coverage html. Did you compile i3 with test coverage support?\n");
+    } else {
+        print("Test coverage report generated in latest/i3-coverage\n");
+    }
+}
+
 exit ($aggregator->failed > 0);
 
 #
@@ -391,12 +425,15 @@ available in C<latest/xtrace-for-$test.log>.
 
 =item B<--coverage-testing>
 
-Exits i3 cleanly (instead of kill -9) to make coverage testing work properly.
+Generates a test coverage report at C<latest/i3-coverage>. Exits i3 cleanly
+during tests (instead of kill -9) to make coverage testing work properly.
 
 =item B<--parallel>
 
-Number of Xephyr instances to start (if you dont want to start num_cores * 2
+Number of Xephyr instances to start (if you don't want to start num_cores * 2
 instances for some reason).
 
   # Run all tests on a single Xephyr instance
   ./complete-run.pl -p 1
+
+=back
index c149cbd1204a0ddf63bfeff9eff6c710c0dde4c7..ac1a26cadbd4d691350f3a53e02e102c2d84f917 100644 (file)
@@ -606,7 +606,7 @@ sub get_dock_clients {
 
 =head2 cmd($command)
 
-Sends the specified command to i3.
+Sends the specified command to i3 and returns the output.
 
   my $ws = unused_workspace;
   cmd "workspace $ws";
index d6ce0fb864ee6eeb1e39f7ffa4f2723249687478..0aac87f95275f0a69ae31c7395e80396d9102f03 100644 (file)
@@ -60,48 +60,14 @@ is($focus, $bottom->id, "Bottom window focused (wrapping to the top works)");
 $focus = focus_after('focus down');
 is($focus, $top->id, "Top window focused (wrapping to the bottom works)");
 
-###############################################
-# Test focus with empty containers and colspan
-###############################################
+#####################################################################
+# Test focus is only successful if there was a window that could be
+# matched.
+#####################################################################
 
-#my $otmp = get_unused_workspace();
-#$i3->command("workspace $otmp")->recv;
-#
-#$top = i3test::open_standard_window($x);
-#$bottom = i3test::open_standard_window($x);
-#sleep 0.25;
-#
-#$focus = focus_after("mj");
-#$focus = focus_after("mh");
-#$focus = focus_after("k");
-#is($focus, $bottom->id, "Selecting top window without snapping doesn't work");
-#
-#$focus = focus_after("sl");
-#is($focus, $bottom->id, "Bottom window focused");
-#
-#$focus = focus_after("k");
-#is($focus, $top->id, "Top window focused");
-#
-## Same thing, but left/right instead of top/bottom
-#
-#my $o2tmp = get_unused_workspace();
-#$i3->command("workspace $o2tmp")->recv;
-#
-#my $left = i3test::open_standard_window($x);
-#my $right = i3test::open_standard_window($x);
-#sleep 0.25;
-#
-#$focus = focus_after("ml");
-#$focus = focus_after("h");
-#$focus = focus_after("mk");
-#$focus = focus_after("l");
-#is($focus, $left->id, "Selecting right window without snapping doesn't work");
-#
-#$focus = focus_after("sj");
-#is($focus, $left->id, "left window focused");
-#
-#$focus = focus_after("l");
-#is($focus, $right->id, "right window focused");
+my $result = cmd '[con_mark=__does_not_exist] focus';
+is($result->[0]->{success}, 0, 'focus is unsuccessful if no window was matched');
 
+#####################################################################
 
 done_testing;
index bb913819b108526371dbc1430813197e8d7dd80a..e88c37a283e366ca03bf0e02aff4b257746edf26 100644 (file)
@@ -53,10 +53,10 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
 force_display_urgency_hint 0ms
 EOT
 
-my $type;
+my ($type, $tmp, $w1, $w2);
 for ($type = 1; $type <= 2; $type++) {
     my $pid = launch_with_config($config);
-    my $tmp = fresh_workspace;
+    $tmp = fresh_workspace;
 
 #####################################################################
 # Create two windows and put them in stacking mode
@@ -269,8 +269,8 @@ for ($type = 1; $type <= 2; $type++) {
     my $ws1 = fresh_workspace;
     my $ws2 = fresh_workspace;
     cmd "workspace $ws1";
-    my $w1 = open_window;
-    my $w2 = open_window;
+    $w1 = open_window;
+    $w2 = open_window;
     cmd "workspace $ws2";
     sync_with_i3;
     set_urgency($w1, 1, $type);
@@ -284,10 +284,10 @@ for ($type = 1; $type <= 2; $type++) {
 ##############################################################################
 # Check if urgent flag can be unset if we move the window out of the container
 ##############################################################################
-    my $tmp = fresh_workspace;
+    $tmp = fresh_workspace;
     cmd 'layout tabbed';
-    my $w1 = open_window;
-    my $w2 = open_window;
+    $w1 = open_window;
+    $w2 = open_window;
     sync_with_i3;
     cmd '[id="' . $w2->id . '"] focus';
     sync_with_i3;
@@ -307,6 +307,33 @@ for ($type = 1; $type <= 2; $type++) {
     my $ws = get_ws($tmp);
     ok(!$ws->{urgent}, 'urgent flag not set on workspace');
 
+##############################################################################
+# Regression test for #1187: Urgency hint moves to new workspace when moving
+# a container to another workspace.
+##############################################################################
+
+    my $tmp_source = fresh_workspace;
+    my $tmp_target = fresh_workspace;
+    cmd 'workspace ' . $tmp_source;
+    sync_with_i3;
+    $w1 = open_window;
+    $w2 = open_window;
+    sync_with_i3;
+    cmd '[id="' . $w1->id . '"] focus';
+    sync_with_i3;
+    cmd 'mark urgent_con';
+    cmd '[id="' . $w2->id . '"] focus';
+    set_urgency($w1, 1, $type);
+    sync_with_i3;
+    cmd '[con_mark="urgent_con"] move container to workspace ' . $tmp_target;
+    sync_with_i3;
+    my $source_ws = get_ws($tmp_source);
+    my $target_ws = get_ws($tmp_target);
+    ok(!$source_ws->{urgent}, 'Source workspace is no longer marked urgent');
+    is($target_ws->{urgent}, 1, 'Target workspace is now marked urgent');
+
+##############################################################################
+
     exit_gracefully($pid);
 }
 
index d9ff1c39fbf53fe624424339697a460ad7229b93..5e3110adcad16ba649c8bfe3b284df91ff7ff809 100644 (file)
@@ -47,6 +47,7 @@ my $ignore = \"";
 
 my $expected = {
     fullscreen_mode => 0,
+    sticky => $ignore,
     nodes => $ignore,
     window => undef,
     name => 'root',
index 97315c3deb2f5876f404f41058bf42af54db9db9..c5e61a328935404d4a3438ec6e275dca3efebc77 100644 (file)
@@ -17,6 +17,7 @@
 # Tests resizing tiling containers
 use i3test;
 
+my ($left, $right);
 my $tmp = fresh_workspace;
 
 cmd 'split v';
@@ -84,8 +85,8 @@ cmp_float($nodes->[1]->{percent}, 0.75, 'bottom window got 75%');
 
 $tmp = fresh_workspace;
 
-my $left = open_window;
-my $right = open_window;
+$left = open_window;
+$right = open_window;
 
 cmd 'split v';
 
@@ -261,8 +262,8 @@ cmp_ok($content[0]->{rect}->{width}, '==', $oldrect->{width}, 'width the same as
 
 $tmp = fresh_workspace;
 
-my $left = open_floating_window;
-my $right = open_floating_window;
+$left = open_floating_window;
+$right = open_floating_window;
 
 sub get_floating_rect {
     my ($window_id) = @_;
index ada7c5c5bd67c98f79d2be3d088027d54f1c3016..985a7bfd687ec5e8d44e99c81d6ef64a1bafaeca 100644 (file)
@@ -17,6 +17,8 @@
 use i3test i3_autostart => 0;
 use X11::XCB qw(PROP_MODE_REPLACE);
 
+my (@nodes);
+
 ##############################################################
 # 1: test the following directive:
 #    for_window [class="borderless"] border none
@@ -287,9 +289,6 @@ exit_gracefully($pid);
 # 8: check that the role criterion works properly
 ##############################################################
 
-# this configuration is broken because "asdf" is not a valid integer
-# the for_window should therefore recognize this error and don’t add the
-# assignment
 $config = <<EOT;
 # i3 config file (v4)
 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
@@ -329,9 +328,6 @@ exit_gracefully($pid);
 #    *after* the window has been mapped
 ##############################################################
 
-# this configuration is broken because "asdf" is not a valid integer
-# the for_window should therefore recognize this error and don’t add the
-# assignment
 $config = <<EOT;
 # i3 config file (v4)
 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
@@ -370,5 +366,98 @@ is($content[0]->{border}, 'none', 'no border (window_role 2)');
 
 exit_gracefully($pid);
 
+##############################################################
+# 10: check that the criterion 'window_type' works
+##############################################################
+
+# test all window types
+my %window_types = (
+    'normal'        => '_NET_WM_WINDOW_TYPE_NORMAL',
+    'dialog'        => '_NET_WM_WINDOW_TYPE_DIALOG',
+    'utility'       => '_NET_WM_WINDOW_TYPE_UTILITY',
+    'toolbar'       => '_NET_WM_WINDOW_TYPE_TOOLBAR',
+    'splash'        => '_NET_WM_WINDOW_TYPE_SPLASH',
+    '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'
+);
+
+while (my ($window_type, $atom) = each %window_types) {
+
+    $config = <<"EOT";
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [window_type="$window_type"] floating enable, mark branded
+EOT
+
+    $pid = launch_with_config($config);
+    $tmp = fresh_workspace;
+
+    $window = open_window(window_type => $x->atom(name => $atom));
+
+    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)");
+
+    exit_gracefully($pid);
+
+}
+
+##############################################################
+# 11: check that the criterion 'window_type' works if the
+#     _NET_WM_WINDOW_TYPE is changed after managing.
+##############################################################
+
+while (my ($window_type, $atom) = each %window_types) {
+
+    $config = <<"EOT";
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [window_type="$window_type"] floating enable, mark branded
+EOT
+
+    $pid = launch_with_config($config);
+    $tmp = fresh_workspace;
+
+    $window = open_window();
+
+    my $atomname = $x->atom(name => '_NET_WM_WINDOW_TYPE');
+    my $atomtype = $x->atom(name => 'ATOM');
+    $x->change_property(PROP_MODE_REPLACE, $window->id, $atomname->id, $atomtype->id,
+      32, 1, pack('L1', $x->atom(name => $atom)->id));
+    $x->flush;
+    sync_with_i3;
+
+    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)");
+
+    exit_gracefully($pid);
+
+}
+
+##############################################################
+# 12: check that the criterion 'workspace' works
+##############################################################
+
+$config = <<"EOT";
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+for_window [workspace="trigger"] floating enable, mark triggered
+EOT
+
+$pid = launch_with_config($config);
+
+cmd 'workspace trigger';
+$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");
+
+exit_gracefully($pid);
+
+##############################################################
 
 done_testing;
index a0363ef3df4017238a801433c5e67e7fb935ee69..d8f9d2890c4d542d9780935e77937cdb5eb2bc0d 100644 (file)
@@ -147,8 +147,6 @@ $input = <<EOT;
     bindsym Mod1+s restart
     bindsym Mod1+s reload
     bindsym Mod1+s exit
-    bindsym Mod1+s stack-limit cols 2
-    bindsym Mod1+s stack-limit rows 3
     bind Mod1+c exec /usr/bin/urxvt
     mode "asdf" {
         bind 36 mode default
@@ -161,8 +159,6 @@ ok(line_exists($output, qr|^bindsym Mod1\+s mark foo$|), 'mark unchanged');
 ok(line_exists($output, qr|^bindsym Mod1\+s restart$|), 'restart unchanged');
 ok(line_exists($output, qr|^bindsym Mod1\+s reload$|), 'reload unchanged');
 ok(line_exists($output, qr|^bindsym Mod1\+s exit$|), 'exit unchanged');
-ok(line_exists($output, qr|^bindsym Mod1\+s stack-limit cols 2$|), 'stack-limit unchanged');
-ok(line_exists($output, qr|^bindsym Mod1\+s stack-limit rows 3$|), 'stack-limit unchanged');
 ok(line_exists($output, qr|^bindcode Mod1\+c exec /usr/bin/urxvt$|), 'bind changed to bindcode');
 ok(line_exists($output, qr|^mode "asdf" {$|), 'mode asdf unchanged');
 ok(line_exists($output, qr|^bindcode 36 mode \"default\"$|), 'mode default unchanged');
index 8b00abac08569b47f2a583114a4100a49ac26ae2..757a75b4ed5aa1fb5534b75b3934ac444a00d240 100644 (file)
@@ -106,4 +106,23 @@ is_deeply(\@names, [ '3' ], 'i3 starts on workspace 3 without ;exec foo');
 
 exit_gracefully($pid);
 
+################################################################################
+# 6: verify internal binding reordering does not affect startup workspace
+################################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+bindsym Mod1+1 workspace 3
+bindsym Shift+Mod1+1 workspace 4
+EOT
+
+$pid = launch_with_config($config);
+
+@names = @{get_workspace_names()};
+is_deeply(\@names, [ '3' ], 'i3 starts on workspace 3');
+
+exit_gracefully($pid);
+
 done_testing;
index 4ca41799ea5a06f1754c5b4f6333dcca930465d3..b27bed53ed0648c05a742919d26ab1fc98aa3c08 100644 (file)
@@ -155,6 +155,11 @@ cmd "rename workspace temp to $first_ws";
 
 is_num_children($first_ws, 3, 'three containers on the first workspace');
 
+# empty 'from' workspaces should not crash the renaming of startup sequences
+cmd "workspace $first_ws";
+cmd "rename workspace to temp";
+cmd "rename workspace to $first_ws";
+
 # Switch to the first workspace and move the focused window to the
 # second workspace.
 cmd "workspace $first_ws";
index 8675dd7183c003c335753308d140eef89b050867..cc4826c15766be171d652b4a102e813001d52da6 100644 (file)
@@ -66,6 +66,7 @@ ok($bar_config->{workspace_buttons}, 'workspace buttons enabled per default');
 ok($bar_config->{binding_mode_indicator}, 'mode indicator enabled per default');
 is($bar_config->{mode}, 'dock', 'dock mode by default');
 is($bar_config->{position}, 'bottom', 'position bottom by default');
+is($bar_config->{tray_padding}, 2, 'tray_padding ok');
 
 #####################################################################
 # ensure that reloading cleans up the old bar configs
@@ -96,6 +97,7 @@ bar {
 
     tray_output LVDS1
     tray_output HDMI2
+    tray_padding 0
     position top
     mode dock
     font Terminus
@@ -112,6 +114,7 @@ bar {
         active_workspace    #333333 #222222 #888888
         inactive_workspace  #333333 #222222 #888888
         urgent_workspace    #2f343a #900000 #ffffff
+        binding_mode        #abc123 #123abc #ababab
     }
 }
 EOT
@@ -133,6 +136,7 @@ is($bar_config->{mode}, 'dock', 'dock mode');
 is($bar_config->{position}, 'top', 'position top');
 is_deeply($bar_config->{outputs}, [ 'HDMI1', 'HDMI2' ], 'outputs ok');
 is($bar_config->{tray_output}, 'HDMI2', 'tray_output ok');
+is($bar_config->{tray_padding}, 0, 'tray_padding ok');
 is($bar_config->{font}, 'Terminus', 'font ok');
 is($bar_config->{socket_path}, '/tmp/foobar', 'socket_path ok');
 is_deeply($bar_config->{colors},
@@ -151,6 +155,9 @@ is_deeply($bar_config->{colors},
         urgent_workspace_border => '#2f343a',
         urgent_workspace_text => '#ffffff',
         urgent_workspace_bg => '#900000',
+        binding_mode_border => '#abc123',
+        binding_mode_text => '#ababab',
+        binding_mode_bg => '#123abc',
     }, 'colors ok');
 
 exit_gracefully($pid);
index edfc46e0374158eab1eba96380de55f2da92bc3f..eeaee449f96108420082087243d16d8a1a4b2fe2 100644 (file)
@@ -377,7 +377,7 @@ sub verify_scratchpad_move_with_visible_scratch_con {
     # this should bring up window 1
     cmd 'scratchpad show';
 
-    my $ws = get_ws($first);
+    $ws = get_ws($first);
     is(scalar @{$ws->{floating_nodes}}, 1, 'one floating node on ws1');
     is($x->input_focus, $window1->id, "showed the correct scratchpad window1");
 
index 6d67731a1e71a9914485962ebc3fc73215d1b042..4f555b29d4a42c3d5fe3824abdb51ec8bd0f368b 100644 (file)
@@ -144,13 +144,40 @@ is(parser_calls("\nworkspace test"),
 ################################################################################
 
 is(parser_calls('unknown_literal'),
-   "ERROR: Expected one of these tokens: <end>, '[', 'move', 'exec', 'exit', 'restart', 'reload', 'shmlog', 'debuglog', 'border', 'layout', 'append_layout', 'workspace', 'focus', 'kill', 'open', 'fullscreen', 'split', 'floating', 'mark', 'unmark', 'resize', 'rename', 'nop', 'scratchpad', 'mode', 'bar'\n" .
+   "ERROR: Expected one of these tokens: <end>, '[', " .
+   "'move', " .
+   "'exec', " .
+   "'exit', " .
+   "'restart', " .
+   "'reload', " .
+   "'shmlog', " .
+   "'debuglog', " .
+   "'border', " .
+   "'layout', " .
+   "'append_layout', " .
+   "'workspace', " .
+   "'focus', " .
+   "'kill', " .
+   "'open', " .
+   "'fullscreen', " .
+   "'sticky', " .
+   "'split', " .
+   "'floating', " .
+   "'mark', " .
+   "'unmark', " .
+   "'resize', " .
+   "'rename', " .
+   "'nop', " .
+   "'scratchpad', " .
+   "'title_format', " .
+   "'mode', " .
+   "'bar'\n" .
    "ERROR: Your command: unknown_literal\n" .
    "ERROR:               ^^^^^^^^^^^^^^^",
    'error for unknown literal ok');
 
 is(parser_calls('move something to somewhere'),
-   "ERROR: Expected one of these tokens: 'window', 'container', 'to', 'workspace', 'output', 'scratchpad', 'left', 'right', 'up', 'down', 'position', 'absolute'\n" .
+   "ERROR: Expected one of these tokens: 'window', 'container', 'to', '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');
index debbb0a21487742c4971f9e4e23f4dcee69929e1..ad356f4240fadae8a605014182a4b0d218fb6c47 100644 (file)
@@ -186,4 +186,36 @@ is($rect->{y}, $old_y, 'window did not move when trying to resize');
 
 exit_gracefully($pid);
 
+################################################################################
+# 7: check floating_maximum_size with cmd_size
+################################################################################
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+# Test with different dimensions than the i3 default.
+floating_minimum_size 80 x 70
+floating_maximum_size 100 x 90
+EOT
+
+$pid = launch_with_config($config);
+
+my $window = open_floating_window(rect => [ 0, 0, 90, 80 ]);
+cmd 'border none';
+
+cmd 'resize set 101 91';
+sync_with_i3;
+my $rect = $window->rect;
+is($rect->{width}, 100, 'width did not exceed maximum width');
+is($rect->{height}, 90, 'height did not exceed maximum height');
+
+cmd 'resize set 79 69';
+sync_with_i3;
+$rect = $window->rect;
+is($rect->{width}, 80, 'width did not exceed minimum width');
+is($rect->{height}, 70, 'height did not exceed minimum height');
+
+exit_gracefully($pid);
+
 done_testing;
index 1e91d47f9cf5cdcb71f9ccf350aefd1f61f2e364..a2b0a3a937dab2ff2701dd73f5724146c4c4fd53 100644 (file)
@@ -45,19 +45,27 @@ mode "meh" {
     bindsym Mod1 + Shift +   x resize grow
     bindcode Mod1+44 resize shrink
     bindsym --release Mod1+x exec foo
+    bindsym --whole-window button3 nop
+    bindsym --release --whole-window button3 nop
+    bindsym --border button3 nop
+    bindsym --release --border button3 nop
 }
 EOT
 
 my $expected = <<'EOT';
 cfg_enter_mode(meh)
-cfg_mode_binding(bindsym, Mod1,Shift, x, (null), (null), resize grow)
-cfg_mode_binding(bindcode, Mod1, 44, (null), (null), resize shrink)
-cfg_mode_binding(bindsym, Mod1, x, --release, (null), exec foo)
+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)
+cfg_mode_binding(bindsym, (null), button3, (null), (null), --whole-window, nop)
+cfg_mode_binding(bindsym, (null), button3, --release, (null), --whole-window, nop)
+cfg_mode_binding(bindsym, (null), button3, (null), --border, (null), nop)
+cfg_mode_binding(bindsym, (null), button3, --release, --border, (null), nop)
 EOT
 
 is(parser_calls($config),
    $expected,
-   'single number (move workspace 3) ok');
+   'mode bindings ok');
 
 ################################################################################
 # exec and exec_always
@@ -433,7 +441,7 @@ client.focused          #4c7899 #285577 #ffffff #2e9ef4
 EOT
 
 my $expected_all_tokens = <<'EOT';
-ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'mouse_warping', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent', 'client.placeholder'
+ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'no_focus', 'focus_follows_mouse', 'mouse_warping', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'focus_on_window_activation', 'show_marks', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent', 'client.placeholder'
 EOT
 
 my $expected_end = <<'EOT';
@@ -620,7 +628,7 @@ EOT
 
 $expected = <<'EOT';
 cfg_enter_mode(yo)
-cfg_mode_binding(bindsym, (null), x, (null), (null), resize shrink left)
+cfg_mode_binding(bindsym, (null), x, (null), (null), (null), resize shrink left)
 ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', '}'
 ERROR: CONFIG: (in file <stdin>)
 ERROR: CONFIG: Line   1: mode "yo" {
@@ -646,8 +654,9 @@ bar {
 EOT
 
 $expected = <<'EOT';
+cfg_bar_start()
 cfg_bar_output(LVDS-1)
-ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'position', 'output', 'tray_output', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'strip_workspace_numbers', 'verbose', 'colors', '}'
+ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'tray_padding', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'strip_workspace_numbers', 'verbose', 'colors', '}'
 ERROR: CONFIG: (in file <stdin>)
 ERROR: CONFIG: Line   1: bar {
 ERROR: CONFIG: Line   2:     output LVDS-1
index f285338b8208efc2b1e1985d86069405bd0e1b3f..93b26d94294d7eb3bb5f88685ee3460879bbd446 100644 (file)
 #
 # checks if mark and unmark work correctly
 use i3test;
+use List::Util qw(first);
+
+my ($con, $first, $second);
 
 sub get_marks {
     return i3(get_socket_path())->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->{mark};
+}
+
 ##############################################################
 # 1: check that there are no marks set yet
 ##############################################################
@@ -76,4 +86,78 @@ cmd 'unmark';
 
 is_deeply(get_marks(), [], 'all marks removed');
 
+##############################################################
+# 4: mark a con, use same mark to mark another con,
+#    check that only the latter is marked
+##############################################################
+
+$first = open_window;
+$second = open_window;
+
+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');
+ok(!get_mark_for_window_on_workspace($tmp, $second), 'second container lost the mark');
+
+##############################################################
+# 5: mark a con, toggle the mark, check that the mark is gone
+##############################################################
+
+$con = open_window;
+cmd 'mark important';
+cmd 'mark --toggle important';
+ok(!get_mark_for_window_on_workspace($tmp, $con), 'container no longer has the mark');
+
+##############################################################
+# 6: toggle a mark on an unmarked con, check it is marked
+##############################################################
+
+$con = open_window;
+cmd 'mark --toggle important';
+is(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
+#    with the new mark
+##############################################################
+
+$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');
+
+##############################################################
+# 8: mark a con, toggle the mark on another con,
+#    check only the latter has the mark
+##############################################################
+
+$first = open_window;
+$second = open_window;
+
+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');
+ok(!get_mark_for_window_on_workspace($tmp, $second), 'second containr no longer has the mark');
+
+##############################################################
+# 9: try to mark two cons with the same mark and check that
+#    it fails
+##############################################################
+
+$first = open_window(wm_class => 'iamnotunique');
+$second = open_window(wm_class => 'iamnotunique');
+
+my $result = cmd "[instance=iamnotunique] mark important";
+
+is($result->[0]->{success}, 0, 'command was unsuccessful');
+is($result->[0]->{error}, 'A mark must not be put onto more than one window', 'correct error is returned');
+ok(!get_mark_for_window_on_workspace($tmp, $first), 'first container is not marked');
+ok(!get_mark_for_window_on_workspace($tmp, $second), 'second containr is not marked');
+
+##############################################################
+
 done_testing;
index c2de64e4da9a53a0ef6b9c02ef71d7ed008c4942..4c172ff578a2de8014acb9b8802b2f585bcabd54 100644 (file)
@@ -50,7 +50,7 @@ is($e->{container}->{floating}, 'user_on', 'the container should be floating');
 
 $cv = AnyEvent->condvar;
 cmd '[id="' . $win->{id} . '"] floating disable';
-my $e = $cv->recv;
+$e = $cv->recv;
 
 isnt($e, 0, 'disabling floating on a container should send an ipc window event');
 is($e->{container}->{window}, $win->{id}, 'the event should contain information about the window');
index 22a2eb4e2504c2e531ecb3ec76877be48930178d..c023bca479dd15e19e5247c56ccf39ae63a9f84c 100644 (file)
 # Bug still in: 4.8-16-g6888a1f
 use i3test;
 
-my $ws = fresh_workspace;
+my ($ws, $win1, $win2, $win3, $ws_con);
 
-my $win1 = open_window;
-my $win2 = open_window;
-my $win3 = open_window;
+###############################################################################
+# Tets moving with 'id' criterion.
+###############################################################################
+
+$ws = fresh_workspace;
+
+$win1 = open_window;
+$win2 = open_window;
+$win3 = open_window;
 
 # move win1 from the left to the right
 cmd '[id="' . $win1->{id} . '"] move right';
 
 # now they should be switched, with win2 still being focused
-my $ws_con = get_ws($ws);
+$ws_con = get_ws($ws);
 
 # win2 should be on the left
 is($ws_con->{nodes}[0]->{window}, $win2->{id}, 'the `move [direction]` command should work with criteria');
 is($x->input_focus, $win3->{id}, 'it should not disturb focus');
 
+###############################################################################
+# Tets moving with 'window_type' criterion.
+###############################################################################
+
+# test all window types
+my %window_types = ( 
+    'normal'        => '_NET_WM_WINDOW_TYPE_NORMAL',
+    'dialog'        => '_NET_WM_WINDOW_TYPE_DIALOG',
+    'utility'       => '_NET_WM_WINDOW_TYPE_UTILITY',
+    'toolbar'       => '_NET_WM_WINDOW_TYPE_TOOLBAR',
+    'splash'        => '_NET_WM_WINDOW_TYPE_SPLASH',
+    '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'
+);
+
+while (my ($window_type, $atom) = each %window_types) {
+
+    $ws = fresh_workspace;
+
+    $win1 = open_window(window_type => $x->atom(name => $atom));
+    $win2 = open_window;
+    $win3 = open_window;
+
+    cmd '[window_type="' . $window_type . '"] move right';
+
+    $ws_con = get_ws($ws);
+    is($ws_con->{nodes}[0]->{window}, $win2->{id}, 'the `move [direction]` command should work with window_type = ' . $window_type);
+    is($x->input_focus, $win3->{id}, 'it should not disturb focus');
+
+}
+
+###############################################################################
+
 done_testing;
index 2ac9ecbbcc92a82d08620cf03868b5d15ee2d5ad..09226ff1f1ebe650259a86a068f300dedd69ea79 100644 (file)
@@ -59,7 +59,7 @@ is($event->{container}->{urgent}, 1, 'the container should be urgent');
 
 $cv = AnyEvent->condvar;
 $win->delete_hint('urgency');
-my $event = $cv->recv;
+$event = $cv->recv;
 
 isnt($event, 0, 'an urgent con should emit the window::urgent event');
 is($event->{container}->{window}, $win->{id}, 'the event should contain information about the window');
index 614d6b3a8896a4a8b1bcc30101ccfa89ab4a19b3..ec17353e177c56e9d64043e2c82aea44a28908f1 100644 (file)
@@ -20,6 +20,8 @@
 use i3test i3_autostart => 0;
 use File::Temp qw(tempfile);
 
+my ($cfg, $ret, $out);
+
 sub check_config {
     my ($config) = @_;
     my ($fh, $tmpfile) = tempfile(UNLINK => 1);
@@ -35,12 +37,12 @@ sub check_config {
 # 1: test with a bogus configuration file
 ################################################################################
 
-my $cfg = <<EOT;
+$cfg = <<EOT;
 # i3 config file (v4)
 i_am_an_unknown_config option
 EOT
 
-my ($ret, $out) = check_config($cfg);
+($ret, $out) = check_config($cfg);
 is($ret, 1, "exit code == 1");
 like($out, qr/ERROR: *CONFIG: *[Ee]xpected.*tokens/, 'bogus config file');
 
@@ -48,12 +50,12 @@ like($out, qr/ERROR: *CONFIG: *[Ee]xpected.*tokens/, 'bogus config file');
 # 2: test with a valid configuration file
 ################################################################################
 
-my $cfg = <<EOT;
+$cfg = <<EOT;
 # i3 config file (v4)
 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
 EOT
 
-my ($ret, $out) = check_config($cfg);
+($ret, $out) = check_config($cfg);
 is($ret, 0, "exit code == 0");
 is($out, "", 'valid config file');
 
index e6cacdedfeaf3ee153409bb2406020e8c94cb94f..3685b30ce52ad7205d8382a602b1dfa4a778938e 100644 (file)
@@ -31,16 +31,17 @@ EOT
 my $pid = launch_with_config($config);
 
 sub change_window_class {
-    my ($window, $class) = @_;
+    my ($window, $class, $length) = @_;
     my $atomname = $x->atom(name => 'WM_CLASS');
     my $atomtype = $x->atom(name => 'STRING');
+    $length ||= length($class) + 1;
     $x->change_property(
         PROP_MODE_REPLACE,
         $window->id,
         $atomname->id,
         $atomtype->id,
         8,
-        length($class) + 1,
+        $length,
         $class
     );
     sync_with_i3;
@@ -65,6 +66,13 @@ is($con->{window_properties}->{instance}, 'special',
 is($con->{mark}, 'special_class_mark',
     'A `for_window` assignment should run for a match when the window changes class');
 
+change_window_class($win, "abcdefghijklmnopqrstuv\0abcd", 24);
+
+$con = @{get_ws_content($ws)}[0];
+
+is($con->{window_properties}->{class}, 'a',
+    'Non-null-terminated strings should be handled correctly');
+
 exit_gracefully($pid);
 
 done_testing;
diff --git a/testcases/t/240-focus-on-window-activation.t b/testcases/t/240-focus-on-window-activation.t
new file mode 100644 (file)
index 0000000..b2f8cac
--- /dev/null
@@ -0,0 +1,208 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Tests for the focus_on_window_activation directive
+# Ticket: #1426
+use i3test i3_autostart => 0;
+use List::Util qw(first);
+
+my ($config, $pid, $first, $second, $ws1, $ws2);
+
+sub send_net_active_window {
+    my ($id) = @_; 
+
+    my $msg = pack "CCSLLLLLLL",
+        X11::XCB::CLIENT_MESSAGE, # response_type
+        32, # format
+        0, # sequence
+        $id, # destination window
+        $x->atom(name => '_NET_ACTIVE_WINDOW')->id,
+        0, # source
+        0, 0, 0, 0;
+
+    $x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg);
+}
+
+sub get_urgency_for_window_on_workspace {
+    my ($ws, $con) = @_;
+
+    my $current = first { $_->{window} == $con->{id} } @{get_ws_content($ws)};
+    return $current->{urgent};
+}
+
+#####################################################################
+# 1: If mode is set to 'urgent' and the target workspace is visible,
+#    check that the urgent flag is set and focus is not lost.
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+focus_on_window_activation urgent
+EOT
+
+$pid = launch_with_config($config);
+
+my $ws = fresh_workspace;
+$first = open_window;
+$second = open_window;
+
+send_net_active_window($first->id);
+sync_with_i3;
+
+is($x->input_focus, $second->id, 'second window is still focused');
+is(get_urgency_for_window_on_workspace($ws, $first), 1, 'first window is marked urgent');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 2: If mode is set to 'urgent' and the target workspace is not
+#    visible, check that the urgent flag is set and focus is not lost.
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+focus_on_window_activation urgent
+EOT
+
+$pid = launch_with_config($config);
+
+$ws1 = fresh_workspace;
+$first = open_window;
+$ws2 = fresh_workspace;
+$second = open_window;
+
+send_net_active_window($first->id);
+sync_with_i3;
+
+is(focused_ws(), $ws2, 'second workspace is still focused');
+is($x->input_focus, $second->id, 'second window is still focused');
+is(get_urgency_for_window_on_workspace($ws1, $first), 1, 'first window is marked urgent');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 3: If mode is set to 'focus' and the target workspace is visible,
+#    check that the focus is switched.
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+focus_on_window_activation focus
+EOT
+
+$pid = launch_with_config($config);
+
+my $ws = fresh_workspace;
+$first = open_window;
+$second = open_window;
+
+send_net_active_window($first->id);
+sync_with_i3;
+
+is($x->input_focus, $first->id, 'first window is now focused');
+ok(!get_urgency_for_window_on_workspace($ws, $first), 'first window is not marked urgent');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 4: If mode is set to 'focus' and the target workspace is not
+#    visible, check that the focus switched.
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+focus_on_window_activation focus
+EOT
+
+$pid = launch_with_config($config);
+
+$ws1 = fresh_workspace;
+$first = open_window;
+$ws2 = fresh_workspace;
+$second = open_window;
+
+send_net_active_window($first->id);
+sync_with_i3;
+
+is(focused_ws(), $ws1, 'first workspace is now focused');
+is($x->input_focus, $first->id, 'first window is now focused');
+ok(!get_urgency_for_window_on_workspace($ws1, $first), 'first window is not marked urgent');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 5: If mode is set to 'none' and the target workspace is visible,
+#    check that nothing happens.
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+focus_on_window_activation none
+EOT
+
+$pid = launch_with_config($config);
+
+my $ws = fresh_workspace;
+$first = open_window;
+$second = open_window;
+
+send_net_active_window($first->id);
+sync_with_i3;
+
+is($x->input_focus, $second->id, 'second window is still focused');
+ok(!get_urgency_for_window_on_workspace($ws, $first), 'first window is not marked urgent');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 6: If mode is set to 'none' and the target workspace is not
+#    visible, check that nothing happens.
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+focus_on_window_activation none
+EOT
+
+$pid = launch_with_config($config);
+
+$ws1 = fresh_workspace;
+$first = open_window;
+$ws2 = fresh_workspace;
+$second = open_window;
+
+send_net_active_window($first->id);
+sync_with_i3;
+
+is(focused_ws(), $ws2, 'second workspace is still focused');
+is($x->input_focus, $second->id, 'second window is still focused');
+ok(!get_urgency_for_window_on_workspace($ws1, $first), 'first window is not marked urgent');
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/240-tabbed-floating-disable-crash.t b/testcases/t/240-tabbed-floating-disable-crash.t
new file mode 100644 (file)
index 0000000..7947158
--- /dev/null
@@ -0,0 +1,44 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Verifies that i3 does not crash when floating and then unfloating an
+# unfocused window within a tabbed container.
+# Ticket: #1484
+# Bug still in: 4.9.1-124-g856e1f9
+use i3test i3_autostart => 0;
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+workspace_layout tabbed
+EOT
+
+my $pid = launch_with_config($config);
+
+open_window;
+open_window;
+
+# Mark the second window, then focus the workspace.
+cmd 'mark foo, focus parent, focus parent';
+
+# Float and unfloat the marked window (without it being focused).
+cmd '[con_mark=foo] floating enable, floating disable';
+
+does_i3_live;
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/241-consistent-center.t b/testcases/t/241-consistent-center.t
new file mode 100644 (file)
index 0000000..283d173
--- /dev/null
@@ -0,0 +1,106 @@
+#!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 most of i3's centering methods produce consistent results.
+# Decorations are disabled to avoid floating_enable's logic which shifts
+# windows upwards dependent on their decoration height.
+#
+use i3test i3_autostart => 0;
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+new_window none
+new_float none
+EOT
+
+my $pid = launch_with_config($config);
+
+#####################################################################
+# Open a floating window, verifying that its initial position is
+# centered, and also verify that both centering methods leave it in
+# its original spot.
+#####################################################################
+
+my $first = open_floating_window;
+
+my $initial = $first->rect;
+is(int($initial->{x} + $initial->{width} / 2), int($x->root->rect->width / 2),
+   'x coordinates match');
+is(int($initial->{y} + $initial->{height} / 2), int($x->root->rect->height / 2),
+   'y coordinates match');
+
+cmd 'move position center';
+
+my $new = $first->rect;
+is($initial->{x}, $new->{x}, 'x coordinates match');
+is($initial->{y}, $new->{y}, 'y coordinates match');
+
+cmd 'move absolute position center';
+
+$new = $first->rect;
+is($initial->{x}, $new->{x}, 'x coordinates match');
+is($initial->{y}, $new->{y}, 'y coordinates match');
+
+#####################################################################
+# Create a second window and move it into and out of the scratchpad.
+# Because it hasn't been moved or resized, it should be floated in
+# the center of the screen when pulled out of the scratchpad.
+#####################################################################
+
+my $second = open_window;
+
+cmd 'move scratchpad, scratchpad show';
+
+$new = $second->rect;
+my $mid_init = $initial->{x} + int($initial->{width} / 2);
+my $mid_new = $new->{x} + int($new->{width} / 2);
+is($mid_init, $mid_new, 'x midpoint is ws center');
+
+$mid_init = $initial->{y} + int($initial->{height} / 2);
+$mid_new = $new->{y} + int($new->{height} / 2);
+is($mid_init, $mid_new, 'y midpoint is ws center');
+
+#####################################################################
+# Verify that manually floating a tiled window results in proper
+# centering.
+#####################################################################
+
+my $third = open_window;
+
+cmd 'floating enable';
+
+$new = $third->rect;
+is($initial->{x}, $new->{x}, 'x coordinates match');
+is($initial->{y}, $new->{y}, 'y coordinates match');
+
+#####################################################################
+# Create a child window of the previous window, which should result
+# in the new window being centered over the last one.
+#####################################################################
+
+my $fourth = open_window( dont_map => 1, client_leader => $third );
+$fourth->map;
+sync_with_i3;
+
+my $child = $fourth->rect;
+is($new->{x}, $child->{x}, 'x coordinates match');
+is($new->{y}, $child->{y}, 'y coordinates match');
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/242-no-focus.t b/testcases/t/242-no-focus.t
new file mode 100644 (file)
index 0000000..143ae5c
--- /dev/null
@@ -0,0 +1,68 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Test the 'no_focus' directive.
+# Ticket: #1416
+use i3test i3_autostart => 0;
+
+my ($config, $pid, $ws, $first, $second, $focused);
+
+#####################################################################
+# 1: open a window and check that it takes focus
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+EOT
+
+$pid = launch_with_config($config);
+$ws = fresh_workspace;
+$first = open_window;
+$focused = get_focused($ws);
+$second = open_window;
+
+isnt(get_focused($ws), $focused, 'focus has changed');
+
+exit_gracefully($pid);
+
+#####################################################################
+# 2: open a window matched by a no_focus directive and check that
+#    it doesn't take focus
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+no_focus [instance=notme]
+EOT
+
+$pid = launch_with_config($config);
+$ws = fresh_workspace;
+$first = open_window;
+$focused = get_focused($ws);
+$second = open_window(wm_class => 'notme');
+
+is(get_focused($ws), $focused, 'focus has not changed');
+
+exit_gracefully($pid);
+
+#####################################################################
+
+done_testing;
diff --git a/testcases/t/243-move-to-mark.t b/testcases/t/243-move-to-mark.t
new file mode 100644 (file)
index 0000000..58105d7
--- /dev/null
@@ -0,0 +1,341 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Tests for the 'move [window|container] to mark' command
+# Ticket: #1643
+use i3test;
+
+# In the following tests descriptions, we will always use the following names:
+#  * 'S' for the source container which is going to be moved,
+#  * 'M' for the marked target container to which 'S' will be moved.
+
+my ($A, $B, $S, $M, $F, $source_ws, $target_ws, $ws);
+my ($nodes, $focus);
+my $cmd_result;
+
+my $_NET_WM_STATE_REMOVE = 0;
+my $_NET_WM_STATE_ADD = 1;
+my $_NET_WM_STATE_TOGGLE = 2;
+
+sub set_urgency {
+    my ($win, $urgent_flag) = @_; 
+    my $msg = pack "CCSLLLLLL",
+        X11::XCB::CLIENT_MESSAGE, # response_type
+        32, # format
+        0, # sequence
+        $win->id, # window
+        $x->atom(name => '_NET_WM_STATE')->id, # message type
+        ($urgent_flag ? $_NET_WM_STATE_ADD : $_NET_WM_STATE_REMOVE), # data32[0]
+        $x->atom(name => '_NET_WM_STATE_DEMANDS_ATTENTION')->id, # data32[1]
+        0, # data32[2]
+        0, # data32[3]
+        0; # data32[4]
+
+    $x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg);
+}
+
+###############################################################################
+# Given 'M' and 'S' in a horizontal split, when 'S' is moved to 'M', then
+# verify that nothing changed.
+###############################################################################
+
+$ws = fresh_workspace;
+$M = open_window;
+cmd 'mark target';
+$S = open_window;
+
+cmd 'move container to mark target';
+sync_with_i3;
+
+($nodes, $focus) = get_ws_content($ws);
+is(@{$nodes}, 2, 'there are two containers');
+is($nodes->[0]->{window}, $M->{id}, 'M is left of S');
+is($nodes->[1]->{window}, $S->{id}, 'S is right of M');
+
+###############################################################################
+# Given 'S' and 'M' in a horizontal split, when 'S' is moved to 'M', then
+# both containers switch places.
+###############################################################################
+
+$ws = fresh_workspace;
+$S = open_window;
+$M = open_window;
+cmd 'mark target';
+cmd 'focus left';
+
+cmd 'move container to mark target';
+sync_with_i3;
+
+($nodes, $focus) = get_ws_content($ws);
+is(@{$nodes}, 2, 'there are two containers');
+is($nodes->[0]->{window}, $M->{id}, 'M is left of S');
+is($nodes->[1]->{window}, $S->{id}, 'S is right of M');
+
+###############################################################################
+# Given 'S' and no container 'M' exists, when 'S' is moved to 'M', then
+# the command is unsuccessful.
+###############################################################################
+
+$ws = fresh_workspace;
+$S = open_window;
+
+$cmd_result = cmd 'move container to mark absent';
+
+is($cmd_result->[0]->{success}, 0, 'command was unsuccessful');
+
+###############################################################################
+# Given 'S' and 'M' on different workspaces, when 'S' is moved to 'M', then
+# 'S' ends up on the same workspace as 'M'.
+###############################################################################
+
+$source_ws = fresh_workspace;
+$S = open_window;
+$target_ws = fresh_workspace;
+$M = open_window;
+cmd 'mark target';
+
+cmd '[id="' . $S->{id} . '"] move container to mark target';
+sync_with_i3;
+
+($nodes, $focus) = get_ws_content($source_ws);
+is(@{$nodes}, 0, 'source workspace is empty');
+
+($nodes, $focus) = get_ws_content($target_ws);
+is(@{$nodes}, 2, 'both containers are on the target workspace');
+is($nodes->[0]->{window}, $M->{id}, 'M is left of S');
+is($nodes->[1]->{window}, $S->{id}, 'S is right of M');
+
+###############################################################################
+# Given 'S' and 'M' on different workspaces and 'S' is urgent, when 'S' is 
+# moved to 'M', then the urgency flag is transferred to the target workspace.
+###############################################################################
+
+$source_ws = fresh_workspace;
+$S = open_window;
+$F = open_window;
+$target_ws = fresh_workspace;
+$M = open_window;
+cmd 'mark target';
+cmd 'workspace ' . $source_ws;
+set_urgency($S, 1);
+
+cmd '[id="' . $S->{id} . '"] move container to mark target';
+sync_with_i3;
+
+$source_ws = get_ws($source_ws);
+$target_ws = get_ws($target_ws);
+ok(!$source_ws->{urgent}, 'source workspace is no longer urgent');
+ok($target_ws->{urgent}, 'target workspace is urgent');
+
+###############################################################################
+# Given 'S' and 'M' where 'M' is inside a tabbed container, when 'S' is moved
+# to 'M', then 'S' ends up as a new tab.
+###############################################################################
+
+$source_ws = fresh_workspace;
+$S = open_window;
+
+# open tabbed container ['A' 'M' 'B']
+$target_ws = fresh_workspace;
+$A = open_window;
+cmd 'layout tabbed';
+$M = open_window;
+cmd 'mark target';
+$B = open_window;
+
+cmd '[id="' . $S->{id} . '"] move container to mark target';
+sync_with_i3;
+
+($nodes, $focus) = get_ws_content($target_ws);
+is(@{$nodes}, 1, 'there is a tabbed container');
+
+$nodes = $nodes->[0]->{nodes};
+is(@{$nodes}, 4, 'all four containers are on the target workspace');
+is($nodes->[0]->{window}, $A->{id}, 'A is the first tab');
+is($nodes->[1]->{window}, $M->{id}, 'M is the second tab');
+is($nodes->[2]->{window}, $S->{id}, 'S is the third tab');
+is($nodes->[3]->{window}, $B->{id}, 'B is the fourth tab');
+
+###############################################################################
+# Given 'S' and 'M' where 'M' is a tabbed container where the currently focused
+# tab is a nested layout, when 'S' is moved to 'M', then 'S' is a new tab
+# within 'M'.
+###############################################################################
+
+$source_ws = fresh_workspace;
+$S = open_window;
+
+$target_ws = fresh_workspace;
+$A = open_window;
+cmd 'layout tabbed';
+cmd 'focus parent';
+cmd 'mark target';
+cmd 'focus child';
+$B = open_window;
+cmd 'split h';
+$F = open_window;
+
+cmd '[id="' . $S->{id} . '"] move container to mark target';
+sync_with_i3;
+
+($nodes, $focus) = get_ws_content($target_ws);
+is(@{$nodes}, 1, 'there is a tabbed container');
+
+$nodes = $nodes->[0]->{nodes};
+is(@{$nodes}, 3, 'there are three tabs');
+
+is($nodes->[0]->{window}, $A->{id}, 'A is the first tab');
+is($nodes->[2]->{window}, $S->{id}, 'S is the third tab');
+
+###############################################################################
+# Given 'S' and 'M' where 'M' is inside a split container inside a tabbed
+# container, when 'S' is moved to 'M', then 'S' ends up as a container
+# within the same tab as 'M'.
+###############################################################################
+
+$source_ws = fresh_workspace;
+$S = open_window;
+
+# open tabbed container ['A'['B' 'M']]
+$target_ws = fresh_workspace;
+$A = open_window;
+cmd 'layout tabbed';
+$B = open_window;
+cmd 'split h';
+$M = open_window;
+cmd 'mark target';
+
+cmd '[id="' . $S->{id} . '"] move container to mark target';
+sync_with_i3;
+
+($nodes, $focus) = get_ws_content($target_ws);
+is(@{$nodes}, 1, 'there is a tabbed container');
+
+$nodes = $nodes->[0]->{nodes};
+is(@{$nodes}, 2, 'there are two tabs');
+
+$nodes = $nodes->[1]->{nodes};
+is(@{$nodes}, 3, 'the tab with the marked children has three children');
+is($nodes->[0]->{window}, $B->{id}, 'B is the first tab');
+is($nodes->[1]->{window}, $M->{id}, 'M is the second tab');
+is($nodes->[2]->{window}, $S->{id}, 'S is the third tab');
+
+###############################################################################
+# Given 'S', 'A' and 'B' where 'A' and 'B' are inside the tabbed container 'M',
+# when 'S' is moved to 'M', then 'S' ends up as a new tab in 'M'.
+###############################################################################
+
+$source_ws = fresh_workspace;
+$S = open_window;
+$target_ws = fresh_workspace;
+$A = open_window;
+cmd 'layout tabbed';
+$B = open_window;
+cmd 'focus parent';
+cmd 'mark target';
+cmd 'focus child';
+
+cmd '[id="' . $S->{id} . '"] move container to mark target';
+sync_with_i3;
+
+($nodes, $focus) = get_ws_content($target_ws);
+is(@{$nodes}, 1, 'there is a tabbed container');
+
+$nodes = $nodes->[0]->{nodes};
+is(@{$nodes}, 3, 'there are three tabs');
+
+is($nodes->[0]->{window}, $A->{id}, 'A is the first tab');
+is($nodes->[1]->{window}, $B->{id}, 'B is the second tab');
+is($nodes->[2]->{window}, $S->{id}, 'S is the third tab');
+
+###############################################################################
+# Given 'S', 'A', 'F' and 'M', where 'M' is a workspace containing a tabbed
+# container, when 'S' is moved to 'M', then 'S' does not end up as a tab, but
+# rather as a new window next to the tabbed container.
+###############################################################################
+
+$source_ws = fresh_workspace;
+$S = open_window;
+$target_ws = fresh_workspace;
+$A = open_window;
+cmd 'layout tabbed';
+$F = open_window;
+$M = $target_ws;
+cmd 'focus parent';
+cmd 'focus parent';
+cmd 'mark target';
+cmd 'focus ' . $source_ws;
+
+cmd '[id="' . $S->{id} . '"] move container to mark target';
+sync_with_i3;
+
+($nodes, $focus) = get_ws_content($target_ws);
+is(@{$nodes}, 2, 'there is a tabbed container and a window');
+is($nodes->[1]->{window}, $S->{id}, 'S is the second window');
+
+###############################################################################
+# Given 'S' and 'M' where 'S' is floating and 'M' on a different workspace,
+# when 'S' is moved to 'M', then 'S' is a floating container on the same
+# workspaces as 'M'.
+###############################################################################
+
+$source_ws = fresh_workspace;
+$S = open_floating_window;
+$target_ws = fresh_workspace;
+$M = open_window;
+cmd 'mark target';
+
+cmd '[id="' . $S->{id} . '"] move container to mark target';
+sync_with_i3;
+
+is(@{get_ws($target_ws)->{floating_nodes}}, 1, 'target workspace has the container now');
+
+###############################################################################
+# Given 'S' and 'M' where 'M' is floating and on a different workspace,
+# when 'S' is moved to 'M', then 'S' ends up as a tiling container on the
+# same workspace as 'M'.
+###############################################################################
+
+$source_ws = fresh_workspace;
+$S = open_window;
+$target_ws = fresh_workspace;
+$M = open_floating_window;
+cmd 'mark target';
+
+cmd '[id="' . $S->{id} . '"] move container to mark target';
+sync_with_i3;
+
+($nodes, $focus) = get_ws_content($target_ws);
+is(@{$nodes}, 1, 'tiling container moved to the target workspace');
+
+###############################################################################
+# Given 'S' and 'M' are the same container, when 'S' is moved to 'M', then
+# the command is ignored.
+###############################################################################
+
+$ws = fresh_workspace;
+$S = open_window;
+$M = $S;
+cmd 'mark target';
+
+cmd '[id="' . $S->{id} . '"] move container to mark target';
+sync_with_i3;
+
+does_i3_live;
+
+###############################################################################
+
+done_testing;
diff --git a/testcases/t/243-net-wm-state-hidden.t b/testcases/t/243-net-wm-state-hidden.t
new file mode 100644 (file)
index 0000000..3f2301c
--- /dev/null
@@ -0,0 +1,201 @@
+#!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 setting and removing the _NET_WM_STATE_HIDDEN atom properly.
+# Ticket: #1648
+use i3test;
+use X11::XCB qw(:all);
+
+sub is_hidden {
+    sync_with_i3;
+    my $atom = $x->atom(name => '_NET_WM_STATE_HIDDEN');
+
+    my ($con) = @_;
+    my $cookie = $x->get_property(
+        0,
+        $con->{id},
+        $x->atom(name => '_NET_WM_STATE')->id,
+        GET_PROPERTY_TYPE_ANY,
+        0,
+        4096
+    );
+
+    my $reply = $x->get_property_reply($cookie->{sequence});
+    my $len = $reply->{length};
+    return 0 if $len == 0;
+
+    my @atoms = unpack("L$len", $reply->{value});
+    for (my $i = 0; $i < $len; $i++) {
+        return 1 if $atoms[$i] == $atom->id;
+    }
+
+    return 0;
+}
+
+my ($tabA, $tabB, $tabC, $subtabA, $subtabB, $windowA, $windowB);
+
+###############################################################################
+# Given two containers next to each other, when focusing one, then the other
+# one does not have _NET_WM_STATE_HIDDEN set.
+###############################################################################
+
+fresh_workspace;
+$windowA = open_window;
+$windowB = open_window;
+
+ok(!is_hidden($windowA), 'left window does not have _NET_WM_STATE_HIDDEN set');
+ok(!is_hidden($windowB), 'right window does not have _NET_WM_STATE_HIDDEN set');
+
+###############################################################################
+# Given two containers on different workspaces, when one is focused, then
+# the other one does not have _NET_WM_STATE_HIDDEN set.
+###############################################################################
+
+fresh_workspace;
+$windowA = open_window;
+fresh_workspace;
+$windowB = open_window;
+
+ok(!is_hidden($windowA), 'left window does not have _NET_WM_STATE_HIDDEN set');
+ok(!is_hidden($windowB), 'right window does not have _NET_WM_STATE_HIDDEN set');
+
+###############################################################################
+# Given two containers in the same tabbed container, when one is focused, then
+# (only) the other one has _NET_WM_STATE_HIDDEN set.
+# Given the other tab is focused, then the atom is transferred.
+###############################################################################
+
+fresh_workspace;
+$tabA = open_window;
+cmd 'layout tabbed';
+$tabB = open_window;
+
+ok(is_hidden($tabA), 'unfocused tab has _NET_WM_STATE_HIDDEN set');
+ok(!is_hidden($tabB), 'focused tab does not have _NET_WM_STATE_HIDDEN set');
+
+cmd 'focus left';
+
+ok(!is_hidden($tabA), 'focused tab does not have _NET_WM_STATE_HIDDEN set');
+ok(is_hidden($tabB), 'unfocused tab has _NET_WM_STATE_HIDDEN set');
+
+###############################################################################
+# Given three containers in the same stacked container, when the focused tab
+# is moved to another workspace, then the now focused tab does not have
+# _NET_WM_STATE_HIDDEN set anymore.
+###############################################################################
+
+fresh_workspace;
+$tabA = open_window;
+cmd 'layout stacked';
+$tabB = open_window;
+$tabC = open_window;
+cmd 'move window to workspace unused';
+
+ok(is_hidden($tabA), 'unfocused tab has _NET_WM_STATE_HIDDEN set');
+ok(!is_hidden($tabB), 'focused tab does not have _NET_WM_STATE_HIDDEN set');
+ok(!is_hidden($tabC), 'moved window does not have _NET_WM_STATE_HIDDEN set');
+
+###############################################################################
+# Given three containers in the same stacked container, when a not focused
+# tab is moved to another workspace, then it does not have _NET_WM_STATE_HIDDEN
+# set anymore.
+###############################################################################
+
+fresh_workspace;
+$tabA = open_window;
+cmd 'layout stacked';
+$tabB = open_window;
+cmd 'mark moveme';
+$tabC = open_window;
+cmd '[con_mark="moveme"] move window to workspace unused';
+
+ok(is_hidden($tabA), 'unfocused tab has _NET_WM_STATE_HIDDEN set');
+ok(!is_hidden($tabB), 'moved window does not have _NET_WM_STATE_HIDDEN set');
+ok(!is_hidden($tabC), 'focused tab does not have _NET_WM_STATE_HIDDEN set');
+
+###############################################################################
+# Given a tabbed container and some other container, when the latter is moved
+# into the tabbed container, then all other tabs have _NET_WM_STATE_HIDDEN
+# set.
+###############################################################################
+
+fresh_workspace;
+$tabA = open_window;
+cmd 'layout tabbed';
+$tabB = open_window;
+cmd 'focus parent';
+cmd 'split h';
+$tabC = open_window;
+cmd 'move left';
+
+ok(is_hidden($tabA), 'unfocused tab has _NET_WM_STATE_HIDDEN set');
+ok(is_hidden($tabB), 'unfocused tab has _NET_WM_STATE_HIDDEN set');
+ok(!is_hidden($tabC), 'focused tab does not have _NET_WM_STATE_HIDDEN set');
+
+###############################################################################
+# Given a stacked container nested inside another tabbed container with the
+# inner one being in the currently focused tab, then the focused tab of the
+# inner container does not have _NET_WM_STATE_HIDDEN set.
+###############################################################################
+
+fresh_workspace;
+$tabA = open_window;
+cmd 'layout tabbed';
+$tabB = open_window;
+cmd 'split h';
+open_window;
+cmd 'split v';
+cmd 'layout stacked';
+$subtabA = open_window;
+$subtabB = open_window;
+
+ok(is_hidden($tabA), 'unfocused outer tab has _NET_WM_STATE_HIDDEN set');
+ok(!is_hidden($tabB), 'focused outer tab does not have _NET_WM_STATE_HIDDEN set');
+ok(is_hidden($subtabA), 'unfocused inner tab has _NET_WM_STATE_HIDDEN set');
+ok(!is_hidden($subtabB), 'focused inner tab does not have _NET_WM_STATE_HIDDEN set');
+
+cmd 'focus left';
+
+ok(!is_hidden($subtabB), 'focused inner tab does not have _NET_WM_STATE_HIDDEN set');
+
+###############################################################################
+# Given a stacked container nested inside another tabbed container with the
+# inner one being in a currently not focused tab, then all tabs of the inner
+# container have _NET_WM_STATE_HIDDEN set.
+###############################################################################
+
+fresh_workspace;
+$tabA = open_window;
+cmd 'layout tabbed';
+$tabB = open_window;
+cmd 'split h';
+open_window;
+cmd 'split v';
+cmd 'layout stacked';
+$subtabA = open_window;
+$subtabB = open_window;
+cmd 'focus parent';
+cmd 'focus parent';
+cmd 'focus left';
+
+ok(!is_hidden($tabA), 'focused outer tab does not have _NET_WM_STATE_HIDDEN set');
+ok(is_hidden($tabB), 'unfocused outer tab has _NET_WM_STATE_HIDDEN set');
+ok(is_hidden($subtabA), 'unfocused inner tab has _NET_WM_STATE_HIDDEN set');
+ok(is_hidden($subtabB), 'unfocused inner tab has _NET_WM_STATE_HIDDEN set');
+
+###############################################################################
+
+done_testing;
diff --git a/testcases/t/245-move-position-mouse.t b/testcases/t/245-move-position-mouse.t
new file mode 100644 (file)
index 0000000..ae04927
--- /dev/null
@@ -0,0 +1,142 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Tests the 'move [window|container] [to] position mouse|cursor|pointer command.
+# Ticket: #1696
+use i3test i3_autostart => 0;
+use POSIX qw(floor);
+
+sub warp_pointer {
+    my ($pos_x, $pos_y) = @_;
+    $x->root->warp_pointer($pos_x, $pos_y);
+    sync_with_i3;
+}
+
+my ($pid, $config, $ws, $rect, @cons);
+my $root_rect = $x->root->rect;
+
+##########################################################################
+
+##########################################################################
+# Given a floating container and the cursor far from any edges, when
+# moving the container to the mouse, then the container is moved such
+# that the cursor is centered inside it.
+##########################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font pango:monospace 8
+EOT
+$pid = launch_with_config($config);
+
+$ws = fresh_workspace;
+open_floating_window;
+warp_pointer(100, 100);
+
+cmd 'move position mouse';
+
+@cons = @{get_ws($ws)->{floating_nodes}};
+$rect = $cons[0]->{rect};
+
+is(floor($rect->{x} + $rect->{width} / 2), 100, "con is centered around cursor horizontally");
+is(floor($rect->{y} + $rect->{height} / 2), 100, "con is centered around cursor vertically");
+
+exit_gracefully($pid);
+
+##########################################################################
+# Given a floating container and the cursor is in the left upper edge
+# of the output, when moving the container to the mouse, then the 
+# container is moved but adjusted to stay in-bounds.
+##########################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font pango:monospace 8
+EOT
+$pid = launch_with_config($config);
+
+$ws = fresh_workspace;
+open_floating_window;
+warp_pointer(5, 5);
+
+cmd 'move position mouse';
+
+@cons = @{get_ws($ws)->{floating_nodes}};
+$rect = $cons[0]->{rect};
+
+is($rect->{x}, 0, "con is on the left edge");
+is($rect->{y}, 0, "con is on the upper edge");
+
+exit_gracefully($pid);
+
+##########################################################################
+# Given a floating container and the cursor is in the left right lower
+# edge of the output, when moving the container to the mouse, then the 
+# container is moved but adjusted to stay in-bounds.
+##########################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font pango:monospace 8
+EOT
+$pid = launch_with_config($config);
+
+$ws = fresh_workspace;
+open_floating_window;
+warp_pointer($root_rect->width - 5, $root_rect->height - 5);
+
+cmd 'move position mouse';
+
+@cons = @{get_ws($ws)->{floating_nodes}};
+$rect = $cons[0]->{rect};
+
+is($rect->{x} + $rect->{width}, $root_rect->width, "con is on the right edge");
+is($rect->{y} + $rect->{height}, $root_rect->height, "con is on the lower edge");
+
+exit_gracefully($pid);
+
+##########################################################################
+# Given a floating container and the cursor being close to the corner of
+# an output, when the container is moved to the mouse, then the container
+# is moved but adjusted to the output boundaries.
+# This test verifies that boundaries of individual outputs are respected
+# and not just boundaries of the entire X root screen.
+##########################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font pango:monospace 8
+fake-outputs 500x500+0+0,500x500+500+0,500x500+0+500,500x500+500+500
+EOT
+$pid = launch_with_config($config);
+
+$ws = fresh_workspace;
+open_floating_window;
+warp_pointer(495, 495);
+
+cmd 'move position mouse';
+
+@cons = @{get_ws($ws)->{floating_nodes}};
+$rect = $cons[0]->{rect};
+
+is($rect->{x} + $rect->{width}, 500, "con is on the right edge");
+is($rect->{y} + $rect->{height}, 500, "con is on the lower edge");
+
+exit_gracefully($pid);
+
+##########################################################################
+
+done_testing;
diff --git a/testcases/t/246-window-decoration-focus.t b/testcases/t/246-window-decoration-focus.t
new file mode 100644 (file)
index 0000000..06dfad8
--- /dev/null
@@ -0,0 +1,148 @@
+#!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)
+#
+# Ensure that hovering over the window decoration of a window causes it to focus
+# correctly.
+# Ticket: #1056
+# Bug still in: 4.10.2-174-g8029ff0
+use i3test;
+
+my ($ws, $A, $B, $C, $target, $y);
+my @cons;
+
+# ==================================================================================
+# Given the following layout (= denotes the window decoration),
+# when moving the mouse from 1 to 2,
+# then the C should be focused.
+#
+# This should especially be the case if B is the focus head of its vertically split parent container.
+#
+# +===A===+===B===+
+# |       |       |
+# |     1 +=2=C===+
+# |       |       |
+# +-------+-------+
+#
+# ==================================================================================
+
+$ws = fresh_workspace;
+
+open_window;
+$B = open_window;
+cmd 'split v';
+open_window;
+$target = get_focused($ws);
+
+@cons = @{get_ws($ws)->{nodes}};
+$A = $cons[0];
+$C = $cons[1]->{nodes}[1];
+
+$y = $C->{rect}->{y} - 0.5 * $C->{deco_rect}->{height};
+
+# make sure that B is the focus head of its parent
+cmd '[id="' . $B->{id} . '"] focus';
+
+# move pointer to position 1
+$x->root->warp_pointer($C->{rect}->{x} - 0.5 * $A->{rect}->{width}, $y);
+sync_with_i3;
+
+# move pointer to position 2
+$x->root->warp_pointer($C->{rect}->{x} + 0.5 * $C->{rect}->{width}, $y);
+sync_with_i3;
+
+is(get_focused($ws), $target, 'focus switched to container C');
+
+# ==================================================================================
+# Given a tabbed container when the mouse is moved onto the window decoration
+# then the focus head of the tabbed container is focused regardless of which particular
+# tab's decoration the mouse is on.
+#
+# +=========+=========+
+# |         |         |
+# |       1 +=2==|****| <- tab to the right is focus head of tabbed container
+# |         |         |
+# +---------+---------+
+#
+# ==================================================================================
+
+$ws = fresh_workspace;
+
+open_window;
+open_window;
+cmd 'split v';
+open_window;
+cmd 'split h';
+open_window;
+$target = get_focused($ws);
+cmd 'layout tabbed';
+
+@cons = @{get_ws($ws)->{nodes}};
+$A = $cons[0];
+$B = $cons[1]->{nodes}[1]->{nodes}[1];
+
+$y = $B->{rect}->{y} - 0.5 * $B->{deco_rect}->{height};
+
+$x->root->warp_pointer($B->{rect}->{x} - 0.5 * $A->{rect}->{width}, $y);
+sync_with_i3;
+
+$x->root->warp_pointer($B->{rect}->{x} + 0.2 * $B->{rect}->{width}, $y);
+sync_with_i3;
+
+is(get_focused($ws), $target, 'focus switched to the focus head of the tabbed container');
+
+# ==================================================================================
+# Given a stacked container when the mouse is moved onto the window decoration
+# then the focus head of the stacked container is focused regardless of which particular
+# tab's decoration the mouse is on.
+#
+# +=========+=========+
+# |         |         |
+# |       1 +=2=======+
+# |         +*********+ <- the lower tab is the focus head of the stacked container
+# |         |         |
+# +---------+---------+
+#
+# ==================================================================================
+
+$ws = fresh_workspace;
+
+open_window;
+open_window;
+cmd 'split v';
+open_window;
+cmd 'split h';
+open_window;
+$target = get_focused($ws);
+cmd 'layout stacked';
+
+@cons = @{get_ws($ws)->{nodes}};
+$A = $cons[0];
+$B = $cons[1]->{nodes}[1]->{nodes}[0];
+$C = $cons[1]->{nodes}[1]->{nodes}[1];
+
+$y = $B->{rect}->{y} - 1.5 * $B->{deco_rect}->{height};
+
+$x->root->warp_pointer($B->{rect}->{x} - 0.5 * $A->{rect}->{width}, $y);
+sync_with_i3;
+
+$x->root->warp_pointer($B->{rect}->{x} + 0.5 * $B->{rect}->{width}, $y);
+sync_with_i3;
+
+is(get_focused($ws), $target, 'focus switched to the focus head of the stacked container');
+
+# ==================================================================================
+
+done_testing;
diff --git a/testcases/t/247-config-line-continuation.t b/testcases/t/247-config-line-continuation.t
new file mode 100644 (file)
index 0000000..0396d87
--- /dev/null
@@ -0,0 +1,201 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Checks that the line continuation are parsed correctly
+#
+
+use i3test i3_autostart => 0;
+
+# starts i3 with the given config, opens a window, returns its border style
+sub launch_get_border {
+    my ($config) = @_;
+
+    my $pid = launch_with_config($config);
+
+    my $i3 = i3(get_socket_path(0));
+    my $tmp = fresh_workspace;
+
+    my $window = open_window(name => '"special title"');
+
+    my @content = @{get_ws_content($tmp)};
+    cmp_ok(@content, '==', 1, 'one node on this workspace now');
+    my $border = $content[0]->{border};
+
+    exit_gracefully($pid);
+
+    return $border;
+}
+
+#####################################################################
+# test string escaping
+#####################################################################
+
+my $config = <<'EOT';
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+set $vartest \"special title\"
+for_window [title="$vartest"] border none
+EOT
+
+is(launch_get_border($config), 'none', 'no border');
+
+#####################################################################
+# test the line continuation
+#####################################################################
+
+$config = <<'EOT';
+# i3 config file (v4)
+font \
+-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+# Use line contiuation with too many lines (>4096 characters).
+# This config is invalid. Use it to ensure no buffer overflow.
+bindsym Mod1+b \
+0001-This is a very very very very very very very very very very very very very very very very very long cmd \
+0002-This is a very very very very very very very very very very very very very very very very very long cmd \
+0003-This is a very very very very very very very very very very very very very very very very very long cmd \
+0004-This is a very very very very very very very very very very very very very very very very very long cmd \
+0005-This is a very very very very very very very very very very very very very very very very very long cmd \
+0006-This is a very very very very very very very very very very very very very very very very very long cmd \
+0007-This is a very very very very very very very very very very very very very very very very very long cmd \
+0008-This is a very very very very very very very very very very very very very very very very very long cmd \
+0009-This is a very very very very very very very very very very very very very very very very very long cmd \
+0010-This is a very very very very very very very very very very very very very very very very very long cmd \
+0011-This is a very very very very very very very very very very very very very very very very very long cmd \
+0012-This is a very very very very very very very very very very very very very very very very very long cmd \
+0013-This is a very very very very very very very very very very very very very very very very very long cmd \
+0014-This is a very very very very very very very very very very very very very very very very very long cmd \
+0015-This is a very very very very very very very very very very very very very very very very very long cmd \
+0016-This is a very very very very very very very very very very very very very very very very very long cmd \
+0017-This is a very very very very very very very very very very very very very very very very very long cmd \
+0018-This is a very very very very very very very very very very very very very very very very very long cmd \
+0019-This is a very very very very very very very very very very very very very very very very very long cmd \
+0020-This is a very very very very very very very very very very very very very very very very very long cmd \
+0021-This is a very very very very very very very very very very very very very very very very very long cmd \
+0022-This is a very very very very very very very very very very very very very very very very very long cmd \
+0023-This is a very very very very very very very very very very very very very very very very very long cmd \
+0024-This is a very very very very very very very very very very very very very very very very very long cmd \
+0025-This is a very very very very very very very very very very very very very very very very very long cmd \
+0026-This is a very very very very very very very very very very very very very very very very very long cmd \
+0027-This is a very very very very very very very very very very very very very very very very very long cmd \
+0028-This is a very very very very very very very very very very very very very very very very very long cmd \
+0029-This is a very very very very very very very very very very very very very very very very very long cmd \
+0030-This is a very very very very very very very very very very very very very very very very very long cmd \
+0031-This is a very very very very very very very very very very very very very very very very very long cmd \
+0032-This is a very very very very very very very very very very very very very very very very very long cmd \
+0033-This is a very very very very very very very very very very very very very very very very very long cmd \
+0034-This is a very very very very very very very very very very very very very very very very very long cmd \
+0035-This is a very very very very very very very very very very very very very very very very very long cmd \
+0036-This is a very very very very very very very very very very very very very very very very very long cmd \
+0037-This is a very very very very very very very very very very very very very very very very very long cmd \
+0038-This is a very very very very very very very very very very very very very very very very very long cmd \
+0039-This is a very very very very very very very very very very very very very very very very very long cmd \
+0040-This is a very very very very very very very very very very very very very very very very very long cmd \
+0041-This is a very very very very very very very very very very very very very very very very very long cmd \
+0042-This is a very very very very very very very very very very very very very very very very very long cmd \
+0043-This is a very very very very very very very very very very very very very very very very very long cmd \
+0044-This is a very very very very very very very very very very very very very very very very very long cmd \
+0045-This is a very very very very very very very very very very very very very very very very very long cmd \
+0046-This is a very very very very very very very very very very very very very very very very very long cmd \
+0047-This is a very very very very very very very very very very very very very very very very very long cmd \
+0048-This is a very very very very very very very very very very very very very very very very very long cmd \
+0049-This is a very very very very very very very very very very very very very very very very very long cmd \
+0050-This is a very very very very very very very very very very very very very very very very very long cmd \
+0051-This is a very very very very very very very very very very very very very very very very very long cmd \
+0052-This is a very very very very very very very very very very very very very very very very very long cmd \
+0053-This is a very very very very very very very very very very very very very very very very very long cmd \
+0054-This is a very very very very very very very very very very very very very very very very very long cmd \
+0055-This is a very very very very very very very very very very very very very very very very very long cmd \
+0056-This is a very very very very very very very very very very very very very very very very very long cmd \
+0057-This is a very very very very very very very very very very very very very very very very very long cmd \
+0058-This is a very very very very very very very very very very very very very very very very very long cmd \
+0059-This is a very very very very very very very very very very very very very very very very very long cmd \
+0060-This is a very very very very very very very very very very very very very very very very very long cmd \
+0061-This is a very very very very very very very very very very very very very very very very very long cmd \
+0062-This is a very very very very very very very very very very very very very very very very very long cmd \
+0063-This is a very very very very very very very very very very very very very very very very very long cmd \
+0064-This is a very very very very very very very very very very very very very very very very very long cmd \
+0065-This is a very very very very very very very very very very very very very very very very very long cmd \
+0066-This is a very very very very very very very very very very very very very very very very very long cmd \
+0067-This is a very very very very very very very very very very very very very very very very very long cmd \
+0068-This is a very very very very very very very very very very very very very very very very very long cmd \
+0069-This is a very very very very very very very very very very very very very very very very very long cmd \
+0070-This is a very very very very very very very very very very very very very very very very very long cmd \
+0071-This is a very very very very very very very very very very very very very very very very very long cmd \
+0072-This is a very very very very very very very very very very very very very very very very very long cmd \
+0073-This is a very very very very very very very very very very very very very very very very very long cmd \
+0074-This is a very very very very very very very very very very very very very very very very very long cmd \
+0075-This is a very very very very very very very very very very very very very very very very very long cmd \
+0076-This is a very very very very very very very very very very very very very very very very very long cmd \
+0077-This is a very very very very very very very very very very very very very very very very very long cmd \
+0078-This is a very very very very very very very very very very very very very very very very very long cmd \
+0079-This is a very very very very very very very very very very very very very very very very very long cmd \
+0080-This is a very very very very very very very very very very very very very very very very very long cmd \
+0081-This is a very very very very very very very very very very very very very very very very very long cmd \
+0082-This is a very very very very very very very very very very very very very very very very very long cmd \
+0083-This is a very very very very very very very very very very very very very very very very very long cmd \
+0084-This is a very very very very very very very very very very very very very very very very very long cmd \
+0085-This is a very very very very very very very very very very very very very very very very very long cmd \
+0086-This is a very very very very very very very very very very very very very very very very very long cmd \
+0087-This is a very very very very very very very very very very very very very very very very very long cmd \
+0088-This is a very very very very very very very very very very very very very very very very very long cmd \
+0089-This is a very very very very very very very very very very very very very very very very very long cmd \
+0090-This is a very very very very very very very very very very very very very very very very very long cmd \
+0091-This is a very very very very very very very very very very very very very very very very very long cmd \
+0092-This is a very very very very very very very very very very very very very very very very very long cmd \
+0093-This is a very very very very very very very very very very very very very very very very very long cmd \
+0094-This is a very very very very very very very very very very very very very very very very very long cmd \
+0095-This is a very very very very very very very very very very very very very very very very very long cmd \
+0096-This is a very very very very very very very very very very very very very very very very very long cmd \
+0097-This is a very very very very very very very very very very very very very very very very very long cmd \
+0098-This is a very very very very very very very very very very very very very very very very very long cmd \
+0099-This is a very very very very very very very very very very very very very very very very very long cmd
+
+# Use line continuation for variables
+set \
+$vartest \
+\"special title\"
+
+# Use line continuation for commands
+for_window \
+[title="$vartest"] \
+border \
+none
+
+EOT
+
+is(launch_get_border($config), 'none', 'no border');
+
+#####################################################################
+# test the line continuation within a string
+#####################################################################
+
+$config = <<'EOT';
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+set \
+$vartest \
+\"special \
+title\"
+for_window [title="$vartest"] border none
+EOT
+
+is(launch_get_border($config), 'none', 'no border');
+
+
+done_testing;
diff --git a/testcases/t/248-regress-urgency-clear.t b/testcases/t/248-regress-urgency-clear.t
new file mode 100644 (file)
index 0000000..10ef377
--- /dev/null
@@ -0,0 +1,69 @@
+#!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 the urgency hint is cleared properly in the case where i3 set it (due
+# to focus_on_window_activation=urgent), hence the application not clearing it.
+# Ticket: #1825
+# Bug still in: 4.10.3-253-g03799dd
+use i3test i3_autostart => 0;
+
+sub send_net_active_window {
+    my ($id) = @_;
+
+    my $msg = pack "CCSLLLLLLL",
+        X11::XCB::CLIENT_MESSAGE, # response_type
+        32, # format
+        0, # sequence
+        $id, # destination window
+        $x->atom(name => '_NET_ACTIVE_WINDOW')->id,
+        0, # source
+        0, 0, 0, 0;
+
+    $x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg);
+}
+
+my $config = <<'EOT';
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+focus_on_window_activation urgent
+EOT
+
+my $pid = launch_with_config($config);
+my $i3 = i3(get_socket_path(0));
+my $ws = fresh_workspace;
+my $first = open_window;
+my $second = open_window;
+
+send_net_active_window($first->id);
+sync_with_i3;
+is($x->input_focus, $second->id, 'second window still focused');
+
+cmd '[urgent=latest] focus';
+sync_with_i3;
+is($x->input_focus, $first->id, 'first window focused');
+
+cmd 'focus right';
+sync_with_i3;
+is($x->input_focus, $second->id, 'second window focused again');
+
+cmd '[urgent=latest] focus';
+sync_with_i3;
+is($x->input_focus, $second->id, 'second window still focused');
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/249-layout-restore-floating.t b/testcases/t/249-layout-restore-floating.t
new file mode 100644 (file)
index 0000000..805b071
--- /dev/null
@@ -0,0 +1,82 @@
+#!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 floating containers are restored in the correct place in the
+# hierarchy and get a non-zero size.
+# Ticket: #1739
+# Bug still in: 4.10.3-264-g419b73b
+use i3test;
+use File::Temp qw(tempfile);
+use IO::Handle;
+
+my $ws = fresh_workspace;
+
+my @content = @{get_ws_content($ws)};
+is(@content, 0, 'no nodes on the new workspace yet');
+
+my ($fh, $filename) = tempfile(UNLINK => 1);
+print $fh <<'EOT';
+// vim:ts=4:sw=4:et
+{
+    // floating_con with 1 children
+    "border": "none",
+    "floating": "auto_off",
+    "layout": "splith",
+    "percent": null,
+    "type": "floating_con",
+    // NOTE that "rect" is missing here.
+    "nodes": [
+        {
+            "border": "none",
+            "current_border_width": 0,
+            "floating": "user_on",
+            "geometry": {
+               "height": 384,
+               "width": 128,
+               "x": 448,
+               "y": 441
+            },
+            "name": "Splash",
+            "percent": 1,
+            "swallows": [
+               {
+                "class": "^Steam$",
+                "instance": "^Steam$",
+                "title": "^Steam$"
+               // "transient_for": "^$"
+               }
+            ],
+            "type": "con"
+        }
+    ]
+}
+EOT
+$fh->flush;
+my $reply = cmd "append_layout $filename";
+
+does_i3_live;
+
+ok($reply->[0]->{success}, 'Layout successfully loaded');
+
+cmd 'kill';
+
+ok(workspace_exists($ws), 'Workspace still exists');
+
+does_i3_live;
+
+close($fh);
+
+done_testing;
diff --git a/testcases/t/250-layout-restore-multiple-criteria.t b/testcases/t/250-layout-restore-multiple-criteria.t
new file mode 100644 (file)
index 0000000..88c6882
--- /dev/null
@@ -0,0 +1,84 @@
+#!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)
+#
+# TODO: Description of this file.
+# Ticket: #1817
+# Bug still in: 4.10.3-270-g0fb784f
+use i3test;
+use File::Temp qw(tempfile);
+use IO::Handle;
+
+my $ws = fresh_workspace;
+
+my @content = @{get_ws_content($ws)};
+is(@content, 0, 'no nodes on the new workspace yet');
+
+my ($fh, $filename) = tempfile(UNLINK => 1);
+print $fh <<'EOT';
+{
+    // stacked split container with 1 children
+    "border": "normal",
+    "floating": "auto_off",
+    "layout": "stacked",
+    "percent": 0.40,
+    "type": "con",
+    "nodes": [
+        {
+            "border": "normal",
+            "current_border_width": 2,
+            "floating": "auto_off",
+            "geometry": {
+               "height": 460,
+               "width": 804,
+               "x": 0,
+               "y": 0
+            },
+            // "name": "",
+            "percent": 0.5,
+            "swallows": [
+               { "class": "^URxvt$" },
+               { "class": "^Gitk$" },
+               { "class": "^Git-gui$" }
+            ],
+            "type": "con"
+        }
+    ]
+}
+EOT
+$fh->flush;
+my $reply = cmd "append_layout $filename";
+close($fh);
+
+does_i3_live;
+
+@content = @{get_ws_content($ws)};
+is(@content, 1, "one node on the workspace now");
+
+my $should_swallow = open_window(wm_class => 'URxvt');
+
+@content = @{get_ws_content($ws)};
+is(@content, 1, "still one node on the workspace now");
+my @nodes = @{$content[0]->{nodes}};
+is($nodes[0]->{window}, $should_swallow->id, "swallowed window on top");
+
+cmd 'focus parent';
+
+my $should_ignore = open_window(wm_class => 'Gitk');
+
+@content = @{get_ws_content($ws)};
+is(@content, 2, "two nodes on the workspace");
+
+done_testing;
diff --git a/testcases/t/251-command-criteria-focused.t b/testcases/t/251-command-criteria-focused.t
new file mode 100644 (file)
index 0000000..36c7b80
--- /dev/null
@@ -0,0 +1,110 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Tests for the special value "__focused__" in command criteria.
+# Ticket: #1770
+use i3test;
+use X11::XCB qw(PROP_MODE_REPLACE);
+
+my ($ws);
+
+sub open_window_with_role {
+    my ($role) = @_;
+    open_window(
+        before_map => sub {
+            my ($window) = @_;
+            my $atomname = $x->atom(name => 'WM_WINDOW_ROLE');
+            my $atomtype = $x->atom(name => 'STRING');
+            $x->change_property(
+                PROP_MODE_REPLACE,
+                $window->id,
+                $atomname->id,
+                $atomtype->id,
+                8,
+                length($role) + 1,
+                "$role\x00"
+            );
+        }
+    );
+}
+
+###############################################################################
+# 1: Test __focused__ for window class.
+###############################################################################
+
+$ws = fresh_workspace;
+open_window(wm_class => 'notme');
+open_window(wm_class => 'magic');
+open_window(wm_class => 'magic');
+is(@{get_ws($ws)->{nodes}}, 3, 'sanity check: workspace contains three windows');
+
+cmd '[class=__focused__] move to workspace trash';
+is(@{get_ws($ws)->{nodes}}, 1, '__focused__ works for window class');
+
+###############################################################################
+# 2: Test __focused__ for window instance.
+###############################################################################
+
+$ws = fresh_workspace;
+open_window(instance => 'notme', wm_class => 'test');
+open_window(instance => 'magic', wm_class => 'test');
+open_window(instance => 'magic', wm_class => 'test');
+is(@{get_ws($ws)->{nodes}}, 3, 'sanity check: workspace contains three windows');
+
+cmd '[instance=__focused__] move to workspace trash';
+is(@{get_ws($ws)->{nodes}}, 1, '__focused__ works for window instance');
+
+###############################################################################
+# 3: Test __focused__ for window title.
+###############################################################################
+
+$ws = fresh_workspace;
+open_window(name => 'notme');
+open_window(name => 'magic');
+open_window(name => 'magic');
+is(@{get_ws($ws)->{nodes}}, 3, 'sanity check: workspace contains three windows');
+
+cmd '[title=__focused__] move to workspace trash';
+is(@{get_ws($ws)->{nodes}}, 1, '__focused__ works for title');
+
+###############################################################################
+# 4: Test __focused__ for window role.
+###############################################################################
+
+$ws = fresh_workspace;
+open_window_with_role("notme");
+open_window_with_role("magic");
+open_window_with_role("magic");
+is(@{get_ws($ws)->{nodes}}, 3, 'sanity check: workspace contains three windows');
+
+cmd '[window_role=__focused__] move to workspace trash';
+is(@{get_ws($ws)->{nodes}}, 1, '__focused__ works for window_role');
+
+###############################################################################
+# 5: Test __focused__ for workspace.
+###############################################################################
+
+$ws = fresh_workspace;
+open_window;
+open_window;
+is(@{get_ws($ws)->{nodes}}, 2, 'sanity check: workspace contains two windows');
+
+cmd '[workspace=__focused__] move to workspace trash';
+is(@{get_ws($ws)->{nodes}}, 0, '__focused__ works for workspace');
+
+###############################################################################
+
+done_testing;
diff --git a/testcases/t/251-ewmh-visible-name.t b/testcases/t/251-ewmh-visible-name.t
new file mode 100644 (file)
index 0000000..c201b39
--- /dev/null
@@ -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 that _NET_WM_VISIBLE_NAME is set correctly.
+# Ticket: #1872
+use i3test;
+use X11::XCB qw(:all);
+
+my ($con);
+
+sub get_visible_name {
+    sync_with_i3;
+    my ($con) = @_;
+
+    my $cookie = $x->get_property(
+        0,
+        $con->{id},
+        $x->atom(name => '_NET_WM_VISIBLE_NAME')->id,
+        $x->atom(name => 'UTF8_STRING')->id,
+        0,
+        4096
+    );
+
+    my $reply = $x->get_property_reply($cookie->{sequence});
+    return undef if $reply->{value_len} == 0;
+    return $reply->{value};
+}
+
+###############################################################################
+# 1: _NET_WM_VISIBLE_NAME is set when the title format of a window is changed.
+###############################################################################
+
+fresh_workspace;
+$con = open_window(name => 'boring title');
+is(get_visible_name($con), undef, 'sanity check: initially no visible name is set');
+
+cmd 'title_format custom';
+is(get_visible_name($con), 'custom', 'the visible name is updated');
+
+cmd 'title_format "<s>%title</s>"';
+is(get_visible_name($con), '<s>boring title</s>', 'markup is returned as is');
+
+###############################################################################
+# 2: _NET_WM_VISIBLE_NAME is removed if not needed.
+###############################################################################
+
+fresh_workspace;
+$con = open_window(name => 'boring title');
+cmd 'title_format custom';
+is(get_visible_name($con), 'custom', 'sanity check: a visible name is set');
+
+cmd 'title_format %title';
+is(get_visible_name($con), undef, 'the visible name is removed again');
+
+###############################################################################
+
+done_testing;
diff --git a/testcases/t/251-sticky.t b/testcases/t/251-sticky.t
new file mode 100644 (file)
index 0000000..85fc11f
--- /dev/null
@@ -0,0 +1,113 @@
+#!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 sticky windows.
+# Ticket: #1455
+use i3test;
+
+my ($ws, $tmp, $focused);
+
+###############################################################################
+# 1: Given a sticky tiling container, when the workspace is switched, then
+#    nothing happens.
+###############################################################################
+fresh_workspace;
+open_window(wm_class => 'findme');
+cmd 'sticky enable';
+$ws = fresh_workspace;
+
+is(@{get_ws($ws)->{nodes}}, 0, 'tiling sticky container did not move');
+is(@{get_ws($ws)->{floating_nodes}}, 0, 'tiling sticky container did not move');
+cmd '[class="findme"] kill';
+
+###############################################################################
+# 2: Given a sticky floating container, when the workspace is switched, then
+#    the container moves to the new workspace.
+###############################################################################
+$ws = fresh_workspace;
+open_floating_window(wm_class => 'findme');
+$focused = get_focused($ws);
+cmd 'sticky enable';
+$ws = fresh_workspace;
+
+is(@{get_ws($ws)->{floating_nodes}}, 1, 'floating sticky container moved to new workspace');
+is(get_focused($ws), $focused, 'sticky container has focus');
+cmd '[class="findme"] kill';
+
+###############################################################################
+# 3: Given two sticky floating containers, when the workspace is switched,
+#    then both containers move to the new workspace.
+###############################################################################
+fresh_workspace;
+open_floating_window(wm_class => 'findme');
+cmd 'sticky enable';
+open_floating_window(wm_class => 'findme');
+cmd 'sticky enable';
+$ws = fresh_workspace;
+
+is(@{get_ws($ws)->{floating_nodes}}, 2, 'multiple sticky windows can be used at the same time');
+cmd '[class="findme"] kill';
+
+###############################################################################
+# 4: Given an unfocused sticky floating container and a tiling container on the
+#    target workspace, when the workspace is switched, then the tiling container
+#    is focused.
+###############################################################################
+$ws = fresh_workspace;
+open_window;
+$focused = get_focused($ws);
+fresh_workspace;
+open_floating_window(wm_class => 'findme');
+cmd 'sticky enable';
+open_window;
+cmd 'workspace ' . $ws;
+
+is(get_focused($ws), $focused, 'the tiling container has focus');
+cmd '[class="findme"] kill';
+
+###############################################################################
+# 5: Given a focused sticky floating container and a tiling container on the
+#    target workspace, when the workspace is switched, then the tiling container
+#    is focused.
+###############################################################################
+$ws = fresh_workspace;
+open_window;
+$tmp = fresh_workspace;
+open_floating_window(wm_class => 'findme');
+$focused = get_focused($tmp);
+cmd 'sticky enable';
+cmd 'workspace ' . $ws;
+
+is(get_focused($ws), $focused, 'the sticky container has focus');
+cmd '[class="findme"] kill';
+
+###############################################################################
+# 6: Given a floating container on a non-visible workspace, when the window
+#    is made sticky, then the window immediately jumps to the currently
+#    visible workspace.
+###############################################################################
+fresh_workspace;
+open_floating_window(wm_class => 'findme');
+cmd 'mark sticky';
+$ws = fresh_workspace;
+cmd '[con_mark=sticky] sticky enable';
+
+is(@{get_ws($ws)->{floating_nodes}}, 1, 'the sticky window jumps to the front');
+cmd '[class="findme"] kill';
+
+###############################################################################
+
+done_testing;
diff --git a/testcases/t/252-floating-size.t b/testcases/t/252-floating-size.t
new file mode 100644 (file)
index 0000000..ef4861a
--- /dev/null
@@ -0,0 +1,45 @@
+#!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 behavior of "resize <width> <height>" command.
+# Ticket: #1727
+# Bug still in: 4.10.2-1-gc0dbc5d
+use i3test;
+
+################################################################################
+# Check that setting floating windows size works
+################################################################################
+
+my $tmp = fresh_workspace;
+
+open_floating_window;
+
+my @content = @{get_ws($tmp)->{floating_nodes}};
+is(@content, 1, 'one floating node on this ws');
+
+my $oldrect = $content[0]->{rect};
+
+cmd 'resize set 100 px 250 px';
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
+cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y untouched');
+cmp_ok($content[0]->{rect}->{width}, '!=', $oldrect->{width}, 'width changed');
+cmp_ok($content[0]->{rect}->{height}, '!=', $oldrect->{width}, 'height changed');
+cmp_ok($content[0]->{rect}->{width}, '==', 100, 'width changed to 100 px');
+cmp_ok($content[0]->{rect}->{height}, '==', 250, 'height changed to 250 px');
+
+done_testing;
diff --git a/testcases/t/253-multiple-net-wm-state-atoms.t b/testcases/t/253-multiple-net-wm-state-atoms.t
new file mode 100644 (file)
index 0000000..0ce7f9f
--- /dev/null
@@ -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)
+#
+# Ticket: #1873
+use i3test;
+use X11::XCB qw(:all);
+
+sub get_wm_state {
+    sync_with_i3;
+    my $atom = $x->atom(name => '_NET_WM_STATE_HIDDEN');
+
+    my ($con) = @_; 
+    my $cookie = $x->get_property(
+        0,  
+        $con->{id},
+        $x->atom(name => '_NET_WM_STATE')->id,
+        GET_PROPERTY_TYPE_ANY,
+        0,  
+        4096
+    );  
+
+    my $reply = $x->get_property_reply($cookie->{sequence});
+    my $len = $reply->{length};
+    return 0 if $len == 0;
+
+    my @atoms = unpack("L$len", $reply->{value});
+    return \@atoms;
+}
+
+my $wm_state_sticky = $x->atom(name => '_NET_WM_STATE_STICKY')->id;
+my $wm_state_fullscreen = $x->atom(name => '_NET_WM_STATE_FULLSCREEN')->id;
+
+##########################################################################
+# Given a sticky container, when it is fullscreened, then both wm state
+# atoms are set. When the container is unfullscreened, then only the
+# sticky atom is still set.
+##########################################################################
+
+fresh_workspace;
+my $window = open_window;
+cmd 'sticky enable';
+is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'sanity check: _NET_WM_STATE_STICKY is set');
+
+cmd 'fullscreen enable';
+is_deeply(get_wm_state($window), [ $wm_state_sticky, $wm_state_fullscreen ],
+    'both _NET_WM_STATE_FULLSCREEN and _NET_WM_STATE_STICKY are set');
+
+cmd 'sticky disable';
+is_deeply(get_wm_state($window), [ $wm_state_fullscreen ], 'only _NET_WM_STATE_FULLSCREEN is set');
+
+cmd 'sticky enable';
+cmd 'fullscreen disable';
+is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'only _NET_WM_STATE_STICKY is set');
+
+##########################################################################
+
+done_testing;
diff --git a/testcases/t/254-move-to-output-with-criteria.t b/testcases/t/254-move-to-output-with-criteria.t
new file mode 100644 (file)
index 0000000..fb83218
--- /dev/null
@@ -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)
+#
+# Verifies that "move container to output" works correctly when
+# used with command criteria.
+# Bug still in: 4.10.4-349-gee5db87
+use i3test i3_autostart => 0;
+
+my $config = <<EOT;
+# i3 config file (v4)
+font pango:monospace 8
+
+fake-outputs 800x600+0+0,800x600+800+0,800x600+0+600,800x600+800+600
+EOT
+
+my $pid = launch_with_config($config);
+
+my $ws_top_left = fresh_workspace(output => 0);
+my $ws_top_right = fresh_workspace(output => 1);
+my $ws_bottom_left = fresh_workspace(output => 2);
+my $ws_bottom_right = fresh_workspace(output => 3);
+
+cmd "workspace " . $ws_top_left;
+open_window(wm_class => 'moveme');
+cmd "workspace " . $ws_bottom_left;
+open_window(wm_class => 'moveme');
+
+cmd '[class="moveme"] move window to output right';
+
+is_num_children($ws_top_left, 0, 'no containers on the upper left workspace');
+is_num_children($ws_top_right, 1, 'one container on the upper right workspace');
+is_num_children($ws_bottom_left, 0, 'no containers on the lower left workspace');
+is_num_children($ws_bottom_right, 1, 'one container on the lower right workspace');
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/523-move-position-center.t b/testcases/t/523-move-position-center.t
new file mode 100644 (file)
index 0000000..6b58424
--- /dev/null
@@ -0,0 +1,63 @@
+#!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 'move position center' moves floating cons to the center of
+# the appropriate output.
+# Ticket: #1211
+# Bug still in: 4.9.1-108-g037cb31
+use i3test i3_autostart => 0;
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1024x768+0+0,1024x768+1024+0
+
+workspace left output fake-0
+workspace right output fake-1
+EOT
+
+my $pid = launch_with_config($config);
+
+#####################################################################
+# Verify that 'move position center' on a floating window does not
+# move it to another output.
+#####################################################################
+
+cmd 'workspace left';
+
+# Sync in case focus switched outputs due to the workspace change.
+sync_with_i3;
+
+my $floating = open_floating_window;
+
+# Center the window on the left workspace
+cmd 'move position center';
+sync_with_i3;
+
+is(scalar @{get_ws('left')->{floating_nodes}}, 1, 'one floating node on left ws');
+is(scalar @{get_ws('right')->{floating_nodes}}, 0, 'no floating nodes on right ws');
+
+# Center the window on the right workspace
+cmd 'move workspace right; workspace right; move position center';
+sync_with_i3;
+
+is(scalar @{get_ws('left')->{floating_nodes}}, 0, 'no floating nodes on left ws');
+is(scalar @{get_ws('right')->{floating_nodes}}, 1, 'one floating node on right ws');
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/524-move.t b/testcases/t/524-move.t
new file mode 100644 (file)
index 0000000..473bf23
--- /dev/null
@@ -0,0 +1,99 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Tests the behaviour of 'move <direction>' when moving containers across
+# outputs on workspaces that have non-default layouts.
+# Ticket: #1603
+# Bug still in: 4.10.1-40-g0ad097e
+use List::Util qw(first);
+use i3test i3_autostart => 0;
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1024x768+0+0,1024x768+1024+0,1024x768+1024+768,1024x768+0+768
+
+workspace left-top output fake-0
+workspace right-top output fake-1
+workspace right-bottom output fake-2
+workspace left-bottom output fake-3
+
+workspace_layout stacked
+EOT
+
+my $pid = launch_with_config($config);
+
+#####################################################################
+# Create two windows in the upper left workspace and move them
+# clockwise around the workspaces until the end up where they began.
+#####################################################################
+
+cmd 'workspace left-top';
+
+my $first = open_window(wm_class => 'first');
+my $second = open_window(wm_class => 'second');
+
+is_num_children('left-top', 1, 'one child on left-top');
+is_num_children('right-top', 0, 'no children on right-top');
+
+# Move the second window into its own stacked container.
+cmd 'move right';
+is_num_children('left-top', 2, 'two children on left-top');
+
+# Move the second window onto the upper right workspace.
+cmd 'move right';
+is_num_children('left-top', 1, 'one child on left-top');
+is_num_children('right-top', 1, 'one child on right-top');
+
+# Move the first window onto the upper right workspace.
+cmd '[class="first"] move right';
+is_num_children('left-top', 0, 'no children on left-top');
+is_num_children('right-top', 2, 'two children on right-top');
+
+# Move the second window onto the lower right workspace.
+cmd '[class="second"] move down, move down';
+is_num_children('right-top', 1, 'one child on right-top');
+is_num_children('right-bottom', 1, 'two children on right-bottom');
+
+# Move the first window onto the lower right workspace.
+cmd '[class="first"] move down';
+is_num_children('right-top', 0, 'no children on right-top');
+is_num_children('right-bottom', 2, 'two children on right-bottom');
+
+# Move the second windo onto the lower left workspace.
+cmd '[class="second"] move left, move left';
+is_num_children('right-bottom', 1, 'one child on right-bottom');
+is_num_children('left-bottom', 1, 'one on left-bottom');
+
+# Move the first window onto the lower left workspace.
+cmd '[class="first"] move left';
+is_num_children('right-bottom', 0, 'no children on right-bottom');
+is_num_children('left-bottom', 2, 'two children on left-bottom');
+
+# Move the second window onto the upper left workspace.
+cmd '[class="second"] move up, move up';
+is_num_children('left-bottom', 1, 'one child on left-bottom');
+is_num_children('left-top', 1, 'one child on left-top');
+
+# Move the first window onto the upper left workspace.
+cmd '[class="first"] move up';
+is_num_children('left-bottom', 0, 'no children on left-bottom');
+is_num_children('left-top', 2, 'two children on left-top');
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/525-i3bar-mouse-bindings.t b/testcases/t/525-i3bar-mouse-bindings.t
new file mode 100644 (file)
index 0000000..eabcad7
--- /dev/null
@@ -0,0 +1,104 @@
+#!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 mouse bindings on the i3bar work correctly.
+# Ticket: #1695
+use i3test i3_autostart => 0;
+
+my ($cv, $timer);
+sub reset_test {
+    $cv = AE::cv;
+    $timer = AE::timer(1, 0, sub { $cv->send(0); });
+}
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+focus_follows_mouse no
+
+bar {
+    font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+    position top
+
+    bindsym button1 focus left
+    bindsym button2 focus right
+    bindsym button3 focus left
+    bindsym button4 focus right
+    bindsym button5 focus left
+}
+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;
+
+    reset_test;
+    $i3->subscribe({
+        window => sub {
+            my ($event) = @_;
+            if ($event->{change} eq 'focus') {
+                $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;
+
+    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;
+
+    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;
+
+    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;
+
+    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;
+
+    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;
+
+    exit_gracefully($pid);
+
+}
+
+done_testing;
diff --git a/testcases/t/526-reconfigure-dock.t b/testcases/t/526-reconfigure-dock.t
new file mode 100644 (file)
index 0000000..9e3ed3f
--- /dev/null
@@ -0,0 +1,57 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Test reconfiguration of dock clients.
+# Ticket: #1883
+use i3test i3_autostart => 0;
+
+my ($config, $pid, $window, $rect);
+my (@docks);
+
+###############################################################################
+# 1: Given two screens A and B and a dock client on screen A, when the dock
+#    client is reconfigured to be positioned on screen B, then the client is
+#    moved to the correct position.
+###############################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1024x768+0+0,1024x768+1024+0
+EOT
+$pid = launch_with_config($config);
+
+$window = open_window({
+        window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK')
+    });
+
+$rect = $window->rect;
+is($rect->x, 0, 'sanity check: dock client is on the left screen');
+
+$window->rect(X11::XCB::Rect->new(x => 1024, y => 0, width => 1024, height => 30));
+sync_with_i3;
+
+@docks = get_dock_clients;
+is(@docks, 1, 'there is still exactly one dock');
+
+is($docks[0]->{rect}->{x}, 1024, 'dock client has moved to the other screen');
+
+exit_gracefully($pid);
+
+###############################################################################
+
+done_testing;
diff --git a/testcases/t/527-focus-fallback.t b/testcases/t/527-focus-fallback.t
new file mode 100644 (file)
index 0000000..5956e67
--- /dev/null
@@ -0,0 +1,45 @@
+#!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: #1378
+use i3test;
+use X11::XCB qw(:all);
+
+sub get_ewmh_window {
+    my $cookie = $x->get_property(
+        0,
+        $x->get_root_window(),
+        $x->atom(name => '_NET_SUPPORTING_WM_CHECK')->id,
+        $x->atom(name => 'WINDOW')->id,
+        0,
+        4096
+    );
+
+    my $reply = $x->get_property_reply($cookie->{sequence});
+    my $len = $reply->{length};
+    return undef if $len == 0;
+
+    return unpack("L", $reply->{value});
+}
+
+my $window = open_window;
+sync_with_i3;
+is($x->input_focus, $window->id, 'sanity check: window has input focus');
+cmd 'kill';
+sync_with_i3;
+is($x->input_focus, get_ewmh_window(), 'focus falls back to the EWMH window');
+
+done_testing;
diff --git a/tests/queue.h b/tests/queue.h
deleted file mode 100644 (file)
index 0b3a9c0..0000000
+++ /dev/null
@@ -1,543 +0,0 @@
-/*     $OpenBSD: queue.h,v 1.1 2007/10/26 03:14:08 niallo Exp $        */
-/*     $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $       */
-
-/*
- * Copyright (c) 1991, 1993
- *     The Regents of the University of California.  All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the University nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- *
- *     @(#)queue.h     8.5 (Berkeley) 8/20/94
- */
-
-#pragma once
-
-/*
- * This file defines five types of data structures: singly-linked lists,
- * lists, simple queues, tail queues, and circular queues.
- *
- *
- * A singly-linked list is headed by a single forward pointer. The elements
- * are singly linked for minimum space and pointer manipulation overhead at
- * the expense of O(n) removal for arbitrary elements. New elements can be
- * added to the list after an existing element or at the head of the list.
- * Elements being removed from the head of the list should use the explicit
- * macro for this purpose for optimum efficiency. A singly-linked list may
- * only be traversed in the forward direction.  Singly-linked lists are ideal
- * for applications with large datasets and few or no removals or for
- * implementing a LIFO queue.
- *
- * A list is headed by a single forward pointer (or an array of forward
- * pointers for a hash table header). The elements are doubly linked
- * so that an arbitrary element can be removed without a need to
- * traverse the list. New elements can be added to the list before
- * or after an existing element or at the head of the list. A list
- * may only be traversed in the forward direction.
- *
- * A simple queue is headed by a pair of pointers, one the head of the
- * list and the other to the tail of the list. The elements are singly
- * linked to save space, so elements can only be removed from the
- * head of the list. New elements can be added to the list before or after
- * an existing element, at the head of the list, or at the end of the
- * list. A simple queue may only be traversed in the forward direction.
- *
- * A tail queue is headed by a pair of pointers, one to the head of the
- * list and the other to the tail of the list. The elements are doubly
- * linked so that an arbitrary element can be removed without a need to
- * traverse the list. New elements can be added to the list before or
- * after an existing element, at the head of the list, or at the end of
- * the list. A tail queue may be traversed in either direction.
- *
- * A circle queue is headed by a pair of pointers, one to the head of the
- * list and the other to the tail of the list. The elements are doubly
- * linked so that an arbitrary element can be removed without a need to
- * traverse the list. New elements can be added to the list before or after
- * an existing element, at the head of the list, or at the end of the list.
- * A circle queue may be traversed in either direction, but has a more
- * complex end of list detection.
- *
- * For details on the use of these macros, see the queue(3) manual page.
- */
-
-#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC))
-#define _Q_INVALIDATE(a) (a) = ((void *)-1)
-#else
-#define _Q_INVALIDATE(a)
-#endif
-
-/*
- * Singly-linked List definitions.
- */
-#define SLIST_HEAD(name, type)                      \
-    struct name {                                   \
-        struct type *slh_first; /* first element */ \
-    }
-
-#define SLIST_HEAD_INITIALIZER(head) \
-    { NULL }
-
-#define SLIST_ENTRY(type)                         \
-    struct {                                      \
-        struct type *sle_next; /* next element */ \
-    }
-
-/*
- * Singly-linked List access methods.
- */
-#define SLIST_FIRST(head) ((head)->slh_first)
-#define SLIST_END(head) NULL
-#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head))
-#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
-
-#define SLIST_FOREACH(var, head, field) \
-    for ((var) = SLIST_FIRST(head);     \
-         (var) != SLIST_END(head);      \
-         (var) = SLIST_NEXT(var, field))
-
-#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \
-    for ((varp) = &SLIST_FIRST((head));               \
-         ((var) = *(varp)) != SLIST_END(head);        \
-         (varp) = &SLIST_NEXT((var), field))
-
-/*
- * Singly-linked List functions.
- */
-#define SLIST_INIT(head)                     \
-    {                                        \
-        SLIST_FIRST(head) = SLIST_END(head); \
-    }
-
-#define SLIST_INSERT_AFTER(slistelm, elm, field)            \
-    do {                                                    \
-        (elm)->field.sle_next = (slistelm)->field.sle_next; \
-        (slistelm)->field.sle_next = (elm);                 \
-    } while (0)
-
-#define SLIST_INSERT_HEAD(head, elm, field)        \
-    do {                                           \
-        (elm)->field.sle_next = (head)->slh_first; \
-        (head)->slh_first = (elm);                 \
-    } while (0)
-
-#define SLIST_REMOVE_NEXT(head, elm, field)                            \
-    do {                                                               \
-        (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \
-    } while (0)
-
-#define SLIST_REMOVE_HEAD(head, field)                         \
-    do {                                                       \
-        (head)->slh_first = (head)->slh_first->field.sle_next; \
-    } while (0)
-
-#define SLIST_REMOVE(head, elm, type, field)                                 \
-    do {                                                                     \
-        if ((head)->slh_first == (elm)) {                                    \
-            SLIST_REMOVE_HEAD((head), field);                                \
-        } else {                                                             \
-            struct type *curelm = (head)->slh_first;                         \
-                                                                             \
-            while (curelm->field.sle_next != (elm))                          \
-                curelm = curelm->field.sle_next;                             \
-            curelm->field.sle_next = curelm->field.sle_next->field.sle_next; \
-            _Q_INVALIDATE((elm)->field.sle_next);                            \
-        }                                                                    \
-    } while (0)
-
-/*
- * List definitions.
- */
-#define LIST_HEAD(name, type)                      \
-    struct name {                                  \
-        struct type *lh_first; /* first element */ \
-    }
-
-#define LIST_HEAD_INITIALIZER(head) \
-    { NULL }
-
-#define LIST_ENTRY(type)                                              \
-    struct {                                                          \
-        struct type *le_next;  /* next element */                     \
-        struct type **le_prev; /* address of previous next element */ \
-    }
-
-/*
- * List access methods
- */
-#define LIST_FIRST(head) ((head)->lh_first)
-#define LIST_END(head) NULL
-#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head))
-#define LIST_NEXT(elm, field) ((elm)->field.le_next)
-
-#define LIST_FOREACH(var, head, field) \
-    for ((var) = LIST_FIRST(head);     \
-         (var) != LIST_END(head);      \
-         (var) = LIST_NEXT(var, field))
-
-/*
- * List functions.
- */
-#define LIST_INIT(head)                    \
-    do {                                   \
-        LIST_FIRST(head) = LIST_END(head); \
-    } while (0)
-
-#define LIST_INSERT_AFTER(listelm, elm, field)                               \
-    do {                                                                     \
-        if (((elm)->field.le_next = (listelm)->field.le_next) != NULL)       \
-            (listelm)->field.le_next->field.le_prev = &(elm)->field.le_next; \
-        (listelm)->field.le_next = (elm);                                    \
-        (elm)->field.le_prev = &(listelm)->field.le_next;                    \
-    } while (0)
-
-#define LIST_INSERT_BEFORE(listelm, elm, field)           \
-    do {                                                  \
-        (elm)->field.le_prev = (listelm)->field.le_prev;  \
-        (elm)->field.le_next = (listelm);                 \
-        *(listelm)->field.le_prev = (elm);                \
-        (listelm)->field.le_prev = &(elm)->field.le_next; \
-    } while (0)
-
-#define LIST_INSERT_HEAD(head, elm, field)                           \
-    do {                                                             \
-        if (((elm)->field.le_next = (head)->lh_first) != NULL)       \
-            (head)->lh_first->field.le_prev = &(elm)->field.le_next; \
-        (head)->lh_first = (elm);                                    \
-        (elm)->field.le_prev = &(head)->lh_first;                    \
-    } while (0)
-
-#define LIST_REMOVE(elm, field)                                         \
-    do {                                                                \
-        if ((elm)->field.le_next != NULL)                               \
-            (elm)->field.le_next->field.le_prev = (elm)->field.le_prev; \
-        *(elm)->field.le_prev = (elm)->field.le_next;                   \
-        _Q_INVALIDATE((elm)->field.le_prev);                            \
-        _Q_INVALIDATE((elm)->field.le_next);                            \
-    } while (0)
-
-#define LIST_REPLACE(elm, elm2, field)                                     \
-    do {                                                                   \
-        if (((elm2)->field.le_next = (elm)->field.le_next) != NULL)        \
-            (elm2)->field.le_next->field.le_prev = &(elm2)->field.le_next; \
-        (elm2)->field.le_prev = (elm)->field.le_prev;                      \
-        *(elm2)->field.le_prev = (elm2);                                   \
-        _Q_INVALIDATE((elm)->field.le_prev);                               \
-        _Q_INVALIDATE((elm)->field.le_next);                               \
-    } while (0)
-
-/*
- * Simple queue definitions.
- */
-#define SIMPLEQ_HEAD(name, type)                                \
-    struct name {                                               \
-        struct type *sqh_first; /* first element */             \
-        struct type **sqh_last; /* addr of last next element */ \
-    }
-
-#define SIMPLEQ_HEAD_INITIALIZER(head) \
-    { NULL, &(head).sqh_first }
-
-#define SIMPLEQ_ENTRY(type)                       \
-    struct {                                      \
-        struct type *sqe_next; /* next element */ \
-    }
-
-/*
- * Simple queue access methods.
- */
-#define SIMPLEQ_FIRST(head) ((head)->sqh_first)
-#define SIMPLEQ_END(head) NULL
-#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head))
-#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next)
-
-#define SIMPLEQ_FOREACH(var, head, field) \
-    for ((var) = SIMPLEQ_FIRST(head);     \
-         (var) != SIMPLEQ_END(head);      \
-         (var) = SIMPLEQ_NEXT(var, field))
-
-/*
- * Simple queue functions.
- */
-#define SIMPLEQ_INIT(head)                     \
-    do {                                       \
-        (head)->sqh_first = NULL;              \
-        (head)->sqh_last = &(head)->sqh_first; \
-    } while (0)
-
-#define SIMPLEQ_INSERT_HEAD(head, elm, field)                    \
-    do {                                                         \
-        if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \
-            (head)->sqh_last = &(elm)->field.sqe_next;           \
-        (head)->sqh_first = (elm);                               \
-    } while (0)
-
-#define SIMPLEQ_INSERT_TAIL(head, elm, field)      \
-    do {                                           \
-        (elm)->field.sqe_next = NULL;              \
-        *(head)->sqh_last = (elm);                 \
-        (head)->sqh_last = &(elm)->field.sqe_next; \
-    } while (0)
-
-#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field)                  \
-    do {                                                                 \
-        if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL) \
-            (head)->sqh_last = &(elm)->field.sqe_next;                   \
-        (listelm)->field.sqe_next = (elm);                               \
-    } while (0)
-
-#define SIMPLEQ_REMOVE_HEAD(head, field)                                     \
-    do {                                                                     \
-        if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \
-            (head)->sqh_last = &(head)->sqh_first;                           \
-    } while (0)
-
-/*
- * Tail queue definitions.
- */
-#define TAILQ_HEAD(name, type)                                  \
-    struct name {                                               \
-        struct type *tqh_first; /* first element */             \
-        struct type **tqh_last; /* addr of last next element */ \
-    }
-
-#define TAILQ_HEAD_INITIALIZER(head) \
-    { NULL, &(head).tqh_first }
-
-#define TAILQ_ENTRY(type)                                              \
-    struct {                                                           \
-        struct type *tqe_next;  /* next element */                     \
-        struct type **tqe_prev; /* address of previous next element */ \
-    }
-
-/*
- * tail queue access methods
- */
-#define TAILQ_FIRST(head) ((head)->tqh_first)
-#define TAILQ_END(head) NULL
-#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
-#define TAILQ_LAST(head, headname) \
-    (*(((struct headname *)((head)->tqh_last))->tqh_last))
-/* XXX */
-#define TAILQ_PREV(elm, headname, field) \
-    (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
-#define TAILQ_EMPTY(head) \
-    (TAILQ_FIRST(head) == TAILQ_END(head))
-
-#define TAILQ_FOREACH(var, head, field) \
-    for ((var) = TAILQ_FIRST(head);     \
-         (var) != TAILQ_END(head);      \
-         (var) = TAILQ_NEXT(var, field))
-
-#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
-    for ((var) = TAILQ_LAST(head, headname);              \
-         (var) != TAILQ_END(head);                        \
-         (var) = TAILQ_PREV(var, headname, field))
-
-/*
- * Tail queue functions.
- */
-#define TAILQ_INIT(head)                       \
-    do {                                       \
-        (head)->tqh_first = NULL;              \
-        (head)->tqh_last = &(head)->tqh_first; \
-    } while (0)
-
-#define TAILQ_INSERT_HEAD(head, elm, field)                             \
-    do {                                                                \
-        if (((elm)->field.tqe_next = (head)->tqh_first) != NULL)        \
-            (head)->tqh_first->field.tqe_prev = &(elm)->field.tqe_next; \
-        else                                                            \
-            (head)->tqh_last = &(elm)->field.tqe_next;                  \
-        (head)->tqh_first = (elm);                                      \
-        (elm)->field.tqe_prev = &(head)->tqh_first;                     \
-    } while (0)
-
-#define TAILQ_INSERT_TAIL(head, elm, field)        \
-    do {                                           \
-        (elm)->field.tqe_next = NULL;              \
-        (elm)->field.tqe_prev = (head)->tqh_last;  \
-        *(head)->tqh_last = (elm);                 \
-        (head)->tqh_last = &(elm)->field.tqe_next; \
-    } while (0)
-
-#define TAILQ_INSERT_AFTER(head, listelm, elm, field)                       \
-    do {                                                                    \
-        if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)    \
-            (elm)->field.tqe_next->field.tqe_prev = &(elm)->field.tqe_next; \
-        else                                                                \
-            (head)->tqh_last = &(elm)->field.tqe_next;                      \
-        (listelm)->field.tqe_next = (elm);                                  \
-        (elm)->field.tqe_prev = &(listelm)->field.tqe_next;                 \
-    } while (0)
-
-#define TAILQ_INSERT_BEFORE(listelm, elm, field)            \
-    do {                                                    \
-        (elm)->field.tqe_prev = (listelm)->field.tqe_prev;  \
-        (elm)->field.tqe_next = (listelm);                  \
-        *(listelm)->field.tqe_prev = (elm);                 \
-        (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \
-    } while (0)
-
-#define TAILQ_REMOVE(head, elm, field)                                     \
-    do {                                                                   \
-        if (((elm)->field.tqe_next) != NULL)                               \
-            (elm)->field.tqe_next->field.tqe_prev = (elm)->field.tqe_prev; \
-        else                                                               \
-            (head)->tqh_last = (elm)->field.tqe_prev;                      \
-        *(elm)->field.tqe_prev = (elm)->field.tqe_next;                    \
-        _Q_INVALIDATE((elm)->field.tqe_prev);                              \
-        _Q_INVALIDATE((elm)->field.tqe_next);                              \
-    } while (0)
-
-#define TAILQ_REPLACE(head, elm, elm2, field)                                 \
-    do {                                                                      \
-        if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL)         \
-            (elm2)->field.tqe_next->field.tqe_prev = &(elm2)->field.tqe_next; \
-        else                                                                  \
-            (head)->tqh_last = &(elm2)->field.tqe_next;                       \
-        (elm2)->field.tqe_prev = (elm)->field.tqe_prev;                       \
-        *(elm2)->field.tqe_prev = (elm2);                                     \
-        _Q_INVALIDATE((elm)->field.tqe_prev);                                 \
-        _Q_INVALIDATE((elm)->field.tqe_next);                                 \
-    } while (0)
-
-/*
- * Circular queue definitions.
- */
-#define CIRCLEQ_HEAD(name, type)                    \
-    struct name {                                   \
-        struct type *cqh_first; /* first element */ \
-        struct type *cqh_last;  /* last element */  \
-    }
-
-#define CIRCLEQ_HEAD_INITIALIZER(head) \
-    { CIRCLEQ_END(&head), CIRCLEQ_END(&head) }
-
-#define CIRCLEQ_ENTRY(type)                           \
-    struct {                                          \
-        struct type *cqe_next; /* next element */     \
-        struct type *cqe_prev; /* previous element */ \
-    }
-
-/*
- * Circular queue access methods
- */
-#define CIRCLEQ_FIRST(head) ((head)->cqh_first)
-#define CIRCLEQ_LAST(head) ((head)->cqh_last)
-#define CIRCLEQ_END(head) ((void *)(head))
-#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next)
-#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev)
-#define CIRCLEQ_EMPTY(head) \
-    (CIRCLEQ_FIRST(head) == CIRCLEQ_END(head))
-
-#define CIRCLEQ_FOREACH(var, head, field) \
-    for ((var) = CIRCLEQ_FIRST(head);     \
-         (var) != CIRCLEQ_END(head);      \
-         (var) = CIRCLEQ_NEXT(var, field))
-
-#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \
-    for ((var) = CIRCLEQ_LAST(head);              \
-         (var) != CIRCLEQ_END(head);              \
-         (var) = CIRCLEQ_PREV(var, field))
-
-/*
- * Circular queue functions.
- */
-#define CIRCLEQ_INIT(head)                     \
-    do {                                       \
-        (head)->cqh_first = CIRCLEQ_END(head); \
-        (head)->cqh_last = CIRCLEQ_END(head);  \
-    } while (0)
-
-#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field)        \
-    do {                                                       \
-        (elm)->field.cqe_next = (listelm)->field.cqe_next;     \
-        (elm)->field.cqe_prev = (listelm);                     \
-        if ((listelm)->field.cqe_next == CIRCLEQ_END(head))    \
-            (head)->cqh_last = (elm);                          \
-        else                                                   \
-            (listelm)->field.cqe_next->field.cqe_prev = (elm); \
-        (listelm)->field.cqe_next = (elm);                     \
-    } while (0)
-
-#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field)       \
-    do {                                                       \
-        (elm)->field.cqe_next = (listelm);                     \
-        (elm)->field.cqe_prev = (listelm)->field.cqe_prev;     \
-        if ((listelm)->field.cqe_prev == CIRCLEQ_END(head))    \
-            (head)->cqh_first = (elm);                         \
-        else                                                   \
-            (listelm)->field.cqe_prev->field.cqe_next = (elm); \
-        (listelm)->field.cqe_prev = (elm);                     \
-    } while (0)
-
-#define CIRCLEQ_INSERT_HEAD(head, elm, field)          \
-    do {                                               \
-        (elm)->field.cqe_next = (head)->cqh_first;     \
-        (elm)->field.cqe_prev = CIRCLEQ_END(head);     \
-        if ((head)->cqh_last == CIRCLEQ_END(head))     \
-            (head)->cqh_last = (elm);                  \
-        else                                           \
-            (head)->cqh_first->field.cqe_prev = (elm); \
-        (head)->cqh_first = (elm);                     \
-    } while (0)
-
-#define CIRCLEQ_INSERT_TAIL(head, elm, field)         \
-    do {                                              \
-        (elm)->field.cqe_next = CIRCLEQ_END(head);    \
-        (elm)->field.cqe_prev = (head)->cqh_last;     \
-        if ((head)->cqh_first == CIRCLEQ_END(head))   \
-            (head)->cqh_first = (elm);                \
-        else                                          \
-            (head)->cqh_last->field.cqe_next = (elm); \
-        (head)->cqh_last = (elm);                     \
-    } while (0)
-
-#define CIRCLEQ_REMOVE(head, elm, field)                                   \
-    do {                                                                   \
-        if ((elm)->field.cqe_next == CIRCLEQ_END(head))                    \
-            (head)->cqh_last = (elm)->field.cqe_prev;                      \
-        else                                                               \
-            (elm)->field.cqe_next->field.cqe_prev = (elm)->field.cqe_prev; \
-        if ((elm)->field.cqe_prev == CIRCLEQ_END(head))                    \
-            (head)->cqh_first = (elm)->field.cqe_next;                     \
-        else                                                               \
-            (elm)->field.cqe_prev->field.cqe_next = (elm)->field.cqe_next; \
-        _Q_INVALIDATE((elm)->field.cqe_prev);                              \
-        _Q_INVALIDATE((elm)->field.cqe_next);                              \
-    } while (0)
-
-#define CIRCLEQ_REPLACE(head, elm, elm2, field)                                    \
-    do {                                                                           \
-        if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == CIRCLEQ_END(head)) \
-            (head)->cqh_last = (elm2);                                             \
-        else                                                                       \
-            (elm2)->field.cqe_next->field.cqe_prev = (elm2);                       \
-        if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == CIRCLEQ_END(head)) \
-            (head)->cqh_first = (elm2);                                            \
-        else                                                                       \
-            (elm2)->field.cqe_prev->field.cqe_next = (elm2);                       \
-        _Q_INVALIDATE((elm)->field.cqe_prev);                                      \
-        _Q_INVALIDATE((elm)->field.cqe_next);                                      \
-    } while (0)
diff --git a/tests/swap.c b/tests/swap.c
deleted file mode 100644 (file)
index abc3b3b..0000000
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * vim:ts=4:sw=4:expandtab
- */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <stdbool.h>
-#include <stdint.h>
-
-#include "queue.h"
-
-struct obj {
-    int abc;
-    TAILQ_ENTRY(obj) entry;
-};
-
-TAILQ_HEAD(objhead, obj) head;
-
-void dump() {
-    struct obj *e;
-    printf("dump:\n");
-    e = TAILQ_FIRST(&head);
-    printf("first: %d\n", e->abc);
-    e = TAILQ_LAST(&head, objhead);
-    printf("last: %d\n", e->abc);
-    TAILQ_FOREACH(e, &head, entry) {
-        printf("  %d\n", e->abc);
-    }
-    printf("again, but reverse:\n");
-    TAILQ_FOREACH_REVERSE(e, &head, objhead, entry) {
-        printf("  %d\n", e->abc);
-    }
-    printf("done\n\n");
-}
-
-#define TAILQ_SWAP(first, second, head, field)                                     \
-    do {                                                                           \
-        *((first)->field.tqe_prev) = (second);                                     \
-        (second)->field.tqe_prev = (first)->field.tqe_prev;                        \
-        (first)->field.tqe_prev = &((second)->field.tqe_next);                     \
-        (first)->field.tqe_next = (second)->field.tqe_next;                        \
-        if ((second)->field.tqe_next)                                              \
-            (second)->field.tqe_next->field.tqe_prev = &((first)->field.tqe_next); \
-        (second)->field.tqe_next = first;                                          \
-        if ((head)->tqh_last == &((second)->field.tqe_next))                       \
-            (head)->tqh_last = &((first)->field.tqe_next);                         \
-    } while (0)
-
-void _TAILQ_SWAP(struct obj *first, struct obj *second, struct objhead *head) {
-    struct obj **tqe_prev = first->entry.tqe_prev;
-    *tqe_prev = second;
-
-    second->entry.tqe_prev = first->entry.tqe_prev;
-
-    first->entry.tqe_prev = &(second->entry.tqe_next);
-
-    first->entry.tqe_next = second->entry.tqe_next;
-
-    if (second->entry.tqe_next) {
-        struct obj *tqe_next = second->entry.tqe_next;
-        tqe_next->entry.tqe_prev = &(first->entry.tqe_next);
-    }
-
-    second->entry.tqe_next = first;
-
-    if (head->tqh_last == &(second->entry.tqe_next))
-        head->tqh_last = &(first->entry.tqe_next);
-}
-
-int main() {
-    printf("hello\n");
-
-    TAILQ_INIT(&head);
-
-    struct obj first;
-    first.abc = 123;
-
-    struct obj second;
-    second.abc = 456;
-
-    struct obj third;
-    third.abc = 789;
-
-    struct obj fourth;
-    fourth.abc = 999;
-
-    struct obj fifth;
-    fifth.abc = 5555;
-
-    /*
-     * ************************************************
-     */
-    printf("swapping first two elements:\n");
-
-    TAILQ_INSERT_TAIL(&head, &first, entry);
-    TAILQ_INSERT_TAIL(&head, &second, entry);
-    TAILQ_INSERT_TAIL(&head, &third, entry);
-
-    dump();
-
-    TAILQ_SWAP(&first, &second, &head, entry);
-
-    dump();
-
-    /*
-     * ************************************************
-     */
-    printf("swapping last two elements:\n");
-
-    TAILQ_INIT(&head);
-
-    TAILQ_INSERT_TAIL(&head, &first, entry);
-    TAILQ_INSERT_TAIL(&head, &second, entry);
-    TAILQ_INSERT_TAIL(&head, &third, entry);
-
-    dump();
-
-    TAILQ_SWAP(&second, &third, &head, entry);
-
-    dump();
-
-    /*
-     * ************************************************
-     */
-    printf("longer list:\n");
-
-    TAILQ_INIT(&head);
-
-    TAILQ_INSERT_TAIL(&head, &first, entry);
-    TAILQ_INSERT_TAIL(&head, &second, entry);
-    TAILQ_INSERT_TAIL(&head, &third, entry);
-    TAILQ_INSERT_TAIL(&head, &fourth, entry);
-
-    dump();
-
-    TAILQ_SWAP(&first, &second, &head, entry);
-
-    dump();
-
-    /*
-     * ************************************************
-     */
-    printf("longer list 2:\n");
-
-    TAILQ_INIT(&head);
-
-    TAILQ_INSERT_TAIL(&head, &first, entry);
-    TAILQ_INSERT_TAIL(&head, &second, entry);
-    TAILQ_INSERT_TAIL(&head, &third, entry);
-    TAILQ_INSERT_TAIL(&head, &fourth, entry);
-
-    dump();
-
-    TAILQ_SWAP(&second, &third, &head, entry);
-
-    dump();
-
-    /*
-     * ************************************************
-     */
-    printf("longer list, swap, then insert:\n");
-
-    TAILQ_INIT(&head);
-
-    TAILQ_INSERT_TAIL(&head, &first, entry);
-    TAILQ_INSERT_TAIL(&head, &second, entry);
-    TAILQ_INSERT_TAIL(&head, &third, entry);
-    TAILQ_INSERT_TAIL(&head, &fourth, entry);
-
-    dump();
-
-    TAILQ_SWAP(&second, &third, &head, entry);
-
-    dump();
-
-    TAILQ_INSERT_AFTER(&head, &third, &fifth, entry);
-
-    dump();
-
-    /*
-     * ************************************************
-     */
-    printf("longer list, swap, then append:\n");
-
-    TAILQ_INIT(&head);
-
-    TAILQ_INSERT_TAIL(&head, &first, entry);
-    TAILQ_INSERT_TAIL(&head, &second, entry);
-    TAILQ_INSERT_TAIL(&head, &third, entry);
-    TAILQ_INSERT_TAIL(&head, &fourth, entry);
-
-    dump();
-
-    TAILQ_SWAP(&second, &third, &head, entry);
-
-    dump();
-
-    TAILQ_INSERT_TAIL(&head, &fifth, entry);
-
-    dump();
-
-    /*
-     * ************************************************
-     */
-    printf("longer list, swap, then remove:\n");
-
-    TAILQ_INIT(&head);
-
-    TAILQ_INSERT_TAIL(&head, &first, entry);
-    TAILQ_INSERT_TAIL(&head, &second, entry);
-    TAILQ_INSERT_TAIL(&head, &third, entry);
-    TAILQ_INSERT_TAIL(&head, &fourth, entry);
-
-    dump();
-
-    TAILQ_SWAP(&second, &third, &head, entry);
-
-    dump();
-
-    TAILQ_REMOVE(&head, &second, entry);
-
-    dump();
-}
diff --git a/tests/test_table.c b/tests/test_table.c
deleted file mode 100644 (file)
index 554be3e..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-#include <stdio.h>
-#include "table.h"
-
-void print_table() {
-    int r, c;
-    printf("printing table...\n");
-    for (c = 0; c < table_dims.x; c++)
-        for (r = 0; r < table_dims.y; r++)
-            printf("table[%d][%d] = %p\n", c, r, table[c][r]);
-    printf("done\n");
-}
-
-int main() {
-    printf("table.c tests\n");
-    printf("table_dimensions = %d, %d\n", table_dims.x, table_dims.y);
-    init_table();
-    printf("table_dimensions = %d, %d\n", table_dims.x, table_dims.y);
-    print_table();
-
-    printf("expand_table_cols()\n");
-    expand_table_cols();
-    printf("table_dimensions = %d, %d\n", table_dims.x, table_dims.y);
-    print_table();
-
-    printf("expand_table_rows()\n");
-    expand_table_rows();
-    printf("table_dimensions = %d, %d\n", table_dims.x, table_dims.y);
-    print_table();
-}