diff --git a/.clang-format b/.clang-format index 57948c31..1d840132 100644 --- a/.clang-format +++ b/.clang-format @@ -7,5 +7,4 @@ AlwaysBreakBeforeMultilineStrings: false IndentWidth: 4 PointerBindsToType: false ColumnLimit: 0 -ForEachMacros: [ TAILQ_FOREACH, TAILQ_FOREACH_REVERSE, SLIST_FOREACH, CIRCLEQ_FOREACH, CIRCLEQ_FOREACH_REVERSE, NODES_FOREACH, NODES_FOREACH_REVERSE ] SpaceBeforeParens: ControlStatements diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..7a5e0c5f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,20 @@ +# i3status/i3lock bugreports/feature requests + +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 + +# i3 bugreports/feature requests + +1. Read 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). + +# 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 + “feature request” or “enhancement” 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 diff --git a/DEPENDS b/DEPENDS index 8a9f9dca..d5dae142 100644 --- a/DEPENDS +++ b/DEPENDS @@ -4,25 +4,27 @@ "min" means minimum required version "lkgv" means last known good version -┌─────────────┬────────┬────────┬────────────────────────────────────────┐ -│ dependency │ min. │ lkgv │ URL │ -├─────────────┼────────┼────────┼────────────────────────────────────────┤ -│ pkg-config │ 0.25 │ 0.26 │ http://pkgconfig.freedesktop.org/ │ -│ libxcb │ 1.1.93 │ 1.7 │ http://xcb.freedesktop.org/dist/ │ -│ xcb-util │ 0.3.3 │ 0.3.8 │ http://xcb.freedesktop.org/dist/ │ -│ 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/ │ -│ 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/ │ -│ 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/ │ -└─────────────┴────────┴────────┴────────────────────────────────────────┘ +┌──────────────┬────────┬────────┬────────────────────────────────────────┐ +│ 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/ │ +│ 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/ │ +│ 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/ │ +│ libsn¹ │ 0.10 │ 0.12 │ http://freedesktop.org/wiki/Software/startup-notification +│ pango │ 1.30.0 | 1.30.0 │ http://www.pango.org/ │ +│ cairo │ 1.12.2 │ 1.12.2 │ http://cairographics.org/ │ +└──────────────┴────────┴────────┴────────────────────────────────────────┘ ¹ libsn = libstartup-notification ² Pod::Simple is a Perl module required for converting the testsuite documentation to HTML. See http://michael.stapelberg.de/cpan/#Pod::Simple @@ -35,6 +37,6 @@ i3-migrate-config-to-v4 and i3-dmenu-desktop are implemented in Perl, but have no dependencies besides Perl 5.10. - i3-save-tree is also implemented in Perl and needs AnyEvent::I3 and JSON::XS. - While i3-save-tree is not required for running i3 itself, it is strongly - recommended to provide it in distribution packages. + i3-save-tree is also implemented in Perl and needs AnyEvent::I3 (>= 0.12) and + JSON::XS. While i3-save-tree is not required for running i3 itself, it is + strongly recommended to provide it in distribution packages. diff --git a/RELEASE-NOTES-4.8 b/RELEASE-NOTES-4.8 deleted file mode 100644 index f0f5923f..00000000 --- a/RELEASE-NOTES-4.8 +++ /dev/null @@ -1,138 +0,0 @@ - - ┌──────────────────────────────┐ - │ Release notes for i3 v4.8 │ - └──────────────────────────────┘ - -This is i3 v4.8. This version is considered stable. All users of i3 are -strongly encouraged to upgrade. - -The biggest new feature certainly is layout saving/restoring. See -http://i3wm.org/docs/layout-saving.html for more details. tl;dr: export your -current layout as JSON file, load it into new i3 sessions, get placeholder -windows that will be replaced by the actual apps once you start them. - -Also very important for owners of HiDPI/“retina” displays is that i3 will now -respect your configured DPI and scale up its UI elements accordingly. Use -“xrandr --dpi 184” to set your dpi to 184, in case your setup does not figure -it out automatically. To get properly scaling fonts, we also changed the -default font from a bitmap font to a pango font (“DejaVu Sans Mono 8”). - -Multiple changes improve the compatibility of i3 with other software, e.g. -java-based software (focus handling, once again) or external pagers (we now -provide _NET_CLIENT_LIST and let pager applications change workspaces). - -For packagers, another change is that yajl ≥ 2.0 is now required for compiling -i3. This should not be a problem for anyone, as that version is pretty old by -now. - -For contributors, note that we have starting formatting the source code with -clang-format-3.5. This means that there will no longer be a need to argue about -coding style when discussing patches :). - - ┌────────────────────────────┐ - │ Changes in v4.8 │ - └────────────────────────────┘ - - • docs/ipc: reformat/update list of ipc libraries - • docs/ipc: fix current_workspace outputs reply member - • docs/ipc: update ipc COMMAND reply docs - • docs/userguide: fix multiple typos - • docs/debugging: use bzip2 - • docs/debugging: explain how to enable logging on the fly - • docs/debugging: merge the debug symbols/backtrace section - • docs/debugging: recommend i3 --moreversion - • man/i3-nagbar.man: update manpage to document all options - • i3bar: Amend status line error 127 message - • i3bar: don’t kill watcher on EOF, leads to better error messages - • i3bar: send mouse wheel events to child too - • i3bar: do click handling and tray padding retina-correctly - • i3bar: render separators render-correctly - • i3bar: reinit colors on barconfig update - • i3bar: Don't start child unless status_command - • i3bar: implement custom workspace numbers config - • resize floating windows when right-clicking the decoration - • enable shmlog when invoked as i3-with-shmlog - • Disable pointer warps when focus_follows_mouse is disabled - • Movement into a branch considers movement direction - • set ewmh desktop properties on startup - • handle ButtonPress events with child != XCB_NONE - • implement layout restoring - • only LOG() the DPI when it changes, DLOG() it otherwise - • send IPC window events for focus and title changes - • these types of windows are now floating by default: - dialog, utility, toolbar and splash windows, modal windows, windows with an - equal minimum and maximum size - • send last event timestamp with WM_TAKE_FOCUS message - • maintain the _NET_CLIENT_LIST property - • don’t set input focus _and_ send WM_TAKE_FOCUS - • respect CFLAGS in linking command - • fix parallel make - • reset SIGPIPE handler before executing a command - • render default window border width retina-correctly - • draw workspace buttons and padded text blocks retina-correctly - • render resize windows retina-correctly - • delegate click handling to dock clients - • send complete config on barconfig_update - • implement the window::fullscreen_mode ipc event - • make all workspaces starting with "__" internal - • improve error messages for i3-internal workspace names - • allow _NET_ACTIVE_WINDOW requests to switch workspaces if they indicate - that they are a pager (following the spec) - • workspace assignments by number - • add configuration option for disabling mouse warping - • set _NET_ACTIVE_WINDOW to None when none has focus - • set X-LightDM-DesktopName in i3.xsession.desktop to fix autostart on Ubuntu - • don’t ELOG ipc EOF - • replace all printf()s with D?LOG - • delete ipc socket when exiting, cleanup tmpdir - • default config: switch to DejaVu Sans Mono 8 as default font - • cleanup tmpdir when restarting and not using XDG_RUNTIME_DIR - • Snap pointer to resize bar on drag resize - • Size resizebar according to container size - • Fix clang -Wextra except -Wunused-parameter - • Respect Motif hint for window decorations - - ┌────────────────────────────┐ - │ Bugfixes │ - └────────────────────────────┘ - - • create con pixmaps when not needed - • i3bar: fix resource leak: statusline_ctx needs to be freed first - • tree_split should not split floating cons - • fix memory leak with ipc_receive_message - • fix invalid reads by setting con->window to NULL in tree_close - • fix memory leak when closing windows - • fix memory leak when matching window by criteria - • fix memory leak when matching window by con_id - • ignore dock clients in the resize command - • clear wm_size_hints if they are not set - • resize window check should check for NULL - • fix window event crash with no window - • i3-dmenu-desktop: also quote the %c field code - • new_window and new_float can now be used simultaneously with different - border widths - • fix crash when using multiple for_window statements that move windows - • Set input focus with last timestamp - • handle windows whose WM_TRANSIENT_FOR points to themselve - • don’t overwrite the original size of floating windows when changing border - • don’t errnously render floating fullscreen windows during restart - • ensure floating windows don’t drop out of fullscreen when restarting - • don’t overwrite the window’s geometry after restartingnext - • i3bar: Set `mapped` flag on trayclient creation - • i3bar: don't show "EOF" status line error - - ┌────────────────────────────┐ - │ Thanks! │ - └────────────────────────────┘ - -Thanks for testing, bugfixes, discussions and everything I forgot go out to: - -Aleksi Blinnikka, Alexander Berntsen, Alexander Kedrik, Antonio, Arun -Persaud, Atte Peltomaki, bo, Campbell Barton, chris, David Coppa, eeemsi, -Holger Langenau, Jean-Philippe Ouellet, Jens, jeroentbt, Jonas Maaskola, -Julian Ospald, Kernc, Koston, lasers, lkraav, Marcin, Marco Hunsicker, -Marcus Crestani, Matthias Thubauville, Maxime, Michael Stapelberg, Peter -Boström, Petr Písař, Quentin Glidic, Steve Jones, TonyC, Tony Crisci, -Vivien Didelot, Wieland Hoffmann, x33a, xeen - --- Michael Stapelberg, 2014-06-15 diff --git a/RELEASE-NOTES-4.9 b/RELEASE-NOTES-4.9 new file mode 100644 index 00000000..9012646a --- /dev/null +++ b/RELEASE-NOTES-4.9 @@ -0,0 +1,124 @@ + + ┌──────────────────────────────┐ + │ Release notes for i3 v4.9 │ + └──────────────────────────────┘ + +This is i3 v4.9. This version is considered stable. All users of i3 are +strongly encouraged to upgrade. + +Notable new features include mouse button bindings and improved EWMH +compatibility, meaning more external pager programs work with i3 now. + +Aside from that, this release contains plenty of bugfixes and little +enhancements. + +The new dependency on libxkbcommon ≥ 0.4.0 is notable for distribution +packages. This dependency allowed us to drop our last direct dependency +on Xlib :). + +It’s also worth mentioning that all i3 repositories are now on GitHub, see +http://thread.gmane.org/gmane.comp.window-managers.i3.general/1666 for the +announcement. + + ┌────────────────────────────┐ + │ Changes in v4.9 │ + └────────────────────────────┘ + + • docs/ipc: use an actual event type + • docs/debugging: use logs.i3wm.org + • docs/testsuite: add hint to use xvfb-run + • testcases: use Xephyr instead of XDummy + • i3-sensible-*: use command -v (built-in) instead of which(1) + • i3.xsession.desktop: set DesktopNames (which gdm uses) + • i3-save-tree: interpret commandline parameters as utf-8 + • i3-save-tree: add 'mark' as allowed key to i3-save-tree output + • i3bar-protocol: ensure align = left is the default + • i3bar: implement custom mouse wheel commands + • i3bar: improve error message when a full_text property is missing + • i3bar: respect the urgency flag on status blocks + • i3bar: inset the urgent background of a status block for consistency with + workspace buttons + • i3bar: suspend the child when bars are fully obscured + • i3bar: use Pango markup + • ipc: implement the window::close event + • ipc: implement the window::move event + • ipc: implement the window::floating event + • ipc: implement the window::urgent event + • ipc: set ws reply "num" member to -1 when named + • ipc: add deco_rect property to con in ipc response + • ipc: include workspace con in workspace event + • ewmh: implement property _NET_NUMBER_OF_DESKTOPS + • ewmh: implement property _NET_DESKTOP_VIEWPORT + • ewmh: implement property _NET_DESKTOP_NAMES + • ewmh: handle _NET_CURRENT_DESKTOP requests + • ewmh: handle _NET_CLOSE_WINDOW requests + • ewmh: handle _NET_WM_MOVERESIZE requests + • implement mouse bindings (e.g. bindsym button3 kill) + • add mouse binding --whole-window flag + • add mouse binding --release flag + • switch to xcb-xkb and libxkbcommon, removing our last direct Xlib dep + • make “move [direction]” work with criteria + • make “move to position” work with criteria + • “workspace ” and “move to workspace ” now look for a workspace + starting with number (unless there is a workspace exactly matching that + number). I.e., “workspace 4” will go to a workspace called “4: www” unless + you have a workspace “4” + • “focus ” now focuses floating containers when there are no + tiling containers on the destination output + • take the motif border into account when calculating floating window + geometry + • revert “Disable pointer warps when focus_follows_mouse is disabled” as it + was unexpected by a number of users. Sorry for the back-and-forth + • handle WM_CLASS changes + • raise floating windows on “focus ” + • align lower line of bar decoration to border width + • parse tray_output as a word, not string + • allow to validate the config file without X + • do not resend focus on click, fixes compatibility problems with some wine + or mono apps (e.g. Office 2010) + • don't draw borders wider than actual width + • prevent workspace change during global fullscreen + • extend the fullscreen command (fullscreen [global]) + • fix start_application() doc about which shell is used + + ┌────────────────────────────┐ + │ Bugfixes │ + └────────────────────────────┘ + + • i3-dmenu-desktop: quote path + • i3bar: fix a double free when changing color configuration + • i3bar: render bars after the first chunk of JSON + • i3bar: add a sync call to confirm reparents before exiting (fixes tray + restart issues) + • i3bar: correctly calculate clicks on i3bar status blocks + • i3bar: make click events on status blocks work with 'workspace_buttons no' + • retina support: convert logical to physical pixels for default_border_width + • retina support: treat everything up to 120 dpi as 96 dpi + • don’t set input focus if not accepted (fixes problems with xfce4-notifyd) + • don’t focus unmapped container on manage + • create the directory for storing the restart state + • avoid changing border width when changing containers from tiling to + floating + • layout saving: properly restore workspace containers + • rerender the decoration when the container requires a pixmap and doesn’t + have one + • don’t set focus in con_set_layout() on invisible workspaces + • properly handle windows unsetting WM_TRANSIENT_FOR + • use the command parser to properly extract workspace names + • copy binding before run (fixes reloads) + • revert "Bugfix: Set input focus with last timestamp" + • render floating windows during global fullscreen + • actually parse client.placeholder + + ┌────────────────────────────┐ + │ Thanks! │ + └────────────────────────────┘ + +Thanks for testing, bugfixes, discussions and everything I forgot go out to: + + Alexander Monakov, aszlig, cornerman, dmurph, Mats, dsargrad, hercek, hjem, + Ingo, Ingo Bürk, Janus, javier, jefvel, Lukas K, Marein Konings, Mats, + Michael Stapelberg, Mii, nikolaus, okraits, Peter, smlb, sur5r, Tony Crisci, + val, vals, xeen, Yves-Alexis + +-- Michael Stapelberg, 2015-02-28 diff --git a/common.mk b/common.mk index b086bc85..b9e15a28 100644 --- a/common.mk +++ b/common.mk @@ -92,6 +92,7 @@ else XCB_CFLAGS += $(call cflags_for_lib, xcb-util) XCB_LIBS += $(call ldflags_for_lib, xcb-util) endif +XCB_XKB_LIBS := $(call ldflags_for_lib, xcb-xkb,xcb-xkb) # XCB keyboard stuff XCB_KBD_CFLAGS := $(call cflags_for_lib, xcb-keysyms) @@ -105,9 +106,10 @@ XCB_WM_LIBS := $(call ldflags_for_lib, xcb-icccm,xcb-icccm) XCB_WM_LIBS += $(call ldflags_for_lib, xcb-xinerama,xcb-xinerama) XCB_WM_LIBS += $(call ldflags_for_lib, xcb-randr,xcb-randr) -# Xlib -X11_CFLAGS := $(call cflags_for_lib, x11) -X11_LIBS := $(call ldflags_for_lib, x11,X11) +XKB_COMMON_CFLAGS := $(call cflags_for_lib, xkbcommon,xkbcommon) +XKB_COMMON_LIBS := $(call ldflags_for_lib, xkbcommon,xkbcommon) +XKB_COMMON_X11_CFLAGS := $(call cflags_for_lib, xkbcommon-x11,xkbcommon-x11) +XKB_COMMON_X11_LIBS := $(call ldflags_for_lib, xkbcommon-x11,xkbcommon-x11) # Xcursor XCURSOR_CFLAGS := $(call cflags_for_lib, xcb-cursor) diff --git a/debian/changelog b/debian/changelog index ac79c8bb..02fb0e53 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,23 @@ -i3-wm (4.7.3-1) unstable; urgency=low +i3-wm (4.8.1-1) unstable; urgency=medium * NOT YET RELEASED - -- Michael Stapelberg Thu, 23 Jan 2014 23:11:48 +0100 + -- Michael Stapelberg Sun, 15 Jun 2014 19:37:32 +0200 + +i3-wm (4.8-2) unstable; urgency=medium + + * Backport two bugfixes: + - backport-dpi-fix.patch (Closes: #778460) + - backport-i3bar-tray-fix.patch (Closes: #778461) + + -- Michael Stapelberg Sun, 15 Feb 2015 13:24:42 +0100 + +i3-wm (4.8-1) unstable; urgency=medium + + * New upstream release. + * Bump standards-version to 3.9.5 (no changes necessary) + + -- Michael Stapelberg Sun, 15 Jun 2014 19:15:29 +0200 i3-wm (4.7.2-1) unstable; urgency=low diff --git a/debian/control b/debian/control index 97b11a5e..cc9660f7 100644 --- a/debian/control +++ b/debian/control @@ -10,6 +10,9 @@ Build-Depends: debhelper (>= 7.0.50~), libxcb-randr0-dev, libxcb-icccm4-dev, libxcb-cursor-dev, + libxcb-xkb-dev, + libxkbcommon-dev (>= 0.4.0), + libxkbcommon-x11-dev (>= 0.4.0), asciidoc (>= 8.4.4), xmlto, docbook-xml, @@ -21,7 +24,7 @@ Build-Depends: debhelper (>= 7.0.50~), libcairo2-dev, libpango1.0-dev, libpod-simple-perl -Standards-Version: 3.9.4 +Standards-Version: 3.9.5 Homepage: http://i3wm.org/ Package: i3 @@ -38,7 +41,7 @@ Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, x11-utils Provides: x-window-manager Suggests: rxvt-unicode | x-terminal-emulator -Recommends: xfonts-base, fonts-dejavu-core, libanyevent-i3-perl, libjson-xs-perl +Recommends: xfonts-base, fonts-dejavu-core, libanyevent-i3-perl (>= 0.12), libjson-xs-perl Description: improved dynamic tiling window manager Key features of i3 are good documentation, reasonable defaults (changeable in a simple configuration file) and good multi-monitor support. The user diff --git a/debian/i3-wm.manpages b/debian/i3-wm.manpages index 58569b87..1953b6a8 100644 --- a/debian/i3-wm.manpages +++ b/debian/i3-wm.manpages @@ -9,4 +9,5 @@ man/i3-sensible-pager.1 man/i3-sensible-editor.1 man/i3-sensible-terminal.1 man/i3-dmenu-desktop.1 +man/i3-save-tree.1 man/i3bar.1 diff --git a/debian/patches/manpage-x-terminal-emulator.patch b/debian/patches/manpage-x-terminal-emulator.patch deleted file mode 100644 index 17b8ee4c..00000000 --- a/debian/patches/manpage-x-terminal-emulator.patch +++ /dev/null @@ -1,20 +0,0 @@ -Description: list x-terminal-emulator as one of i3-sensible-terminal’s choices -Author: Michael Stapelberg -Origin: vendor -Forwarded: not-needed -Last-Update: 2011-12-28 - ---- - -Index: i3-4.1.1/man/i3-sensible-terminal.man -=================================================================== ---- i3-4.1.1.orig/man/i3-sensible-terminal.man 2011-12-28 23:56:55.487581000 +0100 -+++ i3-4.1.1/man/i3-sensible-terminal.man 2011-12-28 23:57:06.725802633 +0100 -@@ -22,6 +22,7 @@ - It tries to start one of the following (in that order): - - * $TERMINAL (this is a non-standard variable) -+* x-terminal-emulator - * urxvt - * rxvt - * terminator diff --git a/debian/patches/series b/debian/patches/series deleted file mode 100644 index 08d60ae0..00000000 --- a/debian/patches/series +++ /dev/null @@ -1,2 +0,0 @@ -use-x-terminal-emulator.patch -manpage-x-terminal-emulator.patch diff --git a/debian/patches/use-x-terminal-emulator.patch b/debian/patches/use-x-terminal-emulator.patch deleted file mode 100644 index 96161624..00000000 --- a/debian/patches/use-x-terminal-emulator.patch +++ /dev/null @@ -1,25 +0,0 @@ -Description: i3-sensible-terminal: try x-terminal-emulator first -Author: Michael Stapelberg -Origin: vendor -Forwarded: not-needed -Last-Update: 2012-09-19 - ---- - -Index: i3-4.3/i3-sensible-terminal -=================================================================== ---- i3-4.3.orig/i3-sensible-terminal 2012-09-19 18:08:09.000000000 +0200 -+++ i3-4.3/i3-sensible-terminal 2012-09-19 18:32:06.393883488 +0200 -@@ -4,11 +4,7 @@ - # - # This script tries to exec a terminal emulator by trying some known terminal - # emulators. --# --# Distributions/packagers should enhance this script with a --# distribution-specific mechanism to find the preferred terminal emulator. On --# Debian, there is the x-terminal-emulator symlink for example. --for terminal in $TERMINAL urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal; do -+for terminal in $TERMINAL x-terminal-emulator urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal; do - if which $terminal > /dev/null 2>&1; then - exec $terminal "$@" - fi diff --git a/docs/debugging b/docs/debugging index 9dec3056..1253b0c5 100644 --- a/docs/debugging +++ b/docs/debugging @@ -72,15 +72,17 @@ i3-msg 'debuglog on; shmlog on; reload' 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. -To save a compressed version of the logfile (suitable for attaching it to a -bugreport), use: --------------------------------------------------------------------- -DISPLAY=:0 i3-dump-log | bzip2 -c > /tmp/i3.log.bz2 --------------------------------------------------------------------- +To upload a compressed version of the logfile (for a bugreport), use: +------------------------------------------------------------------------------ +DISPLAY=:0 i3-dump-log | bzip2 -c | curl --data-binary @- http://logs.i3wm.org +------------------------------------------------------------------------------ This command does not depend on i3 (it also works while i3 displays the crash dialog), but it requires a working X11 connection. +After running it, you will get a URL to the logfile. Please include that URL in +your bug report. + == On crashes: Obtaining a backtrace When i3 crashes, it will display a dialog stating “i3 just crashed”, offering diff --git a/docs/hacking-howto b/docs/hacking-howto index f4e3f031..a591047e 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -956,10 +956,10 @@ 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. When working on bugfixes, please make sure you mention that you are working on -it in the corresponding bugreport at http://bugs.i3wm.org/. In case there is no -bugreport yet, please create one. +it in the corresponding bugreport at https://github.com/i3/i3/issues In case +there is no bugreport yet, please create one. -After you are done, please submit your work for review at http://cr.i3wm.org/ +After you are done, please submit your work for review 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 diff --git a/docs/i3bar-protocol b/docs/i3bar-protocol index 92763154..8fd51ae9 100644 --- a/docs/i3bar-protocol +++ b/docs/i3bar-protocol @@ -119,7 +119,8 @@ click_events:: full_text:: The most simple block you can think of is one which just includes the only required key, the +full_text+ key. i3bar will display the string - value and that’s it. + value parsed as + https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup]. short_text:: Where appropriate, the +short_text+ (string) entry should also be provided. It will be used in case the status line needs to be shortened @@ -148,7 +149,7 @@ min_width:: when you want to set a sensible minimum width regardless of which font you are using, and at what particular size. align:: - Align text on the +center+ (default), +right+ or +left+ of the block, when + Align text on the +center+, +right+ or +left+ (default) of the block, when the minimum width of the latter, specified by the +min_width+ key, is not reached. name and instance:: diff --git a/docs/ipc b/docs/ipc index c9768705..ff7c8aae 100644 --- a/docs/ipc +++ b/docs/ipc @@ -1,7 +1,7 @@ IPC interface (interprocess communication) ========================================== Michael Stapelberg -February 2014 +October 2014 This document describes how to interface with i3 from a separate process. This is useful for example to remote-control i3 (to write test cases for example) or @@ -156,7 +156,7 @@ following properties: num (integer):: The logical number of the workspace. Corresponds to the command - to switch to this workspace. + to switch to this workspace. For named workspaces, this will be -1. name (string):: The name of this workspace (by default num+1), as changed by the user. Encoded in UTF-8. @@ -316,6 +316,10 @@ window_rect (map):: So, when using the +default+ layout, you will have a 2 pixel border on each side, making the window_rect +{ "x": 2, "y": 0, "width": 632, "height": 366 }+ (for example). +deco_rect (map):: + The coordinates of the *window decoration* inside its container. These + coordinates are relative to the container and do not include the actual + client window. geometry (map):: The original geometry the window specified when i3 mapped it. Used when switching a window to floating mode, for example. @@ -613,7 +617,7 @@ you can register to an event. *Example:* --------------------------------- type: SUBSCRIBE -payload: [ "workspace", "focus" ] +payload: [ "workspace", "output" ] --------------------------------- @@ -638,6 +642,9 @@ window (3):: barconfig_update (4):: Sent when the hidden_state or mode field in the barconfig of any bar instance was updated and when the config is reloaded. +binding (5):: + Sent when a configured command binding is triggered with the keyboard or + mouse *Example:* -------------------------------------------------------------------- @@ -661,15 +668,16 @@ if ($is_event) { This event consists of a single serialized map containing a property +change (string)+ which indicates the type of the change ("focus", "init", -"empty", "urgent"). +"empty", "urgent"). A +current (object)+ property will be present with the +affected workspace whenever the type of event affects a workspace (otherwise, +it will be +null). -Moreover, when the change is "focus", an +old (object)+ and a +current -(object)+ properties will be present with the previous and current -workspace respectively. When the first switch occurs (when i3 focuses -the workspace visible at the beginning) there is no previous -workspace, and the +old+ property will be set to +null+. Also note -that if the previous is empty it will get destroyed when switching, -but will still be present in the "old" property. +When the change is "focus", an +old (object)+ property will be present with the +previous workspace. When the first switch occurs (when i3 focuses the +workspace visible at the beginning) there is no previous workspace, and the ++old+ property will be set to +null+. Also note that if the previous is empty +it will get destroyed when switching, but will still be present in the "old" +property. *Example:* --------------------- @@ -717,9 +725,13 @@ This event consists of a single serialized map containing a property +change (string)+ which indicates the type of the change * +new+ - the window has become managed by i3 +* +close+ - the window has closed * +focus+ - the window has received input focus * +title+ - the window's title has changed * +fullscreen_mode+ - the window has entered or exited fullscreen mode +* +move+ - the window has changed its position in the tree +* +floating+ - the window has transitioned to or from floating +* +urgent+ - the window has become urgent or lost its urgent status Additionally a +container (object)+ field will be present, which consists of the window's parent container. Be aware that for the "new" event, the @@ -745,6 +757,47 @@ This event consists of a single serialized map reporting on options from the barconfig of the specified bar_id that were updated in i3. This event is the same as a +GET_BAR_CONFIG+ reply for the bar with the given id. +=== binding event + +This event consists of a single serialized map reporting on the details of a +binding that ran a command because of user input. The +change (sring)+ field +indicates what sort of binding event was triggered (right now it will always be ++"run"+ but may be expanded in the future). + +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. +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 + the number of the mouse button that was pressed. Otherwise it will be 0. +symbol (string or null):: + If this is a keyboard binding that was configured with +bindsym+, this + field will contain the given symbol. Otherwise it will be +null+. +input_type (string):: + This will be +"keyboard"+ or +"mouse"+ depending on whether or not this was + a keyboard or a mouse binding. + +*Example:* +--------------------------- +{ + "change": "run", + "binding": { + "command": "nop", + "mods": [ + "shift", + "ctrl" + ], + "input_code": 0, + "symbol": "t", + "input_type": "keyboard" + } +} +--------------------------- + == See also (existing libraries) [[libraries]] @@ -754,15 +807,14 @@ all this on your own). This list names some (if you wrote one, please let me know): C:: - i3 includes a headerfile +i3/ipc.h+ which provides you all constants. - - https://github.com/acrisci/i3ipc-glib + * i3 includes a headerfile +i3/ipc.h+ which provides you all constants. + * https://github.com/acrisci/i3ipc-glib Go:: * https://github.com/proxypoke/i3ipc JavaScript:: * https://github.com/acrisci/i3ipc-gjs Lua:: - * https:/github.com/acrisci/i3ipc-lua + * https://github.com/acrisci/i3ipc-lua Perl:: * https://metacpan.org/module/AnyEvent::I3 Python:: @@ -770,4 +822,4 @@ Python:: * https://github.com/whitelynx/i3ipc (not maintained) * https://github.com/ziberna/i3-py (not maintained) Ruby:: - http://github.com/badboy/i3-ipc + * http://github.com/badboy/i3-ipc diff --git a/docs/testsuite b/docs/testsuite index 6c3a36d9..29a35218 100644 --- a/docs/testsuite +++ b/docs/testsuite @@ -74,6 +74,9 @@ client, simply called +cpan+. It comes with every Perl installation and can be used to install the testsuite. Many users prefer to use the more modern +cpanminus+ instead, though (because it asks no questions and just works): +The tests additionally require +Xephyr(1)+ to run a nested X server. Install ++xserver-xephyr+ on Debian or +xorg-xserver-xephyr+ on Arch Linux. + .Installing testsuite dependencies using cpanminus (preferred) -------------------------------------------------------------------------------- $ cd ~/i3/testcases @@ -102,7 +105,12 @@ more testcases. Also, it takes care of starting up a separate instance of i3 with an appropriate configuration file and creates a folder for each run containing the appropriate i3 logfile for each testcase. The latest folder can always be found under the symlink +latest/+. Unless told differently, it will -run the tests on a separate X server instance (using the Xdummy script). +run the tests on a separate X server instance (using Xephyr). + +Xephyr will open a window where you can inspect the running test. You can run +the tests without an X session with Xvfb, such as with +xvfb-run +./complete-run+. This will also speed up the tests signficantly especially on +machines without a powerful video card. .Example invocation of complete-run.pl+ --------------------------------------- @@ -146,12 +154,11 @@ $ less latest/i3-log-for-04-floating.t If your attempt to run the tests with a bare call to ./complete-run.pl fails, try this: --------------------------------------------------- -$ ./complete-run.pl --parallel=1 --keep-xdummy-output +$ ./complete-run.pl --parallel=1 --keep-xserver-output --------------------------------------------------- -One common cause of failures is not having the X dummy server module -installed. Under Debian and Ubuntu this is the package -+xserver-xorg-video-dummy+. +This will show the output of Xephyr, which is the X server implementation we +use for testing. ==== IPC interface @@ -175,10 +182,9 @@ manager. === Filesystem structure In the git root of i3, the testcases live in the folder +testcases+. This -folder contains the +complete-run.pl+ and +Xdummy+ scripts and a base -configuration file which will be used for the tests. The different testcases -(their file extension is .t, not .pl) themselves can be found in the -conventionally named subfolder +t+: +folder contains the +complete-run.pl+ and a base configuration file which will +be used for the tests. The different testcases (their file extension is .t, not +.pl) themselves can be found in the conventionally named subfolder +t+: .Filesystem structure -------------------------------------------- @@ -197,7 +203,6 @@ conventionally named subfolder +t+: │   │   ├── omitted for brevity │   │   ├── ... │   │   └── 74-regress-focus-toggle.t -│   └── Xdummy -------------------------------------------- == Anatomy of a testcase diff --git a/docs/userguide b/docs/userguide index ff46d62f..45b05a06 100644 --- a/docs/userguide +++ b/docs/userguide @@ -91,7 +91,7 @@ To display a window in fullscreen mode or to go out of fullscreen mode again, press +$mod+f+. There is also a global fullscreen mode in i3 in which the client will span all -available outputs (the command is +fullscreen global+). +available outputs (the command is +fullscreen toggle global+). === Opening other applications @@ -153,6 +153,7 @@ to upgrade to a newer version of i3) you can use +$mod+Shift+r+. === Exiting i3 To cleanly exit i3 without killing your X server, you can use +$mod+Shift+e+. +By default, a dialog will ask you to confirm if you really want to quit. === Floating @@ -366,7 +367,7 @@ bindcode [--release] [Modifiers+]keycode command *Examples*: -------------------------------- # Fullscreen -bindsym $mod+f fullscreen +bindsym $mod+f fullscreen toggle # Restart bindsym $mod+Shift+r restart @@ -393,6 +394,41 @@ 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 :-). +[[mousebindings]] + +=== Mouse bindings + +A mouse binding makes i3 execute a command upon pressing a specific mouse +button in the scope of the clicked container (see <>). You +can configure mouse bindings in a similar way to key bindings. + +*Syntax*: +---------------------------------- +bindsym [--release] [--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. + +*Examples*: +-------------------------------- +# The middle button over a titlebar kills the window +bindsym --release button2 kill + +# The middle button and a modifer over any part of the window kills the window +bindsym --whole-window $mod+button2 kill + +# The right button toggles floating +bindsym button3 floating toggle +bindsym $mod+button3 floating toggle + +# The side buttons move the window around +bindsym button9 move left +bindsym button8 move right +-------------------------------- + [[floating_modifier]] === The floating modifier @@ -1069,6 +1105,27 @@ bar { 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. + +*Syntax*: +--------------------- +wheel_up_cmd +wheel_down_cmd +--------------------- + +*Example*: +--------------------- +bar { + wheel_up_cmd nop + wheel_down_cmd exec ~/.i3/scripts/custom_wheel_down +} +--------------------- + === Bar ID Specifies the bar ID for the configured bar instance. If this option is missing, @@ -1446,9 +1503,13 @@ Use +layout toggle split+, +layout stacking+, +layout tabbed+, +layout splitv+ or +layout splith+ to change the current container layout to splith/splitv, stacking, tabbed layout, splitv or splith, respectively. -To make the current window (!) fullscreen, use +fullscreen+, to make -it floating (or tiling again) use +floating enable+ respectively +floating disable+ -(or +floating toggle+): +To make the current window (!) fullscreen, use +fullscreen enable+ (or ++fullscreen enable global+ for the global mode), to leave either fullscreen +mode use +fullscreen disable+, and to toggle between these two states use ++fullscreen toggle+ (or +fullscreen toggle global+). + +Likewise, to make the current window floating (or tiling again) use +floating +enable+ respectively +floating disable+ (or +floating toggle+): *Syntax*: -------------- @@ -1469,7 +1530,7 @@ bindsym $mod+x layout toggle bindsym $mod+x layout toggle all # Toggle fullscreen -bindsym $mod+f fullscreen +bindsym $mod+f fullscreen toggle # Toggle floating/tiling bindsym $mod+t floating toggle @@ -1564,6 +1625,10 @@ container to the next/previous workspace and +move container to workspace curren See <> for how to move a container/workspace to a different RandR output. +Workspace names are parsed as +https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup] +by i3bar. + [[back_and_forth]] To switch back to the previously focused workspace, use +workspace back_and_forth+; likewise, you can move containers to the previously focused @@ -1585,6 +1650,7 @@ move [window|container] [to] workspace ------------------------- bindsym $mod+1 workspace 1 bindsym $mod+2 workspace 2 +bindsym $mod+3 workspace 3:vim ... bindsym $mod+Shift+1 move container to workspace 1 diff --git a/i3-config-wizard/i3-config-wizard.mk b/i3-config-wizard/i3-config-wizard.mk index e759b4bd..1dab6452 100644 --- a/i3-config-wizard/i3-config-wizard.mk +++ b/i3-config-wizard/i3-config-wizard.mk @@ -4,8 +4,8 @@ CLEAN_TARGETS += clean-i3-config-wizard i3_config_wizard_SOURCES := $(wildcard i3-config-wizard/*.c) i3_config_wizard_HEADERS := $(wildcard i3-config-wizard/*.h) -i3_config_wizard_CFLAGS = $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(X11_CFLAGS) $(PANGO_CFLAGS) -i3_config_wizard_LIBS = $(XCB_LIBS) $(XCB_KBD_LIBS) $(X11_LIBS) $(PANGO_LIBS) +i3_config_wizard_CFLAGS = $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(PANGO_CFLAGS) $(XKB_COMMON_CFLAGS) $(XKB_COMMON_X11_CFLAGS) +i3_config_wizard_LIBS = $(XCB_LIBS) $(XCB_KBD_LIBS) $(PANGO_LIBS) $(XKB_COMMON_LIBS) $(XKB_COMMON_X11_LIBS) i3_config_wizard_OBJECTS := $(i3_config_wizard_SOURCES:.c=.o) diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index a76c211e..c09d459d 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -43,6 +43,9 @@ #include #include +#include +#include + #include #include #include @@ -83,7 +86,9 @@ static xcb_pixmap_t pixmap; static xcb_gcontext_t pixmap_gc; static xcb_key_symbols_t *symbols; xcb_window_t root; -Display *dpy; +static struct xkb_keymap *xkb_keymap; +static uint8_t xkb_base_event; +static uint8_t xkb_base_error; static void finish(); @@ -250,12 +255,24 @@ static char *next_state(const cmdp_token *token) { * This reduces a lot of confusion for users who switch keyboard * layouts from qwerty to qwertz or other slight variations of * qwerty (yes, that happens quite often). */ - KeySym sym = XkbKeycodeToKeysym(dpy, keycode, 0, 0); - if (!keysym_used_on_other_key(sym, keycode)) + const xkb_keysym_t *syms; + int num = xkb_keymap_key_get_syms_by_level(xkb_keymap, keycode, 0, 0, &syms); + if (num == 0) + errx(1, "xkb_keymap_key_get_syms_by_level returned no symbols for keycode %d", keycode); + if (!keysym_used_on_other_key(syms[0], keycode)) level = 0; } - KeySym sym = XkbKeycodeToKeysym(dpy, keycode, 0, level); - char *str = XKeysymToString(sym); + + const xkb_keysym_t *syms; + int num = xkb_keymap_key_get_syms_by_level(xkb_keymap, keycode, 0, level, &syms); + if (num == 0) + errx(1, "xkb_keymap_key_get_syms_by_level returned no symbols for keycode %d", keycode); + if (num > 1) + printf("xkb_keymap_key_get_syms_by_level (keycode = %d) returned %d symbolsinstead of 1, using only the first one.\n", keycode, num); + + char str[4096]; + if (xkb_keysym_get_name(syms[0], str, sizeof(str)) == -1) + errx(EXIT_FAILURE, "xkb_keysym_get_name(%u) failed", syms[0]); const char *release = get_string("release"); char *res; char *modrep = (modifiers == NULL ? sstrdup("") : sstrdup(modifiers)); @@ -642,8 +659,14 @@ static void handle_button_press(xcb_button_press_event_t *event) { static void finish() { printf("creating \"%s\"...\n", config_path); - if (!(dpy = XOpenDisplay(NULL))) - errx(1, "Could not connect to X11"); + struct xkb_context *xkb_context; + + if ((xkb_context = xkb_context_new(0)) == NULL) + errx(1, "could not create xkbcommon context"); + + int32_t device_id = xkb_x11_get_core_keyboard_device_id(conn); + if ((xkb_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL) + errx(1, "xkb_x11_keymap_new_from_device failed"); FILE *kc_config = fopen(SYSCONFDIR "/i3/config.keycodes", "r"); if (kc_config == NULL) @@ -797,6 +820,16 @@ int main(int argc, char *argv[]) { xcb_connection_has_error(conn)) errx(1, "Cannot open display\n"); + if (xkb_x11_setup_xkb_extension(conn, + XKB_X11_MIN_MAJOR_XKB_VERSION, + XKB_X11_MIN_MINOR_XKB_VERSION, + 0, + NULL, + NULL, + &xkb_base_event, + &xkb_base_error) != 1) + errx(EXIT_FAILURE, "Could not setup XKB extension."); + if (socket_path == NULL) socket_path = root_atom_contents("I3_SOCKET_PATH", conn, screen); diff --git a/i3-config-wizard/xcb.h b/i3-config-wizard/xcb.h index d51f979a..f3e204e8 100644 --- a/i3-config-wizard/xcb.h +++ b/i3-config-wizard/xcb.h @@ -1,8 +1,8 @@ #pragma once /* from X11/keysymdef.h */ -#define XCB_NUM_LOCK 0xff7f +#define XCB_NUM_LOCK 0xff7f -#define xmacro(atom) xcb_atom_t A_ ## atom; +#define xmacro(atom) xcb_atom_t A_##atom; #include "atoms.xmacro" #undef xmacro diff --git a/i3-input/i3-input.h b/i3-input/i3-input.h index 104296cf..fcf9edc3 100644 --- a/i3-input/i3-input.h +++ b/i3-input/i3-input.h @@ -3,12 +3,12 @@ #include #define die(...) errx(EXIT_FAILURE, __VA_ARGS__); -#define FREE(pointer) do { \ +#define FREE(pointer) \ + do { \ if (pointer != NULL) { \ - free(pointer); \ - pointer = NULL; \ - } \ -} \ -while (0) + free(pointer); \ + pointer = NULL; \ + } \ + } while (0) extern xcb_window_t root; diff --git a/i3-nagbar/i3-nagbar.h b/i3-nagbar/i3-nagbar.h index 9aac709b..fc076d72 100644 --- a/i3-nagbar/i3-nagbar.h +++ b/i3-nagbar/i3-nagbar.h @@ -3,15 +3,15 @@ #include #define die(...) errx(EXIT_FAILURE, __VA_ARGS__); -#define FREE(pointer) do { \ +#define FREE(pointer) \ + do { \ if (pointer != NULL) { \ - free(pointer); \ - pointer = NULL; \ - } \ -} \ -while (0) + free(pointer); \ + pointer = NULL; \ + } \ + } while (0) -#define xmacro(atom) xcb_atom_t A_ ## atom; +#define xmacro(atom) xcb_atom_t A_##atom; #include "atoms.xmacro" #undef xmacro diff --git a/i3-save-tree b/i3-save-tree index add4c8c1..c64fc72a 100755 --- a/i3-save-tree +++ b/i3-save-tree @@ -92,6 +92,7 @@ my %allowed_keys = map { ($_, 1) } qw( name geometry window_properties + mark ); sub strip_containers { diff --git a/i3-sensible-editor b/i3-sensible-editor index b3afceb7..4e7456b7 100755 --- a/i3-sensible-editor +++ b/i3-sensible-editor @@ -10,7 +10,7 @@ # Hopefully one of these is installed (no flamewars about preference please!): for editor in $VISUAL $EDITOR nano vim vi emacs pico qe mg jed gedit mc-edit; do - if which $editor > /dev/null 2>&1; then + if command -v $editor > /dev/null 2>&1; then exec $editor "$@" fi done diff --git a/i3-sensible-pager b/i3-sensible-pager index df463251..5ad78606 100755 --- a/i3-sensible-pager +++ b/i3-sensible-pager @@ -12,7 +12,7 @@ # 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 - if which $pager > /dev/null 2>&1; then + if command -v $pager > /dev/null 2>&1; then exec $pager "$@" fi done diff --git a/i3-sensible-terminal b/i3-sensible-terminal index fddefae1..747a9280 100755 --- a/i3-sensible-terminal +++ b/i3-sensible-terminal @@ -5,11 +5,11 @@ # This script tries to exec a terminal emulator by trying some known terminal # emulators. # -# Distributions/packagers should enhance this script with a -# distribution-specific mechanism to find the preferred terminal emulator. On -# Debian, there is the x-terminal-emulator symlink for example. -for terminal in $TERMINAL urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal; do - if which $terminal > /dev/null 2>&1; then +# 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 + if command -v $terminal > /dev/null 2>&1; then exec $terminal "$@" fi done diff --git a/i3.config b/i3.config index 30b3f6a8..d7e96fe9 100644 --- a/i3.config +++ b/i3.config @@ -75,7 +75,7 @@ bindsym Mod1+h split h bindsym Mod1+v split v # enter fullscreen mode for the focused container -bindsym Mod1+f fullscreen +bindsym Mod1+f fullscreen toggle # change container layout (stacked, tabbed, toggle split) bindsym Mod1+s layout stacking diff --git a/i3.config.keycodes b/i3.config.keycodes index 27398515..93528a54 100644 --- a/i3.config.keycodes +++ b/i3.config.keycodes @@ -69,7 +69,7 @@ bindcode $mod+43 split h bindcode $mod+55 split v # enter fullscreen mode for the focused container -bindcode $mod+41 fullscreen +bindcode $mod+41 fullscreen toggle # change container layout (stacked, tabbed, toggle split) bindcode $mod+39 layout stacking diff --git a/i3.xsession.desktop b/i3.xsession.desktop index d1e3dc2d..d1340053 100644 --- a/i3.xsession.desktop +++ b/i3.xsession.desktop @@ -5,3 +5,4 @@ Exec=i3 TryExec=i3 Type=Application X-LightDM-DesktopName=i3 +DesktopNames=i3 diff --git a/i3bar/i3bar.mk b/i3bar/i3bar.mk index 06780250..53227a8e 100644 --- a/i3bar/i3bar.mk +++ b/i3bar/i3bar.mk @@ -4,8 +4,8 @@ CLEAN_TARGETS += clean-i3bar i3bar_SOURCES := $(wildcard i3bar/src/*.c) i3bar_HEADERS := $(wildcard i3bar/include/*.h) -i3bar_CFLAGS = $(XCB_CFLAGS) $(X11_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) -i3bar_LIBS = $(XCB_LIBS) $(X11_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) +i3bar_CFLAGS = $(XCB_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) +i3bar_LIBS = $(XCB_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) $(XCB_XKB_LIBS) i3bar_OBJECTS := $(i3bar_SOURCES:.c=.o) diff --git a/i3bar/include/common.h b/i3bar/include/common.h index d63780dc..e8b6be0a 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -15,9 +15,9 @@ typedef struct rect_t rect; -struct ev_loop* main_loop; -char *statusline; -char *statusline_buffer; +struct ev_loop *main_loop; +char *statusline; +char *statusline_buffer; struct rect_t { int x; @@ -27,6 +27,7 @@ struct rect_t { }; typedef enum { + /* First value to make it the default. */ ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT diff --git a/i3bar/include/config.h b/i3bar/include/config.h index a2c16887..2c399305 100644 --- a/i3bar/include/config.h +++ b/i3bar/include/config.h @@ -18,27 +18,32 @@ typedef enum { } position_t; /* Bar display mode (hide unless modifier is pressed or show in dock mode or always hide in invisible mode) */ -typedef enum { M_DOCK = 0, M_HIDE = 1, M_INVISIBLE = 2 } bar_display_mode_t; +typedef enum { M_DOCK = 0, + M_HIDE = 1, + M_INVISIBLE = 2 } bar_display_mode_t; typedef struct config_t { - int modifier; - position_t position; - int verbose; + int modifier; + char *wheel_up_cmd; + char *wheel_down_cmd; + position_t position; + int verbose; struct xcb_color_strings_t colors; - bool disable_binding_mode_indicator; - bool disable_ws; - bool strip_ws_numbers; - char *bar_id; - char *command; - char *fontname; - char *tray_output; - int num_outputs; - char **outputs; + bool disable_binding_mode_indicator; + bool disable_ws; + bool strip_ws_numbers; + char *bar_id; + char *command; + char *fontname; + char *tray_output; + int num_outputs; + char **outputs; bar_display_mode_t hide_on_modifier; /* The current hidden_state of the bar, which indicates whether it is hidden or shown */ - enum { S_HIDE = 0, S_SHOW = 1 } hidden_state; + enum { S_HIDE = 0, + S_SHOW = 1 } hidden_state; } config_t; config_t config; diff --git a/i3bar/include/ipc.h b/i3bar/include/ipc.h index 5de23878..c357be89 100644 --- a/i3bar/include/ipc.h +++ b/i3bar/include/ipc.h @@ -29,7 +29,7 @@ void destroy_connection(void); * type must be a valid I3_IPC_MESSAGE_TYPE (see i3/ipc.h for further information) * */ -int i3_send_msg(uint32_t type, const char* payload); +int i3_send_msg(uint32_t type, const char *payload); /* * Subscribe to all the i3-events, we need diff --git a/i3bar/include/outputs.h b/i3bar/include/outputs.h index 9f6add11..bd41e776 100644 --- a/i3bar/include/outputs.h +++ b/i3bar/include/outputs.h @@ -16,7 +16,7 @@ typedef struct i3_output i3_output; SLIST_HEAD(outputs_head, i3_output); -struct outputs_head *outputs; +struct outputs_head* outputs; /* * Start parsing the received json-string @@ -37,18 +37,19 @@ void init_outputs(void); i3_output* get_output_by_name(char* name); struct i3_output { - char* name; /* Name of the output */ - bool active; /* If the output is active */ - bool primary; /* If it is the primary output */ - int ws; /* The number of the currently visible ws */ - rect rect; /* The rect (relative to the root-win) */ + char* name; /* Name of the output */ + bool active; /* If the output is active */ + bool primary; /* If it is the primary output */ + bool visible; /* If the bar is visible on this output */ + int ws; /* The number of the currently visible ws */ + rect rect; /* The rect (relative to the root-win) */ - xcb_window_t bar; /* The id of the bar of the output */ - xcb_pixmap_t buffer; /* An extra pixmap for double-buffering */ - xcb_gcontext_t bargc; /* The graphical context of the bar */ + xcb_window_t bar; /* The id of the bar of the output */ + xcb_pixmap_t buffer; /* An extra pixmap for double-buffering */ + xcb_gcontext_t bargc; /* The graphical context of the bar */ - struct ws_head *workspaces; /* The workspaces on this output */ - struct tc_head *trayclients; /* The tray clients on this output */ + struct ws_head* workspaces; /* The workspaces on this output */ + struct tc_head* trayclients; /* The tray clients on this output */ SLIST_ENTRY(i3_output) slist; /* Pointer for the SLIST-Macro */ }; diff --git a/i3bar/include/trayclients.h b/i3bar/include/trayclients.h index 7a7e537e..1720ec3b 100644 --- a/i3bar/include/trayclients.h +++ b/i3bar/include/trayclients.h @@ -14,9 +14,9 @@ typedef struct trayclient trayclient; TAILQ_HEAD(tc_head, trayclient); struct trayclient { - xcb_window_t win; /* The window ID of the tray client */ - bool mapped; /* Whether this window is mapped */ - int xe_version; /* The XEMBED version supported by the client */ + xcb_window_t win; /* The window ID of the tray client */ + bool mapped; /* Whether this window is mapped */ + int xe_version; /* The XEMBED version supported by the client */ - TAILQ_ENTRY(trayclient) tailq; /* Pointer for the TAILQ-Macro */ + TAILQ_ENTRY(trayclient) tailq; /* Pointer for the TAILQ-Macro */ }; diff --git a/i3bar/include/util.h b/i3bar/include/util.h index 9ffd4467..cb0903c1 100644 --- a/i3bar/include/util.h +++ b/i3bar/include/util.h @@ -11,55 +11,60 @@ /* Get the maximum/minimum of x and y */ #undef MAX -#define MAX(x,y) ((x) > (y) ? (x) : (y)) +#define MAX(x, y) ((x) > (y) ? (x) : (y)) #undef MIN -#define MIN(x,y) ((x) < (y) ? (x) : (y)) +#define MIN(x, y) ((x) < (y) ? (x) : (y)) #define STARTS_WITH(string, len, needle) ((len >= strlen(needle)) && strncasecmp(string, needle, strlen(needle)) == 0) /* Securely free p */ -#define FREE(p) do { \ - if (p != NULL) { \ - free(p); \ - p = NULL; \ - } \ -} while (0) +#define FREE(p) \ + do { \ + if (p != NULL) { \ + free(p); \ + p = NULL; \ + } \ + } while (0) /* Securely fee single-linked list */ -#define FREE_SLIST(l, type) do { \ - type *walk = SLIST_FIRST(l); \ - while (!SLIST_EMPTY(l)) { \ - SLIST_REMOVE_HEAD(l, slist); \ - FREE(walk); \ - walk = SLIST_FIRST(l); \ - } \ -} while (0) +#define FREE_SLIST(l, type) \ + do { \ + type *walk = SLIST_FIRST(l); \ + while (!SLIST_EMPTY(l)) { \ + SLIST_REMOVE_HEAD(l, slist); \ + FREE(walk); \ + walk = SLIST_FIRST(l); \ + } \ + } while (0) /* Securely fee tail-queues */ -#define FREE_TAILQ(l, type) do { \ - type *walk = TAILQ_FIRST(l); \ - while (!TAILQ_EMPTY(l)) { \ - TAILQ_REMOVE(l, TAILQ_FIRST(l), tailq); \ - FREE(walk); \ - walk = TAILQ_FIRST(l); \ - } \ -} while (0) +#define FREE_TAILQ(l, type) \ + do { \ + type *walk = TAILQ_FIRST(l); \ + while (!TAILQ_EMPTY(l)) { \ + TAILQ_REMOVE(l, TAILQ_FIRST(l), tailq); \ + FREE(walk); \ + walk = TAILQ_FIRST(l); \ + } \ + } while (0) #if defined(DLOG) #undef DLOG #endif /* Use cool logging-macros */ -#define DLOG(fmt, ...) do { \ - if (config.verbose) { \ - printf("[%s:%d] " fmt, __FILE__, __LINE__, ##__VA_ARGS__); \ - } \ -} while(0) +#define DLOG(fmt, ...) \ + do { \ + if (config.verbose) { \ + printf("[%s:%d] " fmt, __FILE__, __LINE__, ##__VA_ARGS__); \ + } \ + } while (0) /* We will include libi3.h which define its own version of ELOG. * We want *our* version, so we undef the libi3 one. */ #if defined(ELOG) #undef ELOG #endif -#define ELOG(fmt, ...) do { \ - fprintf(stderr, "[%s:%d] ERROR: " fmt, __FILE__, __LINE__, ##__VA_ARGS__); \ -} while(0) +#define ELOG(fmt, ...) \ + do { \ + fprintf(stderr, "[%s:%d] ERROR: " fmt, __FILE__, __LINE__, ##__VA_ARGS__); \ + } while (0) diff --git a/i3bar/include/workspaces.h b/i3bar/include/workspaces.h index d3d23c84..b9cefa84 100644 --- a/i3bar/include/workspaces.h +++ b/i3bar/include/workspaces.h @@ -30,15 +30,15 @@ void parse_workspaces_json(char *json); void free_workspaces(void); struct i3_ws { - int num; /* The internal number of the ws */ - char *canonical_name; /* The true name of the ws according to the ipc */ - i3String *name; /* The name of the ws that is displayed on the bar */ - int name_width; /* The rendered width of the name */ - bool visible; /* If the ws is currently visible on an output */ - bool focused; /* If the ws is currently focused */ - bool urgent; /* If the urgent-hint of the ws is set */ - rect rect; /* The rect of the ws (not used (yet)) */ - struct i3_output *output; /* The current output of the ws */ + int num; /* The internal number of the ws */ + char *canonical_name; /* The true name of the ws according to the ipc */ + i3String *name; /* The name of the ws that is displayed on the bar */ + int name_width; /* The rendered width of the name */ + bool visible; /* If the ws is currently visible on an output */ + bool focused; /* If the ws is currently focused */ + bool urgent; /* If the urgent-hint of the ws is set */ + rect rect; /* The rect of the ws (not used (yet)) */ + struct i3_output *output; /* The current output of the ws */ - TAILQ_ENTRY(i3_ws) tailq; /* Pointer for the TAILQ-Macro */ + TAILQ_ENTRY(i3_ws) tailq; /* Pointer for the TAILQ-Macro */ }; diff --git a/i3bar/include/xcb.h b/i3bar/include/xcb.h index 2740f330..32b52f4d 100644 --- a/i3bar/include/xcb.h +++ b/i3bar/include/xcb.h @@ -18,11 +18,11 @@ #define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0 #define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1 -#define SYSTEM_TRAY_REQUEST_DOCK 0 -#define SYSTEM_TRAY_BEGIN_MESSAGE 1 -#define SYSTEM_TRAY_CANCEL_MESSAGE 2 -#define XEMBED_MAPPED (1 << 0) -#define XEMBED_EMBEDDED_NOTIFY 0 +#define SYSTEM_TRAY_REQUEST_DOCK 0 +#define SYSTEM_TRAY_BEGIN_MESSAGE 1 +#define SYSTEM_TRAY_CANCEL_MESSAGE 2 +#define XEMBED_MAPPED (1 << 0) +#define XEMBED_EMBEDDED_NOTIFY 0 struct xcb_color_strings_t { char *bar_fg; diff --git a/i3bar/src/child.c b/i3bar/src/child.c index 31b99d42..2f7dd76e 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -182,21 +182,21 @@ static int stdin_boolean(void *context, int val) { static int stdin_string(void *context, const unsigned char *val, size_t len) { parser_ctx *ctx = context; if (strcasecmp(ctx->last_map_key, "full_text") == 0) { - ctx->block.full_text = i3string_from_utf8_with_length((const char *)val, len); + ctx->block.full_text = i3string_from_markup_with_length((const char *)val, len); } if (strcasecmp(ctx->last_map_key, "color") == 0) { sasprintf(&(ctx->block.color), "%.*s", len, val); } if (strcasecmp(ctx->last_map_key, "align") == 0) { - if (len == strlen("left") && !strncmp((const char *)val, "left", strlen("left"))) { - ctx->block.align = ALIGN_LEFT; + if (len == strlen("center") && !strncmp((const char *)val, "center", strlen("center"))) { + ctx->block.align = ALIGN_CENTER; } else if (len == strlen("right") && !strncmp((const char *)val, "right", strlen("right"))) { ctx->block.align = ALIGN_RIGHT; } else { - ctx->block.align = ALIGN_CENTER; + ctx->block.align = ALIGN_LEFT; } } else if (strcasecmp(ctx->last_map_key, "min_width") == 0) { - i3String *text = i3string_from_utf8_with_length((const char *)val, len); + i3String *text = i3string_from_markup_with_length((const char *)val, len); ctx->block.min_width = (uint32_t)predict_text_width(text); i3string_free(text); } @@ -233,7 +233,7 @@ static int stdin_end_map(void *context) { /* Ensure we have a full_text set, so that when it is missing (or null), * i3bar doesn’t crash and the user gets an annoying message. */ if (!new_block->full_text) - new_block->full_text = i3string_from_utf8("SPEC VIOLATION (null)"); + new_block->full_text = i3string_from_utf8("SPEC VIOLATION: full_text is NULL!"); if (new_block->urgent) ctx->has_urgent = true; TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks); @@ -243,7 +243,7 @@ static int stdin_end_map(void *context) { static int stdin_end_array(void *context) { DLOG("dumping statusline:\n"); struct status_block *current; - TAILQ_FOREACH (current, &statusline_head, blocks) { + TAILQ_FOREACH(current, &statusline_head, blocks) { DLOG("full_text = %s\n", i3string_as_utf8(current->full_text)); DLOG("color = %s\n", current->color); } @@ -304,7 +304,7 @@ static void read_flat_input(char *buffer, int length) { buffer[length - 1] = '\0'; else buffer[length] = '\0'; - first->full_text = i3string_from_utf8(buffer); + first->full_text = i3string_from_markup(buffer); } static bool read_json_input(unsigned char *input, int length) { diff --git a/i3bar/src/config.c b/i3bar/src/config.c index 809b1ab4..bb322619 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -112,6 +112,20 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len return 1; } + 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); + return 1; + } + + 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); + return 1; + } + if (!strcmp(cur_key, "position")) { DLOG("position = %.*s\n", len, val); config.position = (len == 3 && !strncmp((const char *)val, "top", strlen("top")) ? POS_TOP : POS_BOT); @@ -246,9 +260,9 @@ void parse_config_json(char *json) { * */ void free_colors(struct xcb_color_strings_t *colors) { -#define FREE_COLOR(x) \ - do { \ - FREE(colors->x); \ +#define FREE_COLOR(x) \ + do { \ + FREE(colors->x); \ } while (0) FREE_COLOR(bar_fg); FREE_COLOR(bar_bg); diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index 6ff83979..e1b343e5 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -67,7 +67,7 @@ void got_output_reply(char *reply) { reconfig_windows(false); i3_output *o_walk; - SLIST_FOREACH (o_walk, outputs, slist) { + SLIST_FOREACH(o_walk, outputs, slist) { kick_tray_clients(o_walk); } @@ -159,6 +159,9 @@ void got_bar_config_update(char *event) { if (found_id == NULL) return; + /* reconfigure the bar based on the current outputs */ + i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL); + free_colors(&(config.colors)); /* update the configuration with the received settings */ @@ -169,6 +172,8 @@ void got_bar_config_update(char *event) { reconfig_windows(true); } + /* update fonts and colors */ + init_xcb_late(config.fontname); init_colors(&(config.colors)); realloc_sl_buffer(); diff --git a/i3bar/src/outputs.c b/i3bar/src/outputs.c index 5508a5a2..02b718d3 100644 --- a/i3bar/src/outputs.c +++ b/i3bar/src/outputs.c @@ -296,7 +296,7 @@ i3_output *get_output_by_name(char *name) { if (name == NULL) { return NULL; } - SLIST_FOREACH (walk, outputs, slist) { + SLIST_FOREACH(walk, outputs, slist) { if (!strcmp(walk->name, name)) { break; } diff --git a/i3bar/src/workspaces.c b/i3bar/src/workspaces.c index 91307b0c..45b511ff 100644 --- a/i3bar/src/workspaces.c +++ b/i3bar/src/workspaces.c @@ -123,12 +123,12 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t /* Offset may be equal to length, in which case display the number */ params->workspaces_walk->name = (offset < len - ? i3string_from_utf8_with_length(ws_name + offset, len - offset) - : i3string_from_utf8(ws_num)); + ? i3string_from_markup_with_length(ws_name + offset, len - offset) + : i3string_from_markup(ws_num)); } else { /* Default case: just save the name */ - params->workspaces_walk->name = i3string_from_utf8_with_length(ws_name, len); + params->workspaces_walk->name = i3string_from_markup_with_length(ws_name, len); } /* Save its rendered width */ @@ -266,9 +266,9 @@ void free_workspaces(void) { } i3_ws *ws_walk; - SLIST_FOREACH (outputs_walk, outputs, slist) { + SLIST_FOREACH(outputs_walk, outputs, slist) { if (outputs_walk->workspaces != NULL && !TAILQ_EMPTY(outputs_walk->workspaces)) { - TAILQ_FOREACH (ws_walk, outputs_walk->workspaces, tailq) { + TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) { I3STRING_FREE(ws_walk->name); FREE(ws_walk->canonical_name); } diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index b417110a..3a1b8cb2 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -8,6 +8,7 @@ * */ #include +#include #include #include @@ -63,8 +64,7 @@ static i3Font font; int bar_height; /* These are only relevant for XKB, which we only need for grabbing modifiers */ -Display *xkb_dpy; -int xkb_event_base; +int xkb_base; int mod_pressed = 0; /* Because the statusline is the same on all outputs, we have @@ -117,6 +117,12 @@ int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) { return 0; } +uint32_t get_sep_offset(struct status_block *block) { + if (!block->no_separator && block->sep_block_width > 0) + return block->sep_block_width / 2 + block->sep_block_width % 2; + return 0; +} + /* * Redraws the statusline to the buffer * @@ -128,7 +134,7 @@ void refresh_statusline(void) { statusline_width = 0; /* Predict the text width of all blocks (in pixels). */ - TAILQ_FOREACH (block, &statusline_head, blocks) { + TAILQ_FOREACH(block, &statusline_head, blocks) { if (i3string_get_num_bytes(block->full_text) == 0) continue; @@ -148,15 +154,15 @@ void refresh_statusline(void) { block->x_offset = padding_width; break; case ALIGN_CENTER: - block->x_offset = padding_width / logical_px(2); - block->x_append = padding_width / logical_px(2) + padding_width % logical_px(2); + block->x_offset = padding_width / 2; + block->x_append = padding_width / 2 + padding_width % 2; break; } } /* If this is not the last block, add some pixels for a separator. */ if (TAILQ_NEXT(block, blocks) != NULL) - block->width += block->sep_block_width; + statusline_width += block->sep_block_width; statusline_width += block->width + block->x_offset + block->x_append; } @@ -168,30 +174,49 @@ void refresh_statusline(void) { realloc_sl_buffer(); /* Clear the statusline pixmap. */ - xcb_rectangle_t rect = {0, 0, root_screen->width_in_pixels, font.height + logical_px(5)}; + xcb_rectangle_t rect = {0, 0, root_screen->width_in_pixels, bar_height}; xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_clear, 1, &rect); /* Draw the text of each block. */ uint32_t x = 0; - TAILQ_FOREACH (block, &statusline_head, blocks) { + TAILQ_FOREACH(block, &statusline_head, blocks) { if (i3string_get_num_bytes(block->full_text) == 0) continue; + uint32_t fg_color; - uint32_t colorpixel = (block->color ? get_colorpixel(block->color) : colors.bar_fg); - set_font_colors(statusline_ctx, colorpixel, colors.bar_bg); - draw_text(block->full_text, statusline_pm, statusline_ctx, x + block->x_offset, 1, block->width); - x += block->width + block->x_offset + block->x_append; + /* If this block is urgent, draw it with the defined color and border. */ + if (block->urgent) { + fg_color = colors.urgent_ws_fg; - if (TAILQ_NEXT(block, blocks) != NULL && !block->no_separator && block->sep_block_width > 0) { + uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND; + + /* Draw the background */ + uint32_t bg_color = colors.urgent_ws_bg; + uint32_t bg_values[] = { bg_color, bg_color }; + xcb_change_gc(xcb_connection, statusline_ctx, mask, bg_values); + + /* The urgent background “overshoots” by 2 px so that the text that + * is printed onto it will not be look so cut off. */ + xcb_rectangle_t bg_rect = { x - logical_px(2), logical_px(1), block->width + logical_px(4), bar_height - logical_px(2) }; + xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_ctx, 1, &bg_rect); + } else { + fg_color = (block->color ? get_colorpixel(block->color) : colors.bar_fg); + } + + set_font_colors(statusline_ctx, fg_color, colors.bar_bg); + draw_text(block->full_text, statusline_pm, statusline_ctx, x + block->x_offset, 3, block->width); + x += block->width + block->sep_block_width + block->x_offset + block->x_append; + + uint32_t sep_offset = get_sep_offset(block); + if (TAILQ_NEXT(block, blocks) != NULL && sep_offset > 0) { /* This is not the last block, draw a separator. */ - uint32_t sep_offset = block->sep_block_width / 2 + block->sep_block_width % 2; uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_LINE_WIDTH; uint32_t values[] = {colors.sep_fg, colors.bar_bg, logical_px(1)}; xcb_change_gc(xcb_connection, statusline_ctx, mask, values); xcb_poly_line(xcb_connection, XCB_COORD_MODE_ORIGIN, statusline_pm, statusline_ctx, 2, - (xcb_point_t[]) {{x - sep_offset, 2}, - {x - sep_offset, font.height - 2}}); + (xcb_point_t[]) { { x - sep_offset, logical_px(4) }, + { x - sep_offset, bar_height - logical_px(4) } }); } } } @@ -206,7 +231,7 @@ void hide_bars(void) { } i3_output *walk; - SLIST_FOREACH (walk, outputs, slist) { + SLIST_FOREACH(walk, outputs, slist) { if (!walk->active) { continue; } @@ -231,7 +256,7 @@ void unhide_bars(void) { cont_child(); - SLIST_FOREACH (walk, outputs, slist) { + SLIST_FOREACH(walk, outputs, slist) { if (walk->bar == XCB_NONE) { continue; } @@ -303,7 +328,7 @@ void handle_button(xcb_button_press_event_t *event) { /* Determine, which bar was clicked */ i3_output *walk; xcb_window_t bar = event->event; - SLIST_FOREACH (walk, outputs, slist) { + SLIST_FOREACH(walk, outputs, slist) { if (walk->bar == bar) { break; } @@ -326,27 +351,36 @@ void handle_button(xcb_button_press_event_t *event) { /* First calculate width of tray area */ trayclient *trayclient; int tray_width = 0; - TAILQ_FOREACH_REVERSE (trayclient, walk->trayclients, tc_head, tailq) { + TAILQ_FOREACH_REVERSE(trayclient, walk->trayclients, tc_head, tailq) { if (!trayclient->mapped) continue; tray_width += (font.height + logical_px(2)); } + if (tray_width > 0) + tray_width += logical_px(2); int block_x = 0, last_block_x; - int offset = (walk->rect.w - (statusline_width + tray_width)) - logical_px(10); + int offset = walk->rect.w - statusline_width - tray_width - logical_px(4); x = original_x - offset; if (x >= 0) { struct status_block *block; + int sep_offset_remainder = 0; TAILQ_FOREACH (block, &statusline_head, blocks) { + if (i3string_get_num_bytes(block->full_text) == 0) + continue; + last_block_x = block_x; - block_x += block->width + block->x_offset + block->x_append; + block_x += block->width + block->x_offset + block->x_append + + get_sep_offset(block) + sep_offset_remainder; if (x <= block_x && x >= last_block_x) { send_block_clicked(event->detail, block->name, block->instance, event->root_x, event->root_y); return; } + + sep_offset_remainder = block->sep_block_width - get_sep_offset(block); } } x = original_x; @@ -370,6 +404,14 @@ 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_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; @@ -380,6 +422,14 @@ 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; @@ -387,7 +437,7 @@ void handle_button(xcb_button_press_event_t *event) { break; case 1: /* Check if this event regards a workspace button */ - TAILQ_FOREACH (cur_ws, walk->workspaces, tailq) { + TAILQ_FOREACH(cur_ws, walk->workspaces, tailq) { DLOG("x = %d\n", x); if (x >= 0 && x < cur_ws->name_width + logical_px(10)) { break; @@ -398,7 +448,7 @@ void handle_button(xcb_button_press_event_t *event) { /* Otherwise, focus our currently visible workspace if it is not * already focused */ if (cur_ws == NULL) { - TAILQ_FOREACH (cur_ws, walk->workspaces, tailq) { + TAILQ_FOREACH(cur_ws, walk->workspaces, tailq) { if (cur_ws->visible && !cur_ws->focused) break; } @@ -446,6 +496,39 @@ void handle_button(xcb_button_press_event_t *event) { free(buffer); } +/* + * Handle visibility notifications: when none of the bars are visible, e.g. + * if windows are in full-screen on each output, suspend the child process. + * + */ +static void handle_visibility_notify(xcb_visibility_notify_event_t *event) { + bool visible = (event->state != XCB_VISIBILITY_FULLY_OBSCURED); + int num_visible = 0; + i3_output *output; + + SLIST_FOREACH (output, outputs, slist) { + if (!output->active) { + continue; + } + if (output->bar == event->window) { + if (output->visible == visible) { + return; + } + output->visible = visible; + } + num_visible += output->visible; + } + + if (num_visible == 0) { + stop_child(); + } else if (num_visible == visible) { + /* Wake the child only when transitioning from 0 to 1 visible bar. + * We cannot transition from 0 to 2 or more visible bars at once since + * visibility events are delivered to each window separately */ + cont_child(); + } +} + /* * Adjusts the size of the tray window and alignment of the tray clients by * configuring their respective x coordinates. To be called when mapping or @@ -455,12 +538,12 @@ void handle_button(xcb_button_press_event_t *event) { static void configure_trayclients(void) { trayclient *trayclient; i3_output *output; - SLIST_FOREACH (output, outputs, slist) { + SLIST_FOREACH(output, outputs, slist) { if (!output->active) continue; int clients = 0; - TAILQ_FOREACH_REVERSE (trayclient, output->trayclients, tc_head, tailq) { + TAILQ_FOREACH_REVERSE(trayclient, output->trayclients, tc_head, tailq) { if (!trayclient->mapped) continue; clients++; @@ -544,7 +627,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { DLOG("X window %08x requested docking\n", client); i3_output *walk, *output = NULL; - SLIST_FOREACH (walk, outputs, slist) { + SLIST_FOREACH(walk, outputs, slist) { if (!walk->active) continue; if (config.tray_output) { @@ -562,7 +645,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { if (output == NULL && config.tray_output && strcasecmp("primary", config.tray_output) == 0) { - SLIST_FOREACH (walk, outputs, slist) { + SLIST_FOREACH(walk, outputs, slist) { if (!walk->active) continue; DLOG("Falling back to output %s because no primary output is configured\n", walk->name); @@ -650,12 +733,12 @@ static void handle_destroy_notify(xcb_destroy_notify_event_t *event) { DLOG("DestroyNotify for window = %08x, event = %08x\n", event->window, event->event); i3_output *walk; - SLIST_FOREACH (walk, outputs, slist) { + SLIST_FOREACH(walk, outputs, slist) { if (!walk->active) continue; DLOG("checking output %s\n", walk->name); trayclient *trayclient; - TAILQ_FOREACH (trayclient, walk->trayclients, tailq) { + TAILQ_FOREACH(trayclient, walk->trayclients, tailq) { if (trayclient->win != event->window) continue; @@ -679,12 +762,12 @@ static void handle_map_notify(xcb_map_notify_event_t *event) { DLOG("MapNotify for window = %08x, event = %08x\n", event->window, event->event); i3_output *walk; - SLIST_FOREACH (walk, outputs, slist) { + SLIST_FOREACH(walk, outputs, slist) { if (!walk->active) continue; DLOG("checking output %s\n", walk->name); trayclient *trayclient; - TAILQ_FOREACH (trayclient, walk->trayclients, tailq) { + TAILQ_FOREACH(trayclient, walk->trayclients, tailq) { if (trayclient->win != event->window) continue; @@ -707,12 +790,12 @@ static void handle_unmap_notify(xcb_unmap_notify_event_t *event) { DLOG("UnmapNotify for window = %08x, event = %08x\n", event->window, event->event); i3_output *walk; - SLIST_FOREACH (walk, outputs, slist) { + SLIST_FOREACH(walk, outputs, slist) { if (!walk->active) continue; DLOG("checking output %s\n", walk->name); trayclient *trayclient; - TAILQ_FOREACH (trayclient, walk->trayclients, tailq) { + TAILQ_FOREACH(trayclient, walk->trayclients, tailq) { if (trayclient->win != event->window) continue; @@ -739,11 +822,11 @@ static void handle_property_notify(xcb_property_notify_event_t *event) { DLOG("xembed_info updated\n"); trayclient *trayclient = NULL, *walk; i3_output *o_walk; - SLIST_FOREACH (o_walk, outputs, slist) { + SLIST_FOREACH(o_walk, outputs, slist) { if (!o_walk->active) continue; - TAILQ_FOREACH (walk, o_walk->trayclients, tailq) { + TAILQ_FOREACH(walk, o_walk->trayclients, tailq) { if (walk->win != event->window) continue; trayclient = walk; @@ -802,12 +885,12 @@ static void handle_configure_request(xcb_configure_request_event_t *event) { trayclient *trayclient; i3_output *output; - SLIST_FOREACH (output, outputs, slist) { + SLIST_FOREACH(output, outputs, slist) { if (!output->active) continue; int clients = 0; - TAILQ_FOREACH_REVERSE (trayclient, output->trayclients, tc_head, tailq) { + TAILQ_FOREACH_REVERSE(trayclient, output->trayclients, tc_head, tailq) { if (!trayclient->mapped) continue; clients++; @@ -854,7 +937,66 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) { } while ((event = xcb_poll_for_event(xcb_connection)) != NULL) { - switch (event->response_type & ~0x80) { + int type = (event->response_type & ~0x80); + + if (type == xkb_base && xkb_base > -1) { + DLOG("received an xkb event\n"); + + xcb_xkb_state_notify_event_t *state = (xcb_xkb_state_notify_event_t *)event; + if (state->xkbType == XCB_XKB_STATE_NOTIFY) { + int modstate = state->mods & config.modifier; + +#define DLOGMOD(modmask, status) \ + do { \ + switch (modmask) { \ + case ShiftMask: \ + DLOG("ShiftMask got " #status "!\n"); \ + break; \ + case ControlMask: \ + DLOG("ControlMask got " #status "!\n"); \ + break; \ + case Mod1Mask: \ + DLOG("Mod1Mask got " #status "!\n"); \ + break; \ + case Mod2Mask: \ + DLOG("Mod2Mask got " #status "!\n"); \ + break; \ + case Mod3Mask: \ + DLOG("Mod3Mask got " #status "!\n"); \ + break; \ + case Mod4Mask: \ + DLOG("Mod4Mask got " #status "!\n"); \ + break; \ + case Mod5Mask: \ + DLOG("Mod5Mask got " #status "!\n"); \ + break; \ + } \ + } while (0) + + if (modstate != mod_pressed) { + if (modstate == 0) { + DLOGMOD(config.modifier, released); + if (!activated_mode) + hide_bars(); + } else { + DLOGMOD(config.modifier, pressed); + activated_mode = false; + unhide_bars(); + } + mod_pressed = modstate; + } +#undef DLOGMOD + } + + free(event); + continue; + } + + switch (type) { + case XCB_VISIBILITY_NOTIFY: + /* Visibility change: a bar is [un]obscured by other window */ + handle_visibility_notify((xcb_visibility_notify_event_t *)event); + break; case XCB_EXPOSE: /* Expose-events happen, when the window needs to be redrawn */ redraw_bars(); @@ -900,76 +1042,6 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) { void xcb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { } -/* - * We need to bind to the modifier per XKB. Sadly, XCB does not implement this - * - */ -void xkb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { - XkbEvent ev; - int modstate = 0; - - DLOG("Got XKB-Event!\n"); - - while (XPending(xkb_dpy)) { - XNextEvent(xkb_dpy, (XEvent *)&ev); - - if (ev.type != xkb_event_base) { - ELOG("No Xkb-Event!\n"); - continue; - } - - if (ev.any.xkb_type != XkbStateNotify) { - ELOG("No State Notify!\n"); - continue; - } - - unsigned int mods = ev.state.mods; - modstate = mods & config.modifier; - } - -#define DLOGMOD(modmask, status) \ - do { \ - switch (modmask) { \ - case ShiftMask: \ - DLOG("ShiftMask got " #status "!\n"); \ - break; \ - case ControlMask: \ - DLOG("ControlMask got " #status "!\n"); \ - break; \ - case Mod1Mask: \ - DLOG("Mod1Mask got " #status "!\n"); \ - break; \ - case Mod2Mask: \ - DLOG("Mod2Mask got " #status "!\n"); \ - break; \ - case Mod3Mask: \ - DLOG("Mod3Mask got " #status "!\n"); \ - break; \ - case Mod4Mask: \ - DLOG("Mod4Mask got " #status "!\n"); \ - break; \ - case Mod5Mask: \ - DLOG("Mod5Mask got " #status "!\n"); \ - break; \ - } \ - } while (0) - - if (modstate != mod_pressed) { - if (modstate == 0) { - DLOGMOD(config.modifier, released); - if (!activated_mode) - hide_bars(); - } else { - DLOGMOD(config.modifier, pressed); - activated_mode = false; - unhide_bars(); - } - mod_pressed = modstate; - } - -#undef DLOGMOD -} - /* * Early initialization of the connection to X11: Everything which does not * depend on 'config'. @@ -1053,44 +1125,23 @@ char *init_xcb_early() { * */ void register_xkb_keyevents() { - if (xkb_dpy == NULL) { - int xkb_major, xkb_minor, xkb_errbase, xkb_err; - xkb_major = XkbMajorVersion; - xkb_minor = XkbMinorVersion; - - xkb_dpy = XkbOpenDisplay(NULL, - &xkb_event_base, - &xkb_errbase, - &xkb_major, - &xkb_minor, - &xkb_err); - - if (xkb_dpy == NULL) { - ELOG("No XKB!\n"); - exit(EXIT_FAILURE); - } - - if (fcntl(ConnectionNumber(xkb_dpy), F_SETFD, FD_CLOEXEC) == -1) { - ELOG("Could not set FD_CLOEXEC on xkbdpy: %s\n", strerror(errno)); - exit(EXIT_FAILURE); - } - - int i1; - if (!XkbQueryExtension(xkb_dpy, &i1, &xkb_event_base, &xkb_errbase, &xkb_major, &xkb_minor)) { - ELOG("XKB not supported by X-server!\n"); - exit(EXIT_FAILURE); - } - - if (!XkbSelectEvents(xkb_dpy, XkbUseCoreKbd, XkbStateNotifyMask, XkbStateNotifyMask)) { - ELOG("Could not grab Key!\n"); - exit(EXIT_FAILURE); - } - - xkb_io = smalloc(sizeof(ev_io)); - ev_io_init(xkb_io, &xkb_io_cb, ConnectionNumber(xkb_dpy), EV_READ); - ev_io_start(main_loop, xkb_io); - XFlush(xkb_dpy); + const xcb_query_extension_reply_t *extreply; + extreply = xcb_get_extension_data(conn, &xcb_xkb_id); + if (!extreply->present) { + ELOG("xkb is not present on this server\n"); + exit(EXIT_FAILURE); } + DLOG("initializing xcb-xkb\n"); + xcb_xkb_use_extension(conn, XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION); + xcb_xkb_select_events(conn, + XCB_XKB_ID_USE_CORE_KBD, + XCB_XKB_EVENT_TYPE_STATE_NOTIFY, + 0, + XCB_XKB_EVENT_TYPE_STATE_NOTIFY, + 0xff, + 0xff, + NULL); + xkb_base = extreply->first_event; } /* @@ -1098,13 +1149,14 @@ void register_xkb_keyevents() { * */ void deregister_xkb_keyevents() { - if (xkb_dpy != NULL) { - ev_io_stop(main_loop, xkb_io); - XCloseDisplay(xkb_dpy); - close(xkb_io->fd); - FREE(xkb_io); - xkb_dpy = NULL; - } + xcb_xkb_select_events(conn, + XCB_XKB_ID_USE_CORE_KBD, + 0, + 0, + 0, + 0xff, + 0xff, + NULL); } /* @@ -1280,7 +1332,7 @@ void init_tray_colors(void) { void clean_xcb(void) { i3_output *o_walk; free_workspaces(); - SLIST_FOREACH (o_walk, outputs, slist) { + SLIST_FOREACH(o_walk, outputs, slist) { destroy_window(o_walk); FREE(o_walk->trayclients); FREE(o_walk->workspaces); @@ -1435,7 +1487,7 @@ void reconfig_windows(bool redraw_bars) { static bool tray_configured = false; i3_output *walk; - SLIST_FOREACH (walk, outputs, slist) { + SLIST_FOREACH(walk, outputs, slist) { if (!walk->active) { /* If an output is not active, we destroy its bar */ /* FIXME: Maybe we rather want to unmap? */ @@ -1462,6 +1514,12 @@ void reconfig_windows(bool redraw_bars) { values[2] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_BUTTON_PRESS; + if (config.hide_on_modifier == M_DOCK) { + /* If the bar is normally visible, catch visibility change events to suspend + * the status process when the bar is obscured by full-screened windows. */ + values[2] |= XCB_EVENT_MASK_VISIBILITY_CHANGE; + walk->visible = true; + } xcb_void_cookie_t win_cookie = xcb_create_window_checked(xcb_connection, root_screen->root_depth, walk->bar, @@ -1595,7 +1653,7 @@ void reconfig_windows(bool redraw_bars) { * VGA-1 but output == [HDMI-1]). */ i3_output *output; - SLIST_FOREACH (output, outputs, slist) { + SLIST_FOREACH(output, outputs, slist) { if (strcasecmp(output->name, tray_output) == 0 || (strcasecmp(tray_output, "primary") == 0 && output->primary)) { init_tray(); @@ -1684,7 +1742,7 @@ void draw_bars(bool unhide) { refresh_statusline(); i3_output *outputs_walk; - SLIST_FOREACH (outputs_walk, outputs, slist) { + SLIST_FOREACH(outputs_walk, outputs, slist) { if (!outputs_walk->active) { DLOG("Output %s inactive, skipping...\n", outputs_walk->name); continue; @@ -1714,29 +1772,29 @@ void draw_bars(bool unhide) { * position */ trayclient *trayclient; int traypx = 0; - TAILQ_FOREACH (trayclient, outputs_walk->trayclients, tailq) { + TAILQ_FOREACH(trayclient, outputs_walk->trayclients, tailq) { if (!trayclient->mapped) continue; /* We assume the tray icons are quadratic (we use the font * *height* as *width* of the icons) because we configured them * like this. */ - traypx += font.height + 2; + traypx += font.height + logical_px(2); } /* Add 2px of padding if there are any tray icons */ if (traypx > 0) - traypx += 2; + traypx += logical_px(2); xcb_copy_area(xcb_connection, statusline_pm, outputs_walk->buffer, outputs_walk->bargc, - MAX(0, (int16_t)(statusline_width - outputs_walk->rect.w + 4)), 0, - MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - traypx - 4)), 3, - MIN(outputs_walk->rect.w - traypx - 4, (int)statusline_width), font.height + 2); + MAX(0, (int16_t)(statusline_width - outputs_walk->rect.w + logical_px(4))), 0, + MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - traypx - logical_px(4))), 0, + MIN(outputs_walk->rect.w - traypx - logical_px(4), (int)statusline_width), bar_height); } if (!config.disable_ws) { i3_ws *ws_walk; - TAILQ_FOREACH (ws_walk, outputs_walk->workspaces, tailq) { + TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) { DLOG("Drawing Button for WS %s at x = %d, len = %d\n", i3string_as_utf8(ws_walk->name), i, ws_walk->name_width); uint32_t fg_color = colors.inactive_ws_fg; @@ -1852,7 +1910,7 @@ void draw_bars(bool unhide) { */ void redraw_bars(void) { i3_output *outputs_walk; - SLIST_FOREACH (outputs_walk, outputs, slist) { + SLIST_FOREACH(outputs_walk, outputs, slist) { if (!outputs_walk->active) { continue; } diff --git a/include/atoms.xmacro b/include/atoms.xmacro index 90b02616..1f28c014 100644 --- a/include/atoms.xmacro +++ b/include/atoms.xmacro @@ -1,6 +1,7 @@ xmacro(_NET_SUPPORTED) xmacro(_NET_SUPPORTING_WM_CHECK) xmacro(_NET_WM_NAME) +xmacro(_NET_WM_MOVERESIZE) xmacro(_NET_WM_STATE_FULLSCREEN) xmacro(_NET_WM_STATE_DEMANDS_ATTENTION) xmacro(_NET_WM_STATE_MODAL) @@ -16,7 +17,11 @@ xmacro(_NET_WM_STRUT_PARTIAL) xmacro(_NET_CLIENT_LIST) xmacro(_NET_CLIENT_LIST_STACKING) xmacro(_NET_CURRENT_DESKTOP) +xmacro(_NET_NUMBER_OF_DESKTOPS) +xmacro(_NET_DESKTOP_NAMES) +xmacro(_NET_DESKTOP_VIEWPORT) xmacro(_NET_ACTIVE_WINDOW) +xmacro(_NET_CLOSE_WINDOW) xmacro(_NET_STARTUP_ID) xmacro(_NET_WORKAREA) xmacro(WM_PROTOCOLS) @@ -34,3 +39,4 @@ xmacro(I3_PID) xmacro(_NET_REQUEST_FRAME_EXTENTS) xmacro(_NET_FRAME_EXTENTS) xmacro(_MOTIF_WM_HINTS) +xmacro(WM_CHANGE_STATE) diff --git a/include/bindings.h b/include/bindings.h index e51f5e99..19345f8c 100644 --- a/include/bindings.h +++ b/include/bindings.h @@ -24,7 +24,7 @@ const char *DEFAULT_BINDING_MODE; * */ Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code, - const char *release, const char *command, const char *mode); + const char *release, const char *whole_window, const char *command, const char *mode); /** * Grab the bound keys (tell X to send us keypress events for those keycodes) @@ -61,9 +61,15 @@ void switch_mode(const char *new_mode); void check_for_duplicate_bindings(struct context *context); /** - * Runs the given binding and handles parse errors. Returns a CommandResult for - * running the binding's command. Caller should render tree if - * needs_tree_render is true. Free with command_result_free(). + * Frees the binding. If bind is null, it simply returns. + */ +void binding_free(Binding *bind); + +/** + * Runs the given binding and handles parse errors. If con is passed, it will + * execute the command binding with that container selected by criteria. + * Returns a CommandResult for running the binding's command. Caller should + * render tree if needs_tree_render is true. Free with command_result_free(). * */ -CommandResult *run_binding(Binding *bind); +CommandResult *run_binding(Binding *bind, Con *con); diff --git a/include/commands.h b/include/commands.h index cb687890..780a9e8e 100644 --- a/include/commands.h +++ b/include/commands.h @@ -187,10 +187,10 @@ void cmd_focus_level(I3_CMD, char *level); void cmd_focus(I3_CMD); /** - * Implementation of 'fullscreen [global]'. + * Implementation of 'fullscreen [enable|disable|toggle] [global]'. * */ -void cmd_fullscreen(I3_CMD, char *fullscreen_mode); +void cmd_fullscreen(I3_CMD, char *action, char *fullscreen_mode); /** * Implementation of 'move [ [px]]'. diff --git a/include/commands_parser.h b/include/commands_parser.h index 6e531e9b..cfa44dd5 100644 --- a/include/commands_parser.h +++ b/include/commands_parser.h @@ -44,6 +44,14 @@ struct CommandResult { bool needs_tree_render; }; +/** + * Parses a string (or word, if as_word is true). Extracted out of + * parse_command so that it can be used in src/workspace.c for interpreting + * workspace commands. + * + */ +char *parse_string(const char **walk, bool as_word); + /** * Parses and executes the given command. If a caller-allocated yajl_gen is * passed, a json reply will be generated in the format specified by the ipc diff --git a/include/con.h b/include/con.h index b8fd60ad..b025adab 100644 --- a/include/con.h +++ b/include/con.h @@ -18,7 +18,6 @@ */ Con *con_new_skeleton(Con *parent, i3Window *window); - /* A wrapper for con_new_skeleton, to retain the old con_new behaviour * */ @@ -173,6 +172,18 @@ void con_fix_percent(Con *con); */ void con_toggle_fullscreen(Con *con, int fullscreen_mode); +/** + * Enables fullscreen mode for the given container, if necessary. + * + */ +void con_enable_fullscreen(Con *con, fullscreen_mode_t fullscreen_mode); + +/** + * Disables fullscreen mode for the given container, if necessary. + * + */ +void con_disable_fullscreen(Con *con); + /** * Moves the given container to the currently focused container on the given * workspace. @@ -345,3 +356,9 @@ void con_set_urgency(Con *con, bool urgent); * */ char *con_get_tree_representation(Con *con); + +/** + * force parent split containers to be redrawn + * + */ +void con_force_split_parents_redraw(Con *con); diff --git a/include/config.h b/include/config.h index 71b37a80..dea26d96 100644 --- a/include/config.h +++ b/include/config.h @@ -241,10 +241,13 @@ struct Barconfig { char *socket_path; /** Bar display mode (hide unless modifier is pressed or show in dock mode or always hide in invisible mode) */ - enum { M_DOCK = 0, M_HIDE = 1, M_INVISIBLE = 2 } mode; + enum { M_DOCK = 0, + M_HIDE = 1, + M_INVISIBLE = 2 } mode; /* The current hidden_state of the bar, which indicates whether it is hidden or shown */ - enum { S_HIDE = 0, S_SHOW = 1 } hidden_state; + enum { S_HIDE = 0, + S_SHOW = 1 } hidden_state; /** Bar modifier (to show bar when in hide mode). */ enum { @@ -258,8 +261,17 @@ 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; + /** Bar position (bottom by default). */ - enum { P_BOTTOM = 0, P_TOP = 1 } position; + enum { P_BOTTOM = 0, + P_TOP = 1 } position; /** Command that should be run to execute i3bar, give a full path if i3bar is not * in your $PATH. @@ -314,6 +326,20 @@ struct Barconfig { TAILQ_ENTRY(Barconfig) configs; }; +/** + * Finds the configuration file to use (either the one specified by + * override_configpath), the user’s one or the system default) and calls + * parse_file(). + * + * If you specify override_configpath, only this path is used to look for a + * configuration file. + * + * If use_nagbar is false, don't try to start i3-nagbar but log the errors to + * stdout/stderr instead. + * + */ +bool parse_configuration(const char *override_configpath, bool use_nagbar); + /** * Reads the configuration from ~/.i3/config or /etc/i3/config if not found. * diff --git a/include/config_directives.h b/include/config_directives.h index 9dedad15..0f1a6620 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -24,7 +24,7 @@ uint32_t modifiers_from_str(const char *str); * using 'call cfg_foo()' in parser-specs/.*.spec. Useful so that we don’t need * to repeat the definition all the time. */ #define CFGFUN(name, ...) \ - void cfg_ ## name (I3_CFG, ## __VA_ARGS__ ) + void cfg_##name(I3_CFG, ##__VA_ARGS__) /* The following functions are called by the config parser, see * parser-specs/config.spec. They get the parsed parameters and store them in @@ -61,10 +61,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 *command); +CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, 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 *command); +CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *whole_window, const char *command); CFGFUN(bar_font, const char *font); CFGFUN(bar_mode, const char *mode); @@ -73,6 +73,8 @@ CFGFUN(bar_id, const char *bar_id); CFGFUN(bar_output, const char *output); 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_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); diff --git a/include/config_parser.h b/include/config_parser.h index e18e5cf2..9fc3bf2f 100644 --- a/include/config_parser.h +++ b/include/config_parser.h @@ -33,7 +33,10 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context); /** * Parses the given file by first replacing the variables, then calling - * parse_config and possibly launching i3-nagbar. + * parse_config and launching i3-nagbar if use_nagbar is true. + * + * The return value is a boolean indicating whether there were errors during + * parsing. * */ -void parse_file(const char *f); +bool parse_file(const char *f, bool use_nagbar); diff --git a/include/data.h b/include/data.h index 6ac228ec..8f2c197d 100644 --- a/include/data.h +++ b/include/data.h @@ -47,34 +47,42 @@ typedef struct Match Match; typedef struct Assignment Assignment; typedef struct Window i3Window; - /****************************************************************************** * Helper types *****************************************************************************/ -typedef enum { D_LEFT, D_RIGHT, D_UP, D_DOWN } direction_t; -typedef enum { NO_ORIENTATION = 0, HORIZ, VERT } orientation_t; -typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_PIXEL = 2 } border_style_t; +typedef enum { D_LEFT, + D_RIGHT, + D_UP, + D_DOWN } direction_t; +typedef enum { NO_ORIENTATION = 0, + HORIZ, + VERT } orientation_t; +typedef enum { BS_NORMAL = 0, + BS_NONE = 1, + BS_PIXEL = 2 } border_style_t; /** parameter to specify whether tree_close() and x_window_kill() should kill * only this specific window or the whole X11 client */ -typedef enum { DONT_KILL_WINDOW = 0, KILL_WINDOW = 1, KILL_CLIENT = 2 } kill_window_t; +typedef enum { DONT_KILL_WINDOW = 0, + KILL_WINDOW = 1, + KILL_CLIENT = 2 } kill_window_t; /** describes if the window is adjacent to the output (physical screen) edges. */ typedef enum { ADJ_NONE = 0, ADJ_LEFT_SCREEN_EDGE = (1 << 0), ADJ_RIGHT_SCREEN_EDGE = (1 << 1), ADJ_UPPER_SCREEN_EDGE = (1 << 2), - ADJ_LOWER_SCREEN_EDGE = (1 << 4)} adjacent_t; + 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_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) }; @@ -247,6 +255,11 @@ 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 any part of the window, not just the + * title bar (default). */ + bool whole_window; + uint32_t number_keycodes; /** Keycode to bind */ @@ -267,7 +280,6 @@ struct Binding { * This is an array of number_keycodes size. */ xcb_keycode_t *translated_to; - /** Command, like in command mode */ char *command; @@ -367,7 +379,9 @@ struct Window { bool doesnt_accept_focus; /** Whether the window says it is a dock window */ - enum { W_NODOCK = 0, W_DOCK_TOP = 1, W_DOCK_BOTTOM = 2 } dock; + enum { W_NODOCK = 0, + W_DOCK_TOP = 1, + W_DOCK_BOTTOM = 2 } dock; /** When this window was marked urgent. 0 means not urgent */ struct timeval urgent; @@ -407,7 +421,9 @@ struct Match { M_DOCK_BOTTOM = 3 } dock; xcb_window_t id; - enum { M_ANY = 0, M_TILING, M_FLOATING } floating; + enum { M_ANY = 0, + M_TILING, + M_FLOATING } floating; Con *con_id; /* Where the window looking for a match should be inserted: @@ -419,7 +435,9 @@ struct Match { * (dockareas) * */ - enum { M_HERE = 0, M_ASSIGN_WS, M_BELOW } insert_where; + enum { M_HERE = 0, + M_ASSIGN_WS, + M_BELOW } insert_where; TAILQ_ENTRY(Match) matches; @@ -450,10 +468,10 @@ struct Assignment { * */ enum { - A_ANY = 0, - A_COMMAND = (1 << 0), + A_ANY = 0, + A_COMMAND = (1 << 0), A_TO_WORKSPACE = (1 << 1), - A_TO_OUTPUT = (1 << 2) + A_TO_OUTPUT = (1 << 2) } type; /** the criteria to check if a window matches */ @@ -470,7 +488,9 @@ struct Assignment { }; /** Fullscreen modes. Used by Con.fullscreen_mode. */ -typedef enum { CF_NONE = 0, CF_OUTPUT = 1, CF_GLOBAL = 2 } fullscreen_mode_t; +typedef enum { CF_NONE = 0, + CF_OUTPUT = 1, + CF_GLOBAL = 2 } fullscreen_mode_t; /** * A 'Con' represents everything from the X11 root window down to a single X11 window. @@ -596,7 +616,7 @@ struct Con { TAILQ_ENTRY(Con) floating_windows; /** callbacks */ - void(*on_remove_child)(Con *); + void (*on_remove_child)(Con *); enum { /* Not a scratchpad window. */ diff --git a/include/ewmh.h b/include/ewmh.h index 46d6c985..3b580628 100644 --- a/include/ewmh.h +++ b/include/ewmh.h @@ -18,6 +18,24 @@ */ void ewmh_update_current_desktop(void); +/** + * Updates _NET_NUMBER_OF_DESKTOPS which we interpret as the number of + * noninternal workspaces. + */ +void ewmh_update_number_of_desktops(void); + +/** + * Updates _NET_DESKTOP_NAMES: "The names of all virtual desktops. This is a + * list of NULL-terminated strings in UTF-8 encoding" + */ +void ewmh_update_desktop_names(void); + +/** + * Updates _NET_DESKTOP_VIEWPORT, which is an array of pairs of cardinals that + * define the top left corner of each desktop's viewport. + */ +void ewmh_update_desktop_viewport(void); + /** * Updates _NET_ACTIVE_WINDOW with the currently focused window. * diff --git a/include/floating.h b/include/floating.h index fa3bdcc3..bea5f7a2 100644 --- a/include/floating.h +++ b/include/floating.h @@ -12,18 +12,18 @@ #include "tree.h" /** Callback for dragging */ -typedef void(*callback_t)(Con*, Rect*, uint32_t, uint32_t, const void*); +typedef void (*callback_t)(Con *, Rect *, uint32_t, uint32_t, const void *); /** Macro to create a callback function for dragging */ -#define DRAGGING_CB(name) \ - static void name(Con *con, Rect *old_rect, uint32_t new_x, \ - uint32_t new_y, const void *extra) +#define DRAGGING_CB(name) \ + static void name(Con *con, Rect *old_rect, uint32_t new_x, \ + uint32_t new_y, const void *extra) /** On which border was the dragging initiated? */ -typedef enum { BORDER_LEFT = (1 << 0), - BORDER_RIGHT = (1 << 1), - BORDER_TOP = (1 << 2), - BORDER_BOTTOM = (1 << 3)} border_t; +typedef enum { BORDER_LEFT = (1 << 0), + BORDER_RIGHT = (1 << 1), + BORDER_TOP = (1 << 2), + BORDER_BOTTOM = (1 << 3) } border_t; /** * Enables floating mode for the given container by detaching it from its @@ -164,8 +164,8 @@ typedef enum { * */ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, - xcb_window_t confine_to, border_t border, int cursor, - callback_t callback, const void *extra); + xcb_window_t confine_to, border_t border, int cursor, + callback_t callback, const void *extra); /** * Repositions the CT_FLOATING_CON to have the coordinates specified by diff --git a/include/handlers.h b/include/handlers.h index db7d06b5..82f6b982 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -13,6 +13,7 @@ #include extern int randr_base; +extern int xkb_base; /** * Adds the given sequence to the list of events which are ignored. diff --git a/include/i3.h b/include/i3.h index 4ed0d8d5..5ca87541 100644 --- a/include/i3.h +++ b/include/i3.h @@ -13,6 +13,7 @@ #include #include +#include #include @@ -42,7 +43,7 @@ extern xcb_key_symbols_t *keysyms; extern char **start_argv; extern Display *xlibdpy, *xkbdpy; extern int xkb_current_group; -extern TAILQ_HEAD(bindings_head, Binding) *bindings; +extern TAILQ_HEAD(bindings_head, Binding) * bindings; extern TAILQ_HEAD(autostarts_head, Autostart) autostarts; extern TAILQ_HEAD(autostarts_always_head, Autostart) autostarts_always; extern TAILQ_HEAD(ws_assignments_head, Workspace_Assignment) ws_assignments; diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 94a39904..f1b50dec 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -17,7 +17,7 @@ typedef struct i3_ipc_header { char magic[6]; uint32_t size; uint32_t type; -} __attribute__ ((packed)) i3_ipc_header_t; +} __attribute__((packed)) i3_ipc_header_t; /* * Messages from clients to i3 @@ -25,31 +25,31 @@ typedef struct i3_ipc_header { */ /** Never change this, only on major IPC breakage (don’t do that) */ -#define I3_IPC_MAGIC "i3-ipc" +#define I3_IPC_MAGIC "i3-ipc" /** The payload of the message will be interpreted as a command */ -#define I3_IPC_MESSAGE_TYPE_COMMAND 0 +#define I3_IPC_MESSAGE_TYPE_COMMAND 0 /** Requests the current workspaces from i3 */ -#define I3_IPC_MESSAGE_TYPE_GET_WORKSPACES 1 +#define I3_IPC_MESSAGE_TYPE_GET_WORKSPACES 1 /** Subscribe to the specified events */ -#define I3_IPC_MESSAGE_TYPE_SUBSCRIBE 2 +#define I3_IPC_MESSAGE_TYPE_SUBSCRIBE 2 /** Requests the current outputs from i3 */ -#define I3_IPC_MESSAGE_TYPE_GET_OUTPUTS 3 +#define I3_IPC_MESSAGE_TYPE_GET_OUTPUTS 3 /** Requests the tree layout from i3 */ -#define I3_IPC_MESSAGE_TYPE_GET_TREE 4 +#define I3_IPC_MESSAGE_TYPE_GET_TREE 4 /** Request the current defined marks from i3 */ -#define I3_IPC_MESSAGE_TYPE_GET_MARKS 5 +#define I3_IPC_MESSAGE_TYPE_GET_MARKS 5 /** Request the configuration for a specific 'bar' */ -#define I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG 6 +#define I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG 6 /** Request the i3 version */ -#define I3_IPC_MESSAGE_TYPE_GET_VERSION 7 +#define I3_IPC_MESSAGE_TYPE_GET_VERSION 7 /* * Messages from i3 to clients @@ -57,46 +57,49 @@ typedef struct i3_ipc_header { */ /** Command reply type */ -#define I3_IPC_REPLY_TYPE_COMMAND 0 +#define I3_IPC_REPLY_TYPE_COMMAND 0 /** Workspaces reply type */ -#define I3_IPC_REPLY_TYPE_WORKSPACES 1 +#define I3_IPC_REPLY_TYPE_WORKSPACES 1 /** Subscription reply type */ -#define I3_IPC_REPLY_TYPE_SUBSCRIBE 2 +#define I3_IPC_REPLY_TYPE_SUBSCRIBE 2 /** Outputs reply type */ -#define I3_IPC_REPLY_TYPE_OUTPUTS 3 +#define I3_IPC_REPLY_TYPE_OUTPUTS 3 /** Tree reply type */ -#define I3_IPC_REPLY_TYPE_TREE 4 +#define I3_IPC_REPLY_TYPE_TREE 4 /** Marks reply type */ -#define I3_IPC_REPLY_TYPE_MARKS 5 +#define I3_IPC_REPLY_TYPE_MARKS 5 /** Bar config reply type */ -#define I3_IPC_REPLY_TYPE_BAR_CONFIG 6 +#define I3_IPC_REPLY_TYPE_BAR_CONFIG 6 /** i3 version reply type */ -#define I3_IPC_REPLY_TYPE_VERSION 7 +#define I3_IPC_REPLY_TYPE_VERSION 7 /* * Events from i3 to clients. Events have the first bit set high. * */ -#define I3_IPC_EVENT_MASK (1 << 31) +#define I3_IPC_EVENT_MASK (1 << 31) /* The workspace event will be triggered upon changes in the workspace list */ -#define I3_IPC_EVENT_WORKSPACE (I3_IPC_EVENT_MASK | 0) +#define I3_IPC_EVENT_WORKSPACE (I3_IPC_EVENT_MASK | 0) /* The output event will be triggered upon changes in the output list */ -#define I3_IPC_EVENT_OUTPUT (I3_IPC_EVENT_MASK | 1) +#define I3_IPC_EVENT_OUTPUT (I3_IPC_EVENT_MASK | 1) /* The output event will be triggered upon mode changes */ -#define I3_IPC_EVENT_MODE (I3_IPC_EVENT_MASK | 2) +#define I3_IPC_EVENT_MODE (I3_IPC_EVENT_MASK | 2) /* The window event will be triggered upon window changes */ -#define I3_IPC_EVENT_WINDOW (I3_IPC_EVENT_MASK | 3) +#define I3_IPC_EVENT_WINDOW (I3_IPC_EVENT_MASK | 3) /** Bar config update will be triggered to update the bar config */ -#define I3_IPC_EVENT_BARCONFIG_UPDATE (I3_IPC_EVENT_MASK | 4) +#define I3_IPC_EVENT_BARCONFIG_UPDATE (I3_IPC_EVENT_MASK | 4) + +/** The binding event will be triggered when bindings run */ +#define I3_IPC_EVENT_BINDING (I3_IPC_EVENT_MASK | 5) diff --git a/include/ipc.h b/include/ipc.h index 5fa4334a..96a60a1f 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -23,13 +23,13 @@ extern char *current_socketpath; typedef struct ipc_client { - int fd; + int fd; - /* The events which this client wants to receive */ - int num_events; - char **events; + /* The events which this client wants to receive */ + int num_events; + char **events; - TAILQ_ENTRY(ipc_client) clients; + TAILQ_ENTRY(ipc_client) clients; } ipc_client; /* @@ -42,13 +42,13 @@ typedef struct ipc_client { * message_type is the type of the message as the sender specified it. * */ -typedef void(*handler_t)(int, uint8_t*, int, uint32_t, uint32_t); +typedef void (*handler_t)(int, uint8_t *, int, uint32_t, uint32_t); /* Macro to declare a callback */ -#define IPC_HANDLER(name) \ - static void handle_ ## name (int fd, uint8_t *message, \ - int size, uint32_t message_size, \ - uint32_t message_type) +#define IPC_HANDLER(name) \ + static void handle_##name(int fd, uint8_t *message, \ + int size, uint32_t message_size, \ + uint32_t message_type) /** * Emulates mkdir -p (creates any missing folders) @@ -89,11 +89,17 @@ void ipc_shutdown(void); void dump_node(yajl_gen gen, Con *con, bool inplace_restart); /** - * For the workspace "focus" event we send, along the usual "change" field, - * also the current and previous workspace, in "current" and "old" - * respectively. + * Generates a json workspace event. Returns a dynamically allocated yajl + * generator. Free with yajl_gen_free(). */ -void ipc_send_workspace_focus_event(Con *current, Con *old); +yajl_gen ipc_marshal_workspace_event(const char *change, Con *current, Con *old); + +/** + * For the workspace events we send, along with the usual "change" field, also + * the workspace container in "current". For focus events, we send the + * previously focused workspace in "old". + */ +void ipc_send_workspace_event(const char *change, Con *current, Con *old); /** * For the window events we send, along the usual "change" field, @@ -105,3 +111,8 @@ void ipc_send_window_event(const char *property, Con *con); * For the barconfig update events, we send the serialized barconfig. */ void ipc_send_barconfig_update_event(Barconfig *barconfig); + +/** + * For the binding events, we send the serialized binding struct. + */ +void ipc_send_binding_event(const char *event_type, Binding *bind); diff --git a/include/libi3.h b/include/libi3.h index dc5d537f..c1a11dfc 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -72,17 +72,17 @@ struct Font { * infrastructure, we define a fallback. */ #if !defined(LOG) void verboselog(char *fmt, ...) - __attribute__ ((format (printf, 1, 2))); + __attribute__((format(printf, 1, 2))); #define LOG(fmt, ...) verboselog("[libi3] " __FILE__ " " fmt, ##__VA_ARGS__) #endif #if !defined(ELOG) void errorlog(char *fmt, ...) - __attribute__ ((format (printf, 1, 2))); + __attribute__((format(printf, 1, 2))); #define ELOG(fmt, ...) errorlog("[libi3] ERROR: " fmt, ##__VA_ARGS__) #endif #if !defined(DLOG) void debuglog(char *fmt, ...) - __attribute__ ((format (printf, 1, 2))); + __attribute__((format(printf, 1, 2))); #define DLOG(fmt, ...) debuglog("%s:%s:%d - " fmt, I3__FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__) #endif @@ -141,6 +141,12 @@ int sasprintf(char **strp, const char *fmt, ...); */ i3String *i3string_from_utf8(const char *from_utf8); +/** + * Build an i3String from an UTF-8 encoded string in Pango markup. + * + */ +i3String *i3string_from_markup(const char *from_markup); + /** * Build an i3String from an UTF-8 encoded string with fixed length. * To be used when no proper NUL-terminaison is available. @@ -149,6 +155,13 @@ i3String *i3string_from_utf8(const char *from_utf8); */ i3String *i3string_from_utf8_with_length(const char *from_utf8, size_t num_bytes); +/** + * Build an i3String from an UTF-8 encoded string in Pango markup with fixed + * length. + * + */ +i3String *i3string_from_markup_with_length(const char *from_markup, size_t num_bytes); + /** * Build an i3String from an UCS-2 encoded string. * Returns the newly-allocated i3String. @@ -167,13 +180,13 @@ void i3string_free(i3String *str); * to prevent accidentally using freed memory. * */ -#define I3STRING_FREE(str) \ -do { \ - if (str != NULL) { \ - i3string_free(str); \ - str = NULL; \ - } \ -} while (0) +#define I3STRING_FREE(str) \ + do { \ + if (str != NULL) { \ + i3string_free(str); \ + str = NULL; \ + } \ + } while (0) /** * Returns the UTF-8 encoded version of the i3String. @@ -193,6 +206,11 @@ const xcb_char2b_t *i3string_as_ucs2(i3String *str); */ size_t i3string_get_num_bytes(i3String *str); +/** + * Whether the given i3String is in Pango markup. + */ +bool i3string_is_markup(i3String *str); + /** * Returns the number of glyphs in an i3String. * @@ -285,12 +303,13 @@ uint32_t aio_get_mod_mask_for(uint32_t keysym, xcb_key_symbols_t *symbols); * */ uint32_t get_mod_mask_for(uint32_t keysym, - xcb_key_symbols_t *symbols, - xcb_get_modifier_mapping_reply_t *modmap_reply); + xcb_key_symbols_t *symbols, + xcb_get_modifier_mapping_reply_t *modmap_reply); /** * Loads a font for usage, also getting its height. If fallback is true, - * the fonts 'fixed' or '-misc-*' will be loaded instead of exiting. + * the fonts 'fixed' or '-misc-*' will be loaded instead of exiting. If any + * font was previously loaded, it will be freed. * */ i3Font load_font(const char *pattern, const bool fallback); @@ -302,7 +321,8 @@ i3Font load_font(const char *pattern, const bool fallback); void set_font(i3Font *font); /** - * Frees the resources taken by the current font. + * Frees the resources taken by the current font. If no font was previously + * loaded, it simply returns. * */ void free_font(void); @@ -338,14 +358,14 @@ void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background * */ void draw_text(i3String *text, xcb_drawable_t drawable, - xcb_gcontext_t gc, int x, int y, int max_width); + xcb_gcontext_t gc, int x, int y, int max_width); /** * ASCII version of draw_text to print static strings. * */ void draw_text_ascii(const char *text, xcb_drawable_t drawable, - xcb_gcontext_t gc, int x, int y, int max_width); + xcb_gcontext_t gc, int x, int y, int max_width); /** * Predict the text width in pixels for the given text. Text must be diff --git a/include/log.h b/include/log.h index 2400092b..a5086dbe 100644 --- a/include/log.h +++ b/include/log.h @@ -78,14 +78,14 @@ void set_verbosity(bool _verbose); * */ void debuglog(char *fmt, ...) - __attribute__ ((format (printf, 1, 2))); + __attribute__((format(printf, 1, 2))); /** * Logs the given message to stdout while prefixing the current time to it. * */ void errorlog(char *fmt, ...) - __attribute__ ((format (printf, 1, 2))); + __attribute__((format(printf, 1, 2))); /** * Logs the given message to stdout while prefixing the current time to it, @@ -93,7 +93,7 @@ void errorlog(char *fmt, ...) * */ void verboselog(char *fmt, ...) - __attribute__ ((format (printf, 1, 2))); + __attribute__((format(printf, 1, 2))); /** * Deletes the unused log files. Useful if i3 exits immediately, eg. diff --git a/include/move.h b/include/move.h index 5c8a7d20..939665ec 100644 --- a/include/move.h +++ b/include/move.h @@ -10,8 +10,8 @@ #pragma once /** - * Moves the current container in the given direction (TOK_LEFT, TOK_RIGHT, + * Moves the given container in the given direction (TOK_LEFT, TOK_RIGHT, * TOK_UP, TOK_DOWN from cmdparse.l) * */ -void tree_move(int direction); +void tree_move(Con *con, int direction); diff --git a/include/queue.h b/include/queue.h index 2307149b..9fb9ba5e 100644 --- a/include/queue.h +++ b/include/queue.h @@ -90,448 +90,468 @@ /* * Singly-linked List definitions. */ -#define SLIST_HEAD(name, type) \ -struct name { \ - struct type *slh_first; /* first element */ \ -} +#define SLIST_HEAD(name, type) \ + struct name { \ + struct type *slh_first; /* first element */ \ + } -#define SLIST_HEAD_INITIALIZER(head) \ - { NULL } +#define SLIST_HEAD_INITIALIZER(head) \ + { NULL } -#define SLIST_ENTRY(type) \ -struct { \ - struct type *sle_next; /* next element */ \ -} +#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_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(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)) +#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_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_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_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_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(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) +#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(name, type) \ + struct name { \ + struct type *lh_first; /* first element */ \ + } -#define LIST_HEAD_INITIALIZER(head) \ - { NULL } +#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 */ \ -} +#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_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)) +#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_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_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_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_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_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) +#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(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_HEAD_INITIALIZER(head) \ + { NULL, &(head).sqh_first } -#define SIMPLEQ_ENTRY(type) \ -struct { \ - struct type *sqe_next; /* next element */ \ -} +#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_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)) +#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_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_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_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_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) +#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(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_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 */ \ -} +#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)) +#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_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(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)) +#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_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_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_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_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_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_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) +#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) /* Swaps two consecutive elements. 'second' *MUST* follow 'first' */ -#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) +#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) /* * 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(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_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 */ \ -} +#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_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(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)) +#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_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_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_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_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_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_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) +#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/include/sd-daemon.h b/include/sd-daemon.h index 8746a3ad..543580ec 100644 --- a/include/sd-daemon.h +++ b/include/sd-daemon.h @@ -68,15 +68,15 @@ extern "C" { #ifndef _sd_printf_attr_ #if __GNUC__ >= 4 -#define _sd_printf_attr_(a,b) __attribute__ ((format (printf, a, b))) +#define _sd_printf_attr_(a, b) __attribute__((format(printf, a, b))) #else -#define _sd_printf_attr_(a,b) +#define _sd_printf_attr_(a, b) #endif #endif #ifndef _sd_hidden_ #if (__GNUC__ >= 4) && !defined(SD_EXPORT_SYMBOLS) -#define _sd_hidden_ __attribute__ ((visibility("hidden"))) +#define _sd_hidden_ __attribute__((visibility("hidden"))) #else #define _sd_hidden_ #endif @@ -89,14 +89,14 @@ extern "C" { This is similar to printk() usage in the kernel. */ -#define SD_EMERG "<0>" /* system is unusable */ -#define SD_ALERT "<1>" /* action must be taken immediately */ -#define SD_CRIT "<2>" /* critical conditions */ -#define SD_ERR "<3>" /* error conditions */ -#define SD_WARNING "<4>" /* warning conditions */ -#define SD_NOTICE "<5>" /* normal but significant condition */ -#define SD_INFO "<6>" /* informational */ -#define SD_DEBUG "<7>" /* debug-level messages */ +#define SD_EMERG "<0>" /* system is unusable */ +#define SD_ALERT "<1>" /* action must be taken immediately */ +#define SD_CRIT "<2>" /* critical conditions */ +#define SD_ERR "<3>" /* error conditions */ +#define SD_WARNING "<4>" /* warning conditions */ +#define SD_NOTICE "<5>" /* normal but significant condition */ +#define SD_INFO "<6>" /* informational */ +#define SD_DEBUG "<7>" /* debug-level messages */ /* The first passed file descriptor is fd 3 */ #define SD_LISTEN_FDS_START 3 @@ -242,7 +242,7 @@ int sd_notify(int unset_environment, const char *state) _sd_hidden_; See sd_notifyf(3) for more information. */ -int sd_notifyf(int unset_environment, const char *format, ...) _sd_printf_attr_(2,3) _sd_hidden_; +int sd_notifyf(int unset_environment, const char *format, ...) _sd_printf_attr_(2, 3) _sd_hidden_; /* Returns > 0 if the system was booted with systemd. Returns < 0 on diff --git a/include/startup.h b/include/startup.h index fb017103..2f28baa7 100644 --- a/include/startup.h +++ b/include/startup.h @@ -21,8 +21,8 @@ * (immediately), the application is reparented to init (process-id 1), which * correctly handles childs, so we don’t have to do it :-). * - * The shell is determined by looking for the SHELL environment variable. If - * it does not exist, /bin/sh is used. + * The shell used to start applications is the system's bourne shell (i.e., + * /bin/sh). * * The no_startup_id flag determines whether a startup notification context * (and ID) should be created, which is the default and encouraged behavior. @@ -49,7 +49,7 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata); * */ struct Startup_Sequence *startup_sequence_get(i3Window *cwindow, - xcb_get_property_reply_t *startup_id_reply, bool ignore_mapped_leader); + xcb_get_property_reply_t *startup_id_reply, bool ignore_mapped_leader); /** * Checks if the given window belongs to a startup notification by checking if diff --git a/include/util.h b/include/util.h index 514a47c2..dec68116 100644 --- a/include/util.h +++ b/include/util.h @@ -15,43 +15,45 @@ #include "data.h" #define die(...) errx(EXIT_FAILURE, __VA_ARGS__); -#define exit_if_null(pointer, ...) { if (pointer == NULL) die(__VA_ARGS__); } +#define exit_if_null(pointer, ...) \ + { \ + if (pointer == NULL) \ + die(__VA_ARGS__); \ + } #define STARTS_WITH(string, needle) (strncasecmp(string, needle, strlen(needle)) == 0) -#define CIRCLEQ_NEXT_OR_NULL(head, elm, field) (CIRCLEQ_NEXT(elm, field) != CIRCLEQ_END(head) ? \ - CIRCLEQ_NEXT(elm, field) : NULL) -#define CIRCLEQ_PREV_OR_NULL(head, elm, field) (CIRCLEQ_PREV(elm, field) != CIRCLEQ_END(head) ? \ - CIRCLEQ_PREV(elm, field) : NULL) -#define FOR_TABLE(workspace) \ - for (int cols = 0; cols < (workspace)->cols; cols++) \ - for (int rows = 0; rows < (workspace)->rows; rows++) +#define CIRCLEQ_NEXT_OR_NULL(head, elm, field) (CIRCLEQ_NEXT(elm, field) != CIRCLEQ_END(head) ? CIRCLEQ_NEXT(elm, field) : NULL) +#define CIRCLEQ_PREV_OR_NULL(head, elm, field) (CIRCLEQ_PREV(elm, field) != CIRCLEQ_END(head) ? CIRCLEQ_PREV(elm, field) : NULL) +#define FOR_TABLE(workspace) \ + for (int cols = 0; cols < (workspace)->cols; cols++) \ + for (int rows = 0; rows < (workspace)->rows; rows++) -#define NODES_FOREACH(head) \ - for (Con *child = (Con*)-1; (child == (Con*)-1) && ((child = 0), true);) \ - TAILQ_FOREACH(child, &((head)->nodes_head), nodes) +#define NODES_FOREACH(head) \ + for (Con *child = (Con *)-1; (child == (Con *)-1) && ((child = 0), true);) \ + TAILQ_FOREACH(child, &((head)->nodes_head), nodes) -#define NODES_FOREACH_REVERSE(head) \ - for (Con *child = (Con*)-1; (child == (Con*)-1) && ((child = 0), true);) \ - TAILQ_FOREACH_REVERSE(child, &((head)->nodes_head), nodes_head, nodes) +#define NODES_FOREACH_REVERSE(head) \ + for (Con *child = (Con *)-1; (child == (Con *)-1) && ((child = 0), true);) \ + TAILQ_FOREACH_REVERSE(child, &((head)->nodes_head), nodes_head, nodes) /* greps the ->nodes of the given head and returns the first node that matches the given condition */ #define GREP_FIRST(dest, head, condition) \ - NODES_FOREACH(head) { \ - if (!(condition)) \ - continue; \ - \ - (dest) = child; \ - break; \ + NODES_FOREACH(head) { \ + if (!(condition)) \ + continue; \ + \ + (dest) = child; \ + break; \ } -#define FREE(pointer) do { \ +#define FREE(pointer) \ + do { \ if (pointer != NULL) { \ - free(pointer); \ - pointer = NULL; \ - } \ -} \ -while (0) + free(pointer); \ + pointer = NULL; \ + } \ + } while (0) -#define CALL(obj, member, ...) obj->member(obj, ## __VA_ARGS__) +#define CALL(obj, member, ...) obj->member(obj, ##__VA_ARGS__) int min(int a, int b); int max(int a, int b); diff --git a/include/workspace.h b/include/workspace.h index 463ccf19..9ee6f156 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -68,25 +68,25 @@ void workspace_show_by_name(const char *num); * Returns the next workspace. * */ -Con* workspace_next(void); +Con *workspace_next(void); /** * Returns the previous workspace. * */ -Con* workspace_prev(void); +Con *workspace_prev(void); /** * Returns the next workspace on the same output * */ -Con* workspace_next_on_output(void); +Con *workspace_next_on_output(void); /** * Returns the previous workspace on the same output * */ -Con* workspace_prev_on_output(void); +Con *workspace_prev_on_output(void); /** * Focuses the previously focused workspace. @@ -100,7 +100,6 @@ void workspace_back_and_forth(void); */ Con *workspace_back_and_forth_get(void); - #if 0 /** * Assigns the given workspace to the given screen by correctly updating its diff --git a/include/xcb.h b/include/xcb.h index 4df7f639..9f4ea91f 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -12,48 +12,48 @@ #include "data.h" #include "xcursor.h" -#define _NET_WM_STATE_REMOVE 0 -#define _NET_WM_STATE_ADD 1 -#define _NET_WM_STATE_TOGGLE 2 +#define _NET_WM_STATE_REMOVE 0 +#define _NET_WM_STATE_ADD 1 +#define _NET_WM_STATE_TOGGLE 2 /** This is the equivalent of XC_left_ptr. I’m not sure why xcb doesn’t have a * constant for that. */ -#define XCB_CURSOR_LEFT_PTR 68 +#define XCB_CURSOR_LEFT_PTR 68 #define XCB_CURSOR_SB_H_DOUBLE_ARROW 108 #define XCB_CURSOR_SB_V_DOUBLE_ARROW 116 #define XCB_CURSOR_WATCH 150 /* from X11/keysymdef.h */ -#define XCB_NUM_LOCK 0xff7f +#define XCB_NUM_LOCK 0xff7f /* The event masks are defined here because we don’t only set them once but we need to set slight variations of them (without XCB_EVENT_MASK_ENTER_WINDOW while rendering the layout) */ /** The XCB_CW_EVENT_MASK for the child (= real window) */ -#define CHILD_EVENT_MASK (XCB_EVENT_MASK_PROPERTY_CHANGE | \ +#define CHILD_EVENT_MASK (XCB_EVENT_MASK_PROPERTY_CHANGE | \ XCB_EVENT_MASK_STRUCTURE_NOTIFY | \ XCB_EVENT_MASK_FOCUS_CHANGE) /** The XCB_CW_EVENT_MASK for its frame */ -#define FRAME_EVENT_MASK (XCB_EVENT_MASK_BUTTON_PRESS | /* …mouse is pressed/released */ \ - XCB_EVENT_MASK_BUTTON_RELEASE | \ - XCB_EVENT_MASK_POINTER_MOTION | /* …mouse is moved */ \ - XCB_EVENT_MASK_EXPOSURE | /* …our window needs to be redrawn */ \ - XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* …the frame gets destroyed */ \ +#define FRAME_EVENT_MASK (XCB_EVENT_MASK_BUTTON_PRESS | /* …mouse is pressed/released */ \ + XCB_EVENT_MASK_BUTTON_RELEASE | \ + XCB_EVENT_MASK_POINTER_MOTION | /* …mouse is moved */ \ + XCB_EVENT_MASK_EXPOSURE | /* …our window needs to be redrawn */ \ + XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* …the frame gets destroyed */ \ XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | /* …the application tries to resize itself */ \ - XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | /* …subwindows get notifies */ \ + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | /* …subwindows get notifies */ \ XCB_EVENT_MASK_ENTER_WINDOW) /* …user moves cursor inside our window */ -#define ROOT_EVENT_MASK (XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | \ - XCB_EVENT_MASK_BUTTON_PRESS | \ - XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video \ - projector), the root window gets a \ - ConfigureNotify */ \ - XCB_EVENT_MASK_POINTER_MOTION | \ - XCB_EVENT_MASK_PROPERTY_CHANGE | \ - XCB_EVENT_MASK_ENTER_WINDOW) +#define ROOT_EVENT_MASK (XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | \ + XCB_EVENT_MASK_BUTTON_PRESS | \ + XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video \ + projector), the root window gets a \ + ConfigureNotify */ \ + XCB_EVENT_MASK_POINTER_MOTION | \ + XCB_EVENT_MASK_PROPERTY_CHANGE | \ + XCB_EVENT_MASK_ENTER_WINDOW) -#define xmacro(atom) xcb_atom_t A_ ## atom; +#define xmacro(atom) xcb_atom_t A_##atom; #include "atoms.xmacro" #undef xmacro @@ -65,7 +65,7 @@ extern unsigned int xcb_numlock_mask; * */ xcb_window_t create_window(xcb_connection_t *conn, Rect r, uint16_t depth, xcb_visualid_t visual, - uint16_t window_class, enum xcursor_cursor_t cursor, bool map, uint32_t mask, uint32_t *values); + uint16_t window_class, enum xcursor_cursor_t cursor, bool map, uint32_t mask, uint32_t *values); /** * Draws a line from x,y to to_x,to_y using the given color @@ -108,7 +108,6 @@ 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); - bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom); /** diff --git a/include/yajl_utils.h b/include/yajl_utils.h index 747e0ca0..e8422aab 100644 --- a/include/yajl_utils.h +++ b/include/yajl_utils.h @@ -14,8 +14,8 @@ #include /* Shorter names for all those yajl_gen_* functions */ -#define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__) -#define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str)) +#define y(x, ...) yajl_gen_##x(gen, ##__VA_ARGS__) +#define ystr(str) yajl_gen_string(gen, (unsigned char *)str, strlen(str)) #define ygenalloc() yajl_gen_alloc(NULL) #define yalloc(callbacks, client) yajl_alloc(callbacks, NULL, client) diff --git a/libi3/dpi.c b/libi3/dpi.c index 090a6560..37d5b215 100644 --- a/libi3/dpi.c +++ b/libi3/dpi.c @@ -1,3 +1,10 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2014 Michael Stapelberg and contributors (see also: LICENSE) + * + */ #include "libi3.h" #include @@ -12,5 +19,13 @@ extern xcb_screen_t *root_screen; int logical_px(const int logical) { const int dpi = (double)root_screen->height_in_pixels * 25.4 / (double)root_screen->height_in_millimeters; + /* There are many misconfigurations out there, i.e. systems with screens + * whose dpi is in fact higher than 96 dpi, but not significantly higher, + * so software was never adapted. We could tell people to reconfigure their + * systems to 96 dpi in order to get the behavior they expect/are used to, + * but since we can easily detect this case in code, let’s do it for them. + */ + if ((dpi / 96.0) < 1.25) + return logical; return ceil((dpi / 96.0) * logical); } diff --git a/libi3/font.c b/libi3/font.c index fc868e60..f2a7e1fb 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -102,7 +102,8 @@ static bool load_pango_font(i3Font *font, const char *desc) { * */ static void draw_text_pango(const char *text, size_t text_len, - xcb_drawable_t drawable, int x, int y, int max_width) { + xcb_drawable_t drawable, int x, int y, + int max_width, bool is_markup) { /* Create the Pango layout */ /* root_visual_type is cached in load_pango_font */ cairo_surface_t *surface = cairo_xcb_surface_create(conn, drawable, @@ -116,7 +117,10 @@ static void draw_text_pango(const char *text, size_t text_len, pango_layout_set_wrap(layout, PANGO_WRAP_CHAR); pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); - pango_layout_set_text(layout, text, text_len); + if (is_markup) + pango_layout_set_markup(layout, text, text_len); + else + pango_layout_set_text(layout, text, text_len); /* Do the drawing */ cairo_set_source_rgb(cr, pango_font_red, pango_font_green, pango_font_blue); @@ -135,7 +139,7 @@ static void draw_text_pango(const char *text, size_t text_len, * Calculate the text width using Pango rendering. * */ -static int predict_text_width_pango(const char *text, size_t text_len) { +static int predict_text_width_pango(const char *text, size_t text_len, bool is_markup) { /* Create a dummy Pango layout */ /* root_visual_type is cached in load_pango_font */ cairo_surface_t *surface = cairo_xcb_surface_create(conn, root_screen->root, root_visual_type, 1, 1); @@ -145,7 +149,12 @@ static int predict_text_width_pango(const char *text, size_t text_len) { /* Get the font width */ gint width; pango_layout_set_font_description(layout, savedFont->specific.pango_desc); - pango_layout_set_text(layout, text, text_len); + + if (is_markup) + pango_layout_set_markup(layout, text, text_len); + else + pango_layout_set_text(layout, text, text_len); + pango_cairo_update_layout(cr, layout); pango_layout_get_pixel_size(layout, &width, NULL); @@ -160,13 +169,23 @@ static int predict_text_width_pango(const char *text, size_t text_len) { /* * Loads a font for usage, also getting its metrics. If fallback is true, - * the fonts 'fixed' or '-misc-*' will be loaded instead of exiting. + * the fonts 'fixed' or '-misc-*' will be loaded instead of exiting. If any + * font was previously loaded, it will be freed. * */ i3Font load_font(const char *pattern, const bool fallback) { + /* if any font was previously loaded, free it now */ + free_font(); + i3Font font; font.type = FONT_TYPE_NONE; + /* No XCB connction, return early because we're just validating the + * configuration file. */ + if (conn == NULL) { + return font; + } + #if PANGO_SUPPORT /* Try to load a pango font if specified */ if (strlen(pattern) > strlen("pango:") && !strncmp(pattern, "pango:", strlen("pango:"))) { @@ -251,10 +270,15 @@ void set_font(i3Font *font) { } /* - * Frees the resources taken by the current font. + * Frees the resources taken by the current font. If no font was previously + * loaded, it simply returns. * */ void free_font(void) { + /* if there is no saved font, simply return */ + if (savedFont == NULL) + return; + free(savedFont->pattern); switch (savedFont->type) { case FONT_TYPE_NONE: @@ -277,6 +301,8 @@ void free_font(void) { assert(false); break; } + + savedFont = NULL; } /* @@ -366,7 +392,7 @@ void draw_text(i3String *text, xcb_drawable_t drawable, case FONT_TYPE_PANGO: /* Render the text using Pango */ draw_text_pango(i3string_as_utf8(text), i3string_get_num_bytes(text), - drawable, x, y, max_width); + drawable, x, y, max_width, i3string_is_markup(text)); return; #endif default: @@ -405,7 +431,7 @@ void draw_text_ascii(const char *text, xcb_drawable_t drawable, case FONT_TYPE_PANGO: /* Render the text using Pango */ draw_text_pango(text, strlen(text), - drawable, x, y, max_width); + drawable, x, y, max_width, false); return; #endif default: @@ -501,7 +527,8 @@ int predict_text_width(i3String *text) { #if PANGO_SUPPORT case FONT_TYPE_PANGO: /* Calculate extents using Pango */ - return predict_text_width_pango(i3string_as_utf8(text), i3string_get_num_bytes(text)); + return predict_text_width_pango(i3string_as_utf8(text), i3string_get_num_bytes(text), + i3string_is_markup(text)); #endif default: assert(false); diff --git a/libi3/string.c b/libi3/string.c index 009312d6..afeca974 100644 --- a/libi3/string.c +++ b/libi3/string.c @@ -20,6 +20,7 @@ struct _i3String { xcb_char2b_t *ucs2; size_t num_glyphs; size_t num_bytes; + bool is_markup; }; /* @@ -39,6 +40,19 @@ i3String *i3string_from_utf8(const char *from_utf8) { return str; } +/* + * Build an i3String from an UTF-8 encoded string in Pango markup. + * + */ +i3String *i3string_from_markup(const char *from_markup) { + i3String *str = i3string_from_utf8(from_markup); + + /* Set the markup flag */ + str->is_markup = true; + + return str; +} + /* * Build an i3String from an UTF-8 encoded string with fixed length. * To be used when no proper NUL-terminaison is available. @@ -59,6 +73,20 @@ i3String *i3string_from_utf8_with_length(const char *from_utf8, size_t num_bytes return str; } +/* + * Build an i3String from an UTF-8 encoded string in Pango markup with fixed + * length. + * + */ +i3String *i3string_from_markup_with_length(const char *from_markup, size_t num_bytes) { + i3String *str = i3string_from_utf8_with_length(from_markup, num_bytes); + + /* set the markup flag */ + str->is_markup = true; + + return str; +} + /* * Build an i3String from an UCS-2 encoded string. * Returns the newly-allocated i3String. @@ -133,6 +161,13 @@ size_t i3string_get_num_bytes(i3String *str) { return str->num_bytes; } +/* + * Whether the given i3String is in Pango markup. + */ +bool i3string_is_markup(i3String *str) { + return str->is_markup; +} + /* * Returns the number of glyphs in an i3String. * diff --git a/man/asciidoc.conf b/man/asciidoc.conf index 84b6d52c..f358e81b 100644 --- a/man/asciidoc.conf +++ b/man/asciidoc.conf @@ -7,7 +7,7 @@ template::[header-declarations] {mantitle} {manvolnum} i3 -4.8 +4.9 i3 Manual diff --git a/man/i3-sensible-terminal.man b/man/i3-sensible-terminal.man index 49d8831c..d1ad5198 100644 --- a/man/i3-sensible-terminal.man +++ b/man/i3-sensible-terminal.man @@ -22,6 +22,7 @@ is appropriate for the distribution. It tries to start one of the following (in that order): * $TERMINAL (this is a non-standard variable) +* x-terminal-emulator (only present on Debian and derivatives) * urxvt * rxvt * terminator diff --git a/man/i3.man b/man/i3.man index ea232fcf..203b42ee 100644 --- a/man/i3.man +++ b/man/i3.man @@ -230,7 +230,7 @@ bindsym Mod1+h split h bindsym Mod1+v split v # enter fullscreen mode for the focused container -bindsym Mod1+f fullscreen +bindsym Mod1+f fullscreen toggle # change container layout (stacked, tabbed, default) bindsym Mod1+s layout stacking diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index e3da62c9..82348df7 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -156,12 +156,28 @@ state KILL: end -> call cmd_kill($kill_mode) +# fullscreen enable|toggle [global] +# fullscreen disable # fullscreen [global] state FULLSCREEN: - fullscreen_mode = 'global' - -> call cmd_fullscreen($fullscreen_mode) + action = 'disable' + -> call cmd_fullscreen($action, "output") + action = 'enable', 'toggle' + -> FULLSCREEN_MODE + action = '' + -> FULLSCREEN_COMPAT + +state FULLSCREEN_MODE: + mode = 'global' + -> call cmd_fullscreen($action, $mode) end - -> call cmd_fullscreen($fullscreen_mode) + -> call cmd_fullscreen($action, "output") + +state FULLSCREEN_COMPAT: + mode = 'global' + -> call cmd_fullscreen("toggle", $mode) + end + -> call cmd_fullscreen("toggle", "output") # split v|h|vertical|horizontal state SPLIT: diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 7a93b9fb..dbdf83c2 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -278,6 +278,8 @@ state FONT: state BINDING: release = '--release' -> + whole_window = '--whole-window' + -> modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', '$mod' -> '+' @@ -288,8 +290,10 @@ state BINDING: state BINDCOMMAND: release = '--release' -> + whole_window = '--whole-window' + -> command = string - -> call cfg_binding($bindtype, $modifiers, $key, $release, $command) + -> call cfg_binding($bindtype, $modifiers, $key, $release, $whole_window, $command) ################################################################################ # Mode configuration @@ -333,8 +337,10 @@ state MODE_BINDING: state MODE_BINDCOMMAND: release = '--release' -> + whole_window = '--whole-window' + -> command = string - -> call cfg_mode_binding($bindtype, $modifiers, $key, $release, $command); MODE + -> call cfg_mode_binding($bindtype, $modifiers, $key, $release, $whole_window, $command); MODE ################################################################################ # Bar configuration (i3bar) @@ -358,6 +364,8 @@ state BAR: 'hidden_state' -> BAR_HIDDEN_STATE 'id' -> BAR_ID 'modifier' -> BAR_MODIFIER + 'wheel_up_cmd' -> BAR_WHEEL_UP_CMD + 'wheel_down_cmd' -> BAR_WHEEL_DOWN_CMD 'position' -> BAR_POSITION 'output' -> BAR_OUTPUT 'tray_output' -> BAR_TRAY_OUTPUT @@ -403,6 +411,14 @@ state BAR_MODIFIER: modifier = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Control', 'Ctrl', 'Shift' -> call cfg_bar_modifier($modifier); BAR +state BAR_WHEEL_UP_CMD: + command = string + -> call cfg_bar_wheel_up_cmd($command); BAR + +state BAR_WHEEL_DOWN_CMD: + command = string + -> call cfg_bar_wheel_down_cmd($command); BAR + state BAR_POSITION: position = 'top', 'bottom' -> call cfg_bar_position($position); BAR @@ -412,7 +428,7 @@ state BAR_OUTPUT: -> call cfg_bar_output($output); BAR state BAR_TRAY_OUTPUT: - output = string + output = word -> call cfg_bar_tray_output($output); BAR state BAR_FONT: diff --git a/src/assignments.c b/src/assignments.c index 2545bde0..96834f64 100644 --- a/src/assignments.c +++ b/src/assignments.c @@ -23,7 +23,7 @@ void run_assignments(i3Window *window) { /* Check if any assignments match */ Assignment *current; - TAILQ_FOREACH (current, &assignments, assignments) { + TAILQ_FOREACH(current, &assignments, assignments) { if (!match_matches_window(&(current->match), window)) continue; @@ -72,7 +72,7 @@ void run_assignments(i3Window *window) { Assignment *assignment_for(i3Window *window, int type) { Assignment *assignment; - TAILQ_FOREACH (assignment, &assignments, assignments) { + TAILQ_FOREACH(assignment, &assignments, assignments) { if ((type != A_ANY && (assignment->type & type) == 0) || !match_matches_window(&(assignment->match), window)) continue; diff --git a/src/bindings.c b/src/bindings.c index b17ec13c..8f9767e6 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -8,6 +8,8 @@ */ #include "all.h" +#include + pid_t command_error_nagbar_pid = -1; /* @@ -25,7 +27,7 @@ static struct Mode *mode_from_name(const char *name) { struct Mode *mode; /* Try to find the mode in the list of modes and return it */ - SLIST_FOREACH (mode, &modes, modes) { + SLIST_FOREACH(mode, &modes, modes) { if (strcmp(mode->name, name) == 0) return mode; } @@ -47,10 +49,11 @@ 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 *command, const char *modename) { + const char *release, const char *whole_window, const char *command, const char *modename) { Binding *new_binding = scalloc(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->whole_window = (whole_window != NULL); if (strcmp(bindtype, "bindsym") == 0) { new_binding->input_type = (strncasecmp(input_code, "button", (sizeof("button") - 1)) == 0 ? B_MOUSE @@ -104,7 +107,7 @@ static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint */ void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) { Binding *bind; - TAILQ_FOREACH (bind, bindings, bindings) { + 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)) @@ -133,7 +136,7 @@ static Binding *get_binding(uint16_t modifiers, bool is_release, uint16_t input_ if (!is_release) { /* On a press event, we first reset all B_UPON_KEYRELEASE_IGNORE_MODS * bindings back to B_UPON_KEYRELEASE */ - TAILQ_FOREACH (bind, bindings, bindings) { + TAILQ_FOREACH(bind, bindings, bindings) { if (bind->input_type != input_type) continue; if (bind->release == B_UPON_KEYRELEASE_IGNORE_MODS) @@ -141,7 +144,7 @@ static Binding *get_binding(uint16_t modifiers, bool is_release, uint16_t input_ } } - TAILQ_FOREACH (bind, bindings, bindings) { + TAILQ_FOREACH(bind, bindings, bindings) { /* First compare the modifiers (unless this is a * B_UPON_KEYRELEASE_IGNORE_MODS binding and this is a KeyRelease * event) */ @@ -248,7 +251,7 @@ void translate_keysyms(void) { min_keycode = xcb_get_setup(conn)->min_keycode; max_keycode = xcb_get_setup(conn)->max_keycode; - TAILQ_FOREACH (bind, bindings, bindings) { + TAILQ_FOREACH(bind, bindings, bindings) { if (bind->input_type == B_MOUSE) { int button = atoi(bind->symbol + (sizeof("button") - 1)); bind->keycode = button; @@ -263,8 +266,8 @@ void translate_keysyms(void) { continue; /* We need to translate the symbol to a keycode */ - keysym = XStringToKeysym(bind->symbol); - if (keysym == NoSymbol) { + 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; @@ -304,7 +307,7 @@ void switch_mode(const char *new_mode) { DLOG("Switching to mode %s\n", new_mode); - SLIST_FOREACH (mode, &modes, modes) { + SLIST_FOREACH(mode, &modes, modes) { if (strcasecmp(mode->name, new_mode) != 0) continue; @@ -334,8 +337,8 @@ void switch_mode(const char *new_mode) { */ void check_for_duplicate_bindings(struct context *context) { Binding *bind, *current; - TAILQ_FOREACH (current, bindings, bindings) { - TAILQ_FOREACH (bind, bindings, bindings) { + TAILQ_FOREACH(current, bindings, bindings) { + TAILQ_FOREACH(bind, bindings, bindings) { /* Abort when we reach the current keybinding, only check the * bindings before */ if (bind == current) @@ -379,18 +382,57 @@ void check_for_duplicate_bindings(struct context *context) { } /* - * Runs the given binding and handles parse errors. Returns a CommandResult for - * running the binding's command. Caller should render tree if - * needs_tree_render is true. Free with command_result_free(). + * Creates a dynamically allocated copy of bind. + */ +static Binding *binding_copy(Binding *bind) { + Binding *ret = smalloc(sizeof(Binding)); + *ret = *bind; + if (bind->symbol != NULL) + ret->symbol = strdup(bind->symbol); + if (bind->command != NULL) + ret->command = strdup(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); + } + return ret; +} + +/* + * Frees the binding. If bind is null, it simply returns. + */ +void binding_free(Binding *bind) { + if (bind == NULL) { + return; + } + + FREE(bind->symbol); + FREE(bind->translated_to); + FREE(bind->command); + FREE(bind); +} + +/* + * Runs the given binding and handles parse errors. If con is passed, it will + * execute the command binding with that container selected by criteria. + * Returns a CommandResult for running the binding's command. Caller should + * render tree if needs_tree_render is true. Free with command_result_free(). * */ -CommandResult *run_binding(Binding *bind) { - /* We need to copy the command since “reload” may be part of the command, - * and then the memory that bind->command points to may not contain the +CommandResult *run_binding(Binding *bind, Con *con) { + char *command; + + /* We need to copy the binding and command since “reload” may be part of + * the command, and then the memory that bind points to may not contain the * same data anymore. */ - char *command_copy = sstrdup(bind->command); - CommandResult *result = parse_command(command_copy, NULL); - free(command_copy); + if (con == NULL) + command = sstrdup(bind->command); + else + sasprintf(&command, "[con_id=\"%d\"] %s", con, bind->command); + + Binding *bind_cp = binding_copy(bind); + CommandResult *result = parse_command(command, NULL); + free(command); if (result->needs_tree_render) tree_render(); @@ -414,7 +456,8 @@ CommandResult *run_binding(Binding *bind) { free(pageraction); } - /* TODO: emit event for running a binding */ + ipc_send_binding_event("run", bind_cp); + binding_free(bind_cp); return result; } diff --git a/src/click.c b/src/click.c index 2cf02178..51ffcf3a 100644 --- a/src/click.c +++ b/src/click.c @@ -177,6 +177,34 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod if (con->parent->type == CT_DOCKAREA) goto done; + /* if the user has bound an action to this click, it should override the + * default behavior. */ + if (dest == CLICK_DECORATION || dest == CLICK_INSIDE) { + 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)) { + CommandResult *result = run_binding(bind, con); + + /* ASYNC_POINTER eats the event */ + xcb_allow_events(conn, XCB_ALLOW_ASYNC_POINTER, event->time); + xcb_flush(conn); + + if (result->needs_tree_render) + tree_render(); + + command_result_free(result); + + return 0; + } + } + + /* There is no default behavior for button release events so we are done. */ + if (event->response_type == XCB_BUTTON_RELEASE) { + goto done; + } + /* Any click in a workspace should focus that workspace. If the * workspace is on another output we need to do a workspace_show in * order for i3bar (and others) to notice the change in workspace. */ @@ -191,7 +219,6 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod if (ws != focused_workspace) workspace_show(ws); - focused_id = XCB_NONE; /* get the floating con */ Con *floatingcon = con_inside_floating(con); @@ -300,6 +327,7 @@ done: xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); xcb_flush(conn); tree_render(); + return 0; } @@ -313,9 +341,10 @@ done: */ int handle_button_press(xcb_button_press_event_t *event) { Con *con; - DLOG("Button %d pressed on window 0x%08x (child 0x%08x) at (%d, %d) (root %d, %d)\n", - event->state, event->event, event->child, event->event_x, event->event_y, - event->root_x, event->root_y); + 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"), + event->event, event->child, event->event_x, event->event_y, event->root_x, + event->root_y); last_timestamp = event->time; @@ -328,9 +357,9 @@ int handle_button_press(xcb_button_press_event_t *event) { if (!(con = con_by_frame_id(event->event))) { /* If the root window is clicked, find the relevant output from the * click coordinates and focus the output's active workspace. */ - if (event->event == root) { + if (event->event == root && event->response_type == XCB_BUTTON_PRESS) { Con *output, *ws; - TAILQ_FOREACH (output, &(croot->nodes_head), nodes) { + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { if (con_is_internal(output) || !rect_contains(output->rect, event->event_x, event->event_y)) continue; @@ -358,7 +387,7 @@ int handle_button_press(xcb_button_press_event_t *event) { /* Check if the click was on the decoration of a child */ Con *child; - TAILQ_FOREACH (child, &(con->nodes_head), nodes) { + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { if (!rect_contains(child->deco_rect, event->event_x, event->event_y)) continue; diff --git a/src/commands.c b/src/commands.c index 73db2802..498c25c8 100644 --- a/src/commands.c +++ b/src/commands.c @@ -273,7 +273,7 @@ void cmd_criteria_init(I3_CMD) { } TAILQ_INIT(&owindows); /* copy all_cons */ - TAILQ_FOREACH (con, &all_cons, all_cons) { + TAILQ_FOREACH(con, &all_cons, all_cons) { ow = smalloc(sizeof(owindow)); ow->con = con; TAILQ_INSERT_TAIL(&owindows, ow, owindows); @@ -324,7 +324,7 @@ void cmd_criteria_match_windows(I3_CMD) { } } - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); } } @@ -448,7 +448,7 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) { return; } - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); con_move_to_workspace(current->con, ws, true, false); } @@ -475,7 +475,7 @@ void cmd_move_con_to_workspace_back_and_forth(I3_CMD) { HANDLE_EMPTY_MATCH; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); con_move_to_workspace(current->con, ws, true, false); } @@ -513,13 +513,33 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) { LOG("should move window to workspace %s\n", name); /* get the workspace */ - Con *ws = workspace_get(name, NULL); + Con *ws = NULL; + Con *output = NULL; + + /* first look for a workspace with this name */ + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + GREP_FIRST(ws, output_get_content(output), !strcasecmp(child->name, name)); + } + + /* if the name is plain digits, we interpret this as a "workspace number" + * command */ + if (!ws && name_is_digits(name)) { + long parsed_num = ws_name_to_number(name); + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + GREP_FIRST(ws, output_get_content(output), + child->num == parsed_num); + } + } + + /* if no workspace was found, make a new one */ + if (!ws) + ws = workspace_get(name, NULL); ws = maybe_auto_back_and_forth_workspace(ws); HANDLE_EMPTY_MATCH; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); con_move_to_workspace(current->con, ws, true, false); } @@ -550,21 +570,18 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { /* get the workspace */ Con *output, *workspace = NULL; - char *endptr = NULL; - long parsed_num = strtol(which, &endptr, 10); - if (parsed_num == LONG_MIN || - parsed_num == LONG_MAX || - parsed_num < 0 || - endptr == which) { + long parsed_num = ws_name_to_number(which); + + if (parsed_num == -1) { LOG("Could not parse initial part of \"%s\" as a number.\n", which); // TODO: better error message yerror("Could not parse number"); return; } - TAILQ_FOREACH (output, &(croot->nodes_head), nodes) - GREP_FIRST(workspace, output_get_content(output), - child->num == parsed_num); + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) + GREP_FIRST(workspace, output_get_content(output), + child->num == parsed_num); if (!workspace) { workspace = workspace_get(which, NULL); @@ -574,7 +591,7 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { HANDLE_EMPTY_MATCH; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); con_move_to_workspace(current->con, workspace, true, false); } @@ -728,7 +745,7 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, char *way, char /* Ensure all the other children have a percentage set. */ Con *child; - TAILQ_FOREACH (child, &(current->parent->nodes_head), nodes) { + TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) { LOG("child->percent = %f (child %p)\n", child->percent, child); if (child->percent == 0.0) child->percent = percentage; @@ -740,7 +757,7 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, char *way, char LOG("subtract_percent = %f\n", subtract_percent); /* Ensure that the new percentages are positive and greater than * 0.05 to have a reasonable minimum size. */ - TAILQ_FOREACH (child, &(current->parent->nodes_head), nodes) { + TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) { if (child == current) continue; if (!definitelyGreaterThan(child->percent - subtract_percent, 0.05, DBL_EPSILON)) { @@ -758,7 +775,7 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, char *way, char current->percent += ((double)ppt / 100.0); LOG("current->percent after = %f\n", current->percent); - TAILQ_FOREACH (child, &(current->parent->nodes_head), nodes) { + TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) { if (child == current) continue; child->percent -= subtract_percent; @@ -786,7 +803,7 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz HANDLE_EMPTY_MATCH; owindow *current; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH(current, &owindows, owindows) { /* Don't handle dock windows (issue #1201) */ if (current->con->window && current->con->window->dock) { DLOG("This is a dock window. Not resizing (con = %p)\n)", current->con); @@ -823,7 +840,7 @@ void cmd_border(I3_CMD, char *border_style_str, char *border_width) { HANDLE_EMPTY_MATCH; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); int border_style = current->con->border_style; char *end; @@ -927,7 +944,7 @@ void cmd_append_layout(I3_CMD, char *path) { restore_open_placeholder_windows(parent); if (content == JSON_CONTENT_WORKSPACE) - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"restored\"}"); + ipc_send_workspace_event("restored", parent, NULL); cmd_output->needs_tree_render = true; } @@ -941,6 +958,12 @@ void cmd_workspace(I3_CMD, char *which) { DLOG("which=%s\n", which); + if (con_get_fullscreen_con(croot, CF_GLOBAL)) { + LOG("Cannot switch workspace while in global fullscreen\n"); + ysuccess(false); + return; + } + if (strcmp(which, "next") == 0) ws = workspace_next(); else if (strcmp(which, "prev") == 0) @@ -969,22 +992,24 @@ void cmd_workspace(I3_CMD, char *which) { void cmd_workspace_number(I3_CMD, char *which) { Con *output, *workspace = NULL; - char *endptr = NULL; - long parsed_num = strtol(which, &endptr, 10); - if (parsed_num == LONG_MIN || - parsed_num == LONG_MAX || - parsed_num < 0 || - endptr == which) { - LOG("Could not parse initial part of \"%s\" as a number.\n", which); - // TODO: better error message - yerror("Could not parse number"); - + if (con_get_fullscreen_con(croot, CF_GLOBAL)) { + LOG("Cannot switch workspace while in global fullscreen\n"); + ysuccess(false); return; } - TAILQ_FOREACH (output, &(croot->nodes_head), nodes) - GREP_FIRST(workspace, output_get_content(output), - child->num == parsed_num); + long parsed_num = ws_name_to_number(which); + + if (parsed_num == -1) { + LOG("Could not parse initial part of \"%s\" as a number.\n", which); + // TODO: better error message + yerror("Could not parse number"); + return; + } + + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) + GREP_FIRST(workspace, output_get_content(output), + child->num == parsed_num); if (!workspace) { LOG("There is no workspace with number %ld, creating a new one.\n", parsed_num); @@ -1007,6 +1032,12 @@ void cmd_workspace_number(I3_CMD, char *which) { * */ void cmd_workspace_back_and_forth(I3_CMD) { + if (con_get_fullscreen_con(croot, CF_GLOBAL)) { + LOG("Cannot switch workspace while in global fullscreen\n"); + ysuccess(false); + return; + } + workspace_back_and_forth(); cmd_output->needs_tree_render = true; @@ -1025,10 +1056,39 @@ void cmd_workspace_name(I3_CMD, char *name) { return; } + if (con_get_fullscreen_con(croot, CF_GLOBAL)) { + LOG("Cannot switch workspace while in global fullscreen\n"); + ysuccess(false); + return; + } + DLOG("should switch to workspace %s\n", name); if (maybe_back_and_forth(cmd_output, name)) return; - workspace_show_by_name(name); + + Con *ws = NULL; + Con *output = NULL; + + /* first look for a workspace with this name */ + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + GREP_FIRST(ws, output_get_content(output), !strcasecmp(child->name, name)); + } + + /* if the name is only digits, we interpret this as a "workspace number" + * command */ + if (!ws && name_is_digits(name)) { + long parsed_num = ws_name_to_number(name); + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + GREP_FIRST(ws, output_get_content(output), + child->num == parsed_num); + } + } + + /* if no workspace was found, make a new one */ + if (!ws) + ws = workspace_get(name, NULL); + + workspace_show(ws); cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply @@ -1043,7 +1103,7 @@ void cmd_mark(I3_CMD, char *mark) { DLOG("Clearing all windows which have that mark first\n"); Con *con; - TAILQ_FOREACH (con, &all_cons, all_cons) { + TAILQ_FOREACH(con, &all_cons, all_cons) { if (con->mark && strcmp(con->mark, mark) == 0) FREE(con->mark); } @@ -1053,7 +1113,7 @@ void cmd_mark(I3_CMD, char *mark) { HANDLE_EMPTY_MATCH; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); current->con->mark = sstrdup(mark); } @@ -1070,13 +1130,13 @@ 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) { + 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) { + TAILQ_FOREACH(con, &all_cons, all_cons) { if (con->mark && strcmp(con->mark, mark) == 0) FREE(con->mark); } @@ -1116,8 +1176,8 @@ void cmd_move_con_to_output(I3_CMD, char *name) { Output *output; // TODO: fix the handling of criteria - TAILQ_FOREACH (current, &owindows, owindows) - current_output = get_output_of_con(current->con); + TAILQ_FOREACH(current, &owindows, owindows) + current_output = get_output_of_con(current->con); assert(current_output != NULL); @@ -1147,7 +1207,7 @@ void cmd_move_con_to_output(I3_CMD, char *name) { return; } - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); con_move_to_workspace(current->con, ws, true, false); } @@ -1168,7 +1228,7 @@ void cmd_floating(I3_CMD, char *floating_mode) { HANDLE_EMPTY_MATCH; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); if (strcmp(floating_mode, "toggle") == 0) { DLOG("should toggle mode\n"); @@ -1198,7 +1258,7 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) { HANDLE_EMPTY_MATCH; owindow *current; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH(current, &owindows, owindows) { Output *current_output = get_output_of_con(current->con); if (!current_output) { ELOG("Cannot get current output. This is a bug in i3.\n"); @@ -1228,15 +1288,15 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) { /* check if we can find a workspace assigned to this output */ bool used_assignment = false; struct Workspace_Assignment *assignment; - TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) { + TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { if (strcmp(assignment->output, current_output->name) != 0) continue; /* check if this workspace is already attached to the tree */ Con *workspace = NULL, *out; - TAILQ_FOREACH (out, &(croot->nodes_head), nodes) - GREP_FIRST(workspace, output_get_content(out), - !strcasecmp(child->name, assignment->name)); + TAILQ_FOREACH(out, &(croot->nodes_head), nodes) + GREP_FIRST(workspace, output_get_content(out), + !strcasecmp(child->name, assignment->name)); if (workspace != NULL) continue; @@ -1253,7 +1313,7 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) { create_workspace_on_output(current_output, ws->parent); /* notify the IPC listeners */ - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); + ipc_send_workspace_event("init", ws, NULL); } DLOG("Detaching\n"); @@ -1271,10 +1331,10 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) { /* fix the coordinates of the floating containers */ Con *floating_con; - TAILQ_FOREACH (floating_con, &(ws->floating_head), floating_windows) - floating_fix_coordinates(floating_con, &(old_content->rect), &(content->rect)); + TAILQ_FOREACH(floating_con, &(ws->floating_head), floating_windows) + floating_fix_coordinates(floating_con, &(old_content->rect), &(content->rect)); - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"move\"}"); + ipc_send_workspace_event("move", ws, NULL); if (workspace_was_visible) { /* Focus the moved workspace on the destination output. */ workspace_show(ws); @@ -1285,7 +1345,7 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) { * focus order/number of other workspaces on the output. * Instead, we loop through the available workspaces and only work with * previously_visible_ws if we still find it. */ - TAILQ_FOREACH (ws, &(content->nodes_head), nodes) { + TAILQ_FOREACH(ws, &(content->nodes_head), nodes) { if (ws != previously_visible_ws) continue; @@ -1313,7 +1373,7 @@ void cmd_split(I3_CMD, char *direction) { if (match_is_empty(current_match)) tree_split(focused, (direction[0] == 'v' ? VERT : HORIZ)); else { - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); tree_split(current->con, (direction[0] == 'v' ? VERT : HORIZ)); } @@ -1350,7 +1410,7 @@ void cmd_kill(I3_CMD, char *kill_mode_str) { if (match_is_empty(current_match)) tree_close_con(kill_mode); else { - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); tree_close(current->con, kill_mode, false, false); } @@ -1418,7 +1478,7 @@ void cmd_focus_window_mode(I3_CMD, char *window_mode) { else window_mode = "floating"; } - TAILQ_FOREACH (current, &(ws->focus_head), focused) { + 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)) continue; @@ -1480,7 +1540,7 @@ void cmd_focus(I3_CMD) { Con *__i3_scratch = workspace_get("__i3_scratch", NULL); int count = 0; owindow *current; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH(current, &owindows, owindows) { Con *ws = con_get_workspace(current->con); /* If no workspace could be found, this was a dock window. * Just skip it, you cannot focus dock windows. */ @@ -1538,20 +1598,26 @@ void cmd_focus(I3_CMD) { } /* - * Implementation of 'fullscreen [global]'. + * Implementation of 'fullscreen enable|toggle [global]' and + * 'fullscreen disable' * */ -void cmd_fullscreen(I3_CMD, char *fullscreen_mode) { - if (fullscreen_mode == NULL) - fullscreen_mode = "output"; - DLOG("toggling fullscreen, mode = %s\n", fullscreen_mode); +void cmd_fullscreen(I3_CMD, char *action, char *fullscreen_mode) { + fullscreen_mode_t mode = strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT; + DLOG("%s fullscreen, mode = %s\n", action, fullscreen_mode); owindow *current; HANDLE_EMPTY_MATCH; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); - con_toggle_fullscreen(current->con, (strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT)); + if (strcmp(action, "toggle") == 0) { + con_toggle_fullscreen(current->con, mode); + } else if (strcmp(action, "enable") == 0) { + con_enable_fullscreen(current->con, mode); + } else if (strcmp(action, "disable") == 0) { + con_disable_fullscreen(current->con); + } } cmd_output->needs_tree_render = true; @@ -1567,26 +1633,36 @@ void cmd_move_direction(I3_CMD, char *direction, char *move_px) { // 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 px = atoi(move_px); - /* TODO: make 'move' work with criteria. */ - DLOG("moving in direction %s, px %s\n", direction, move_px); - if (con_is_floating(focused)) { - DLOG("floating move with %d pixels\n", px); - Rect newrect = focused->parent->rect; - if (strcmp(direction, "left") == 0) { - newrect.x -= px; - } else if (strcmp(direction, "right") == 0) { - newrect.x += px; - } else if (strcmp(direction, "up") == 0) { - newrect.y -= px; - } else if (strcmp(direction, "down") == 0) { - newrect.y += px; + owindow *current; + HANDLE_EMPTY_MATCH; + + Con *initially_focused = focused; + + TAILQ_FOREACH(current, &owindows, owindows) { + DLOG("moving in direction %s, px %s\n", direction, move_px); + if (con_is_floating(current->con)) { + DLOG("floating move with %d pixels\n", px); + Rect newrect = current->con->parent->rect; + if (strcmp(direction, "left") == 0) { + newrect.x -= px; + } else if (strcmp(direction, "right") == 0) { + newrect.x += px; + } else if (strcmp(direction, "up") == 0) { + newrect.y -= px; + } else if (strcmp(direction, "down") == 0) { + newrect.y += px; + } + floating_reposition(current->con->parent, newrect); + } else { + tree_move(current->con, (strcmp(direction, "right") == 0 ? D_RIGHT : (strcmp(direction, "left") == 0 ? D_LEFT : (strcmp(direction, "up") == 0 ? D_UP : D_DOWN)))); + cmd_output->needs_tree_render = true; } - floating_reposition(focused->parent, newrect); - } else { - tree_move((strcmp(direction, "right") == 0 ? D_RIGHT : (strcmp(direction, "left") == 0 ? D_LEFT : (strcmp(direction, "up") == 0 ? D_UP : D_DOWN)))); - cmd_output->needs_tree_render = true; } + /* the move command should not disturb focus */ + if (focused != initially_focused) + con_focus(initially_focused); + // XXX: default reply for now, make this a better reply ysuccess(true); } @@ -1622,7 +1698,7 @@ void cmd_layout(I3_CMD, char *layout_str) { if (match_is_empty(current_match)) con_set_layout(focused, layout); else { - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); con_set_layout(current->con, layout); } @@ -1649,7 +1725,7 @@ void cmd_layout_toggle(I3_CMD, char *toggle_mode) { if (match_is_empty(current_match)) con_toggle_layout(focused, toggle_mode); else { - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); con_toggle_layout(current->con, toggle_mode); } @@ -1685,7 +1761,7 @@ void cmd_reload(I3_CMD) { load_configuration(conn, NULL, true); x_set_i3_atoms(); /* Send an IPC event just in case the ws names have changed */ - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}"); + ipc_send_workspace_event("reload", NULL, NULL); /* Send an update event for the barconfig just in case it has changed */ update_barconfig(); @@ -1745,8 +1821,8 @@ void cmd_focus_output(I3_CMD, char *name) { Output *current_output = NULL; Output *output; - TAILQ_FOREACH (current, &owindows, owindows) - current_output = get_output_of_con(current->con); + TAILQ_FOREACH(current, &owindows, owindows) + current_output = get_output_of_con(current->con); assert(current_output != NULL); output = get_output_from_string(current_output, name); @@ -1779,34 +1855,46 @@ void cmd_focus_output(I3_CMD, char *name) { void cmd_move_window_to_position(I3_CMD, char *method, char *cx, char *cy) { int x = atoi(cx); int y = atoi(cy); + bool has_error = false; - if (!con_is_floating(focused)) { - ELOG("Cannot change position. The window/container is not floating\n"); - yerror("Cannot change position. The window/container is not floating."); - return; - } + owindow *current; + HANDLE_EMPTY_MATCH; - if (strcmp(method, "absolute") == 0) { - focused->parent->rect.x = x; - focused->parent->rect.y = y; + TAILQ_FOREACH(current, &owindows, owindows) { + if (!con_is_floating(current->con)) { + ELOG("Cannot change position. The window/container is not floating\n"); - DLOG("moving to absolute position %d %d\n", x, y); - floating_maybe_reassign_ws(focused->parent); - cmd_output->needs_tree_render = true; - } + if (!has_error) { + yerror("Cannot change position of a window/container because it is not floating."); + has_error = true; + } - if (strcmp(method, "position") == 0) { - Rect newrect = focused->parent->rect; + continue; + } - DLOG("moving to position %d %d\n", x, y); - newrect.x = x; - newrect.y = y; + if (strcmp(method, "absolute") == 0) { + current->con->parent->rect.x = x; + current->con->parent->rect.y = y; - floating_reposition(focused->parent, newrect); + DLOG("moving to absolute position %d %d\n", x, y); + floating_maybe_reassign_ws(current->con->parent); + cmd_output->needs_tree_render = true; + } + + if (strcmp(method, "position") == 0) { + Rect newrect = current->con->parent->rect; + + DLOG("moving to position %d %d\n", x, y); + newrect.x = x; + newrect.y = y; + + floating_reposition(current->con->parent, newrect); + } } // XXX: default reply for now, make this a better reply - ysuccess(true); + if (!has_error) + ysuccess(true); } /* @@ -1856,7 +1944,7 @@ void cmd_move_scratchpad(I3_CMD) { HANDLE_EMPTY_MATCH; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); scratchpad_move(current->con); } @@ -1877,7 +1965,7 @@ void cmd_scratchpad_show(I3_CMD) { if (match_is_empty(current_match)) { scratchpad_show(NULL); } else { - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); scratchpad_show(current->con); } @@ -1906,9 +1994,9 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) { Con *output, *workspace = NULL; if (old_name) { - TAILQ_FOREACH (output, &(croot->nodes_head), nodes) - GREP_FIRST(workspace, output_get_content(output), - !strcasecmp(child->name, old_name)); + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) + GREP_FIRST(workspace, output_get_content(output), + !strcasecmp(child->name, old_name)); } else { workspace = con_get_workspace(focused); } @@ -1922,9 +2010,9 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) { } Con *check_dest = NULL; - TAILQ_FOREACH (output, &(croot->nodes_head), nodes) - GREP_FIRST(check_dest, output_get_content(output), - !strcasecmp(child->name, new_name)); + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) + GREP_FIRST(check_dest, output_get_content(output), + !strcasecmp(child->name, new_name)); if (check_dest != NULL) { // TODO: we should include the new workspace name here and use yajl for @@ -1937,15 +2025,8 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) { /* Change the name and try to parse it as a number. */ FREE(workspace->name); workspace->name = sstrdup(new_name); - char *endptr = NULL; - long parsed_num = strtol(new_name, &endptr, 10); - if (parsed_num == LONG_MIN || - parsed_num == LONG_MAX || - parsed_num < 0 || - endptr == new_name) - workspace->num = -1; - else - workspace->num = parsed_num; + + workspace->num = ws_name_to_number(new_name); LOG("num = %d\n", workspace->num); /* By re-attaching, the sort order will be correct afterwards. */ @@ -1959,7 +2040,10 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) { cmd_output->needs_tree_render = true; ysuccess(true); - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"rename\"}"); + ipc_send_workspace_event("rename", workspace, NULL); + ewmh_update_desktop_names(); + ewmh_update_desktop_viewport(); + ewmh_update_current_desktop(); } /* @@ -1984,7 +2068,7 @@ bool cmd_bar_mode(char *bar_mode, char *bar_id) { bool changed_sth = false; Barconfig *current = NULL; - TAILQ_FOREACH (current, &barconfigs, configs) { + TAILQ_FOREACH(current, &barconfigs, configs) { if (bar_id && strcmp(current->id, bar_id) != 0) continue; @@ -2027,7 +2111,7 @@ bool cmd_bar_hidden_state(char *bar_hidden_state, char *bar_id) { bool changed_sth = false; Barconfig *current = NULL; - TAILQ_FOREACH (current, &barconfigs, configs) { + TAILQ_FOREACH(current, &barconfigs, configs) { if (bar_id && strcmp(current->id, bar_id) != 0) continue; diff --git a/src/commands_parser.c b/src/commands_parser.c index 31729676..f325a048 100644 --- a/src/commands_parser.c +++ b/src/commands_parser.c @@ -204,6 +204,61 @@ static void next_state(const cmdp_token *token) { } } +/* + * Parses a string (or word, if as_word is true). Extracted out of + * parse_command so that it can be used in src/workspace.c for interpreting + * workspace commands. + * + */ +char *parse_string(const char **walk, bool as_word) { + const char *beginning = *walk; + /* Handle quoted strings (or words). */ + if (**walk == '"') { + beginning++; + (*walk)++; + while (**walk != '\0' && (**walk != '"' || *(*walk - 1) == '\\')) + (*walk)++; + } else { + if (!as_word) { + /* For a string (starting with 's'), the delimiters are + * comma (,) and semicolon (;) which introduce a new + * operation or command, respectively. Also, newlines + * end a command. */ + while (**walk != ';' && **walk != ',' && + **walk != '\0' && **walk != '\r' && + **walk != '\n') + (*walk)++; + } else { + /* For a word, the delimiters are white space (' ' or + * '\t'), closing square bracket (]), comma (,) and + * semicolon (;). */ + while (**walk != ' ' && **walk != '\t' && + **walk != ']' && **walk != ',' && + **walk != ';' && **walk != '\r' && + **walk != '\n' && **walk != '\0') + (*walk)++; + } + } + if (*walk == beginning) + return NULL; + + char *str = scalloc(*walk - beginning + 1); + /* We copy manually to handle escaping of characters. */ + int inpos, outpos; + for (inpos = 0, outpos = 0; + inpos < (*walk - beginning); + inpos++, outpos++) { + /* We only handle escaped double quotes to not break + * backwards compatibility with people using \w in + * regular expressions etc. */ + if (beginning[inpos] == '\\' && beginning[inpos + 1] == '"') + inpos++; + str[outpos] = beginning[inpos]; + } + + return str; +} + /* * Parses and executes the given command. If a caller-allocated yajl_gen is * passed, a json reply will be generated in the format specified by the ipc @@ -262,48 +317,8 @@ CommandResult *parse_command(const char *input, yajl_gen gen) { if (strcmp(token->name, "string") == 0 || strcmp(token->name, "word") == 0) { - const char *beginning = walk; - /* Handle quoted strings (or words). */ - if (*walk == '"') { - beginning++; - walk++; - while (*walk != '\0' && (*walk != '"' || *(walk - 1) == '\\')) - walk++; - } else { - if (token->name[0] == 's') { - /* For a string (starting with 's'), the delimiters are - * comma (,) and semicolon (;) which introduce a new - * operation or command, respectively. Also, newlines - * end a command. */ - while (*walk != ';' && *walk != ',' && - *walk != '\0' && *walk != '\r' && - *walk != '\n') - walk++; - } else { - /* For a word, the delimiters are white space (' ' or - * '\t'), closing square bracket (]), comma (,) and - * semicolon (;). */ - while (*walk != ' ' && *walk != '\t' && - *walk != ']' && *walk != ',' && - *walk != ';' && *walk != '\r' && - *walk != '\n' && *walk != '\0') - walk++; - } - } - if (walk != beginning) { - char *str = scalloc(walk - beginning + 1); - /* We copy manually to handle escaping of characters. */ - int inpos, outpos; - for (inpos = 0, outpos = 0; - inpos < (walk - beginning); - inpos++, outpos++) { - /* We only handle escaped double quotes to not break - * backwards compatibility with people using \w in - * regular expressions etc. */ - if (beginning[inpos] == '\\' && beginning[inpos + 1] == '"') - inpos++; - str[outpos] = beginning[inpos]; - } + char *str = parse_string(&walk, (token->name[0] != 's')); + if (str != NULL) { if (token->identifier) push_string(token->identifier, str); /* If we are at the end of a quoted string, skip the ending diff --git a/src/con.c b/src/con.c index 42eb74f7..4957c303 100644 --- a/src/con.c +++ b/src/con.c @@ -12,18 +12,7 @@ * */ #include "all.h" - -char *colors[] = { - "#ff0000", - "#00FF00", - "#0000FF", - "#ff00ff", - "#00ffff", - "#ffff00", - "#aa0000", - "#00aa00", - "#0000aa", - "#aa00aa"}; +#include "yajl_utils.h" static void con_on_remove_child(Con *con); @@ -31,7 +20,7 @@ static void con_on_remove_child(Con *con); * force parent split containers to be redrawn * */ -static void con_force_split_parents_redraw(Con *con) { +void con_force_split_parents_redraw(Con *con) { Con *parent = con; while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) { @@ -59,16 +48,7 @@ Con *con_new_skeleton(Con *parent, i3Window *window) { new->depth = window->depth; else new->depth = XCB_COPY_FROM_PARENT; - static int cnt = 0; - DLOG("opening window %d\n", cnt); - - /* TODO: remove window coloring after test-phase */ - DLOG("color %s\n", colors[cnt]); - new->name = strdup(colors[cnt]); - //uint32_t cp = get_colorpixel(colors[cnt]); - cnt++; - if ((cnt % (sizeof(colors) / sizeof(char *))) == 0) - cnt = 0; + DLOG("opening window\n"); TAILQ_INIT(&(new->floating_head)); TAILQ_INIT(&(new->nodes_head)); @@ -142,7 +122,7 @@ void con_attach(Con *con, Con *parent, bool ignore_focus) { } else { if (!ignore_focus) { /* Get the first tiling container in focus stack */ - TAILQ_FOREACH (loop, &(parent->focus_head), focused) { + TAILQ_FOREACH(loop, &(parent->focus_head), focused) { if (loop->type == CT_FLOATING_CON) continue; current = loop; @@ -231,6 +211,7 @@ void con_focus(Con *con) { con->urgent = false; con_update_parents_urgency(con); workspace_update_urgent_flag(con_get_workspace(con)); + ipc_send_window_event("urgent", con); } } @@ -388,13 +369,13 @@ Con *con_get_fullscreen_con(Con *con, fullscreen_mode_t fullscreen_mode) { TAILQ_REMOVE(&bfs_head, entry, entries); free(entry); - TAILQ_FOREACH (child, &(current->nodes_head), nodes) { + TAILQ_FOREACH(child, &(current->nodes_head), nodes) { entry = smalloc(sizeof(struct bfs_entry)); entry->con = child; TAILQ_INSERT_TAIL(&bfs_head, entry, entries); } - TAILQ_FOREACH (child, &(current->floating_head), floating_windows) { + TAILQ_FOREACH(child, &(current->floating_head), floating_windows) { entry = smalloc(sizeof(struct bfs_entry)); entry->con = child; TAILQ_INSERT_TAIL(&bfs_head, entry, entries); @@ -460,9 +441,9 @@ bool con_inside_focused(Con *con) { */ Con *con_by_window_id(xcb_window_t window) { Con *con; - TAILQ_FOREACH (con, &all_cons, all_cons) - if (con->window != NULL && con->window->id == window) - return con; + TAILQ_FOREACH(con, &all_cons, all_cons) + if (con->window != NULL && con->window->id == window) + return con; return NULL; } @@ -473,9 +454,9 @@ Con *con_by_window_id(xcb_window_t window) { */ Con *con_by_frame_id(xcb_window_t frame) { Con *con; - TAILQ_FOREACH (con, &all_cons, all_cons) - if (con->frame == frame) - return con; + TAILQ_FOREACH(con, &all_cons, all_cons) + if (con->frame == frame) + return con; return NULL; } @@ -490,8 +471,8 @@ Con *con_for_window(Con *con, i3Window *window, Match **store_match) { //DLOG("searching con for window %p starting at con %p\n", window, con); //DLOG("class == %s\n", window->class_class); - TAILQ_FOREACH (child, &(con->nodes_head), nodes) { - TAILQ_FOREACH (match, &(child->swallow_head), matches) { + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + TAILQ_FOREACH(match, &(child->swallow_head), matches) { if (!match_matches_window(match, window)) continue; if (store_match != NULL) @@ -503,8 +484,8 @@ Con *con_for_window(Con *con, i3Window *window, Match **store_match) { return result; } - TAILQ_FOREACH (child, &(con->floating_head), floating_windows) { - TAILQ_FOREACH (match, &(child->swallow_head), matches) { + TAILQ_FOREACH(child, &(con->floating_head), floating_windows) { + TAILQ_FOREACH(match, &(child->swallow_head), matches) { if (!match_matches_window(match, window)) continue; if (store_match != NULL) @@ -527,8 +508,8 @@ int con_num_children(Con *con) { Con *child; int children = 0; - TAILQ_FOREACH (child, &(con->nodes_head), nodes) - children++; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) + children++; return children; } @@ -547,7 +528,7 @@ void con_fix_percent(Con *con) { // with a percentage set we have double total = 0.0; int children_with_percent = 0; - TAILQ_FOREACH (child, &(con->nodes_head), nodes) { + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { if (child->percent > 0.0) { total += child->percent; ++children_with_percent; @@ -557,7 +538,7 @@ void con_fix_percent(Con *con) { // if there were children without a percentage set, set to a value that // will make those children proportional to all others if (children_with_percent != children) { - TAILQ_FOREACH (child, &(con->nodes_head), nodes) { + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { if (child->percent <= 0.0) { if (children_with_percent == 0) total += (child->percent = 1.0); @@ -570,11 +551,11 @@ void con_fix_percent(Con *con) { // if we got a zero, just distribute the space equally, otherwise // distribute according to the proportions we got if (total == 0.0) { - TAILQ_FOREACH (child, &(con->nodes_head), nodes) - child->percent = 1.0 / children; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) + child->percent = 1.0 / children; } else if (total != 1.0) { - TAILQ_FOREACH (child, &(con->nodes_head), nodes) - child->percent /= total; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) + child->percent /= total; } } @@ -585,37 +566,27 @@ void con_fix_percent(Con *con) { * */ void con_toggle_fullscreen(Con *con, int fullscreen_mode) { - Con *workspace, *fullscreen; - if (con->type == CT_WORKSPACE) { DLOG("You cannot make a workspace fullscreen.\n"); return; } DLOG("toggling fullscreen for %p / %s\n", con, con->name); - if (con->fullscreen_mode == CF_NONE) { - /* 1: check if there already is a fullscreen con */ - if (fullscreen_mode == CF_GLOBAL) - fullscreen = con_get_fullscreen_con(croot, CF_GLOBAL); - else { - workspace = con_get_workspace(con); - fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT); - } - if (fullscreen != NULL) { - /* Disable fullscreen for the currently fullscreened - * container and enable it for the one the user wants - * to have in fullscreen mode. */ - LOG("Disabling fullscreen for (%p/%s) upon user request\n", - fullscreen, fullscreen->name); - fullscreen->fullscreen_mode = CF_NONE; - } - /* 2: enable fullscreen */ - con->fullscreen_mode = fullscreen_mode; - } else { - /* 1: disable fullscreen */ - con->fullscreen_mode = CF_NONE; - } + if (con->fullscreen_mode == CF_NONE) + con_enable_fullscreen(con, fullscreen_mode); + else + con_disable_fullscreen(con); +} + +/* + * Sets the specified fullscreen mode for the given container, sends the + * “fullscreen_mode” event and changes the XCB fullscreen property of the + * container’s window, if any. + * + */ +static void con_set_fullscreen_mode(Con *con, fullscreen_mode_t fullscreen_mode) { + con->fullscreen_mode = fullscreen_mode; DLOG("mode now: %d\n", con->fullscreen_mode); @@ -638,6 +609,79 @@ void con_toggle_fullscreen(Con *con, int fullscreen_mode) { A__NET_WM_STATE, XCB_ATOM_ATOM, 32, num, values); } +/* + * Enables fullscreen mode for the given container, if necessary. + * + * If the container’s mode is already CF_OUTPUT or CF_GLOBAL, the container is + * kept fullscreen but its mode is set to CF_GLOBAL and CF_OUTPUT, + * respectively. + * + * Other fullscreen containers will be disabled first, if they hide the new + * one. + * + */ +void con_enable_fullscreen(Con *con, fullscreen_mode_t fullscreen_mode) { + if (con->type == CT_WORKSPACE) { + DLOG("You cannot make a workspace fullscreen.\n"); + return; + } + + assert(fullscreen_mode == CF_GLOBAL || fullscreen_mode == CF_OUTPUT); + + if (fullscreen_mode == CF_GLOBAL) + DLOG("enabling global fullscreen for %p / %s\n", con, con->name); + else + DLOG("enabling fullscreen for %p / %s\n", con, con->name); + + if (con->fullscreen_mode == fullscreen_mode) { + DLOG("fullscreen already enabled for %p / %s\n", con, con->name); + return; + } + + Con *con_ws = con_get_workspace(con); + + /* Disable any fullscreen container that would conflict the new one. */ + Con *fullscreen = con_get_fullscreen_con(croot, CF_GLOBAL); + if (fullscreen == NULL) + fullscreen = con_get_fullscreen_con(con_ws, CF_OUTPUT); + if (fullscreen != NULL) + con_disable_fullscreen(fullscreen); + + /* Set focus to new fullscreen container. Unless in global fullscreen mode + * and on another workspace restore focus afterwards. + * Switch to the container’s workspace if mode is global. */ + Con *cur_ws = con_get_workspace(focused); + Con *old_focused = focused; + if (fullscreen_mode == CF_GLOBAL && cur_ws != con_ws) + workspace_show(con_ws); + con_focus(con); + if (fullscreen_mode != CF_GLOBAL && cur_ws != con_ws) + con_focus(old_focused); + + con_set_fullscreen_mode(con, fullscreen_mode); +} + +/* + * Disables fullscreen mode for the given container regardless of the mode, if + * necessary. + * + */ +void con_disable_fullscreen(Con *con) { + if (con->type == CT_WORKSPACE) { + DLOG("You cannot make a workspace fullscreen.\n"); + return; + } + + DLOG("disabling fullscreen for %p / %s\n", con, con->name); + + if (con->fullscreen_mode == CF_NONE) { + DLOG("fullscreen already disabled for %p / %s\n", con, con->name); + return; + } + + con_set_fullscreen_mode(con, CF_NONE); +} + /* * Moves the given container to the currently focused container on the given * workspace. @@ -807,7 +851,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool if (!con_is_leaf(con)) { Con *child; - TAILQ_FOREACH (child, &(con->nodes_head), nodes) { + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { if (!child->window) continue; @@ -832,6 +876,8 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool } CALL(parent, on_remove_child); + + ipc_send_window_event("move", con); } /* @@ -1003,7 +1049,7 @@ Con *con_descend_tiling_focused(Con *con) { return next; do { before = next; - TAILQ_FOREACH (child, &(next->focus_head), focused) { + TAILQ_FOREACH(child, &(next->focus_head), focused) { if (child->type == CT_FLOATING_CON) continue; @@ -1038,7 +1084,7 @@ Con *con_descend_direction(Con *con, direction_t direction) { /* Wrong orientation. We use the last focused con. Within that con, * we recurse to chose the left/right con or at least the last * focused one. */ - TAILQ_FOREACH (current, &(con->focus_head), focused) { + TAILQ_FOREACH(current, &(con->focus_head), focused) { if (current->type != CT_FLOATING_CON) { most = current; break; @@ -1063,7 +1109,7 @@ Con *con_descend_direction(Con *con, direction_t direction) { /* Wrong orientation. We use the last focused con. Within that con, * we recurse to chose the top/bottom con or at least the last * focused one. */ - TAILQ_FOREACH (current, &(con->focus_head), focused) { + TAILQ_FOREACH(current, &(con->focus_head), focused) { if (current->type != CT_FLOATING_CON) { most = current; break; @@ -1390,8 +1436,15 @@ static void con_on_remove_child(Con *con) { if (con->type == CT_WORKSPACE) { if (TAILQ_EMPTY(&(con->focus_head)) && !workspace_is_visible(con)) { LOG("Closing old workspace (%p / %s), it is empty\n", con, con->name); + yajl_gen gen = ipc_marshal_workspace_event("empty", con, NULL); tree_close(con, DONT_KILL_WINDOW, false, false); - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}"); + + const unsigned char *payload; + ylength length; + y(get_buf, &payload, &length); + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, (const char *)payload); + + y(free); } return; } @@ -1432,7 +1485,7 @@ Rect con_minimum_size(Con *con) { if (con->layout == L_STACKED || con->layout == L_TABBED) { uint32_t max_width = 0, max_height = 0, deco_height = 0; Con *child; - TAILQ_FOREACH (child, &(con->nodes_head), nodes) { + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { Rect min = con_minimum_size(child); deco_height += child->deco_rect.height; max_width = max(max_width, min.width); @@ -1449,7 +1502,7 @@ Rect con_minimum_size(Con *con) { if (con_is_split(con)) { uint32_t width = 0, height = 0; Con *child; - TAILQ_FOREACH (child, &(con->nodes_head), nodes) { + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { Rect min = con_minimum_size(child); if (con->layout == L_SPLITH) { width += min.width; @@ -1544,7 +1597,7 @@ bool con_has_urgent_child(Con *con) { /* We are not interested in floating windows since they can only be * attached to a workspace → nodes_head instead of focus_head */ - TAILQ_FOREACH (child, &(con->nodes_head), nodes) { + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { if (con_has_urgent_child(child)) return true; } @@ -1603,14 +1656,16 @@ void con_set_urgency(Con *con, bool urgent) { con_update_parents_urgency(con); - if (con->urgent == urgent) - LOG("Urgency flag changed to %d\n", con->urgent); - Con *ws; /* Set the urgency flag on the workspace, if a workspace could be found * (for dock clients, that is not the case). */ if ((ws = con_get_workspace(con)) != NULL) workspace_update_urgent_flag(ws); + + if (con->urgent == urgent) { + LOG("Urgency flag changed to %d\n", con->urgent); + ipc_send_window_event("urgent", con); + } } /* @@ -1657,7 +1712,7 @@ char *con_get_tree_representation(Con *con) { /* 2) append representation of children */ Con *child; - TAILQ_FOREACH (child, &(con->nodes_head), nodes) { + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { char *child_txt = con_get_tree_representation(child); char *tmp_buf; diff --git a/src/config.c b/src/config.c index 7e88a9ef..b41f0e1b 100644 --- a/src/config.c +++ b/src/config.c @@ -11,9 +11,7 @@ * */ #include "all.h" - -/* We need Xlib for XStringToKeysym */ -#include +#include char *current_configpath = NULL; Config config; @@ -36,7 +34,7 @@ void ungrab_all_keys(xcb_connection_t *conn) { */ void update_barconfig() { Barconfig *current; - TAILQ_FOREACH (current, &barconfigs, configs) { + TAILQ_FOREACH(current, &barconfigs, configs) { ipc_send_barconfig_update_event(current); } } @@ -114,12 +112,19 @@ static char *get_config_path(const char *override_configpath) { * parse_file(). * */ -static void parse_configuration(const char *override_configpath) { +bool parse_configuration(const char *override_configpath, bool use_nagbar) { char *path = get_config_path(override_configpath); LOG("Parsing configfile %s\n", path); FREE(current_configpath); current_configpath = path; - parse_file(path); + + /* initialize default bindings if we're just validating the config file */ + if (!use_nagbar && bindings == NULL) { + bindings = scalloc(sizeof(struct bindings_head)); + TAILQ_INIT(bindings); + } + + return parse_file(path, use_nagbar); } /* @@ -142,9 +147,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, while (!TAILQ_EMPTY(bindings)) { bind = TAILQ_FIRST(bindings); TAILQ_REMOVE(bindings, bind, bindings); - FREE(bind->translated_to); - FREE(bind->command); - FREE(bind); + binding_free(bind); } FREE(bindings); SLIST_REMOVE(&modes, mode, Mode, modes); @@ -204,8 +207,8 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, /* Invalidate pixmap caches in case font or colors changed */ Con *con; - TAILQ_FOREACH (con, &all_cons, all_cons) - FREE(con->deco_render_params); + TAILQ_FOREACH(con, &all_cons, all_cons) + FREE(con->deco_render_params); /* Get rid of the current font */ free_font(); @@ -262,7 +265,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, if (config.workspace_urgency_timer == 0) config.workspace_urgency_timer = 0.5; - parse_configuration(override_configpath); + parse_configuration(override_configpath, true); if (reload) { translate_keysyms(); diff --git a/src/config_directives.c b/src/config_directives.c index 781d6b36..80d7876b 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -171,8 +171,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 *command) { - configure_binding(bindtype, modifiers, key, release, command, DEFAULT_BINDING_MODE); +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); } /******************************************************************************* @@ -181,8 +181,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 *command) { - configure_binding(bindtype, modifiers, key, release, command, 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(enter_mode, const char *modename) { @@ -271,13 +271,15 @@ CFGFUN(new_window, const char *windowtype, const char *border, const long width) } if (strcmp(windowtype, "new_window") == 0) { - DLOG("default tiled border style = %d and border width = %d\n", border_style, border_width); + DLOG("default tiled border style = %d and border width = %d (%d physical px)\n", + border_style, border_width, logical_px(border_width)); config.default_border = border_style; - config.default_border_width = border_width; + config.default_border_width = logical_px(border_width); } else { - DLOG("default floating border style = %d and border width = %d\n", border_style, border_width); + DLOG("default floating border style = %d and border width = %d (%d physical px)\n", + border_style, border_width, logical_px(border_width)); config.default_floating_border = border_style; - config.default_floating_border_width = border_width; + config.default_floating_border_width = logical_px(border_width); } } @@ -334,7 +336,7 @@ CFGFUN(workspace, const char *workspace, const char *output) { * outputs */ struct Workspace_Assignment *assignment; bool duplicate = false; - TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) { + TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { if (strcasecmp(assignment->name, workspace) == 0) { ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n", workspace); @@ -460,6 +462,16 @@ CFGFUN(bar_modifier, const char *modifier) { current_bar.modifier = M_SHIFT; } +CFGFUN(bar_wheel_up_cmd, const char *command) { + FREE(current_bar.wheel_up_cmd); + current_bar.wheel_up_cmd = sstrdup(command); +} + +CFGFUN(bar_wheel_down_cmd, const char *command) { + FREE(current_bar.wheel_down_cmd); + current_bar.wheel_down_cmd = sstrdup(command); +} + CFGFUN(bar_position, const char *position) { current_bar.position = (strcmp(position, "top") == 0 ? P_TOP : P_BOTTOM); } diff --git a/src/config_parser.c b/src/config_parser.c index 21cd06fb..24cebcec 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -840,7 +840,7 @@ static char *migrate_config(char *input, off_t size) { * parse_config and possibly launching i3-nagbar. * */ -void parse_file(const char *f) { +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; struct stat stbuf; @@ -917,7 +917,7 @@ void parse_file(const char *f) { * variables (otherwise we will count them twice, which is bad when * 'extra' is negative) */ char *bufcopy = sstrdup(buf); - SLIST_FOREACH (current, &variables, variables) { + SLIST_FOREACH(current, &variables, variables) { int extra = (strlen(current->value) - strlen(current->key)); char *next; for (next = bufcopy; @@ -937,11 +937,11 @@ void parse_file(const char *f) { destwalk = new; while (walk < (buf + stbuf.st_size)) { /* Find the next variable */ - SLIST_FOREACH (current, &variables, variables) - current->next_match = strcasestr(walk, current->key); + SLIST_FOREACH(current, &variables, variables) + current->next_match = strcasestr(walk, current->key); nearest = NULL; int distance = stbuf.st_size; - SLIST_FOREACH (current, &variables, variables) { + SLIST_FOREACH(current, &variables, variables) { if (current->next_match == NULL) continue; if ((current->next_match - walk) < distance) { @@ -1000,7 +1000,7 @@ void parse_file(const char *f) { check_for_duplicate_bindings(context); - if (context->has_errors || context->has_warnings) { + if (use_nagbar && (context->has_errors || context->has_warnings)) { ELOG("FYI: You are using i3 version " I3_VERSION "\n"); if (version == 3) ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n"); @@ -1030,6 +1030,8 @@ void parse_file(const char *f) { free(pageraction); } + bool has_errors = context->has_errors; + FREE(context->line_copy); free(context); free(new); @@ -1042,6 +1044,8 @@ void parse_file(const char *f) { SLIST_REMOVE_HEAD(&variables, variables); FREE(current); } + + return !has_errors; } #endif diff --git a/src/ewmh.c b/src/ewmh.c index 986523dd..844a0db9 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -24,9 +24,9 @@ void ewmh_update_current_desktop(void) { uint32_t idx = 0; /* We count to get the index of this workspace because named workspaces * don’t have the ->num property */ - TAILQ_FOREACH (output, &(croot->nodes_head), nodes) { + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { Con *ws; - TAILQ_FOREACH (ws, &(output_get_content(output)->nodes_head), nodes) { + TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { if (STARTS_WITH(ws->name, "__")) continue; @@ -40,6 +40,102 @@ void ewmh_update_current_desktop(void) { } } +/* + * Updates _NET_NUMBER_OF_DESKTOPS which we interpret as the number of + * noninternal workspaces. + */ +void ewmh_update_number_of_desktops(void) { + Con *output; + uint32_t idx = 0; + + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + Con *ws; + TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { + if (STARTS_WITH(ws->name, "__")) + continue; + ++idx; + } + } + + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, + A__NET_NUMBER_OF_DESKTOPS, XCB_ATOM_CARDINAL, 32, 1, &idx); +} + +/* + * Updates _NET_DESKTOP_NAMES: "The names of all virtual desktops. This is a + * list of NULL-terminated strings in UTF-8 encoding" + */ +void ewmh_update_desktop_names(void) { + Con *output; + int msg_length = 0; + + /* count the size of the property message to set */ + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + Con *ws; + TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { + if (STARTS_WITH(ws->name, "__")) + continue; + msg_length += strlen(ws->name) + 1; + } + } + + char desktop_names[msg_length]; + int current_position = 0; + + /* fill the buffer with the names of the i3 workspaces */ + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + Con *ws; + TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { + if (STARTS_WITH(ws->name, "__")) + continue; + + for (size_t i = 0; i < strlen(ws->name) + 1; i++) { + desktop_names[current_position++] = ws->name[i]; + } + } + } + + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, + A__NET_DESKTOP_NAMES, A_UTF8_STRING, 8, msg_length, desktop_names); +} + +/* + * Updates _NET_DESKTOP_VIEWPORT, which is an array of pairs of cardinals that + * define the top left corner of each desktop's viewport. + */ +void ewmh_update_desktop_viewport(void) { + Con *output; + int num_desktops = 0; + /* count number of desktops */ + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + Con *ws; + TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { + if (STARTS_WITH(ws->name, "__")) + continue; + + num_desktops++; + } + } + + uint32_t viewports[num_desktops * 2]; + + int current_position = 0; + /* fill the viewport buffer */ + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + Con *ws; + TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { + if (STARTS_WITH(ws->name, "__")) + continue; + + viewports[current_position++] = output->rect.x; + viewports[current_position++] = output->rect.y; + } + } + + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, + A__NET_DESKTOP_VIEWPORT, XCB_ATOM_CARDINAL, 32, current_position, &viewports); +} + /* * Updates _NET_ACTIVE_WINDOW with the currently focused window. * @@ -138,5 +234,6 @@ void ewmh_setup_hints(void) { /* I’m not entirely sure if we need to keep _NET_WM_NAME on root. */ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3"); - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, 19, supported_atoms); + /* 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); } diff --git a/src/fake_outputs.c b/src/fake_outputs.c index 548c21c6..4f274099 100644 --- a/src/fake_outputs.c +++ b/src/fake_outputs.c @@ -20,9 +20,9 @@ static int num_screens; */ static Output *get_screen_at(unsigned int x, unsigned int y) { Output *output; - TAILQ_FOREACH (output, &outputs, outputs) - if (output->rect.x == x && output->rect.y == y) - return output; + TAILQ_FOREACH(output, &outputs, outputs) + if (output->rect.x == x && output->rect.y == y) + return output; return NULL; } diff --git a/src/floating.c b/src/floating.c index 7a8df508..8a2fde2c 100644 --- a/src/floating.c +++ b/src/floating.c @@ -21,7 +21,7 @@ static Rect total_outputs_dimensions(void) { Output *output; /* Use Rect to encapsulate dimensions, ignoring x/y */ Rect outputs_dimensions = {0, 0, 0, 0}; - TAILQ_FOREACH (output, &outputs, outputs) { + TAILQ_FOREACH(output, &outputs, outputs) { outputs_dimensions.height += output->rect.height; outputs_dimensions.width += output->rect.width; } @@ -205,7 +205,7 @@ void floating_enable(Con *con, bool automatic) { if (memcmp(&(nc->rect), &zero, sizeof(Rect)) == 0) { DLOG("Geometry not set, combining children\n"); Con *child; - TAILQ_FOREACH (child, &(con->nodes_head), nodes) { + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { DLOG("child geometry: %d x %d\n", child->geometry.width, child->geometry.height); nc->rect.width += child->geometry.width; nc->rect.height = max(nc->rect.height, child->geometry.height); @@ -298,16 +298,22 @@ void floating_enable(Con *con, bool automatic) { /* Check if we need to re-assign it to a different workspace because of its * coordinates and exit if that was done successfully. */ - if (floating_maybe_reassign_ws(nc)) + if (floating_maybe_reassign_ws(nc)) { + ipc_send_window_event("floating", con); return; + } /* Sanitize coordinates: Check if they are on any output */ - if (get_output_containing(nc->rect.x, nc->rect.y) != NULL) + if (get_output_containing(nc->rect.x, nc->rect.y) != NULL) { + ipc_send_window_event("floating", con); return; + } 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); + + ipc_send_window_event("floating", con); } void floating_disable(Con *con, bool automatic) { @@ -351,6 +357,8 @@ void floating_disable(Con *con, bool automatic) { if (set_focus) con_focus(con); + + ipc_send_window_event("floating", con); } /* diff --git a/src/handlers.c b/src/handlers.c index c217cbbc..3569710b 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -21,6 +21,8 @@ #include int randr_base = -1; +int xkb_base = -1; +int xkb_current_group; /* After mapping/unmapping windows, a notify event is generated. However, we don’t want it, since it’d trigger an infinite loop of switching between the different windows when @@ -62,7 +64,7 @@ bool event_is_ignored(const int sequence, const int response_type) { event = SLIST_NEXT(event, ignore_events); } - SLIST_FOREACH (event, &ignore_events, ignore_events) { + SLIST_FOREACH(event, &ignore_events, ignore_events) { if (event->sequence != sequence) continue; @@ -163,12 +165,12 @@ static void handle_enter_notify(xcb_enter_notify_event_t *event) { layout_t layout = (enter_child ? con->parent->layout : con->layout); if (layout == L_DEFAULT) { Con *child; - TAILQ_FOREACH (child, &(con->nodes_head), nodes) - if (rect_contains(child->deco_rect, event->event_x, event->event_y)) { - LOG("using child %p / %s instead!\n", child, child->name); - con = child; - break; - } + TAILQ_FOREACH(child, &(con->nodes_head), nodes) + if (rect_contains(child->deco_rect, event->event_x, event->event_y)) { + LOG("using child %p / %s instead!\n", child, child->name); + con = child; + break; + } } #if 0 @@ -231,7 +233,7 @@ static void handle_motion_notify(xcb_motion_notify_event_t *event) { /* see over which rect the user is */ Con *current; - TAILQ_FOREACH (current, &(con->nodes_head), nodes) { + TAILQ_FOREACH(current, &(con->nodes_head), nodes) { if (!rect_contains(current->deco_rect, event->event_x, event->event_y)) continue; @@ -282,7 +284,6 @@ static void handle_map_request(xcb_map_request_event_t *event) { add_ignore_event(event->sequence, -1); manage_window(event->window, cookie, false); - x_push_changes(croot); return; } @@ -487,7 +488,6 @@ static void handle_unmap_notify_event(xcb_unmap_notify_event_t *event) { tree_close(con, DONT_KILL_WINDOW, false, false); tree_render(); - x_push_changes(croot); ignore_end: /* If the client (as opposed to i3) destroyed or unmapped a window, an @@ -651,6 +651,19 @@ static void handle_expose_event(xcb_expose_event_t *event) { return; } +#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0 +#define _NET_WM_MOVERESIZE_SIZE_TOP 1 +#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2 +#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6 +#define _NET_WM_MOVERESIZE_SIZE_LEFT 7 +#define _NET_WM_MOVERESIZE_MOVE 8 /* movement only */ +#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD 9 /* size via keyboard */ +#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */ +#define _NET_WM_MOVERESIZE_CANCEL 11 /* cancel operation */ + /* * Handle client messages (EWMH) * @@ -791,6 +804,98 @@ static void handle_client_message(xcb_client_message_event_t *event) { XCB_ATOM_CARDINAL, 32, 4, &r); xcb_flush(conn); + } else if (event->type == A_WM_CHANGE_STATE) { + /* http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.4 */ + Con *con = con_by_window_id(event->window); + + if (con && event->data.data32[0] == 3) { + /* this request is so we can play some animiation showing the + * window physically moving to the tray before we close it (I + * think) */ + DLOG("Client has requested iconic state. Closing this con. (con = %p)\n", con); + tree_close(con, DONT_KILL_WINDOW, false, false); + tree_render(); + } else { + DLOG("Not handling WM_CHANGE_STATE request. (window = %d, state = %d)\n", event->window, event->data.data32[0]); + } + + } else if (event->type == A__NET_CURRENT_DESKTOP) { + /* This request is used by pagers and bars to change the current + * desktop likely as a result of some user action. We interpret this as + * a request to focus the given workspace. See + * http://standards.freedesktop.org/wm-spec/latest/ar01s03.html#idm140251368135008 + * */ + Con *output; + uint32_t idx = 0; + DLOG("Request to change current desktop to index %d\n", event->data.data32[0]); + + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + Con *ws; + TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { + if (STARTS_WITH(ws->name, "__")) + continue; + + if (idx == event->data.data32[0]) { + /* data32[1] is a timestamp used to prevent focus race conditions */ + if (event->data.data32[1]) + last_timestamp = event->data.data32[1]; + + DLOG("Handling request to focus workspace %s\n", ws->name); + + workspace_show(ws); + tree_render(); + + return; + } + + ++idx; + } + } + } else if (event->type == A__NET_CLOSE_WINDOW) { + /* + * Pagers wanting to close a window MUST send a _NET_CLOSE_WINDOW + * client message request to the root window. + * http://standards.freedesktop.org/wm-spec/wm-spec-latest.html#idm140200472668896 + */ + Con *con = con_by_window_id(event->window); + if (con) { + DLOG("Handling _NET_CLOSE_WINDOW request (con = %p)\n", con); + + if (event->data.data32[0]) + last_timestamp = event->data.data32[0]; + + tree_close(con, KILL_WINDOW, false, false); + tree_render(); + } else { + DLOG("Couldn't find con for _NET_CLOSE_WINDOW request. (window = %d)\n", event->window); + } + } else if (event->type == A__NET_WM_MOVERESIZE) { + /* + * Client-side decorated Gtk3 windows emit this signal when being + * dragged by their GtkHeaderBar + */ + Con *con = con_by_window_id(event->window); + if (!con || !con_is_floating(con)) { + DLOG("Couldn't find con for _NET_WM_MOVERESIZE request, or con not floating (window = %d)\n", event->window); + return; + } + DLOG("Handling _NET_WM_MOVERESIZE request (con = %p)\n", con); + uint32_t direction = event->data.data32[2]; + uint32_t x_root = event->data.data32[0]; + uint32_t y_root = event->data.data32[1]; + /* construct fake xcb_button_press_event_t */ + xcb_button_press_event_t fake = { + .root_x = x_root, + .root_y = y_root, + .event_x = x_root - (con->rect.x), + .event_y = y_root - (con->rect.y)}; + if (direction == _NET_WM_MOVERESIZE_MOVE) { + floating_drag_window(con->parent, &fake); + } else if (direction >= _NET_WM_MOVERESIZE_SIZE_TOPLEFT && direction <= _NET_WM_MOVERESIZE_SIZE_LEFT) { + floating_resize_window(con->parent, FALSE, &fake); + } else { + DLOG("_NET_WM_MOVERESIZE direction %d not implemented\n", direction); + } } else { DLOG("unhandled clientmessage\n"); return; @@ -1045,6 +1150,30 @@ static void handle_focus_in(xcb_focus_in_event_t *event) { return; } +/* + * Handles the WM_CLASS property for assignments and criteria selection. + * + */ +static bool handle_class_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, + xcb_atom_t name, xcb_get_property_reply_t *prop) { + Con *con; + if ((con = con_by_window_id(window)) == NULL || con->window == NULL) + return false; + + if (prop == NULL) { + prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, + false, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0, 32), + NULL); + + if (prop == NULL) + return false; + } + + window_update_class(con->window, prop, false); + + return true; +} + /* Returns false if the event could not be processed (e.g. the window could not * be found), true otherwise */ typedef bool (*cb_property_handler_t)(void *data, xcb_connection_t *c, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *property); @@ -1062,7 +1191,8 @@ static struct property_handler_t property_handlers[] = { {0, UINT_MAX, handle_normal_hints}, {0, UINT_MAX, handle_clientleader_change}, {0, UINT_MAX, handle_transient_for}, - {0, 128, handle_windowrole_change}}; + {0, 128, handle_windowrole_change}, + {0, 128, handle_class_change}}; #define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t)) /* @@ -1080,6 +1210,7 @@ void property_handlers_init(void) { property_handlers[4].atom = A_WM_CLIENT_LEADER; property_handlers[5].atom = XCB_ATOM_WM_TRANSIENT_FOR; property_handlers[6].atom = A_WM_WINDOW_ROLE; + property_handlers[7].atom = XCB_ATOM_WM_CLASS; } static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) { @@ -1115,12 +1246,50 @@ 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 (randr_base > -1 && type == randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY) { handle_screen_change(event); return; } + if (xkb_base > -1 && type == xkb_base) { + DLOG("xkb event, need to handle it.\n"); + + xcb_xkb_state_notify_event_t *state = (xcb_xkb_state_notify_event_t *)event; + 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); + } else { + DLOG("xkb map notify, sequence %d, time %d\n", state->sequence, state->time); + add_ignore_event(event->sequence, type); + ungrab_all_keys(conn); + translate_keysyms(); + grab_all_keys(conn, false); + } + } 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 XkbGroup2Index */ + 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, false); + } + } + + return; + } + switch (type) { case XCB_KEY_PRESS: case XCB_KEY_RELEASE: @@ -1128,6 +1297,7 @@ void handle_event(int type, xcb_generic_event_t *event) { break; case XCB_BUTTON_PRESS: + case XCB_BUTTON_RELEASE: handle_button_press((xcb_button_press_event_t *)event); break; diff --git a/src/i3.mk b/src/i3.mk index 0325d82a..8c763464 100644 --- a/src/i3.mk +++ b/src/i3.mk @@ -5,8 +5,8 @@ CLEAN_TARGETS += clean-i3 i3_SOURCES := $(filter-out $(i3_SOURCES_GENERATED),$(wildcard src/*.c)) i3_HEADERS_CMDPARSER := $(wildcard include/GENERATED_*.h) i3_HEADERS := $(filter-out $(i3_HEADERS_CMDPARSER),$(wildcard include/*.h)) -i3_CFLAGS = $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(XCB_WM_CFLAGS) $(X11_CFLAGS) $(XCURSOR_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) $(PCRE_CFLAGS) $(LIBSN_CFLAGS) -i3_LIBS = $(XCB_LIBS) $(XCB_KBD_LIBS) $(XCB_WM_LIBS) $(X11_LIBS) $(XCURSOR_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) $(PCRE_LIBS) $(LIBSN_LIBS) -lm -lpthread +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 # When using clang, we use pre-compiled headers to speed up the build. With # gcc, this actually makes the build slower. diff --git a/src/ipc.c b/src/ipc.c index 66c28bdc..7dbb6632 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -74,7 +74,7 @@ bool mkdirp(const char *path) { */ void ipc_send_event(const char *event, uint32_t message_type, const char *payload) { ipc_client *current; - TAILQ_FOREACH (current, &all_clients, clients) { + TAILQ_FOREACH(current, &all_clients, clients) { /* see if this client is interested in this event */ bool interested = false; for (int i = 0; i < current->num_events; i++) { @@ -151,6 +151,60 @@ 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"); + y(array_open); + for (int i = 0; i < 8; i++) { + if (bind->mods & (1 << i)) { + switch (1 << i) { + case XCB_MOD_MASK_SHIFT: + ystr("shift"); + break; + case XCB_MOD_MASK_LOCK: + ystr("lock"); + break; + case XCB_MOD_MASK_CONTROL: + ystr("ctrl"); + break; + case XCB_MOD_MASK_1: + ystr("Mod1"); + break; + case XCB_MOD_MASK_2: + ystr("Mod2"); + break; + case XCB_MOD_MASK_3: + ystr("Mod3"); + break; + case XCB_MOD_MASK_4: + ystr("Mod4"); + break; + case XCB_MOD_MASK_5: + ystr("Mod5"); + break; + } + } + } + y(array_close); + + y(map_close); +} + void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { y(map_open); ystr("id"); @@ -293,14 +347,17 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { y(integer, con->current_border_width); dump_rect(gen, "rect", con->rect); + dump_rect(gen, "deco_rect", con->deco_rect); dump_rect(gen, "window_rect", con->window_rect); dump_rect(gen, "geometry", con->geometry); ystr("name"); if (con->window && con->window->name) ystr(i3string_as_utf8(con->window->name)); - else + else if (con->name != NULL) ystr(con->name); + else + y(null); if (con->type == CT_WORKSPACE) { ystr("num"); @@ -337,6 +394,11 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { ystr(i3string_as_utf8(con->window->name)); } + ystr("transient_for"); + if (con->window->transient_for == XCB_NONE) + y(null); + else y(integer, con->window->transient_for); + y(map_close); } @@ -344,7 +406,7 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { y(array_open); Con *node; if (con->type != CT_DOCKAREA || !inplace_restart) { - TAILQ_FOREACH (node, &(con->nodes_head), nodes) { + TAILQ_FOREACH(node, &(con->nodes_head), nodes) { dump_node(gen, node, inplace_restart); } } @@ -352,14 +414,14 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { ystr("floating_nodes"); y(array_open); - TAILQ_FOREACH (node, &(con->floating_head), floating_windows) { + TAILQ_FOREACH(node, &(con->floating_head), floating_windows) { dump_node(gen, node, inplace_restart); } y(array_close); ystr("focus"); y(array_open); - TAILQ_FOREACH (node, &(con->focus_head), focused) { + TAILQ_FOREACH(node, &(con->focus_head), focused) { y(integer, (long int)node); } y(array_close); @@ -386,7 +448,7 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { ystr("swallows"); y(array_open); Match *match; - TAILQ_FOREACH (match, &(con->swallow_head), matches) { + TAILQ_FOREACH(match, &(con->swallow_head), matches) { y(map_open); if (match->dock != -1) { ystr("dock"); @@ -512,6 +574,16 @@ 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); + } + ystr("position"); if (config->position == P_BOTTOM) ystr("bottom"); @@ -591,19 +663,16 @@ IPC_HANDLER(get_workspaces) { Con *focused_ws = con_get_workspace(focused); Con *output; - TAILQ_FOREACH (output, &(croot->nodes_head), nodes) { + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { if (con_is_internal(output)) continue; Con *ws; - TAILQ_FOREACH (ws, &(output_get_content(output)->nodes_head), nodes) { + TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { assert(ws->type == CT_WORKSPACE); y(map_open); ystr("num"); - if (ws->num == -1) - y(null); - else - y(integer, ws->num); + y(integer, ws->num); ystr("name"); ystr(ws->name); @@ -656,7 +725,7 @@ IPC_HANDLER(get_outputs) { y(array_open); Output *output; - TAILQ_FOREACH (output, &outputs, outputs) { + TAILQ_FOREACH(output, &outputs, outputs) { y(map_open); ystr("name"); @@ -710,9 +779,9 @@ IPC_HANDLER(get_marks) { y(array_open); Con *con; - TAILQ_FOREACH (con, &all_cons, all_cons) - if (con->mark != NULL) - ystr(con->mark); + TAILQ_FOREACH(con, &all_cons, all_cons) + if (con->mark != NULL) + ystr(con->mark); y(array_close); @@ -766,7 +835,7 @@ IPC_HANDLER(get_bar_config) { if (message_size == 0) { y(array_open); Barconfig *current; - TAILQ_FOREACH (current, &barconfigs, configs) { + TAILQ_FOREACH(current, &barconfigs, configs) { ystr(current->id); } y(array_close); @@ -786,7 +855,7 @@ IPC_HANDLER(get_bar_config) { strncpy(bar_id, (const char *)message, message_size); LOG("IPC: looking for config for bar ID \"%s\"\n", bar_id); Barconfig *current, *config = NULL; - TAILQ_FOREACH (current, &barconfigs, configs) { + TAILQ_FOREACH(current, &barconfigs, configs) { if (strcmp(current->id, bar_id) != 0) continue; @@ -852,7 +921,7 @@ IPC_HANDLER(subscribe) { ipc_client *current, *client = NULL; /* Search the ipc_client structure for this connection */ - TAILQ_FOREACH (current, &all_clients, clients) { + TAILQ_FOREACH(current, &all_clients, clients) { if (current->fd != fd) continue; @@ -932,7 +1001,7 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { /* Delete the client from the list of clients */ ipc_client *current; - TAILQ_FOREACH (current, &all_clients, clients) { + TAILQ_FOREACH(current, &all_clients, clients) { if (current->fd != w->fd) continue; @@ -1051,21 +1120,23 @@ int ipc_create_socket(const char *filename) { } /* - * For the workspace "focus" event we send, along the usual "change" field, - * also the current and previous workspace, in "current" and "old" - * respectively. + * Generates a json workspace event. Returns a dynamically allocated yajl + * generator. Free with yajl_gen_free(). */ -void ipc_send_workspace_focus_event(Con *current, Con *old) { +yajl_gen ipc_marshal_workspace_event(const char *change, Con *current, Con *old) { setlocale(LC_NUMERIC, "C"); yajl_gen gen = ygenalloc(); y(map_open); ystr("change"); - ystr("focus"); + ystr(change); ystr("current"); - dump_node(gen, current, false); + if (current == NULL) + y(null); + else + dump_node(gen, current, false); ystr("old"); if (old == NULL) @@ -1075,13 +1146,26 @@ void ipc_send_workspace_focus_event(Con *current, Con *old) { y(map_close); + setlocale(LC_NUMERIC, ""); + + return gen; +} + +/* + * For the workspace events we send, along with the usual "change" field, also + * the workspace container in "current". For focus events, we send the + * previously focused workspace in "old". + */ +void ipc_send_workspace_event(const char *change, Con *current, Con *old) { + yajl_gen gen = ipc_marshal_workspace_event(change, current, old); + const unsigned char *payload; ylength length; y(get_buf, &payload, &length); ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, (const char *)payload); + y(free); - setlocale(LC_NUMERIC, ""); } /** @@ -1132,3 +1216,33 @@ void ipc_send_barconfig_update_event(Barconfig *barconfig) { y(free); setlocale(LC_NUMERIC, ""); } + +/* + * For the binding events, we send the serialized binding struct. + */ +void ipc_send_binding_event(const char *event_type, Binding *bind) { + DLOG("Issue IPC binding %s event (sym = %s, code = %d)\n", event_type, bind->symbol, bind->keycode); + + setlocale(LC_NUMERIC, "C"); + + yajl_gen gen = ygenalloc(); + + y(map_open); + + ystr("change"); + ystr(event_type); + + ystr("binding"); + dump_binding(gen, bind); + + y(map_close); + + const unsigned char *payload; + ylength length; + y(get_buf, &payload, &length); + + ipc_send_event("binding", I3_IPC_EVENT_BINDING, (const char *)payload); + + y(free); + setlocale(LC_NUMERIC, ""); +} diff --git a/src/key_press.c b/src/key_press.c index 56021da0..95e5079e 100644 --- a/src/key_press.c +++ b/src/key_press.c @@ -30,7 +30,7 @@ void handle_key_press(xcb_key_press_event_t *event) { if (bind == NULL) return; - CommandResult *result = run_binding(bind); + CommandResult *result = run_binding(bind, NULL); if (result->needs_tree_render) tree_render(); diff --git a/src/load_layout.c b/src/load_layout.c index 5a66828d..ccd71c37 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -24,6 +24,7 @@ static Con *json_node; static Con *to_focus; static bool parsing_swallows; static bool parsing_rect; +static bool parsing_deco_rect; static bool parsing_window_rect; static bool parsing_geometry; static bool parsing_focus; @@ -47,7 +48,7 @@ static int json_start_map(void *ctx) { match_init(current_swallow); TAILQ_INSERT_TAIL(&(json_node->swallow_head), current_swallow, matches); } else { - if (!parsing_rect && !parsing_window_rect && !parsing_geometry) { + if (!parsing_rect && !parsing_deco_rect && !parsing_window_rect && !parsing_geometry) { if (last_key && strcasecmp(last_key, "floating_nodes") == 0) { DLOG("New floating_node\n"); Con *ws = con_get_workspace(json_node); @@ -68,7 +69,7 @@ static int json_start_map(void *ctx) { static int json_end_map(void *ctx) { LOG("end of map\n"); - if (!parsing_swallows && !parsing_rect && !parsing_window_rect && !parsing_geometry) { + if (!parsing_swallows && !parsing_rect && !parsing_deco_rect && !parsing_window_rect && !parsing_geometry) { /* Set a few default values to simplify manually crafted layout files. */ if (json_node->layout == L_DEFAULT) { DLOG("Setting layout = L_SPLITH\n"); @@ -98,26 +99,21 @@ static int json_end_map(void *ctx) { * workspace called “1”. */ Con *output; Con *workspace = NULL; - TAILQ_FOREACH (output, &(croot->nodes_head), nodes) - GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, json_node->name)); + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) + GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, json_node->name)); char *base = sstrdup(json_node->name); int cnt = 1; while (workspace != NULL) { FREE(json_node->name); asprintf(&(json_node->name), "%s_%d", base, cnt++); workspace = NULL; - TAILQ_FOREACH (output, &(croot->nodes_head), nodes) - GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, json_node->name)); + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) + GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, json_node->name)); } free(base); /* Set num accordingly so that i3bar will properly sort it. */ json_node->num = ws_name_to_number(json_node->name); - } else { - // TODO: remove this in the “next” branch. - if (json_node->name == NULL || strcmp(json_node->name, "") == 0) { - json_node->name = sstrdup("#ff0000"); - } } LOG("attaching\n"); @@ -126,12 +122,11 @@ static int json_end_map(void *ctx) { x_con_init(json_node, json_node->depth); json_node = json_node->parent; } - if (parsing_rect) - parsing_rect = false; - if (parsing_window_rect) - parsing_window_rect = false; - if (parsing_geometry) - parsing_geometry = false; + + parsing_rect = false; + parsing_deco_rect = false; + parsing_window_rect = false; + parsing_geometry = false; return 1; } @@ -146,10 +141,10 @@ static int json_end_array(void *ctx) { if (parsing_focus) { /* Clear the list of focus mappings */ struct focus_mapping *mapping; - TAILQ_FOREACH_REVERSE (mapping, &focus_mappings, focus_mappings_head, focus_mappings) { + TAILQ_FOREACH_REVERSE(mapping, &focus_mappings, focus_mappings_head, focus_mappings) { LOG("focus (reverse) %d\n", mapping->old_id); Con *con; - TAILQ_FOREACH (con, &(json_node->focus_head), focused) { + TAILQ_FOREACH(con, &(json_node->focus_head), focused) { if (con->old_id != mapping->old_id) continue; LOG("got it! %p\n", con); @@ -180,6 +175,9 @@ static int json_key(void *ctx, const unsigned char *val, size_t len) { if (strcasecmp(last_key, "rect") == 0) parsing_rect = true; + if (strcasecmp(last_key, "deco_rect") == 0) + parsing_deco_rect = true; + if (strcasecmp(last_key, "window_rect") == 0) parsing_window_rect = true; @@ -553,6 +551,7 @@ void tree_append_json(Con *con, const char *filename, char **errormsg) { to_focus = NULL; parsing_swallows = false; parsing_rect = false; + parsing_deco_rect = false; parsing_window_rect = false; parsing_geometry = false; parsing_focus = false; diff --git a/src/main.c b/src/main.c index 66090dae..b696e031 100644 --- a/src/main.c +++ b/src/main.c @@ -36,10 +36,6 @@ int listen_fds; * temporarily for drag_pointer(). */ static struct ev_check *xcb_check; -static int xkb_event_base; - -int xkb_current_group; - extern Con *focused; char **start_argv; @@ -70,9 +66,6 @@ struct ev_loop *main_loop; xcb_key_symbols_t *keysyms; -/* Those are our connections to X11 for use with libXcursor and XKB */ -Display *xlibdpy, *xkbdpy; - /* Default shmlog size if not set by user. */ const int default_shmlog_size = 25 * 1024 * 1024; @@ -94,12 +87,6 @@ struct ws_assignments_head ws_assignments = TAILQ_HEAD_INITIALIZER(ws_assignment /* We hope that those are supported and set them to true */ bool xcursor_supported = true; -bool xkb_supported = true; - -/* This will be set to true when -C is used so that functions can behave - * slightly differently. We don’t want i3-nagbar to be started when validating - * the config, for example. */ -bool only_check_config = false; /* * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb. @@ -166,73 +153,6 @@ void main_set_x11_cb(bool enable) { } } -/* - * When using xmodmap to change the keyboard mapping, this event - * is only sent via XKB. Therefore, we need this special handler. - * - */ -static void xkb_got_event(EV_P_ struct ev_io *w, int revents) { - DLOG("Handling XKB event\n"); - XkbEvent ev; - - /* When using xmodmap, every change (!) gets an own event. - * Therefore, we just read all events and only handle the - * mapping_notify once. */ - bool mapping_changed = false; - while (XPending(xkbdpy)) { - XNextEvent(xkbdpy, (XEvent *)&ev); - /* While we should never receive a non-XKB event, - * better do sanity checking */ - if (ev.type != xkb_event_base) - continue; - - if (ev.any.xkb_type == XkbMapNotify) { - mapping_changed = true; - continue; - } - - if (ev.any.xkb_type != XkbStateNotify) { - ELOG("Unknown XKB event received (type %d)\n", ev.any.xkb_type); - continue; - } - - /* 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 XkbGroup2Index */ - if (xkb_current_group == ev.state.group) - continue; - - xkb_current_group = ev.state.group; - - if (ev.state.group == XkbGroup2Index) { - DLOG("Mode_switch enabled\n"); - grab_all_keys(conn, true); - } - - if (ev.state.group == XkbGroup1Index) { - DLOG("Mode_switch disabled\n"); - ungrab_all_keys(conn); - grab_all_keys(conn, false); - } - } - - if (!mapping_changed) - return; - - DLOG("Keyboard mapping changed, updating keybindings\n"); - xcb_key_symbols_free(keysyms); - keysyms = xcb_key_symbols_alloc(conn); - - xcb_numlock_mask = aio_get_mod_mask_for(XCB_NUM_LOCK, keysyms); - - ungrab_all_keys(conn); - DLOG("Re-grabbing...\n"); - translate_keysyms(); - grab_all_keys(conn, (xkb_current_group == XkbGroup2Index)); - DLOG("Done\n"); -} - /* * Exit handler which destroys the main_loop. Will trigger cleanup handlers. * @@ -276,6 +196,7 @@ int main(int argc, char *argv[]) { bool force_xinerama = false; char *fake_outputs = NULL; bool disable_signalhandler = false; + bool only_check_config = false; static struct option long_options[] = { {"no-autostart", no_argument, 0, 'a'}, {"config", required_argument, 0, 'c'}, @@ -441,10 +362,14 @@ int main(int argc, char *argv[]) { } } + if (only_check_config) { + exit(parse_configuration(override_configpath, false) ? 0 : 1); + } + /* If the user passes more arguments, we act like i3-msg would: Just send * the arguments as an IPC message to i3. This allows for nice semantic * commands such as 'i3 border none'. */ - if (!only_check_config && optind < argc) { + if (optind < argc) { /* We enable verbose mode so that the user knows what’s going on. * This should make it easier to find mistakes when the user passes * arguments by mistake. */ @@ -567,10 +492,6 @@ int main(int argc, char *argv[]) { xcb_query_pointer_cookie_t pointercookie = xcb_query_pointer(conn, root); load_configuration(conn, override_configpath, false); - if (only_check_config) { - LOG("Done checking configuration file. Exiting.\n"); - exit(0); - } if (config.ipc_socket_path == NULL) { /* Fall back to a file name in /tmp/ based on the PID */ @@ -597,21 +518,7 @@ int main(int argc, char *argv[]) { #include "atoms.xmacro" #undef xmacro - /* Initialize the Xlib connection */ - xlibdpy = xkbdpy = XOpenDisplay(NULL); - - /* Try to load the X cursors and initialize the XKB extension */ - if (xlibdpy == NULL) { - ELOG("ERROR: XOpenDisplay() failed, disabling libXcursor/XKB support\n"); - xcursor_supported = false; - xkb_supported = false; - } else if (fcntl(ConnectionNumber(xlibdpy), F_SETFD, FD_CLOEXEC) == -1) { - ELOG("Could not set FD_CLOEXEC on xkbdpy\n"); - return 1; - } else { - xcursor_load_cursors(); - /*init_xkb();*/ - } + xcursor_load_cursors(); /* Set a cursor for the root window (otherwise the root window will show no cursor until the first client is launched). */ @@ -620,27 +527,22 @@ int main(int argc, char *argv[]) { else xcb_set_root_cursor(XCURSOR_CURSOR_POINTER); - if (xkb_supported) { - int errBase, - major = XkbMajorVersion, - minor = XkbMinorVersion; - - if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) { - fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n"); - return 1; - } - - int i1; - if (!XkbQueryExtension(xkbdpy, &i1, &xkb_event_base, &errBase, &major, &minor)) { - fprintf(stderr, "XKB not supported by X-server\n"); - xkb_supported = false; - } - /* end of ugliness */ - - if (xkb_supported && !XkbSelectEvents(xkbdpy, XkbUseCoreKbd, XkbMapNotifyMask | XkbStateNotifyMask, XkbMapNotifyMask | XkbStateNotifyMask)) { - fprintf(stderr, "Could not set XKB event mask\n"); - return 1; - } + const xcb_query_extension_reply_t *extreply; + extreply = xcb_get_extension_data(conn, &xcb_xkb_id); + if (!extreply->present) { + DLOG("xkb is not present on this server\n"); + } else { + DLOG("initializing xcb-xkb\n"); + xcb_xkb_use_extension(conn, XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION); + xcb_xkb_select_events(conn, + XCB_XKB_ID_USE_CORE_KBD, + XCB_XKB_EVENT_TYPE_STATE_NOTIFY | XCB_XKB_EVENT_TYPE_MAP_NOTIFY, + 0, + XCB_XKB_EVENT_TYPE_STATE_NOTIFY | XCB_XKB_EVENT_TYPE_MAP_NOTIFY, + 0xff, + 0xff, + NULL); + xkb_base = extreply->first_event; } restore_connect(); @@ -769,25 +671,19 @@ int main(int argc, char *argv[]) { x_set_i3_atoms(); ewmh_update_workarea(); - /* Set the _NET_CURRENT_DESKTOP property. */ + /* Set the ewmh desktop properties. */ ewmh_update_current_desktop(); + ewmh_update_number_of_desktops(); + ewmh_update_desktop_names(); + ewmh_update_desktop_viewport(); struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io)); - struct ev_io *xkb = scalloc(sizeof(struct ev_io)); xcb_check = scalloc(sizeof(struct ev_check)); struct ev_prepare *xcb_prepare = scalloc(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); - if (xkb_supported) { - ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ); - ev_io_start(main_loop, xkb); - - /* Flush the buffer so that libev can properly get new events */ - XFlush(xkbdpy); - } - ev_check_init(xcb_check, xcb_check_cb); ev_check_start(main_loop, xcb_check); @@ -888,7 +784,7 @@ int main(int argc, char *argv[]) { /* Autostarting exec-lines */ if (autostart) { struct Autostart *exec; - TAILQ_FOREACH (exec, &autostarts, autostarts) { + TAILQ_FOREACH(exec, &autostarts, autostarts) { LOG("auto-starting %s\n", exec->command); start_application(exec->command, exec->no_startup_id); } @@ -896,14 +792,14 @@ int main(int argc, char *argv[]) { /* Autostarting exec_always-lines */ struct Autostart *exec_always; - TAILQ_FOREACH (exec_always, &autostarts_always, autostarts_always) { + TAILQ_FOREACH(exec_always, &autostarts_always, autostarts_always) { LOG("auto-starting (always!) %s\n", exec_always->command); start_application(exec_always->command, exec_always->no_startup_id); } /* Start i3bar processes for all configured bars */ Barconfig *barconfig; - TAILQ_FOREACH (barconfig, &barconfigs, configs) { + TAILQ_FOREACH(barconfig, &barconfigs, configs) { char *command = NULL; sasprintf(&command, "%s --bar_id=%s --socket=\"%s\"", barconfig->i3bar_command ? barconfig->i3bar_command : "i3bar", diff --git a/src/manage.c b/src/manage.c index a7002bf3..b7ea5e69 100644 --- a/src/manage.c +++ b/src/manage.c @@ -56,16 +56,16 @@ void restore_geometry(void) { DLOG("Restoring geometry\n"); Con *con; - TAILQ_FOREACH (con, &all_cons, all_cons) - if (con->window) { - DLOG("Re-adding X11 border of %d px\n", con->border_width); - con->window_rect.width += (2 * con->border_width); - con->window_rect.height += (2 * con->border_width); - xcb_set_window_rect(conn, con->window->id, con->window_rect); - DLOG("placing window %08x at %d %d\n", con->window->id, con->rect.x, con->rect.y); - xcb_reparent_window(conn, con->window->id, root, - con->rect.x, con->rect.y); - } + TAILQ_FOREACH(con, &all_cons, all_cons) + if (con->window) { + DLOG("Re-adding X11 border of %d px\n", con->border_width); + con->window_rect.width += (2 * con->border_width); + con->window_rect.height += (2 * con->border_width); + xcb_set_window_rect(conn, con->window->id, con->window_rect); + DLOG("placing window %08x at %d %d\n", con->window->id, con->rect.x, con->rect.y); + xcb_reparent_window(conn, con->window->id, root, + con->rect.x, con->rect.y); + } /* Strictly speaking, this line doesn’t really belong here, but since we * are syncing, let’s un-register as a window manager first */ @@ -436,11 +436,6 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki if (nc->geometry.width == 0) nc->geometry = (Rect) {geom->x, geom->y, geom->width, geom->height}; - if (want_floating) { - DLOG("geometry = %d x %d\n", nc->geometry.width, nc->geometry.height); - floating_enable(nc, true); - } - if (motif_border_style != BS_NORMAL) { DLOG("MOTIF_WM_HINTS specifies decorations (border_style = %d)\n", motif_border_style); if (want_floating) { @@ -450,12 +445,18 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki } } - if (nc->border_style == BS_PIXEL) { - /* if the border style is BS_PIXEL, explicitly set the border width of - * the new container */ - nc->current_border_width = (want_floating ? config.default_floating_border_width : config.default_border_width); + if (want_floating) { + DLOG("geometry = %d x %d\n", nc->geometry.width, nc->geometry.height); + /* automatically set the border to the default value if a motif border + * was not specified */ + bool automatic_border = (motif_border_style == BS_NORMAL); + + floating_enable(nc, automatic_border); } + /* explicitly set the border width to the default */ + nc->current_border_width = (want_floating ? config.default_floating_border_width : config.default_border_width); + /* to avoid getting an UnmapNotify event due to reparenting, we temporarily * declare no interest in any state change event of this window */ values[0] = XCB_NONE; @@ -511,7 +512,7 @@ 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->mapped) { + if (set_focus && !nc->window->doesnt_accept_focus && nc->mapped) { DLOG("Now setting focus.\n"); con_focus(nc); } diff --git a/src/match.c b/src/match.c index a810068e..dc4d422f 100644 --- a/src/match.c +++ b/src/match.c @@ -136,7 +136,7 @@ bool match_matches_window(Match *match, i3Window *window) { return false; } /* if we find a window that is newer than this one, bail */ - TAILQ_FOREACH (con, &all_cons, all_cons) { + TAILQ_FOREACH(con, &all_cons, all_cons) { if ((con->window != NULL) && _i3_timercmp(con->window->urgent, window->urgent, > )) { return false; @@ -151,7 +151,7 @@ bool match_matches_window(Match *match, i3Window *window) { return false; } /* if we find a window that is older than this one (and not 0), bail */ - TAILQ_FOREACH (con, &all_cons, all_cons) { + TAILQ_FOREACH(con, &all_cons, all_cons) { if ((con->window != NULL) && (con->window->urgent.tv_sec != 0) && _i3_timercmp(con->window->urgent, window->urgent, < )) { diff --git a/src/move.c b/src/move.c index 42510d52..1999a1fe 100644 --- a/src/move.c +++ b/src/move.c @@ -128,22 +128,21 @@ static void move_to_output_directed(Con *con, direction_t direction) { tree_flatten(croot); - ipc_send_workspace_focus_event(ws, old_ws); + ipc_send_workspace_event("focus", ws, old_ws); } /* - * Moves the current container in the given direction (D_LEFT, D_RIGHT, + * Moves the given container in the given direction (D_LEFT, D_RIGHT, * D_UP, D_DOWN). * */ -void tree_move(int direction) { +void tree_move(Con *con, int direction) { position_t position; Con *target; DLOG("Moving in direction %d\n", direction); /* 1: get the first parent with the same orientation */ - Con *con = focused; if (con->type == CT_WORKSPACE) { DLOG("Not moving workspace\n"); @@ -206,6 +205,7 @@ void tree_move(int direction) { TAILQ_INSERT_HEAD(&(swap->parent->focus_head), con, focused); DLOG("Swapped.\n"); + ipc_send_window_event("move", con); return; } @@ -213,6 +213,7 @@ void tree_move(int direction) { /* If we couldn't find a place to move it on this workspace, * try to move it to a workspace on a different output */ move_to_output_directed(con, direction); + ipc_send_window_event("move", con); return; } @@ -264,4 +265,5 @@ end: FREE(con->deco_render_params); tree_flatten(croot); + ipc_send_window_event("move", con); } diff --git a/src/output.c b/src/output.c index b037335a..6499c65d 100644 --- a/src/output.c +++ b/src/output.c @@ -18,9 +18,9 @@ Con *output_get_content(Con *output) { Con *child; - TAILQ_FOREACH (child, &(output->nodes_head), nodes) - if (child->type == CT_CON) - return child; + TAILQ_FOREACH(child, &(output->nodes_head), nodes) + if (child->type == CT_CON) + return child; return NULL; } diff --git a/src/randr.c b/src/randr.c index 0755c0d7..a4a0f6fd 100644 --- a/src/randr.c +++ b/src/randr.c @@ -37,9 +37,9 @@ static bool randr_disabled = false; */ static Output *get_output_by_id(xcb_randr_output_t id) { Output *output; - TAILQ_FOREACH (output, &outputs, outputs) - if (output->id == id) - return output; + TAILQ_FOREACH(output, &outputs, outputs) + if (output->id == id) + return output; return NULL; } @@ -50,10 +50,10 @@ static Output *get_output_by_id(xcb_randr_output_t id) { */ Output *get_output_by_name(const char *name) { Output *output; - TAILQ_FOREACH (output, &outputs, outputs) - if (output->active && - strcasecmp(output->name, name) == 0) - return output; + TAILQ_FOREACH(output, &outputs, outputs) + if (output->active && + strcasecmp(output->name, name) == 0) + return output; return NULL; } @@ -65,9 +65,9 @@ Output *get_output_by_name(const char *name) { Output *get_first_output(void) { Output *output; - TAILQ_FOREACH (output, &outputs, outputs) - if (output->active) - return output; + TAILQ_FOREACH(output, &outputs, outputs) + if (output->active) + return output; die("No usable outputs available.\n"); } @@ -79,7 +79,7 @@ Output *get_first_output(void) { */ Output *get_output_containing(unsigned int x, unsigned int y) { Output *output; - TAILQ_FOREACH (output, &outputs, outputs) { + TAILQ_FOREACH(output, &outputs, outputs) { if (!output->active) continue; DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n", @@ -104,7 +104,7 @@ bool contained_by_output(Rect rect) { Output *output; int lx = rect.x, uy = rect.y; int rx = rect.x + rect.width, by = rect.y + rect.height; - TAILQ_FOREACH (output, &outputs, outputs) { + TAILQ_FOREACH(output, &outputs, outputs) { if (!output->active) continue; DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n", @@ -163,7 +163,7 @@ Output *get_output_next(direction_t direction, Output *current, output_close_far *other; Output *output, *best = NULL; - TAILQ_FOREACH (output, &outputs, outputs) { + TAILQ_FOREACH(output, &outputs, outputs) { if (!output->active) continue; @@ -256,7 +256,7 @@ void output_init_con(Output *output) { /* Search for a Con with that name directly below the root node. There * might be one from a restored layout. */ - TAILQ_FOREACH (current, &(croot->nodes_head), nodes) { + TAILQ_FOREACH(current, &(croot->nodes_head), nodes) { if (strcmp(current->name, output->name) != 0) continue; @@ -355,15 +355,15 @@ void output_init_con(Output *output) { void init_ws_for_output(Output *output, Con *content) { /* go through all assignments and move the existing workspaces to this output */ struct Workspace_Assignment *assignment; - TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) { + TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { if (strcmp(assignment->output, output->name) != 0) continue; /* check if this workspace actually exists */ Con *workspace = NULL, *out; - TAILQ_FOREACH (out, &(croot->nodes_head), nodes) - GREP_FIRST(workspace, output_get_content(out), - !strcasecmp(child->name, assignment->name)); + TAILQ_FOREACH(out, &(croot->nodes_head), nodes) + GREP_FIRST(workspace, output_get_content(out), + !strcasecmp(child->name, assignment->name)); if (workspace == NULL) continue; @@ -401,10 +401,10 @@ void init_ws_for_output(Output *output, Con *content) { Con *ws_out_content = output_get_content(workspace_out); Con *floating_con; - TAILQ_FOREACH (floating_con, &(workspace->floating_head), floating_windows) - /* NB: We use output->con here because content is not yet rendered, + TAILQ_FOREACH(floating_con, &(workspace->floating_head), floating_windows) + /* NB: We use output->con here because content is not yet rendered, * so it has a rect of {0, 0, 0, 0}. */ - floating_fix_coordinates(floating_con, &(ws_out_content->rect), &(output->con->rect)); + floating_fix_coordinates(floating_con, &(ws_out_content->rect), &(output->con->rect)); con_detach(workspace); con_attach(workspace, content, false); @@ -436,7 +436,7 @@ void init_ws_for_output(Output *output, Con *content) { } /* otherwise, we create the first assigned ws for this output */ - TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) { + TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { if (strcmp(assignment->output, output->name) != 0) continue; @@ -478,8 +478,8 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) { /* Fix the position of all floating windows on this output. * The 'rect' of each workspace will be updated in src/render.c. */ - TAILQ_FOREACH (workspace, &(content->nodes_head), nodes) { - TAILQ_FOREACH (child, &(workspace->floating_head), floating_windows) { + TAILQ_FOREACH(workspace, &(content->nodes_head), nodes) { + TAILQ_FOREACH(child, &(workspace->floating_head), floating_windows) { floating_fix_coordinates(child, &(workspace->rect), &(output->con->rect)); } } @@ -488,7 +488,7 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) { * the workspaces and their childs depending on output resolution. This is * only done for workspaces with maximum one child. */ if (config.default_orientation == NO_ORIENTATION) { - TAILQ_FOREACH (workspace, &(content->nodes_head), nodes) { + TAILQ_FOREACH(workspace, &(content->nodes_head), nodes) { /* Workspaces with more than one child are left untouched because * we do not want to change an existing layout. */ if (con_num_children(workspace) > 1) @@ -640,7 +640,7 @@ void randr_query_outputs(void) { /* Check for clones, disable the clones and reduce the mode to the * lowest common mode */ - TAILQ_FOREACH (output, &outputs, outputs) { + TAILQ_FOREACH(output, &outputs, outputs) { if (!output->active || output->to_be_disabled) continue; DLOG("output %p / %s, position (%d, %d), checking for clones\n", @@ -681,7 +681,7 @@ void randr_query_outputs(void) { * necessary because in the next step, a clone might get disabled. Example: * LVDS1 active, VGA1 gets activated as a clone of LVDS1 (has no con). * LVDS1 gets disabled. */ - TAILQ_FOREACH (output, &outputs, outputs) { + TAILQ_FOREACH(output, &outputs, outputs) { if (output->active && output->con == NULL) { DLOG("Need to initialize a Con for output %s\n", output->name); output_init_con(output); @@ -691,7 +691,7 @@ void randr_query_outputs(void) { /* Handle outputs which have a new mode or are disabled now (either * because the user disabled them or because they are clones) */ - TAILQ_FOREACH (output, &outputs, outputs) { + TAILQ_FOREACH(output, &outputs, outputs) { if (output->to_be_disabled) { output->active = false; DLOG("Output %s disabled, re-assigning workspaces/docks\n", output->name); @@ -731,8 +731,8 @@ void randr_query_outputs(void) { con_attach(current, first_content, false); DLOG("Fixing the coordinates of floating containers\n"); Con *floating_con; - TAILQ_FOREACH (floating_con, &(current->floating_head), floating_windows) - floating_fix_coordinates(floating_con, &(output->con->rect), &(first->con->rect)); + TAILQ_FOREACH(floating_con, &(current->floating_head), floating_windows) + floating_fix_coordinates(floating_con, &(output->con->rect), &(first->con->rect)); DLOG("Done, next\n"); } DLOG("re-attached all workspaces\n"); @@ -745,7 +745,7 @@ void randr_query_outputs(void) { /* 3: move the dock clients to the first output */ Con *child; - TAILQ_FOREACH (child, &(output->con->nodes_head), nodes) { + TAILQ_FOREACH(child, &(output->con->nodes_head), nodes) { if (child->type != CT_DOCKAREA) continue; DLOG("Handling dock con %p\n", child); @@ -788,7 +788,7 @@ void randr_query_outputs(void) { get_first_output(); /* Just go through each active output and assign one workspace */ - TAILQ_FOREACH (output, &outputs, outputs) { + TAILQ_FOREACH(output, &outputs, outputs) { if (!output->active) continue; Con *content = output_get_content(output->con); @@ -799,7 +799,7 @@ void randr_query_outputs(void) { } /* Focus the primary screen, if possible */ - TAILQ_FOREACH (output, &outputs, outputs) { + TAILQ_FOREACH(output, &outputs, outputs) { if (!output->primary || !output->con) continue; diff --git a/src/render.c b/src/render.c index 2f39b082..7f92d4d4 100644 --- a/src/render.c +++ b/src/render.c @@ -41,7 +41,7 @@ static void render_l_output(Con *con) { /* Find the content container and ensure that there is exactly one. Also * check for any non-CT_DOCKAREA clients. */ Con *content = NULL; - TAILQ_FOREACH (child, &(con->nodes_head), nodes) { + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { if (child->type == CT_CON) { if (content != NULL) { DLOG("More than one CT_CON on output container\n"); @@ -77,19 +77,19 @@ static void render_l_output(Con *con) { /* First pass: determine the height of all CT_DOCKAREAs (the sum of their * children) and figure out how many pixels we have left for the rest */ - TAILQ_FOREACH (child, &(con->nodes_head), nodes) { + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { if (child->type != CT_DOCKAREA) continue; child->rect.height = 0; - TAILQ_FOREACH (dockchild, &(child->nodes_head), nodes) - child->rect.height += dockchild->geometry.height; + TAILQ_FOREACH(dockchild, &(child->nodes_head), nodes) + child->rect.height += dockchild->geometry.height; height -= child->rect.height; } /* Second pass: Set the widths/heights */ - TAILQ_FOREACH (child, &(con->nodes_head), nodes) { + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { if (child->type == CT_CON) { child->rect.x = x; child->rect.y = y; @@ -232,7 +232,7 @@ void render_con(Con *con, bool render_fullscreen) { Con *child; int i = 0, assigned = 0; int total = con_orientation(con) == HORIZ ? rect.width : rect.height; - TAILQ_FOREACH (child, &(con->nodes_head), nodes) { + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { double percentage = child->percent > 0.0 ? child->percent : 1.0 / children; assigned += sizes[i++] = percentage * total; } @@ -256,7 +256,7 @@ void render_con(Con *con, bool render_fullscreen) { } else if (con->type == CT_ROOT) { Con *output; if (!fullscreen) { - TAILQ_FOREACH (output, &(con->nodes_head), nodes) { + TAILQ_FOREACH(output, &(con->nodes_head), nodes) { render_con(output, false); } } @@ -266,7 +266,7 @@ void render_con(Con *con, bool render_fullscreen) { * all times. This is important when the user places floating * windows/containers so that they overlap on another output. */ DLOG("Rendering floating windows:\n"); - TAILQ_FOREACH (output, &(con->nodes_head), nodes) { + TAILQ_FOREACH(output, &(con->nodes_head), nodes) { if (con_is_internal(output)) continue; /* Get the active workspace of that output */ @@ -278,7 +278,7 @@ void render_con(Con *con, bool render_fullscreen) { Con *workspace = TAILQ_FIRST(&(content->focus_head)); Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT); Con *child; - TAILQ_FOREACH (child, &(workspace->floating_head), floating_windows) { + TAILQ_FOREACH(child, &(workspace->floating_head), floating_windows) { /* Don’t render floating windows when there is a fullscreen window * on that workspace. Necessary to make floating fullscreen work * correctly (ticket #564). */ @@ -298,6 +298,8 @@ void render_con(Con *con, bool render_fullscreen) { while (transient_con != NULL && transient_con->window != NULL && transient_con->window->transient_for != XCB_NONE) { + DLOG("transient_con = 0x%08x, transient_con->window->transient_for = 0x%08x, fullscreen_id = 0x%08x\n", + transient_con->window->id, transient_con->window->transient_for, fullscreen->window->id); if (transient_con->window->transient_for == fullscreen->window->id) { is_transient_for = true; break; @@ -331,7 +333,7 @@ void render_con(Con *con, bool render_fullscreen) { } else { /* FIXME: refactor this into separate functions: */ Con *child; - TAILQ_FOREACH (child, &(con->nodes_head), nodes) { + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { assert(children > 0); /* default layout */ @@ -438,8 +440,8 @@ void render_con(Con *con, bool render_fullscreen) { /* in a stacking or tabbed container, we ensure the focused client is raised */ if (con->layout == L_STACKED || con->layout == L_TABBED) { - TAILQ_FOREACH_REVERSE (child, &(con->focus_head), focus_head, focused) - x_raise_con(child); + TAILQ_FOREACH_REVERSE(child, &(con->focus_head), focus_head, focused) + x_raise_con(child); if ((child = TAILQ_FIRST(&(con->focus_head)))) { /* By rendering the stacked container again, we handle the case * that we have a non-leaf-container inside the stack. In that diff --git a/src/restore_layout.c b/src/restore_layout.c index ba82f76f..3a657c90 100644 --- a/src/restore_layout.c +++ b/src/restore_layout.c @@ -137,7 +137,7 @@ static void update_placeholder_contents(placeholder_state *state) { Match *swallows; int n = 0; - TAILQ_FOREACH (swallows, &(state->con->swallow_head), matches) { + TAILQ_FOREACH(swallows, &(state->con->swallow_head), matches) { char *serialized = NULL; #define APPEND_REGEX(re_name) \ @@ -197,8 +197,9 @@ static void open_placeholder_window(Con *con) { /* Set the same name as was stored in the layout file. While perhaps * slightly confusing in the first instant, this brings additional * clarity to which placeholder is waiting for which actual window. */ - xcb_change_property(restore_conn, XCB_PROP_MODE_REPLACE, placeholder, - A__NET_WM_NAME, A_UTF8_STRING, 8, strlen(con->name), con->name); + if (con->name != NULL) + xcb_change_property(restore_conn, XCB_PROP_MODE_REPLACE, placeholder, + A__NET_WM_NAME, A_UTF8_STRING, 8, strlen(con->name), con->name); DLOG("Created placeholder window 0x%08x for leaf container %p / %s\n", placeholder, con, con->name); @@ -222,10 +223,10 @@ static void open_placeholder_window(Con *con) { } Con *child; - TAILQ_FOREACH (child, &(con->nodes_head), nodes) { + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { open_placeholder_window(child); } - TAILQ_FOREACH (child, &(con->floating_head), floating_windows) { + TAILQ_FOREACH(child, &(con->floating_head), floating_windows) { open_placeholder_window(child); } } @@ -239,10 +240,10 @@ static void open_placeholder_window(Con *con) { */ void restore_open_placeholder_windows(Con *parent) { Con *child; - TAILQ_FOREACH (child, &(parent->nodes_head), nodes) { + TAILQ_FOREACH(child, &(parent->nodes_head), nodes) { open_placeholder_window(child); } - TAILQ_FOREACH (child, &(parent->floating_head), floating_windows) { + TAILQ_FOREACH(child, &(parent->floating_head), floating_windows) { open_placeholder_window(child); } @@ -258,7 +259,7 @@ void restore_open_placeholder_windows(Con *parent) { */ bool restore_kill_placeholder(xcb_window_t placeholder) { placeholder_state *state; - TAILQ_FOREACH (state, &state_head, state) { + TAILQ_FOREACH(state, &state_head, state) { if (state->window != placeholder) continue; @@ -277,7 +278,7 @@ bool restore_kill_placeholder(xcb_window_t placeholder) { static void expose_event(xcb_expose_event_t *event) { placeholder_state *state; - TAILQ_FOREACH (state, &state_head, state) { + TAILQ_FOREACH(state, &state_head, state) { if (state->window != event->window) continue; @@ -305,7 +306,7 @@ static void expose_event(xcb_expose_event_t *event) { */ static void configure_notify(xcb_configure_notify_event_t *event) { placeholder_state *state; - TAILQ_FOREACH (state, &state_head, state) { + TAILQ_FOREACH(state, &state_head, state) { if (state->window != event->window) continue; diff --git a/src/scratchpad.c b/src/scratchpad.c index 8ca81eae..3b7c2a72 100644 --- a/src/scratchpad.c +++ b/src/scratchpad.c @@ -116,7 +116,7 @@ void scratchpad_show(Con *con) { * unfocused scratchpad on the current workspace and focus it */ Con *walk_con; Con *focused_ws = con_get_workspace(focused); - TAILQ_FOREACH (walk_con, &(focused_ws->floating_head), floating_windows) { + TAILQ_FOREACH(walk_con, &(focused_ws->floating_head), floating_windows) { if (!con && (floating = con_inside_floating(walk_con)) && floating->scratchpad_state != SCRATCHPAD_NONE && floating != con_inside_floating(focused)) { @@ -134,7 +134,7 @@ void scratchpad_show(Con *con) { * visible scratchpad window on another workspace. In this case we move it * to the current workspace. */ focused_ws = con_get_workspace(focused); - TAILQ_FOREACH (walk_con, &all_cons, all_cons) { + TAILQ_FOREACH(walk_con, &all_cons, all_cons) { Con *walk_ws = con_get_workspace(walk_con); if (!con && walk_ws && !con_is_internal(walk_ws) && focused_ws != walk_ws && @@ -257,7 +257,7 @@ void scratchpad_fix_resolution(void) { Con *output; int new_width = -1, new_height = -1; - TAILQ_FOREACH (output, &(croot->nodes_head), nodes) { + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { if (output == __i3_output) continue; DLOG("output %s's resolution: (%d, %d) %d x %d\n", @@ -288,7 +288,7 @@ void scratchpad_fix_resolution(void) { DLOG("Fixing coordinates of scratchpad windows\n"); Con *con; - TAILQ_FOREACH (con, &(__i3_scratch->floating_head), floating_windows) { + TAILQ_FOREACH(con, &(__i3_scratch->floating_head), floating_windows) { floating_fix_coordinates(con, &old_rect, &new_rect); } } diff --git a/src/sighandler.c b/src/sighandler.c index 77333863..8b5bfed4 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -253,7 +253,7 @@ static void open_popups() { /* Open a popup window on each virtual screen */ Output *screen; xcb_window_t win; - TAILQ_FOREACH (screen, &outputs, outputs) { + TAILQ_FOREACH(screen, &outputs, outputs) { if (!screen->active) continue; win = open_input_window(conn, screen->rect, width, height); diff --git a/src/startup.c b/src/startup.c index a5d8e117..ebe8c1d9 100644 --- a/src/startup.c +++ b/src/startup.c @@ -37,7 +37,7 @@ static void startup_timeout(EV_P_ ev_timer *w, int revents) { DLOG("Timeout for startup sequence %s\n", id); struct Startup_Sequence *current, *sequence = NULL; - TAILQ_FOREACH (current, &startup_sequences, sequences) { + TAILQ_FOREACH(current, &startup_sequences, sequences) { if (strcmp(current->id, id) != 0) continue; @@ -123,8 +123,8 @@ void startup_sequence_delete(struct Startup_Sequence *sequence) { * the application is reparented to init (process-id 1), which correctly handles * childs, so we don’t have to do it :-). * - * The shell is determined by looking for the SHELL environment variable. If it - * does not exist, /bin/sh is used. + * The shell used to start applications is the system's bourne shell (i.e., + * /bin/sh). * * The no_startup_id flag determines whether a startup notification context * (and ID) should be created, which is the default and encouraged behavior. @@ -219,7 +219,7 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata) { /* Get the corresponding internal startup sequence */ const char *id = sn_startup_sequence_get_id(snsequence); struct Startup_Sequence *current, *sequence = NULL; - TAILQ_FOREACH (current, &startup_sequences, sequences) { + TAILQ_FOREACH(current, &startup_sequences, sequences) { if (strcmp(current->id, id) != 0) continue; @@ -309,7 +309,7 @@ struct Startup_Sequence *startup_sequence_get(i3Window *cwindow, } struct Startup_Sequence *current, *sequence = NULL; - TAILQ_FOREACH (current, &startup_sequences, sequences) { + TAILQ_FOREACH(current, &startup_sequences, sequences) { if (strcmp(current->id, startup_id) != 0) continue; diff --git a/src/tree.c b/src/tree.c index da73e14d..e99c5063 100644 --- a/src/tree.c +++ b/src/tree.c @@ -167,9 +167,9 @@ Con *tree_open_con(Con *con, i3Window *window) { static bool _is_con_mapped(Con *con) { Con *child; - TAILQ_FOREACH (child, &(con->nodes_head), nodes) - if (_is_con_mapped(child)) - return true; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) + if (_is_con_mapped(child)) + return true; return con->mapped; } @@ -255,6 +255,7 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool * X11 Errors are returned when the window was already destroyed */ add_ignore_event(cookie.sequence, 0); } + ipc_send_window_event("close", con); FREE(con->window->class_class); FREE(con->window->class_instance); i3string_free(con->window->name); @@ -406,8 +407,7 @@ void tree_split(Con *con, orientation_t orientation) { Con *parent = con->parent; /* Force re-rendering to make the indicator border visible. */ - FREE(con->deco_render_params); - FREE(parent->deco_render_params); + con_force_split_parents_redraw(con); /* if we are in a container whose parent contains only one * child (its split functionality is unused so far), we just change the @@ -489,13 +489,13 @@ static void mark_unmapped(Con *con) { Con *current; con->mapped = false; - TAILQ_FOREACH (current, &(con->nodes_head), nodes) - mark_unmapped(current); + TAILQ_FOREACH(current, &(con->nodes_head), nodes) + mark_unmapped(current); if (con->type == CT_WORKSPACE) { /* We need to call mark_unmapped on floating nodes aswell since we can * make containers floating. */ - TAILQ_FOREACH (current, &(con->floating_head), floating_windows) - mark_unmapped(current); + TAILQ_FOREACH(current, &(con->floating_head), floating_windows) + mark_unmapped(current); } } @@ -581,6 +581,13 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) return true; Con *focus = con_descend_direction(workspace, direction); + + /* special case: if there was no tiling con to focus and the workspace + * has a floating con in the focus stack, focus the top of the focus + * stack (which may be floating) */ + if (focus == workspace) + focus = con_descend_focused(workspace); + if (focus) { con_focus(focus); x_set_warp_to(&(focus->rect)); @@ -591,33 +598,38 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) Con *parent = con->parent; if (con->type == CT_FLOATING_CON) { - /* left/right focuses the previous/next floating container */ - if (orientation == HORIZ) { - Con *next; - if (way == 'n') - next = TAILQ_NEXT(con, floating_windows); - else - next = TAILQ_PREV(con, floating_head, floating_windows); - - /* If there is no next/previous container, wrap */ - if (!next) { - if (way == 'n') - next = TAILQ_FIRST(&(parent->floating_head)); - else - next = TAILQ_LAST(&(parent->floating_head), floating_head); - } - - /* Still no next/previous container? bail out */ - if (!next) - return false; - - con_focus(con_descend_focused(next)); - return true; - } else { - /* up/down cycles through the Z-index */ - /* TODO: implement cycling through the z-index */ + if (orientation != HORIZ) return false; + + /* left/right focuses the previous/next floating container */ + Con *next; + if (way == 'n') + next = TAILQ_NEXT(con, floating_windows); + else + next = TAILQ_PREV(con, floating_head, floating_windows); + + /* If there is no next/previous container, wrap */ + if (!next) { + if (way == 'n') + next = TAILQ_FIRST(&(parent->floating_head)); + else + next = TAILQ_LAST(&(parent->floating_head), floating_head); } + + /* Still no next/previous container? bail out */ + if (!next) + return false; + + /* Raise the floating window on top of other windows preserving + * relative stack order */ + while (TAILQ_LAST(&(parent->floating_head), floating_head) != next) { + Con *last = TAILQ_LAST(&(parent->floating_head), floating_head); + TAILQ_REMOVE(&(parent->floating_head), last, floating_windows); + TAILQ_INSERT_HEAD(&(parent->floating_head), last, floating_windows); + } + + con_focus(con_descend_focused(next)); + return true; } /* If the orientation does not match or there is no other con to focus, we diff --git a/src/window.c b/src/window.c index 538f4629..e406752a 100644 --- a/src/window.c +++ b/src/window.c @@ -125,7 +125,8 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bo */ void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop) { if (prop == NULL || xcb_get_property_value_length(prop) == 0) { - DLOG("CLIENT_LEADER not set.\n"); + DLOG("CLIENT_LEADER not set on window 0x%08x.\n", win->id); + win->leader = XCB_NONE; FREE(prop); return; } @@ -149,7 +150,8 @@ void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop) { */ void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop) { if (prop == NULL || xcb_get_property_value_length(prop) == 0) { - DLOG("TRANSIENT_FOR not set.\n"); + DLOG("TRANSIENT_FOR not set on window 0x%08x.\n", win->id); + win->transient_for = XCB_NONE; FREE(prop); return; } @@ -160,7 +162,7 @@ void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop) return; } - DLOG("Transient for changed to %08x\n", transient_for); + DLOG("Transient for changed to 0x%08x (window 0x%08x)\n", transient_for, win->id); win->transient_for = transient_for; diff --git a/src/workspace.c b/src/workspace.c index 739b0e0a..a3056633 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -11,6 +11,7 @@ * */ #include "all.h" +#include "yajl_utils.h" /* Stores a copy of the name of the last used workspace for the workspace * back-and-forth switching. */ @@ -44,8 +45,8 @@ static void _workspace_apply_default_orientation(Con *ws) { Con *workspace_get(const char *num, bool *created) { Con *output, *workspace = NULL; - TAILQ_FOREACH (output, &(croot->nodes_head), nodes) - GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, num)); + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) + GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, num)); if (workspace == NULL) { LOG("Creating new workspace \"%s\"\n", num); @@ -59,7 +60,7 @@ Con *workspace_get(const char *num, bool *created) { * -1. */ long parsed_num = ws_name_to_number(num); - TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) { + TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { if (strcmp(assignment->name, num) == 0) { DLOG("Found workspace name assignment to output \"%s\"\n", assignment->output); GREP_FIRST(output, croot, !strcmp(child->name, assignment->output)); @@ -91,7 +92,10 @@ Con *workspace_get(const char *num, bool *created) { con_attach(workspace, content, false); - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); + ipc_send_workspace_event("init", workspace, NULL); + ewmh_update_number_of_desktops(); + ewmh_update_desktop_names(); + ewmh_update_desktop_viewport(); if (created != NULL) *created = true; } else if (created != NULL) { @@ -117,13 +121,13 @@ Con *create_workspace_on_output(Output *output, Con *content) { /* try the configured workspace bindings first to find a free name */ Binding *bind; - TAILQ_FOREACH (bind, bindings, bindings) { + TAILQ_FOREACH(bind, bindings, bindings) { DLOG("binding with command %s\n", bind->command); if (strlen(bind->command) < strlen("workspace ") || strncasecmp(bind->command, "workspace", strlen("workspace")) != 0) continue; DLOG("relevant command = %s\n", bind->command); - char *target = bind->command + strlen("workspace "); + const char *target = bind->command + strlen("workspace "); while ((*target == ' ' || *target == '\t') && target != '\0') target++; /* We check if this is the workspace @@ -139,16 +143,16 @@ Con *create_workspace_on_output(Output *output, Con *content) { strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0 || strncasecmp(target, "current", strlen("current")) == 0) continue; - if (*target == '"') - target++; - if (strncasecmp(target, "__", strlen("__")) == 0) { + char *target_name = parse_string(&target, false); + if (target_name == NULL) + continue; + if (strncasecmp(target_name, "__", strlen("__")) == 0) { LOG("Cannot create workspace \"%s\". Names starting with __ are i3-internal.\n", target); + free(target_name); continue; } FREE(ws->name); - ws->name = strdup(target); - if (ws->name[strlen(ws->name) - 1] == '"') - ws->name[strlen(ws->name) - 1] = '\0'; + ws->name = target_name; DLOG("trying name *%s*\n", ws->name); /* Ensure that this workspace is not assigned to a different output — @@ -156,7 +160,7 @@ Con *create_workspace_on_output(Output *output, Con *content) { * find a new workspace, etc… */ bool assigned = false; struct Workspace_Assignment *assignment; - TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) { + TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { if (strcmp(assignment->name, ws->name) != 0 || strcmp(assignment->output, output->name) == 0) continue; @@ -169,22 +173,14 @@ Con *create_workspace_on_output(Output *output, Con *content) { continue; current = NULL; - TAILQ_FOREACH (out, &(croot->nodes_head), nodes) - GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name)); + TAILQ_FOREACH(out, &(croot->nodes_head), nodes) + GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name)); exists = (current != NULL); if (!exists) { /* Set ->num to the number of the workspace, if the name actually * is a number or starts with a number */ - char *endptr = NULL; - long parsed_num = strtol(ws->name, &endptr, 10); - if (parsed_num == LONG_MIN || - parsed_num == LONG_MAX || - parsed_num < 0 || - endptr == ws->name) - ws->num = -1; - else - ws->num = parsed_num; + ws->num = ws_name_to_number(ws->name); LOG("Used number %d for workspace with name %s\n", ws->num, ws->name); break; @@ -201,8 +197,8 @@ Con *create_workspace_on_output(Output *output, Con *content) { ws->num = c; current = NULL; - TAILQ_FOREACH (out, &(croot->nodes_head), nodes) - GREP_FIRST(current, output_get_content(out), child->num == ws->num); + TAILQ_FOREACH(out, &(croot->nodes_head), nodes) + GREP_FIRST(current, output_get_content(out), child->num == ws->num); exists = (current != NULL); DLOG("result for ws %d: exists = %d\n", c, exists); @@ -245,7 +241,7 @@ bool workspace_is_visible(Con *ws) { Con *_get_sticky(Con *con, const char *sticky_group, Con *exclude) { Con *current; - TAILQ_FOREACH (current, &(con->nodes_head), nodes) { + TAILQ_FOREACH(current, &(con->nodes_head), nodes) { if (current != exclude && current->sticky_group != NULL && current->window != NULL && @@ -257,7 +253,7 @@ Con *_get_sticky(Con *con, const char *sticky_group, Con *exclude) { return recurse; } - TAILQ_FOREACH (current, &(con->floating_head), floating_windows) { + TAILQ_FOREACH(current, &(con->floating_head), floating_windows) { if (current != exclude && current->sticky_group != NULL && current->window != NULL && @@ -284,7 +280,7 @@ static void workspace_reassign_sticky(Con *con) { /* 1: go through all containers */ /* handle all children and floating windows of this node */ - TAILQ_FOREACH (current, &(con->nodes_head), nodes) { + TAILQ_FOREACH(current, &(con->nodes_head), nodes) { if (current->sticky_group == NULL) { workspace_reassign_sticky(current); continue; @@ -312,8 +308,8 @@ static void workspace_reassign_sticky(Con *con) { LOG("re-assigned window from src %p to dest %p\n", src, current); } - TAILQ_FOREACH (current, &(con->floating_head), floating_windows) - workspace_reassign_sticky(current); + TAILQ_FOREACH(current, &(con->floating_head), floating_windows) + workspace_reassign_sticky(current); } /* @@ -325,11 +321,14 @@ 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; - DLOG("Resetting urgency flag of con %p by timer\n", con); - con->urgent = false; - con_update_parents_urgency(con); - workspace_update_urgent_flag(con_get_workspace(con)); - tree_render(); + if (con->urgent) { + DLOG("Resetting urgency flag of con %p by timer\n", con); + con->urgent = 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); @@ -344,7 +343,7 @@ static void _workspace_show(Con *workspace) { /* disable fullscreen for the other workspaces and get the workspace we are * currently on. */ - TAILQ_FOREACH (current, &(workspace->parent->nodes_head), nodes) { + TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) { if (current->fullscreen_mode == CF_OUTPUT) old = current; current->fullscreen_mode = CF_NONE; @@ -385,6 +384,7 @@ static void _workspace_show(Con *workspace) { * focus and thereby immediately destroy it */ if (next->urgent && (int)(config.workspace_urgency_timer * 1000) > 0) { /* focus for now… */ + next->urgent = false; con_focus(next); /* … but immediately reset urgency flags; they will be set to false by @@ -410,7 +410,7 @@ static void _workspace_show(Con *workspace) { } else con_focus(next); - ipc_send_workspace_focus_event(workspace, current); + ipc_send_workspace_event("focus", workspace, current); DLOG("old = %p / %s\n", old, (old ? old->name : "(null)")); /* Close old workspace if necessary. This must be done *after* doing @@ -422,8 +422,19 @@ static void _workspace_show(Con *workspace) { /* check if this workspace is currently visible */ if (!workspace_is_visible(old)) { LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name); + yajl_gen gen = ipc_marshal_workspace_event("empty", old, NULL); tree_close(old, DONT_KILL_WINDOW, false, false); - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}"); + + const unsigned char *payload; + ylength length; + y(get_buf, &payload, &length); + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, (const char *)payload); + + y(free); + + ewmh_update_number_of_desktops(); + ewmh_update_desktop_names(); + ewmh_update_desktop_viewport(); } } @@ -472,11 +483,11 @@ Con *workspace_next(void) { next = TAILQ_NEXT(current, nodes); } else { /* If currently a numbered workspace, find next numbered workspace. */ - TAILQ_FOREACH (output, &(croot->nodes_head), nodes) { + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { /* Skip outputs starting with __, they are internal. */ if (con_is_internal(output)) continue; - NODES_FOREACH (output_get_content(output)) { + NODES_FOREACH(output_get_content(output)) { if (child->type != CT_WORKSPACE) continue; if (child->num == -1) @@ -493,11 +504,11 @@ Con *workspace_next(void) { /* Find next named workspace. */ if (!next) { bool found_current = false; - TAILQ_FOREACH (output, &(croot->nodes_head), nodes) { + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { /* Skip outputs starting with __, they are internal. */ if (con_is_internal(output)) continue; - NODES_FOREACH (output_get_content(output)) { + NODES_FOREACH(output_get_content(output)) { if (child->type != CT_WORKSPACE) continue; if (child == current) { @@ -512,11 +523,11 @@ Con *workspace_next(void) { /* Find first workspace. */ if (!next) { - TAILQ_FOREACH (output, &(croot->nodes_head), nodes) { + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { /* Skip outputs starting with __, they are internal. */ if (con_is_internal(output)) continue; - NODES_FOREACH (output_get_content(output)) { + NODES_FOREACH(output_get_content(output)) { if (child->type != CT_WORKSPACE) continue; if (!next || (child->num != -1 && child->num < next->num)) @@ -544,11 +555,11 @@ Con *workspace_prev(void) { prev = NULL; } else { /* If numbered workspace, find previous numbered workspace. */ - TAILQ_FOREACH_REVERSE (output, &(croot->nodes_head), nodes_head, nodes) { + TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { /* Skip outputs starting with __, they are internal. */ if (con_is_internal(output)) continue; - NODES_FOREACH_REVERSE (output_get_content(output)) { + NODES_FOREACH_REVERSE(output_get_content(output)) { if (child->type != CT_WORKSPACE || child->num == -1) continue; /* Need to check child against current and previous because we @@ -563,11 +574,11 @@ Con *workspace_prev(void) { /* Find previous named workspace. */ if (!prev) { bool found_current = false; - TAILQ_FOREACH_REVERSE (output, &(croot->nodes_head), nodes_head, nodes) { + TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { /* Skip outputs starting with __, they are internal. */ if (con_is_internal(output)) continue; - NODES_FOREACH_REVERSE (output_get_content(output)) { + NODES_FOREACH_REVERSE(output_get_content(output)) { if (child->type != CT_WORKSPACE) continue; if (child == current) { @@ -582,11 +593,11 @@ Con *workspace_prev(void) { /* Find last workspace. */ if (!prev) { - TAILQ_FOREACH_REVERSE (output, &(croot->nodes_head), nodes_head, nodes) { + TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { /* Skip outputs starting with __, they are internal. */ if (con_is_internal(output)) continue; - NODES_FOREACH_REVERSE (output_get_content(output)) { + NODES_FOREACH_REVERSE(output_get_content(output)) { if (child->type != CT_WORKSPACE) continue; if (!prev || child->num > prev->num) @@ -613,7 +624,7 @@ Con *workspace_next_on_output(void) { next = TAILQ_NEXT(current, nodes); } else { /* If currently a numbered workspace, find next numbered workspace. */ - NODES_FOREACH (output_get_content(output)) { + NODES_FOREACH(output_get_content(output)) { if (child->type != CT_WORKSPACE) continue; if (child->num == -1) @@ -629,7 +640,7 @@ Con *workspace_next_on_output(void) { /* Find next named workspace. */ if (!next) { bool found_current = false; - NODES_FOREACH (output_get_content(output)) { + NODES_FOREACH(output_get_content(output)) { if (child->type != CT_WORKSPACE) continue; if (child == current) { @@ -643,7 +654,7 @@ Con *workspace_next_on_output(void) { /* Find first workspace. */ if (!next) { - NODES_FOREACH (output_get_content(output)) { + NODES_FOREACH(output_get_content(output)) { if (child->type != CT_WORKSPACE) continue; if (!next || (child->num != -1 && child->num < next->num)) @@ -671,7 +682,7 @@ Con *workspace_prev_on_output(void) { prev = NULL; } else { /* If numbered workspace, find previous numbered workspace. */ - NODES_FOREACH_REVERSE (output_get_content(output)) { + NODES_FOREACH_REVERSE(output_get_content(output)) { if (child->type != CT_WORKSPACE || child->num == -1) continue; /* Need to check child against current and previous because we @@ -685,7 +696,7 @@ Con *workspace_prev_on_output(void) { /* Find previous named workspace. */ if (!prev) { bool found_current = false; - NODES_FOREACH_REVERSE (output_get_content(output)) { + NODES_FOREACH_REVERSE(output_get_content(output)) { if (child->type != CT_WORKSPACE) continue; if (child == current) { @@ -699,7 +710,7 @@ Con *workspace_prev_on_output(void) { /* Find last workspace. */ if (!prev) { - NODES_FOREACH_REVERSE (output_get_content(output)) { + NODES_FOREACH_REVERSE(output_get_content(output)) { if (child->type != CT_WORKSPACE) continue; if (!prev || child->num > prev->num) @@ -742,13 +753,13 @@ Con *workspace_back_and_forth_get(void) { static bool get_urgency_flag(Con *con) { Con *child; - TAILQ_FOREACH (child, &(con->nodes_head), nodes) - if (child->urgent || get_urgency_flag(child)) - return true; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) + if (child->urgent || get_urgency_flag(child)) + return true; - TAILQ_FOREACH (child, &(con->floating_head), floating_windows) - if (child->urgent || get_urgency_flag(child)) - return true; + TAILQ_FOREACH(child, &(con->floating_head), floating_windows) + if (child->urgent || get_urgency_flag(child)) + return true; return false; } @@ -764,7 +775,7 @@ void workspace_update_urgent_flag(Con *ws) { DLOG("Workspace urgency flag changed from %d to %d\n", old_flag, ws->urgent); if (old_flag != ws->urgent) - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}"); + ipc_send_workspace_event("urgent", ws, NULL); } /* diff --git a/src/x.c b/src/x.c index 9e3e55bc..b39c19d0 100644 --- a/src/x.c +++ b/src/x.c @@ -15,9 +15,9 @@ /* Stores the X11 window ID of the currently focused window */ xcb_window_t focused_id = XCB_NONE; -/* Because 'focused_id' might be reset to force input focus (after click to - * raise), we separately keep track of the X11 window ID to be able to always - * tell whether the focused window actually changed. */ +/* Because 'focused_id' might be reset to force input focus, we separately keep + * track of the X11 window ID to be able to always tell whether the focused + * window actually changed. */ static xcb_window_t last_focused = XCB_NONE; /* Stores coordinates to warp mouse pointer to if set */ @@ -76,9 +76,9 @@ TAILQ_HEAD(initial_mapping_head, con_state) initial_mapping_head = */ static con_state *state_for_frame(xcb_window_t window) { con_state *state; - CIRCLEQ_FOREACH (state, &state_head, state) - if (state->id == window) - return state; + CIRCLEQ_FOREACH(state, &state_head, state) + if (state->id == window) + return state; /* TODO: better error handling? */ ELOG("No state found\n"); @@ -430,16 +430,16 @@ void x_draw_decoration(Con *con) { xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &leftline); } if (!(borders_to_hide & ADJ_RIGHT_SCREEN_EDGE)) { - xcb_rectangle_t rightline = {r->width + br.width + br.x, 0, r->width, r->height}; + xcb_rectangle_t rightline = {r->width + (br.width + br.x), 0, -(br.width + br.x), r->height}; xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &rightline); } if (!(borders_to_hide & ADJ_LOWER_SCREEN_EDGE)) { - xcb_rectangle_t bottomline = {0, r->height + br.height + br.y, r->width, r->height}; + xcb_rectangle_t bottomline = {br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y)}; xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &bottomline); } /* 1pixel border needs an additional line at the top */ if (p->border_style == BS_PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) { - xcb_rectangle_t topline = {br.x, 0, con->rect.width + br.width + br.x, br.y}; + xcb_rectangle_t topline = {br.x, 0, r->width + br.width, br.y}; xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &topline); } @@ -453,10 +453,10 @@ void x_draw_decoration(Con *con) { xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]) {p->color->indicator}); if (p->parent_layout == L_SPLITH) xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, (xcb_rectangle_t[]) { - {r->width + br.width + br.x, br.y, r->width, r->height + br.height}}); + {r->width + (br.width + br.x), br.y, -(br.width + br.x), r->height + br.height}}); else if (p->parent_layout == L_SPLITV) xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, (xcb_rectangle_t[]) { - {br.x, r->height + br.height + br.y, r->width - (2 * br.x), r->height}}); + {br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y)}}); } } @@ -473,12 +473,12 @@ void x_draw_decoration(Con *con) { /* 5: draw two unconnected horizontal lines in border color */ xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]) {p->color->border}); Rect *dr = &(con->deco_rect); - int deco_diff_l = 2; - int deco_diff_r = 2; - if (parent->layout == L_TABBED) { - if (TAILQ_PREV(con, nodes_head, nodes) != NULL) + adjacent_t borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders; + int deco_diff_l = borders_to_hide & ADJ_LEFT_SCREEN_EDGE ? 0 : con->current_border_width; + int deco_diff_r = borders_to_hide & ADJ_RIGHT_SCREEN_EDGE ? 0 : con-> current_border_width; + if (parent->layout == L_TABBED || + (parent->layout == L_STACKED && TAILQ_NEXT(con, nodes) != NULL)) { deco_diff_l = 0; - if (TAILQ_NEXT(con, nodes) != NULL) deco_diff_r = 0; } xcb_segment_t segments[] = { @@ -579,11 +579,11 @@ void x_deco_recurse(Con *con) { con_state *state = state_for_frame(con->frame); if (!leaf) { - TAILQ_FOREACH (current, &(con->nodes_head), nodes) - x_deco_recurse(current); + TAILQ_FOREACH(current, &(con->nodes_head), nodes) + x_deco_recurse(current); - TAILQ_FOREACH (current, &(con->floating_head), floating_windows) - x_deco_recurse(current); + TAILQ_FOREACH(current, &(con->floating_head), floating_windows) + x_deco_recurse(current); if (state->mapped) xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height); @@ -620,7 +620,7 @@ void x_push_node(Con *con) { /* Calculate the height of all window decorations which will be drawn on to * this frame. */ uint32_t max_y = 0, max_height = 0; - TAILQ_FOREACH (current, &(con->nodes_head), nodes) { + TAILQ_FOREACH(current, &(con->nodes_head), nodes) { Rect *dr = &(current->deco_rect); if (dr->y >= max_y && dr->height >= max_height) { max_y = dr->y; @@ -800,8 +800,8 @@ void x_push_node(Con *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). */ - TAILQ_FOREACH (current, &(con->focus_head), focused) - x_push_node(current); + TAILQ_FOREACH(current, &(con->focus_head), focused) + x_push_node(current); } /* @@ -845,11 +845,11 @@ static void x_push_node_unmaps(Con *con) { } /* handle all children and floating windows of this node */ - TAILQ_FOREACH (current, &(con->nodes_head), nodes) - x_push_node_unmaps(current); + TAILQ_FOREACH(current, &(con->nodes_head), nodes) + x_push_node_unmaps(current); - TAILQ_FOREACH (current, &(con->floating_head), floating_windows) - x_push_node_unmaps(current); + TAILQ_FOREACH(current, &(con->floating_head), floating_windows) + x_push_node_unmaps(current); } /* @@ -862,7 +862,7 @@ static bool is_con_attached(Con *con) { return false; Con *current; - TAILQ_FOREACH (current, &(con->parent->nodes_head), nodes) { + TAILQ_FOREACH(current, &(con->parent->nodes_head), nodes) { if (current == con) return true; } @@ -893,7 +893,7 @@ void x_push_changes(Con *con) { DLOG("-- PUSHING WINDOW STACK --\n"); //DLOG("Disabling EnterNotify\n"); uint32_t values[1] = {XCB_NONE}; - CIRCLEQ_FOREACH_REVERSE (state, &state_head, state) { + CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { if (state->mapped) xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); } @@ -904,9 +904,9 @@ void x_push_changes(Con *con) { /* count first, necessary to (re)allocate memory for the bottom-to-top * stack afterwards */ int cnt = 0; - CIRCLEQ_FOREACH_REVERSE (state, &state_head, state) - if (con_has_managed_window(state->con)) - cnt++; + CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) + if (con_has_managed_window(state->con)) + cnt++; /* The bottom-to-top window stack of all windows which are managed by i3. * Used for x_get_window_stack(). */ @@ -921,7 +921,7 @@ void x_push_changes(Con *con) { xcb_window_t *walk = client_list_windows; /* X11 correctly represents the stack if we push it from bottom to top */ - CIRCLEQ_FOREACH_REVERSE (state, &state_head, state) { + CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { if (con_has_managed_window(state->con)) memcpy(walk++, &(state->con->window->id), sizeof(xcb_window_t)); @@ -952,7 +952,7 @@ void x_push_changes(Con *con) { walk = client_list_windows; /* reorder by initial mapping */ - TAILQ_FOREACH (state, &initial_mapping_head, initial_mapping_order) { + TAILQ_FOREACH(state, &initial_mapping_head, initial_mapping_order) { if (con_has_managed_window(state->con)) *walk++ = state->con->window->id; } @@ -985,7 +985,7 @@ void x_push_changes(Con *con) { //DLOG("Re-enabling EnterNotify\n"); values[0] = FRAME_EVENT_MASK; - CIRCLEQ_FOREACH_REVERSE (state, &state_head, state) { + CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { if (state->mapped) xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); } @@ -1023,7 +1023,7 @@ void x_push_changes(Con *con) { values[0] = CHILD_EVENT_MASK & ~(XCB_EVENT_MASK_FOCUS_CHANGE); xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values); } - xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, to_focus, last_timestamp); + xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, to_focus, XCB_CURRENT_TIME); if (focused->window != NULL) { values[0] = CHILD_EVENT_MASK; xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values); @@ -1041,7 +1041,7 @@ 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, last_timestamp); + xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME); ewmh_update_active_window(XCB_WINDOW_NONE); focused_id = root; } @@ -1057,7 +1057,7 @@ void x_push_changes(Con *con) { * unmapped, the second one appears under the cursor and therefore gets an * EnterNotify event. */ values[0] = FRAME_EVENT_MASK & ~XCB_EVENT_MASK_ENTER_WINDOW; - CIRCLEQ_FOREACH_REVERSE (state, &state_head, state) { + CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { if (!state->unmap_now) continue; xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); @@ -1067,7 +1067,7 @@ void x_push_changes(Con *con) { x_push_node_unmaps(con); /* save the current stack as old stack */ - CIRCLEQ_FOREACH (state, &state_head, state) { + CIRCLEQ_FOREACH(state, &state_head, state) { CIRCLEQ_REMOVE(&old_state_head, state, old_state); CIRCLEQ_INSERT_TAIL(&old_state_head, state, old_state); } @@ -1155,7 +1155,7 @@ void x_mask_event_mask(uint32_t mask) { uint32_t values[] = {FRAME_EVENT_MASK & mask}; con_state *state; - CIRCLEQ_FOREACH_REVERSE (state, &state_head, state) { + CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) { if (state->mapped) xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); } diff --git a/src/xinerama.c b/src/xinerama.c index fead802a..9e412e03 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -23,9 +23,9 @@ static int num_screens; */ static Output *get_screen_at(unsigned int x, unsigned int y) { Output *output; - TAILQ_FOREACH (output, &outputs, outputs) - if (output->rect.x == x && output->rect.y == y) - return output; + TAILQ_FOREACH(output, &outputs, outputs) + if (output->rect.x == x && output->rect.y == y) + return output; return NULL; } diff --git a/testcases/.gitignore b/testcases/.gitignore index 294f0dae..c11c5563 100644 --- a/testcases/.gitignore +++ b/testcases/.gitignore @@ -8,4 +8,3 @@ inc META.yml i3-cfg-for-* - -Xdummy.so diff --git a/testcases/Makefile.PL b/testcases/Makefile.PL index 6bc80d8f..f2c15013 100755 --- a/testcases/Makefile.PL +++ b/testcases/Makefile.PL @@ -11,6 +11,7 @@ WriteMakefile( 'AnyEvent::I3' => '0.15', 'X11::XCB' => '0.09', 'Inline' => 0, + 'Inline::C' => 0, 'ExtUtils::PkgConfig' => 0, 'Test::More' => '0.94', 'IPC::Run' => 0, diff --git a/testcases/Xdummy b/testcases/Xdummy deleted file mode 100755 index 638a7b3b..00000000 --- a/testcases/Xdummy +++ /dev/null @@ -1,1930 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------- -# Copyright (C) 2005-2010 Karl J. Runge -# All rights reserved. -# -# This file is part of Xdummy. -# -# Xdummy is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or (at -# your option) any later version. -# -# Xdummy is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Xdummy; if not, write to the Free Software -# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA -# or see . -# ---------------------------------------------------------------------- -# -# -# Xdummy: an LD_PRELOAD hack to run a stock Xorg(1) or XFree86(1) server -# with the "dummy" video driver to make it avoid Linux VT switching, etc. -# -# Run "Xdummy -help" for more info. -# -install="" -uninstall="" -runit=1 -prconf="" -notweak="" -root="" -nosudo="" -xserver="" -geom="" -nomodelines="" -depth="" -debug="" -strace="" -cmdline_config="" - -PATH=$PATH:/bin:/usr/bin -export PATH - -program=`basename "$0"` - -help () { - ${PAGER:-more} << END -$program: - - A hack to run a stock Xorg(1) or XFree86(1) X server with the "dummy" - (RAM-only framebuffer) video driver such that it AVOIDS the Linux VT - switching, opening device files in /dev, keyboard and mouse conflicts, - and other problems associated with the normal use of "dummy". - - In other words, it tries to make Xorg/XFree86 with the "dummy" - device driver act more like Xvfb(1). - - The primary motivation for the Xdummy script is to provide a virtual X - server for x11vnc but with more features than Xvfb (or Xvnc); however - it could be used for other reasons (e.g. better automated testing - than with Xvfb.) One nice thing is the dummy server supports RANDR - dynamic resizing while Xvfb does not. - - So, for example, x11vnc+Xdummy terminal services are a little better - than x11vnc+Xvfb. - - To achieve this, while running the real Xserver $program intercepts - system and library calls via the LD_PRELOAD method and modifies - the behavior to make it work correctly (e.g. avoid the VT stuff.) - LD_PRELOAD tricks are usually "clever hacks" and so might not work - in all situations or break when something changes. - - WARNING: Take care in using Xdummy, although it never has it is - possible that it could damage hardware. One can use the -prconf - option to have it print out the xorg.conf config that it would use - and then inspect it carefully before actually using it. - - This program no longer needs to be run as root as of 12/2009. - However, if there are problems for certain situations (usually older - servers) it may perform better if run as root (use the -root option.) - When running as root remember the previous paragraph and that Xdummy - comes without any warranty. - - gcc/cc and other build tools are required for this script to be able - to compile the LD_PRELOAD shared object. Be sure they are installed - on the system. See -install and -uninstall described below. - - Your Linux distribution may not install the dummy driver by default, - e.g: - - /usr/lib/xorg/modules/drivers/dummy_drv.so - - some have it in a package named xserver-xorg-video-dummy you that - need to install. - -Usage: - - $program <${program}-args> - - (actually, the arguments can be supplied in any order.) - -Examples: - - $program -install - - $program :1 - - $program -debug :1 - - $program -tmpdir ~/mytmp :1 -nolisten tcp - -startx example: - - startx -e bash -- $program :2 -depth 16 - - (if startx needs to be run as root, you can su(1) to a normal - user in the bash shell and then launch ~/.xinitrc or ~/.xsession, - gnome-session, startkde, startxfce4, etc.) - -xdm example: - - xdm -config /usr/local/dummy/xdm-config -nodaemon - - where the xdm-config file has line: - - DisplayManager.servers: /usr/local/dummy/Xservers - - and /usr/local/dummy/Xservers has lines: - - :1 local /usr/local/dummy/Xdummy :1 -debug - :2 local /usr/local/dummy/Xdummy :2 -debug - - (-debug is optional) - -gdm/kdm example: - - TBD. - -Root permission and x11vnc: - - Update: as of 12/2009 this program no longer must be run as root. - So try it as non-root before running it as root and/or the - following schemes. - - In some circumstances X server program may need to be run as root. - If so, one could run x11vnc as root with -unixpw (it switches - to the user that logs in) and that may be OK, some other ideas: - - - add this to sudo via visudo: - - ALL ALL = NOPASSWD: /usr/local/bin/Xdummy - - - use this little suid wrapper: -/* - * xdummy.c - * - cc -o ./xdummy xdummy.c - sudo cp ./xdummy /usr/local/bin/xdummy - sudo chown root:root /usr/local/bin/xdummy - sudo chmod u+s /usr/local/bin/xdummy - * - */ -#include -#include -#include -#include - -int main (int argc, char *argv[]) { - extern char **environ; - char str[100]; - sprintf(str, "XDUMMY_UID=%d", (int) getuid()); - putenv(str); - setuid(0); - setgid(0); - execv("/usr/local/bin/Xdummy", argv); - exit(1); - return 1; -} - - -Options: - - ${program}-args: - - -install Compile the LD_PRELOAD shared object and install it - next to the $program script file as: - - $0.so - - When that file exists it is used as the LD_PRELOAD - shared object without recompiling. Otherwise, - each time $program is run the LD_PRELOAD shared - object is compiled as a file in /tmp (or -tmpdir) - - If you set the environment variable - INTERPOSE_GETUID=1 when building, then when - $program is run as an ordinary user, the shared - object will interpose getuid() calls and pretend - to be root. Otherwise it doesn't pretend to - be root. - - You can also set the CFLAGS environment variable - to anything else you want on the compile cmdline. - - -uninstall Remove the file: - - $0.so - - The LD_PRELOAD shared object will then be compiled - each time this program is run. - - The X server is not started under -install, -uninstall, or -prconf. - - - :N The DISPLAY (e.g. :15) is often the first - argument. It is passed to the real X server and - also used by the Xdummy script as an identifier. - - -geom geom1[,geom2...] Take the geometry (e.g. 1024x768) or list - of geometries and insert them into the Screen - section of the tweaked X server config file. - Use this to have a different geometry than the - one(s) in the system config file. - - The option -geometry can be used instead of -geom; - x11vnc calls Xdummy and Xvfb this way. - - -nomodelines When you specify -geom/-geometry, $program will - create Modelines for each geometry and put them - in the Monitor section. If you do not want this - then supply -nomodelines. - - -depth n Use pixel color depth n (e.g. 8, 16, or 24). This - makes sure the X config file has a Screen.Display - subsection of this depth. Note this option is - ALSO passed to the X server. - - -DEPTH n Same as -depth, except not passed to X server. - - -tmpdir dir Specify a temporary directory, owned by you and - only writable by you. This is used in place of - /tmp/Xdummy.\$USER/.. to place the $program.so - shared object, tweaked config files, etc. - - -nonroot Run in non-root mode (working 12/2009, now default) - - -root Run as root (may still be needed in some - environments.) Same as XDUMMY_RUN_AS_ROOT=1. - - -nosudo Do not try to use sudo(1) when re-running as root, - use su(1) instead. - - -xserver path Specify the path to the Xserver to use. Default - is to try "Xorg" first and then "XFree86". If - those are not in \$PATH, it tries these locations: - /usr/bin/Xorg - /usr/X11R6/bin/Xorg - /usr/X11R6/bin/XFree86 - - -n Do not run the command to start the X server, - just show the command that $program would run. - The LD_PRELOAD shared object will be built, - if needed. Also note any XDUMMY* environment - variables that need to be set. - - -prconf Print, to stdout, the tweaked Xorg/XFree86 - config file (-config and -xf86config server - options, respectively.) The Xserver is not - started. - - -notweak Do not tweak (modify) the Xorg/XFree86 config file - (system or server command line) at all. The -geom - and similar config file modifications are ignored. - - It is up to you to make sure it is a working - config file (e.g. "dummy" driver, etc.) - Perhaps you want to use a file based on the - -prconf output. - - -debug Extra debugging output. - - -strace strace(1) the Xserver process (for troubleshooting.) - -ltrace ltrace(1) instead of strace (can be slow.) - - -h, -help Print out this help. - - - Xserver-args: - - Most of the Xorg and XFree86 options will work and are simply - passed along if you supply them. Important ones that may be - supplied if missing: - - :N X Display number for server to use. - - vtNN Linux virtual terminal (VT) to use (a VT is currently - still used, just not switched to and from.) - - -config file Driver "dummy" tweaked config file, a - -xf86config file number of settings are tweaked besides Driver. - - If -config/-xf86config is not given, the system one - (e.g. /etc/X11/xorg.conf) is used. If the system one cannot be - found, a built-in one is used. Any settings in the config file - that are not consistent with "dummy" mode will be overwritten - (unless -notweak is specified.) - - Use -config xdummy-builtin to force usage of the builtin config. - - If "file" is only a basename (e.g. "xorg.dummy.conf") with no /'s, - then no tweaking of it is done: the X server will look for that - basename via its normal search algorithm. If the found file does - not refer to the "dummy" driver, etc, then the X server will fail. - -Notes: - - The Xorg/XFree86 "dummy" driver is currently undocumented. It works - well in this mode, but it is evidently not intended for end-users. - So it could be removed or broken at any time. - - If the display Xserver-arg (e.g. :1) is not given, or ":" is given - that indicates $program should try to find a free one (based on - tcp ports.) - - If the display virtual terminal, VT, (e.g. vt9) is not given that - indicates $program should try to find a free one (or guess a high one.) - - This program is not completely secure WRT files in /tmp (but it tries - to a good degree.) Better is to use the -tmpdir option to supply a - directory only writable by you. Even better is to get rid of users - on the local machine you do not trust :-) - - Set XDUMMY_SET_XV=1 to turn on debugging output for this script. - -END -} - -warn() { - echo "$*" 1>&2 -} - -if [ "X$XDUMMY_SET_XV" != "X" ]; then - set -xv -fi - -if [ "X$XDUMMY_UID" = "X" ]; then - XDUMMY_UID=`id -u` - export XDUMMY_UID -fi -if [ "X$XDUMMY_UID" = "X0" ]; then - if [ "X$SUDO_UID" != "X" ]; then - XDUMMY_UID=$SUDO_UID - export XDUMMY_UID - fi -fi - -# check if root=1 first: -# -if [ "X$XDUMMY_RUN_AS_ROOT" = "X1" ]; then - root=1 -fi -for arg in $* -do - if [ "X$arg" = "X-nonroot" ]; then - root="" - elif [ "X$arg" = "X-root" ]; then - root=1 - fi -done - -# See if it really needs to be run as root: -# -if [ "X$XDUMMY_SU_EXEC" = "X" -a "X$root" = "X1" -a "X`id -u`" != "X0" ]; then - # this is to prevent infinite loop in case su/sudo doesn't work: - XDUMMY_SU_EXEC=1 - export XDUMMY_SU_EXEC - - dosu=1 - nosudo="" - - for arg in $* - do - if [ "X$arg" = "X-nonroot" ]; then - dosu="" - elif [ "X$arg" = "X-nosudo" ]; then - nosudo="1" - elif [ "X$arg" = "X-help" ]; then - dosu="" - elif [ "X$arg" = "X-h" ]; then - dosu="" - elif [ "X$arg" = "X-install" ]; then - dosu="" - elif [ "X$arg" = "X-uninstall" ]; then - dosu="" - elif [ "X$arg" = "X-n" ]; then - dosu="" - elif [ "X$arg" = "X-prconf" ]; then - dosu="" - fi - done - if [ $dosu ]; then - # we need to restart it with su/sudo: - if type sudo > /dev/null 2>&1; then - : - else - nosudo=1 - fi - if [ "X$nosudo" = "X" ]; then - warn "$program: supply the sudo password to restart as root:" - if [ "X$XDUMMY_UID" != "X" ]; then - exec sudo $0 -uid $XDUMMY_UID "$@" - else - exec sudo $0 "$@" - fi - else - warn "$program: supply the root password to restart as root:" - if [ "X$XDUMMY_UID" != "X" ]; then - exec su -c "$0 -uid $XDUMMY_UID $*" - else - exec su -c "$0 $*" - fi - fi - # DONE: - exit - fi -fi - -# This will hold the X display, e.g. :20 -# -disp="" -args="" -cmdline_config="" - -# Process Xdummy args: -# -while [ "X$1" != "X" ] -do - if [ "X$1" = "X-config" -o "X$1" = "X-xf86config" ]; then - cmdline_config="$2" - fi - case $1 in - ":"*) disp=$1 - ;; - "-install") install=1; runit="" - ;; - "-uninstall") uninstall=1; runit="" - ;; - "-n") runit="" - ;; - "-no") runit="" - ;; - "-norun") runit="" - ;; - "-prconf") prconf=1; runit="" - ;; - "-notweak") notweak=1 - ;; - "-noconf") notweak=1 - ;; - "-nonroot") root="" - ;; - "-root") root=1 - ;; - "-nosudo") nosudo=1 - ;; - "-xserver") xserver="$2"; shift - ;; - "-uid") XDUMMY_UID="$2"; shift - export XDUMMY_UID - ;; - "-geom") geom="$2"; shift - ;; - "-geometry") geom="$2"; shift - ;; - "-nomodelines") nomodelines=1 - ;; - "-depth") depth="$2"; args="$args -depth $2"; - shift - ;; - "-DEPTH") depth="$2"; shift - ;; - "-tmpdir") XDUMMY_TMPDIR="$2"; shift - ;; - "-debug") debug=1 - ;; - "-nodebug") debug="" - ;; - "-strace") strace=1 - ;; - "-ltrace") strace=2 - ;; - "-h") help; exit 0 - ;; - "-help") help; exit 0 - ;; - *) args="$args $1" - ;; - esac - shift -done - -# Try to get a username for use in our tmp directory, etc. -# -user="" -if [ X`id -u` = "X0" ]; then - user=root # this will also be used below for id=0 -elif [ "X$USER" != "X" ]; then - user=$USER -elif [ "X$LOGNAME" != "X" ]; then - user=$LOGNAME -fi - -# Keep trying... -# -if [ "X$user" = "X" ]; then - user=`whoami 2>/dev/null` -fi -if [ "X$user" = "X" ]; then - user=`basename "$HOME"` -fi -if [ "X$user" = "X" -o "X$user" = "X." ]; then - user="u$$" -fi - -if [ "X$debug" = "X1" -a "X$runit" != "X" ]; then - echo "" - echo "/usr/bin/env:" - env | egrep -v '^(LS_COLORS|TERMCAP)' | sort - echo "" -fi - -# Function to compile the LD_PRELOAD shared object: -# -make_so() { - # extract code embedded in this script into a tmp C file: - n1=`grep -n '^#code_begin' $0 | head -1 | awk -F: '{print $1}'` - n2=`grep -n '^#code_end' $0 | head -1 | awk -F: '{print $1}'` - n1=`expr $n1 + 1` - dn=`expr $n2 - $n1` - - tmp=$tdir/Xdummy.$RANDOM$$.c - rm -f $tmp - if [ -e $tmp -o -h $tmp ]; then - warn "$tmp still exists." - exit 1 - fi - touch $tmp || exit 1 - tail -n +$n1 $0 | head -n $dn > $tmp - - # compile it to Xdummy.so: - if [ -f "$SO" ]; then - mv $SO $SO.$$ - rm -f $SO.$$ - fi - rm -f $SO - touch $SO - if [ ! -f "$SO" ]; then - SO=$tdir/Xdummy.$user.so - warn "warning switching LD_PRELOAD shared object to: $SO" - fi - - if [ -f "$SO" ]; then - mv $SO $SO.$$ - rm -f $SO.$$ - fi - rm -f $SO - - # we assume gcc: - if [ "X$INTERPOSE_GETUID" = "X1" ]; then - CFLAGS="$CFLAGS -DINTERPOSE_GETUID" - fi - echo "$program:" cc -shared -fPIC $CFLAGS -o $SO $tmp - cc -shared -fPIC $CFLAGS -o $SO $tmp - rc=$? - rm -f $tmp - if [ $rc != 0 ]; then - warn "$program: cannot build $SO" - exit 1 - fi - if [ "X$debug" != "X" -o "X$install" != "X" ]; then - warn "$program: created $SO" - ls -l "$SO" - fi -} - -# Set tdir to tmp dir for make_so(): -if [ "X$XDUMMY_TMPDIR" != "X" ]; then - tdir=$XDUMMY_TMPDIR - mkdir -p $tdir -else - tdir="/tmp" -fi - -# Handle -install/-uninstall case: -SO=$0.so -if [ "X$install" != "X" -o "X$uninstall" != "X" ]; then - if [ -e "$SO" -o -h "$SO" ]; then - warn "$program: removing $SO" - fi - if [ -f "$SO" ]; then - mv $SO $SO.$$ - rm -f $SO.$$ - fi - rm -f $SO - if [ -e "$SO" -o -h "$SO" ]; then - warn "warning: $SO still exists." - exit 1 - fi - if [ $install ]; then - make_so - if [ ! -f "$SO" ]; then - exit 1 - fi - fi - exit 0 -fi - -# We need a tmp directory for the .so, tweaked config file, and for -# redirecting filenames we cannot create (under -nonroot) -# -tack="" -if [ "X$XDUMMY_TMPDIR" = "X" ]; then - XDUMMY_TMPDIR="/tmp/Xdummy.$user" - - # try to tack on a unique subdir (display number or pid) - # to allow multiple instances - # - if [ "X$disp" != "X" ]; then - t0=$disp - else - t0=$1 - fi - tack=`echo "$t0" | sed -e 's/^.*://'` - if echo "$tack" | grep '^[0-9][0-9]*$' > /dev/null; then - : - else - tack=$$ - fi - if [ "X$tack" != "X" ]; then - XDUMMY_TMPDIR="$XDUMMY_TMPDIR/$tack" - fi -fi - -tmp=$XDUMMY_TMPDIR -if echo "$tmp" | grep '^/tmp' > /dev/null; then - if [ "X$tmp" != "X/tmp" -a "X$tmp" != "X/tmp/" ]; then - # clean this subdir of /tmp out, otherwise leave it... - rm -rf $XDUMMY_TMPDIR - if [ -e $XDUMMY_TMPDIR ]; then - warn "$XDUMMY_TMPDIR still exists" - exit 1 - fi - fi -fi - -mkdir -p $XDUMMY_TMPDIR -chmod 700 $XDUMMY_TMPDIR -if [ "X$tack" != "X" ]; then - chmod 700 `dirname "$XDUMMY_TMPDIR"` 2>/dev/null -fi - -# See if we can write something there: -# -tfile="$XDUMMY_TMPDIR/test.file" -touch $tfile -if [ ! -f "$tfile" ]; then - XDUMMY_TMPDIR="/tmp/Xdummy.$$.$USER" - warn "warning: setting tmpdir to $XDUMMY_TMPDIR ..." - rm -rf $XDUMMY_TMPDIR || exit 1 - mkdir -p $XDUMMY_TMPDIR || exit 1 -fi -rm -f $tfile - -export XDUMMY_TMPDIR - -# Compile the LD_PRELOAD shared object if needed (needs XDUMMY_TMPDIR) -# -if [ ! -f "$SO" ]; then - SO="$XDUMMY_TMPDIR/Xdummy.so" - make_so -fi - -# Decide which X server to use: -# -if [ "X$xserver" = "X" ]; then - if type Xorg >/dev/null 2>&1; then - xserver="Xorg" - elif type XFree86 >/dev/null 2>&1; then - xserver="XFree86" - elif -x /usr/bin/Xorg; then - xserver="/usr/bin/Xorg" - elif -x /usr/X11R6/bin/Xorg; then - xserver="/usr/X11R6/bin/Xorg" - elif -x /usr/X11R6/bin/XFree86; then - xserver="/usr/X11R6/bin/XFree86" - fi - if [ "X$xserver" = "X" ]; then - # just let it fail below. - xserver="/usr/bin/Xorg" - warn "$program: cannot locate a stock Xserver... assuming $xserver" - fi -fi - -# See if the binary is suid or not readable under -nonroot mode: -# -if [ "X$BASH_VERSION" != "X" ]; then - xserver_path=`type -p $xserver 2>/dev/null` -else - xserver_path=`type $xserver 2>/dev/null | awk '{print $NF}'` -fi -if [ -e "$xserver_path" -a "X$root" = "X" -a "X$runit" != "X" ]; then - if [ ! -r $xserver_path -o -u $xserver_path -o -g $xserver_path ]; then - # XXX not quite correct with rm -rf $XDUMMY_TMPDIR ... - # we keep on a filesystem we know root can write to. - base=`basename "$xserver_path"` - new="/tmp/$base.$user.bin" - if [ -e $new ]; then - snew=`ls -l $new | awk '{print $5}' | grep '^[0-9][0-9]*$'` - sold=`ls -l $xserver_path | awk '{print $5}' | grep '^[0-9][0-9]*$'` - if [ "X$snew" != "X" -a "X$sold" != "X" -a "X$sold" != "X$snew" ]; then - warn "removing different sized copy:" - ls -l $new $xserver_path - rm -f $new - fi - fi - if [ ! -e $new -o ! -s $new ]; then - rm -f $new - touch $new || exit 1 - chmod 700 $new || exit 1 - if [ ! -r $xserver_path ]; then - warn "" - warn "NEED TO COPY UNREADABLE $xserver_path to $new as root:" - warn "" - ls -l $xserver_path 1>&2 - warn "" - warn "This only needs to be done once:" - warn " cat $xserver_path > $new" - warn "" - nos=$nosudo - if type sudo > /dev/null 2>&1; then - : - else - nos=1 - fi - if [ "X$nos" = "X1" ]; then - warn "Please supply root passwd to 'su -c'" - su -c "cat $xserver_path > $new" - else - warn "Please supply the sudo passwd if asked:" - sudo /bin/sh -c "cat $xserver_path > $new" - fi - else - warn "" - warn "COPYING SETUID $xserver_path to $new" - warn "" - ls -l $xserver_path 1>&2 - warn "" - cat $xserver_path > $new - fi - ls -l $new - if [ -s $new ]; then - : - else - rm -f $new - ls -l $new - exit 1 - fi - warn "" - warn "Please restart Xdummy now." - exit 0 - fi - if [ ! -O $new ]; then - warn "file \"$new\" not owned by us!" - ls -l $new - exit 1 - fi - xserver=$new - fi -fi - -# Work out display: -# -if [ "X$disp" != "X" ]; then - : -elif [ "X$1" != "X" ]; then - if echo "$1" | grep '^:[0-9]' > /dev/null; then - disp=$1 - shift - elif [ "X$1" = "X:" ]; then - # ":" means for us to find one. - shift - fi -fi -if [ "X$disp" = "X" -o "X$disp" = "X:" ]; then - # try to find an open display port: - # (tcp outdated...) - ports=`netstat -ant | grep LISTEN | awk '{print $4}' | sed -e 's/^.*://'` - n=0 - while [ $n -le 20 ] - do - port=`printf "60%02d" $n` - if echo "$ports" | grep "^${port}\$" > /dev/null; then - : - else - disp=":$n" - warn "$program: auto-selected DISPLAY $disp" - break - fi - n=`expr $n + 1` - done -fi - -# Work out which vt to use, try to find/guess an open one if necessary. -# -vt="" -for arg in $* -do - if echo "$arg" | grep '^vt' > /dev/null; then - vt=$arg - break - fi -done -if [ "X$vt" = "X" ]; then - if [ "X$user" = "Xroot" ]; then - # root can user fuser(1) to see if it is in use: - if type fuser >/dev/null 2>&1; then - # try /dev/tty17 thru /dev/tty32 - n=17 - while [ $n -le 32 ] - do - dev="/dev/tty$n" - if fuser $dev >/dev/null 2>&1; then - : - else - vt="vt$n" - warn "$program: auto-selected VT $vt => $dev" - break - fi - n=`expr $n + 1` - done - fi - fi - if [ "X$vt" = "X" ]; then - # take a wild guess... - vt=vt16 - warn "$program: selected fallback VT $vt" - fi -else - vt="" -fi - -# Decide flavor of Xserver: -# -stype=`basename "$xserver"` -if echo "$stype" | grep -i xfree86 > /dev/null; then - stype=xfree86 -else - stype=xorg -fi - -tweak_config() { - in="$1" - config2="$XDUMMY_TMPDIR/xdummy_modified_xconfig.conf" - if [ "X$disp" != "X" ]; then - d=`echo "$disp" | sed -e 's,/,,g' -e 's/:/_/g'` - config2="$config2$d" - fi - - # perl script to tweak the config file... add/delete options, etc. - # - env XDUMMY_GEOM=$geom \ - XDUMMY_DEPTH=$depth \ - XDUMMY_NOMODELINES=$nomodelines \ - perl > $config2 < $in -e ' - $n = 0; - $geom = $ENV{XDUMMY_GEOM}; - $depth = $ENV{XDUMMY_DEPTH}; - $nomodelines = $ENV{XDUMMY_NOMODELINES}; - $mode_str = ""; - $videoram = "24000"; - $HorizSync = "30.0 - 130.0"; - $VertRefresh = "50.0 - 250.0"; - if ($geom ne "") { - my $tmp = ""; - foreach $g (split(/,/, $geom)) { - $tmp .= "\"$g\" "; - if (!$nomodelines && $g =~ /(\d+)x(\d+)/) { - my $w = $1; - my $h = $2; - $mode_str .= " Modeline \"$g\" "; - my $dot = sprintf("%.2f", $w * $h * 70 * 1.e-6); - $mode_str .= $dot; - $mode_str .= " " . $w; - $mode_str .= " " . int(1.02 * $w); - $mode_str .= " " . int(1.10 * $w); - $mode_str .= " " . int(1.20 * $w); - $mode_str .= " " . $h; - $mode_str .= " " . int($h + 1); - $mode_str .= " " . int($h + 3); - $mode_str .= " " . int($h + 20); - $mode_str .= "\n"; - } - } - $tmp =~ s/\s*$//; - $geom = $tmp; - } - while (<>) { - if ($ENV{XDUMMY_NOTWEAK}) { - print $_; - next; - } - $n++; - if (/^\s*#/) { - # pass comments straight thru - print; - next; - } - if (/^\s*Section\s+(\S+)/i) { - # start of Section - $sect = $1; - $sect =~ s/\W//g; - $sect =~ y/A-Z/a-z/; - $sects{$sect} = 1; - print; - next; - } - if (/^\s*EndSection/i) { - # end of Section - if ($sect eq "serverflags") { - if (!$got_DontVTSwitch) { - print " ##Xdummy:##\n"; - print " Option \"DontVTSwitch\" \"true\"\n"; - } - if (!$got_AllowMouseOpenFail) { - print " ##Xdummy:##\n"; - print " Option \"AllowMouseOpenFail\" \"true\"\n"; - } - if (!$got_PciForceNone) { - print " ##Xdummy:##\n"; - print " Option \"PciForceNone\" \"true\"\n"; - } - } elsif ($sect eq "device") { - if (!$got_Driver) { - print " ##Xdummy:##\n"; - print " Driver \"dummy\"\n"; - } - if (!$got_VideoRam) { - print " ##Xdummy:##\n"; - print " VideoRam $videoram\n"; - } - } elsif ($sect eq "screen") { - if ($depth ne "" && !got_DefaultDepth) { - print " ##Xdummy:##\n"; - print " DefaultDepth $depth\n"; - } - if ($got_Monitor eq "") { - print " ##Xdummy:##\n"; - print " Monitor \"Monitor0\"\n"; - } - } elsif ($sect eq "monitor") { - if (!got_HorizSync) { - print " ##Xdummy:##\n"; - print " HorizSync $HorizSync\n"; - } - if (!got_VertRefresh) { - print " ##Xdummy:##\n"; - print " VertRefresh $VertRefresh\n"; - } - if (!$nomodelines) { - print " ##Xdummy:##\n"; - print $mode_str; - } - } - $sect = ""; - print; - next; - } - - if (/^\s*SubSection\s+(\S+)/i) { - # start of Section - $subsect = $1; - $subsect =~ s/\W//g; - $subsect =~ y/A-Z/a-z/; - $subsects{$subsect} = 1; - if ($sect eq "screen" && $subsect eq "display") { - $got_Modes = 0; - } - print; - next; - } - if (/^\s*EndSubSection/i) { - # end of SubSection - if ($sect eq "screen") { - if ($subsect eq "display") { - if ($depth ne "" && !$set_Depth) { - print " ##Xdummy:##\n"; - print " Depth\t$depth\n"; - } - if ($geom ne "" && ! $got_Modes) { - print " ##Xdummy:##\n"; - print " Modes\t$geom\n"; - } - } - } - $subsect = ""; - print; - next; - } - - $l = $_; - $l =~ s/#.*$//; - if ($sect eq "serverflags") { - if ($l =~ /^\s*Option.*DontVTSwitch/i) { - $_ =~ s/false/true/ig; - $got_DontVTSwitch = 1; - } - if ($l =~ /^\s*Option.*AllowMouseOpenFail/i) { - $_ =~ s/false/true/ig; - $got_AllowMouseOpenFail = 1; - } - if ($l =~ /^\s*Option.*PciForceNone/i) { - $_ =~ s/false/true/ig; - $got_PciForceNone= 1; - } - } - if ($sect eq "module") { - if ($l =~ /^\s*Load.*\b(dri|fbdevhw)\b/i) { - $_ = "##Xdummy## $_"; - } - } - if ($sect eq "monitor") { - if ($l =~ /^\s*HorizSync/i) { - $got_HorizSync = 1; - } - if ($l =~ /^\s*VertRefresh/i) { - $got_VertRefresh = 1; - } - } - if ($sect eq "device") { - if ($l =~ /^(\s*Driver)\b/i) { - $_ = "$1 \"dummy\"\n"; - $got_Driver = 1; - } - if ($l =~ /^\s*VideoRam/i) { - $got_VideoRam= 1; - } - } - if ($sect eq "inputdevice") { - if ($l =~ /^\s*Option.*\bDevice\b/i) { - print " ##Xdummy:##\n"; - $_ = " Option \"Device\" \"/dev/dilbert$n\"\n"; - } - } - if ($sect eq "screen") { - if ($l =~ /^\s*DefaultDepth\s+(\d+)/i) { - if ($depth ne "") { - print " ##Xdummy:##\n"; - $_ = " DefaultDepth\t$depth\n"; - } - $got_DefaultDepth = 1; - } - if ($l =~ /^\s*Monitor\s+(\S+)/i) { - $got_Monitor = $1; - $got_Monitor =~ s/"//g; - } - if ($subsect eq "display") { - if ($geom ne "") { - if ($l =~ /^(\s*Modes)\b/i) { - print " ##Xdummy:##\n"; - $_ = "$1 $geom\n"; - $got_Modes = 1; - } - } - if ($l =~ /^\s*Depth\s+(\d+)/i) { - my $d = $1; - if (!$set_Depth && $depth ne "") { - $set_Depth = 1; - if ($depth != $d) { - print " ##Xdummy:##\n"; - $_ = " Depth\t$depth\n"; - } - } - } - } - } - print; - } - if ($ENV{XDUMMY_NOTWEAK}) { - exit; - } - # create any crucial sections that are missing: - if (! exists($sects{serverflags})) { - print "\n##Xdummy:##\n"; - print "Section \"ServerFlags\"\n"; - print " Option \"DontVTSwitch\" \"true\"\n"; - print " Option \"AllowMouseOpenFail\" \"true\"\n"; - print " Option \"PciForceNone\" \"true\"\n"; - print "EndSection\n"; - } - if (! exists($sects{device})) { - print "\n##Xdummy:##\n"; - print "Section \"Device\"\n"; - print " Identifier \"Videocard0\"\n"; - print " Driver \"dummy\"\n"; - print " VideoRam $videoram\n"; - print "EndSection\n"; - } - if (! exists($sects{monitor})) { - print "\n##Xdummy:##\n"; - print "Section \"Monitor\"\n"; - print " Identifier \"Monitor0\"\n"; - print " HorizSync $HorizSync\n"; - print " VertRefresh $VertRefresh\n"; - print "EndSection\n"; - } - if (! exists($sects{screen})) { - print "\n##Xdummy:##\n"; - print "Section \"Screen\"\n"; - print " Identifier \"Screen0\"\n"; - print " Device \"Videocard0\"\n"; - if ($got_Monitor ne "") { - print " Monitor \"$got_Monitor\"\n"; - } else { - print " Monitor \"Monitor0\"\n"; - } - if ($depth ne "") { - print " DefaultDepth $depth\n"; - } else { - print " DefaultDepth 24\n"; - } - print " SubSection \"Display\"\n"; - print " Viewport 0 0\n"; - print " Depth 24\n"; - if ($got_Modes) { - ; - } elsif ($geom ne "") { - print " Modes $geom\n"; - } else { - print " Modes \"1280x1024\" \"1024x768\" \"800x600\"\n"; - } - print " EndSubSection\n"; - print "EndSection\n"; - } -'; -} - -# Work out config file and tweak it. -# -if [ "X$cmdline_config" = "X" ]; then - : -elif [ "X$cmdline_config" = "Xxdummy-builtin" ]; then - : -elif echo "$cmdline_config" | grep '/' > /dev/null; then - : -else - # ignore basename only case (let server handle it) - cmdline_config="" - notweak=1 -fi - -config=$cmdline_config - -if [ "X$notweak" = "X1" -a "X$root" = "X" -a -f "$cmdline_config" ]; then - # if not root we need to copy (but not tweak) the specified config. - XDUMMY_NOTWEAK=1 - export XDUMMY_NOTWEAK - notweak="" -fi - -if [ ! $notweak ]; then - # tweaked config will be put in $config2: - config2="" - if [ "X$config" = "X" ]; then - # use the default one: - if [ "X$stype" = "Xxorg" ]; then - config=/etc/X11/xorg.conf - else - if [ -f "/etc/X11/XF86Config-4" ]; then - config="/etc/X11/XF86Config-4" - else - config="/etc/X11/XF86Config" - fi - fi - if [ ! -f "$config" ]; then - for c in /etc/X11/xorg.conf /etc/X11/XF86Config-4 /etc/X11/XF86Config - do - if [ -f $c ]; then - config=$c - break - fi - done - fi - fi - - if [ "X$config" = "Xxdummy-builtin" ]; then - config="" - fi - - if [ ! -f "$config" ]; then - config="$XDUMMY_TMPDIR/xorg.conf" - warn "$program: using minimal built-in xorg.conf settings." - cat > $config < /dev/null; then - so=`echo "$so" | sed -e "s,^\.,$pwd,"` - fi - if echo "$so" | grep '/' > /dev/null; then - : - else - so="$pwd/$so" - fi - warn "env LD_PRELOAD=$so $xserver $disp $args $vt" - warn "" - if [ ! $runit ]; then - exit 0 - fi -fi - -if [ $strace ]; then - if [ "X$strace" = "X2" ]; then - ltrace -f env LD_PRELOAD=$SO $xserver $disp $args $vt - else - strace -f env LD_PRELOAD=$SO $xserver $disp $args $vt - fi -else - exec env LD_PRELOAD=$SO $xserver $disp $args $vt -fi - -exit $? - -######################################################################### - -code() { -#code_begin -#include -#define O_ACCMODE 0003 -#define O_RDONLY 00 -#define O_WRONLY 01 -#define O_RDWR 02 -#define O_CREAT 0100 /* not fcntl */ -#define O_EXCL 0200 /* not fcntl */ -#define O_NOCTTY 0400 /* not fcntl */ -#define O_TRUNC 01000 /* not fcntl */ -#define O_APPEND 02000 -#define O_NONBLOCK 04000 -#define O_NDELAY O_NONBLOCK -#define O_SYNC 010000 -#define O_FSYNC O_SYNC -#define O_ASYNC 020000 - -#include -#include -#include - -#include -#include - -#define __USE_GNU -#include - -static char tmpdir[4096]; -static char str1[4096]; -static char str2[4096]; - -static char devs[256][1024]; -static int debug = -1; -static int root = -1; -static int changed_uid = 0; -static int saw_fonts = 0; -static int saw_lib_modules = 0; - -static time_t start = 0; - -void check_debug(void) { - if (debug < 0) { - if (getenv("XDUMMY_DEBUG") != NULL) { - debug = 1; - } else { - debug = 0; - } - /* prevent other processes using the preload: */ - putenv("LD_PRELOAD="); - } -} -void check_root(void) { - if (root < 0) { - /* script tells us if we are root */ - if (getenv("XDUMMY_ROOT") != NULL) { - root = 1; - } else { - root = 0; - } - } -} - -void check_uid(void) { - if (start == 0) { - start = time(NULL); - if (debug) fprintf(stderr, "START: %u\n", (unsigned int) start); - return; - } else if (changed_uid == 0) { - if (saw_fonts || time(NULL) > start + 20) { - if (getenv("XDUMMY_UID")) { - int uid = atoi(getenv("XDUMMY_UID")); - if (debug) fprintf(stderr, "SETREUID: %d saw_fonts=%d\n", uid, saw_fonts); - if (uid >= 0) { - /* this will simply fail in -nonroot mode: */ - setreuid(uid, -1); - } - } - changed_uid = 1; - } - } -} - -#define CHECKIT if (debug < 0) check_debug(); \ - if (root < 0) check_root(); \ - check_uid(); - -static void set_tmpdir(void) { - char *s; - static int didset = 0; - if (didset) { - return; - } - s = getenv("XDUMMY_TMPDIR"); - if (! s) { - s = "/tmp"; - } - tmpdir[0] = '\0'; - strcat(tmpdir, s); - strcat(tmpdir, "/"); - didset = 1; -} - -static char *tmpdir_path(const char *path) { - char *str; - set_tmpdir(); - strcpy(str2, path); - str = str2; - while (*str) { - if (*str == '/') { - *str = '_'; - } - str++; - } - strcpy(str1, tmpdir); - strcat(str1, str2); - return str1; -} - -int open(const char *pathname, int flags, unsigned short mode) { - int fd; - char *store_dev = NULL; - static int (*real_open)(const char *, int , unsigned short) = NULL; - - CHECKIT - if (! real_open) { - real_open = (int (*)(const char *, int , unsigned short)) - dlsym(RTLD_NEXT, "open"); - } - - if (strstr(pathname, "lib/modules/")) { - /* not currently used. */ - saw_lib_modules = 1; - } - - if (!root) { - if (strstr(pathname, "/dev/") == pathname) { - store_dev = strdup(pathname); - } - if (strstr(pathname, "/dev/tty") == pathname && strcmp(pathname, "/dev/tty")) { - pathname = tmpdir_path(pathname); - if (debug) fprintf(stderr, "OPEN: %s -> %s (as FIFO)\n", store_dev, pathname); - /* we make it a FIFO so ioctl on it does not fail */ - unlink(pathname); - mkfifo(pathname, 0666); - } else if (0) { - /* we used to handle more /dev files ... */ - fd = real_open(pathname, O_WRONLY|O_CREAT, 0777); - close(fd); - } - } - - fd = real_open(pathname, flags, mode); - - if (debug) fprintf(stderr, "OPEN: %s %d %d fd=%d\n", pathname, flags, mode, fd); - - if (! root) { - if (store_dev) { - if (fd < 256) { - strcpy(devs[fd], store_dev); - } - free(store_dev); - } - } - - return(fd); -} - -int open64(const char *pathname, int flags, unsigned short mode) { - int fd; - - CHECKIT - if (debug) fprintf(stderr, "OPEN64: %s %d %d\n", pathname, flags, mode); - - fd = open(pathname, flags, mode); - return(fd); -} - -int rename(const char *oldpath, const char *newpath) { - static int (*real_rename)(const char *, const char *) = NULL; - - CHECKIT - if (! real_rename) { - real_rename = (int (*)(const char *, const char *)) - dlsym(RTLD_NEXT, "rename"); - } - - if (debug) fprintf(stderr, "RENAME: %s %s\n", oldpath, newpath); - - if (root) { - return(real_rename(oldpath, newpath)); - } - - if (strstr(oldpath, "/var/log") == oldpath) { - if (debug) fprintf(stderr, "RENAME: returning 0\n"); - return 0; - } - return(real_rename(oldpath, newpath)); -} - -FILE *fopen(const char *pathname, const char *mode) { - static FILE* (*real_fopen)(const char *, const char *) = NULL; - char *str; - - if (! saw_fonts) { - if (strstr(pathname, "/fonts/")) { - if (strstr(pathname, "fonts.dir")) { - saw_fonts = 1; - } else if (strstr(pathname, "fonts.alias")) { - saw_fonts = 1; - } - } - } - - CHECKIT - if (! real_fopen) { - real_fopen = (FILE* (*)(const char *, const char *)) - dlsym(RTLD_NEXT, "fopen"); - } - - if (debug) fprintf(stderr, "FOPEN: %s %s\n", pathname, mode); - - if (strstr(pathname, "xdummy_modified_xconfig.conf")) { - /* make our config appear to be in /etc/X11, etc. */ - char *q = strrchr(pathname, '/'); - if (q != NULL && getenv("XDUMMY_TMPDIR") != NULL) { - strcpy(str1, getenv("XDUMMY_TMPDIR")); - strcat(str1, q); - if (debug) fprintf(stderr, "FOPEN: %s -> %s\n", pathname, str1); - pathname = str1; - } - } - - if (root) { - return(real_fopen(pathname, mode)); - } - - str = (char *) pathname; - if (strstr(pathname, "/var/log") == pathname) { - str = tmpdir_path(pathname); - if (debug) fprintf(stderr, "FOPEN: %s -> %s\n", pathname, str); - } - return(real_fopen(str, mode)); -} - - -#define RETURN0 if (debug) \ - {fprintf(stderr, "IOCTL: covered %d 0x%x\n", fd, req);} return 0; -#define RETURN1 if (debug) \ - {fprintf(stderr, "IOCTL: covered %d 0x%x\n", fd, req);} return -1; - -int ioctl(int fd, int req, void *ptr) { - static int closed_xf86Info_consoleFd = 0; - static int (*real_ioctl)(int, int , void *) = NULL; - - CHECKIT - if (! real_ioctl) { - real_ioctl = (int (*)(int, int , void *)) - dlsym(RTLD_NEXT, "open"); - } - if (debug) fprintf(stderr, "IOCTL: %d 0x%x %p\n", fd, req, ptr); - - /* based on xorg-x11-6.8.1-dualhead.patch */ - if (req == VT_GETMODE) { - /* close(xf86Info.consoleFd) */ - if (0 && ! closed_xf86Info_consoleFd) { - /* I think better not to close it... */ - close(fd); - closed_xf86Info_consoleFd = 1; - } - RETURN0 - } else if (req == VT_SETMODE) { - RETURN0 - } else if (req == VT_GETSTATE) { - RETURN0 - } else if (req == KDSETMODE) { - RETURN0 - } else if (req == KDSETLED) { - RETURN0 - } else if (req == KDGKBMODE) { - RETURN0 - } else if (req == KDSKBMODE) { - RETURN0 - } else if (req == VT_ACTIVATE) { - RETURN0 - } else if (req == VT_WAITACTIVE) { - RETURN0 - } else if (req == VT_RELDISP) { - if (ptr == (void *) 1) { - RETURN1 - } else if (ptr == (void *) VT_ACKACQ) { - RETURN0 - } - } - - return(real_ioctl(fd, req, ptr)); -} - -typedef void (*sighandler_t)(int); -#define SIGUSR1 10 -#define SIG_DFL ((sighandler_t)0) - -sighandler_t signal(int signum, sighandler_t handler) { - static sighandler_t (*real_signal)(int, sighandler_t) = NULL; - - CHECKIT - if (! real_signal) { - real_signal = (sighandler_t (*)(int, sighandler_t)) - dlsym(RTLD_NEXT, "signal"); - } - - if (debug) fprintf(stderr, "SIGNAL: %d %p\n", signum, handler); - - if (signum == SIGUSR1) { - if (debug) fprintf(stderr, "SIGNAL: skip SIGUSR1\n"); - return SIG_DFL; - } - - return(real_signal(signum, handler)); -} - -int close(int fd) { - static int (*real_close)(int) = NULL; - - CHECKIT - if (! real_close) { - real_close = (int (*)(int)) dlsym(RTLD_NEXT, "close"); - } - - if (debug) fprintf(stderr, "CLOSE: %d\n", fd); - if (!root) { - if (fd < 256) { - devs[fd][0] = '\0'; - } - } - return(real_close(fd)); -} - -struct stat { - int foo; -}; - -int stat(const char *path, struct stat *buf) { - static int (*real_stat)(const char *, struct stat *) = NULL; - - CHECKIT - if (! real_stat) { - real_stat = (int (*)(const char *, struct stat *)) - dlsym(RTLD_NEXT, "stat"); - } - - if (debug) fprintf(stderr, "STAT: %s\n", path); - - return(real_stat(path, buf)); -} - -int stat64(const char *path, struct stat *buf) { - static int (*real_stat64)(const char *, struct stat *) = NULL; - - CHECKIT - if (! real_stat64) { - real_stat64 = (int (*)(const char *, struct stat *)) - dlsym(RTLD_NEXT, "stat64"); - } - - if (debug) fprintf(stderr, "STAT64: %s\n", path); - - return(real_stat64(path, buf)); -} - -int chown(const char *path, uid_t owner, gid_t group) { - static int (*real_chown)(const char *, uid_t, gid_t) = NULL; - - CHECKIT - if (! real_chown) { - real_chown = (int (*)(const char *, uid_t, gid_t)) - dlsym(RTLD_NEXT, "chown"); - } - - if (root) { - return(real_chown(path, owner, group)); - } - - if (debug) fprintf(stderr, "CHOWN: %s %d %d\n", path, owner, group); - - if (strstr(path, "/dev") == path) { - if (debug) fprintf(stderr, "CHOWN: return 0\n"); - return 0; - } - - return(real_chown(path, owner, group)); -} - -extern int *__errno_location (void); -#ifndef ENODEV -#define ENODEV 19 -#endif - -int ioperm(unsigned long from, unsigned long num, int turn_on) { - static int (*real_ioperm)(unsigned long, unsigned long, int) = NULL; - - CHECKIT - if (! real_ioperm) { - real_ioperm = (int (*)(unsigned long, unsigned long, int)) - dlsym(RTLD_NEXT, "ioperm"); - } - if (debug) fprintf(stderr, "IOPERM: %d %d %d\n", (int) from, (int) num, turn_on); - if (root) { - return(real_ioperm(from, num, turn_on)); - } - if (from == 0 && num == 1024 && turn_on == 1) { - /* we want xf86EnableIO to fail */ - if (debug) fprintf(stderr, "IOPERM: setting ENODEV.\n"); - *__errno_location() = ENODEV; - return -1; - } - return 0; -} - -int iopl(int level) { - static int (*real_iopl)(int) = NULL; - - CHECKIT - if (! real_iopl) { - real_iopl = (int (*)(int)) dlsym(RTLD_NEXT, "iopl"); - } - if (debug) fprintf(stderr, "IOPL: %d\n", level); - if (root) { - return(real_iopl(level)); - } - return 0; -} - -#ifdef INTERPOSE_GETUID - -/* - * we got things to work w/o pretending to be root. - * so we no longer interpose getuid(), etc. - */ - -uid_t getuid(void) { - static uid_t (*real_getuid)(void) = NULL; - CHECKIT - if (! real_getuid) { - real_getuid = (uid_t (*)(void)) dlsym(RTLD_NEXT, "getuid"); - } - if (root) { - return(real_getuid()); - } - if (debug) fprintf(stderr, "GETUID: 0\n"); - return 0; -} -uid_t geteuid(void) { - static uid_t (*real_geteuid)(void) = NULL; - CHECKIT - if (! real_geteuid) { - real_geteuid = (uid_t (*)(void)) dlsym(RTLD_NEXT, "geteuid"); - } - if (root) { - return(real_geteuid()); - } - if (debug) fprintf(stderr, "GETEUID: 0\n"); - return 0; -} -uid_t geteuid_kludge1(void) { - static uid_t (*real_geteuid)(void) = NULL; - CHECKIT - if (! real_geteuid) { - real_geteuid = (uid_t (*)(void)) dlsym(RTLD_NEXT, "geteuid"); - } - if (debug) fprintf(stderr, "GETEUID: 0 saw_libmodules=%d\n", saw_lib_modules); - if (root && !saw_lib_modules) { - return(real_geteuid()); - } else { - saw_lib_modules = 0; - return 0; - } -} - -uid_t getuid32(void) { - static uid_t (*real_getuid32)(void) = NULL; - CHECKIT - if (! real_getuid32) { - real_getuid32 = (uid_t (*)(void)) dlsym(RTLD_NEXT, "getuid32"); - } - if (root) { - return(real_getuid32()); - } - if (debug) fprintf(stderr, "GETUID32: 0\n"); - return 0; -} -uid_t geteuid32(void) { - static uid_t (*real_geteuid32)(void) = NULL; - CHECKIT - if (! real_geteuid32) { - real_geteuid32 = (uid_t (*)(void)) dlsym(RTLD_NEXT, "geteuid32"); - } - if (root) { - return(real_geteuid32()); - } - if (debug) fprintf(stderr, "GETEUID32: 0\n"); - return 0; -} - -gid_t getgid(void) { - static gid_t (*real_getgid)(void) = NULL; - CHECKIT - if (! real_getgid) { - real_getgid = (gid_t (*)(void)) dlsym(RTLD_NEXT, "getgid"); - } - if (root) { - return(real_getgid()); - } - if (debug) fprintf(stderr, "GETGID: 0\n"); - return 0; -} -gid_t getegid(void) { - static gid_t (*real_getegid)(void) = NULL; - CHECKIT - if (! real_getegid) { - real_getegid = (gid_t (*)(void)) dlsym(RTLD_NEXT, "getegid"); - } - if (root) { - return(real_getegid()); - } - if (debug) fprintf(stderr, "GETEGID: 0\n"); - return 0; -} -gid_t getgid32(void) { - static gid_t (*real_getgid32)(void) = NULL; - CHECKIT - if (! real_getgid32) { - real_getgid32 = (gid_t (*)(void)) dlsym(RTLD_NEXT, "getgid32"); - } - if (root) { - return(real_getgid32()); - } - if (debug) fprintf(stderr, "GETGID32: 0\n"); - return 0; -} -gid_t getegid32(void) { - static gid_t (*real_getegid32)(void) = NULL; - CHECKIT - if (! real_getegid32) { - real_getegid32 = (gid_t (*)(void)) dlsym(RTLD_NEXT, "getegid32"); - } - if (root) { - return(real_getegid32()); - } - if (debug) fprintf(stderr, "GETEGID32: 0\n"); - return 0; -} -#endif - -#if 0 -/* maybe we need to interpose on strcmp someday... here is the template */ -int strcmp(const char *s1, const char *s2) { - static int (*real_strcmp)(const char *, const char *) = NULL; - CHECKIT - if (! real_strcmp) { - real_strcmp = (int (*)(const char *, const char *)) dlsym(RTLD_NEXT, "strcmp"); - } - if (debug) fprintf(stderr, "STRCMP: '%s' '%s'\n", s1, s2); - return(real_strcmp(s1, s2)); -} -#endif - -#code_end -} diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 7ca89016..61f2ef52 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -19,7 +19,7 @@ use Time::HiRes qw(time); use IO::Handle; # these are shipped with the testsuite use lib qw(lib); -use StartXDummy; +use StartXServer; use StatusLine; use TestWorker; # the following modules are not shipped with Perl @@ -43,7 +43,7 @@ sub Log { say $log "@_" } my %timings; my $help = 0; -# Number of tests to run in parallel. Important to know how many Xdummy +# Number of tests to run in parallel. Important to know how many Xephyr # instances we need to start (unless @displays are given). Defaults to # num_cores * 2. my $parallel = undef; @@ -55,11 +55,11 @@ my %options = ( coverage => 0, restart => 0, ); -my $keep_xdummy_output = 0; +my $keep_xserver_output = 0; my $result = GetOptions( "coverage-testing" => \$options{coverage}, - "keep-xdummy-output" => \$keep_xdummy_output, + "keep-xserver-output" => \$keep_xserver_output, "valgrind" => \$options{valgrind}, "strace" => \$options{strace}, "xtrace" => \$options{xtrace}, @@ -86,6 +86,9 @@ foreach my $binary (@binaries) { die "$binary is not an executable" unless -x $binary; } +qx(Xephyr -help 2>&1); +die "Xephyr was not found in your path. Please install Xephyr (xserver-xephyr on Debian)." if $?; + @displays = split(/,/, join(',', @displays)); @displays = map { s/ //g; $_ } @displays; @@ -97,9 +100,9 @@ my @testfiles = @ARGV; my $numtests = scalar @testfiles; -# No displays specified, let’s start some Xdummy instances. +# No displays specified, let’s start some Xephyr instances. if (@displays == 0) { - @displays = start_xdummy($parallel, $numtests, $keep_xdummy_output); + @displays = start_xserver($parallel, $numtests, $keep_xserver_output); } # 1: create an output directory for this test-run @@ -115,8 +118,7 @@ symlink("$outdir", "latest") or die "Could not symlink latest to $outdir"; # connect to all displays for two reasons: # 1: check if the display actually works # 2: keep the connection open so that i3 is not the only client. this prevents -# the X server from exiting (Xdummy will restart it, but not quick enough -# sometimes) +# the X server from exiting my @single_worker; for my $display (@displays) { my $screen; @@ -131,7 +133,7 @@ for my $display (@displays) { # Read previous timing information, if available. We will be able to roughly # predict the test duration and schedule a good order for the tests. -my $timingsjson = StartXDummy::slurp('.last_run_timings.json'); +my $timingsjson = StartXServer::slurp('.last_run_timings.json'); %timings = %{decode_json($timingsjson)} if length($timingsjson) > 0; # Re-order the files so that those which took the longest time in the previous @@ -220,7 +222,7 @@ printf("\t%s with %.2f seconds\n", $_, $timings{$_}) if ($numtests == 1) { say ''; say 'Test output:'; - say StartXDummy::slurp($logfile); + say StartXServer::slurp($logfile); } END { cleanup() } @@ -346,7 +348,7 @@ complete-run.pl [files...] =head1 EXAMPLE -To run the whole testsuite on a reasonable number of Xdummy instances (your +To run the whole testsuite on a reasonable number of Xephyr instances (your running X11 will not be touched), run: ./complete-run.pl @@ -365,11 +367,11 @@ will parallelize the tests: # Run tests on the second X server ./complete-run.pl -d :1 - # Run four tests in parallel on some Xdummy servers + # Run four tests in parallel on some Xephyr servers ./complete-run.pl -d :1,:2,:3,:4 Note that it is not necessary to specify this anymore. If omitted, -complete-run.pl will start (num_cores * 2) Xdummy instances. +complete-run.pl will start (num_cores * 2) Xephyr instances. =item B<--valgrind> @@ -392,8 +394,8 @@ Exits i3 cleanly (instead of kill -9) to make coverage testing work properly. =item B<--parallel> -Number of Xdummy instances to start (if you don’t 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 Xdummy instance + # Run all tests on a single Xephyr instance ./complete-run.pl -p 1 diff --git a/testcases/i3-test.config b/testcases/i3-test.config index 513dda36..9d0b7d66 100644 --- a/testcases/i3-test.config +++ b/testcases/i3-test.config @@ -20,7 +20,7 @@ bindsym Mod1+h split h bindsym Mod1+v split v # Fullscreen (Mod1+f) -bindsym Mod1+f fullscreen +bindsym Mod1+f fullscreen toggle # Stacking (Mod1+s) bindsym Mod1+s layout stacking diff --git a/testcases/lib/StartXDummy.pm b/testcases/lib/StartXServer.pm similarity index 66% rename from testcases/lib/StartXDummy.pm rename to testcases/lib/StartXServer.pm index 592feb85..032f58c6 100644 --- a/testcases/lib/StartXDummy.pm +++ b/testcases/lib/StartXServer.pm @@ -1,4 +1,4 @@ -package StartXDummy; +package StartXServer; # vim:ts=4:sw=4:expandtab use strict; @@ -7,7 +7,7 @@ use Exporter 'import'; use Time::HiRes qw(sleep); use v5.10; -our @EXPORT = qw(start_xdummy); +our @EXPORT = qw(start_xserver); my @pids; my $x_socketpath = '/tmp/.X11-unix/X'; @@ -19,15 +19,15 @@ sub slurp { <$fh>; } -# forks an Xdummy or Xdmx process +# forks an X server process sub fork_xserver { - my $keep_xdummy_output = shift; + my $keep_xserver_output = shift; my $displaynum = shift; my $pid = fork(); die "Could not fork: $!" unless defined($pid); if ($pid == 0) { - # Child, close stdout/stderr, then start Xdummy. - if (!$keep_xdummy_output) { + # Child, close stdout/stderr, then start Xephyr + if (!$keep_xserver_output) { close STDOUT; close STDERR; } @@ -60,16 +60,17 @@ sub wait_for_x { } } -=head2 start_xdummy($parallel) +=head2 start_xserver($parallel) -Starts C<$parallel> (or number of cores * 2 if undef) Xdummy processes (see -the file ./Xdummy) and returns two arrayrefs: a list of X11 display numbers to -the Xdummy processes and a list of PIDs of the processes. +Starts C<$parallel> (or number of cores * 2 if undef) Xephyr processes (see +http://www.freedesktop.org/wiki/Software/Xephyr/) and returns two arrayrefs: a +list of X11 display numbers to the Xephyr processes and a list of PIDs of the +processes. =cut -sub start_xdummy { - my ($parallel, $numtests, $keep_xdummy_output) = @_; +sub start_xserver { + my ($parallel, $numtests, $keep_xserver_output) = @_; my @displays = (); my @childpids = (); @@ -78,11 +79,8 @@ sub start_xdummy { my $child = waitpid -1, POSIX::WNOHANG; @pids = grep { $_ != $child } @pids; return unless @pids == 0; - print STDERR "All Xdummy processes died.\n"; - print STDERR "Use ./complete-run.pl --parallel 1 --keep-xdummy-output\n"; - print STDERR ""; - print STDERR "A frequent cause for this is missing the DUMMY Xorg module,\n"; - print STDERR "package xserver-xorg-video-dummy on Debian.\n"; + print STDERR "All X server processes died.\n"; + print STDERR "Use ./complete-run.pl --parallel 1 --keep-xserver-output\n"; exit 1; }; @@ -104,16 +102,13 @@ sub start_xdummy { my ($displaynum) = map { /(\d+)$/ } reverse sort glob($x_socketpath . '*'); $displaynum++; - say "Starting $parallel Xdummy instances, starting at :$displaynum..."; + say "Starting $parallel Xephyr instances, starting at :$displaynum..."; my @sockets_waiting; for (1 .. $parallel) { - # We use -config /dev/null to prevent Xdummy from using the system - # Xorg configuration. The tests should be independant from the - # actual system X configuration. - my $socket = fork_xserver($keep_xdummy_output, $displaynum, - './Xdummy', ":$displaynum", '-config', '/dev/null', - '-configdir', '/dev/null', '-nolisten', 'tcp'); + my $socket = fork_xserver($keep_xserver_output, $displaynum, + 'Xephyr', ":$displaynum", '-screen', '1280x800', + '-nolisten', 'tcp'); push(@displays, ":$displaynum"); push(@sockets_waiting, $socket); $displaynum++; diff --git a/testcases/lib/i3test.pm b/testcases/lib/i3test.pm index a6b982ba..212e78fd 100644 --- a/testcases/lib/i3test.pm +++ b/testcases/lib/i3test.pm @@ -337,6 +337,7 @@ sub open_window { $args{name} //= 'Window ' . counter_window(); my $window = $x->root->create_child(%args); + $window->add_hint('input'); if ($before_map) { # TODO: investigate why _create is not needed diff --git a/testcases/t/100-fullscreen.t b/testcases/t/100-fullscreen.t index 206116ee..458fff93 100644 --- a/testcases/t/100-fullscreen.t +++ b/testcases/t/100-fullscreen.t @@ -191,6 +191,26 @@ is($x->input_focus, $window->id, 'fullscreen window focused'); cmd 'focus left'; is($x->input_focus, $window->id, 'fullscreen window still focused'); +################################################################################ +# Verify that changing workspace while in global fullscreen does not work. +################################################################################ + +$tmp = fresh_workspace; +$window = open_window; + +cmd 'fullscreen global'; +is($x->input_focus, $window->id, 'window focused'); +is(focused_ws(), $tmp, 'workspace selected'); + +$other = get_unused_workspace; +cmd "workspace $other"; +is($x->input_focus, $window->id, 'window still focused'); +is(focused_ws(), $tmp, 'workspace still selected'); + +# leave global fullscreen so that is does not interfere with the other tests +$window->fullscreen(0); +sync_with_i3; + ################################################################################ # Verify that fullscreening a window on a second workspace and moving it onto # the first workspace unfullscreens the first window. @@ -235,4 +255,131 @@ $swindow = open_window({ is(fullscreen_windows($tmp), 1, 'one fullscreen window on ws'); is($x->input_focus, $swindow->id, 'fullscreen window focused'); +################################################################################ +# Verify that command ‘fullscreen enable’ works and is idempotent. +################################################################################ + +$tmp = fresh_workspace; + +$window = open_window; +is($x->input_focus, $window->id, 'window focused'); +is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace'); + +cmd 'fullscreen enable'; +is($x->input_focus, $window->id, 'window still focused'); +is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace'); + +cmd 'fullscreen enable'; +is($x->input_focus, $window->id, 'window still focused'); +is(fullscreen_windows($tmp), 1, 'still one fullscreen window on workspace'); + +$window->fullscreen(0); +sync_with_i3; +is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace'); + +################################################################################ +# Verify that command ‘fullscreen enable global’ works and is idempotent. +################################################################################ + +$tmp = fresh_workspace; + +$window = open_window; +is($x->input_focus, $window->id, 'window focused'); +is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace'); + +cmd 'fullscreen enable global'; +is($x->input_focus, $window->id, 'window still focused'); +is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace'); + +cmd 'fullscreen enable global'; +is($x->input_focus, $window->id, 'window still focused'); +is(fullscreen_windows($tmp), 1, 'still one fullscreen window on workspace'); + +$window->fullscreen(0); +sync_with_i3; +is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace'); + +################################################################################ +# Verify that command ‘fullscreen disable’ works and is idempotent. +################################################################################ + +$tmp = fresh_workspace; + +$window = open_window; +is($x->input_focus, $window->id, 'window focused'); +is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace'); + +$window->fullscreen(1); +sync_with_i3; +is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace'); + +cmd 'fullscreen disable'; +is($x->input_focus, $window->id, 'window still focused'); +is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace'); + +cmd 'fullscreen disable'; +is($x->input_focus, $window->id, 'window still focused'); +is(fullscreen_windows($tmp), 0, 'still no fullscreen window on workspace'); + +################################################################################ +# Verify that command ‘fullscreen toggle’ works. +################################################################################ + +$tmp = fresh_workspace; + +$window = open_window; +is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace'); + +cmd 'fullscreen toggle'; +is($x->input_focus, $window->id, 'window still focused'); +is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace'); + +cmd 'fullscreen toggle'; +is($x->input_focus, $window->id, 'window still focused'); +is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace'); + +################################################################################ +# Verify that a window’s fullscreen is disabled when another one is enabled +# on the same workspace. The new fullscreen window should be focused. +################################################################################ + +$tmp = fresh_workspace; + +$window = open_window; +$other = open_window; + +is($x->input_focus, $other->id, 'other window focused'); +is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace'); + +cmd 'fullscreen enable'; +is($x->input_focus, $other->id, 'other window focused'); +is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace'); + +cmd '[id="' . $window->id . '"] fullscreen enable'; +is($x->input_focus, $window->id, 'window focused'); +is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace'); + +################################################################################ +# Verify that when a global fullscreen is enabled the window is focused and +# its workspace is selected, so that disabling the fullscreen keeps the window +# focused and visible. +################################################################################ + +$tmp = fresh_workspace; + +$window = open_window; + +is($x->input_focus, $window->id, 'window focused'); + +cmd 'workspace ' . get_unused_workspace; +isnt($x->input_focus, $window->id, 'window not focused'); +isnt(focused_ws(), $tmp, 'workspace not selected'); + +cmd '[id="' . $window->id . '"] fullscreen enable global'; +is($x->input_focus, $window->id, 'window focused'); + +cmd 'fullscreen disable'; +is($x->input_focus, $window->id, 'window still focused'); +is(focused_ws(), $tmp, 'workspace selected'); + done_testing; diff --git a/testcases/t/112-floating-resize.t b/testcases/t/112-floating-resize.t index ec690b5e..a7390051 100644 --- a/testcases/t/112-floating-resize.t +++ b/testcases/t/112-floating-resize.t @@ -69,7 +69,7 @@ cmd 'border 1pixel'; test_resize; ################################################################################ -# Check if we can position a floating window out of bounds. The XDummy screen +# Check if we can position a floating window out of bounds. The Xephyr screen # is 1280x1024, so x=2864, y=893 is out of bounds. ################################################################################ diff --git a/testcases/t/114-client-leader.t b/testcases/t/114-client-leader.t index 63e92c3c..ec8bff26 100644 --- a/testcases/t/114-client-leader.t +++ b/testcases/t/114-client-leader.t @@ -99,4 +99,27 @@ is($x->input_focus, $child->id, "Child window focused"); } +################################################################################ +# Verify that transient_for can be set and unset. +################################################################################ + +$tmp = fresh_workspace; + +$fwindow = open_window({ dont_map => 1 }); +$fwindow->transient_for($right); +$fwindow->map; + +wait_for_map($fwindow); + +my $floating_con = get_ws($tmp)->{floating_nodes}[0]->{nodes}[0]; +is($floating_con->{window_properties}->{transient_for}, $right->id, 'WM_TRANSIENT_FOR properly parsed'); + +$x->delete_property($fwindow->id, $x->atom(name => 'WM_TRANSIENT_FOR')->id); +$x->flush; + +sync_with_i3; + +$floating_con = get_ws($tmp)->{floating_nodes}[0]->{nodes}[0]; +is($floating_con->{window_properties}->{transient_for}, undef, 'WM_TRANSIENT_FOR properly removed'); + done_testing; diff --git a/testcases/t/115-ipc-workspaces.t b/testcases/t/115-ipc-workspaces.t index 29837430..2bcc6d60 100644 --- a/testcases/t/115-ipc-workspaces.t +++ b/testcases/t/115-ipc-workspaces.t @@ -23,7 +23,7 @@ $i3->connect()->recv; # Workspaces requests and events ################################ -my $focused = get_ws(focused_ws()); +my $old_ws = get_ws(focused_ws()); # Events @@ -36,15 +36,11 @@ $i3->subscribe({ workspace => sub { my ($event) = @_; if ($event->{change} eq 'init') { - $init->send(1); + $init->send($event); } elsif ($event->{change} eq 'focus') { - # Check that we have the old and new workspace - $focus->send( - $event->{current}->{name} == '2' && - $event->{old}->{name} == $focused->{name} - ); + $focus->send($event); } elsif ($event->{change} eq 'empty') { - $empty->send(1); + $empty->send($event); } } })->recv; @@ -61,8 +57,20 @@ $t = AnyEvent->timer( } ); -ok($init->recv, 'Workspace "init" event received'); -ok($focus->recv, 'Workspace "focus" event received'); -ok($empty->recv, 'Workspace "empty" event received'); +my $init_event = $init->recv; +my $focus_event = $focus->recv; +my $empty_event = $empty->recv; + +my $current_ws = get_ws(focused_ws()); + +ok($init_event, 'workspace "init" event received'); +is($init_event->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the initted workspace con'); + +ok($focus_event, 'workspace "focus" event received'); +is($focus_event->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the focused workspace con'); +is($focus_event->{old}->{id}, $old_ws->{id}, 'the "old" property should contain the workspace con that was focused last'); + +ok($empty_event, 'workspace "empty" event received'); +is($empty_event->{current}->{id}, $old_ws->{id}, 'the "current" property should contain the emptied workspace con'); done_testing; diff --git a/testcases/t/116-nestedcons.t b/testcases/t/116-nestedcons.t index eb2fe144..d9ff1c39 100644 --- a/testcases/t/116-nestedcons.t +++ b/testcases/t/116-nestedcons.t @@ -54,6 +54,7 @@ my $expected = { type => 'root', id => $ignore, rect => $ignore, + deco_rect => $ignore, window_rect => $ignore, geometry => $ignore, swallows => $ignore, diff --git a/testcases/t/122-split.t b/testcases/t/122-split.t index 6afdd806..e9d06938 100644 --- a/testcases/t/122-split.t +++ b/testcases/t/122-split.t @@ -48,7 +48,7 @@ sub verify_split_layout { is(@{$first->{nodes}}, 0, 'first container has no children'); is(@{$second->{nodes}}, 0, 'second container has no children (yet)'); - my $old_name = $second->{name}; + my $old_id = $second->{id}; cmd $args{split_command}; cmd 'open'; @@ -60,10 +60,10 @@ sub verify_split_layout { $second = $content->[1]; is(@{$first->{nodes}}, 0, 'first container has no children'); - isnt($second->{name}, $old_name, 'second container was replaced'); + isnt($second->{id}, $old_id, 'second container was replaced'); is($second->{layout}, 'splith', 'orientation is horizontal'); is(@{$second->{nodes}}, 2, 'second container has 2 children'); - is($second->{nodes}->[0]->{name}, $old_name, 'found old second container'); + is($second->{nodes}->[0]->{id}, $old_id, 'found old second container'); } verify_split_layout(split_command => 'split h'); diff --git a/testcases/t/124-move.t b/testcases/t/124-move.t index 28207a3c..88a36a5b 100644 --- a/testcases/t/124-move.t +++ b/testcases/t/124-move.t @@ -245,4 +245,20 @@ my $center_y = int($x->root->rect->height/2) - int($floatcon[0]->{rect}->{height is($floatcon[0]->{rect}->{x}, $center_x, "moved to center at position $center_x x"); is($floatcon[0]->{rect}->{y}, $center_y, "moved to center at position $center_y y"); +# Make sure the command works with criteria +open_floating_window; + +@floatcon = @{get_ws($tmp)->{floating_nodes}}; + +cmd '[con_id="' . $floatcon[0]->{nodes}[0]->{id} . '"] move position 25 px 30 px'; +cmd '[con_id="' . $floatcon[1]->{nodes}[0]->{id} . '"] move position 35 px 40 px'; + +@floatcon = @{get_ws($tmp)->{floating_nodes}}; + +is($floatcon[0]->{rect}->{x}, 25, 'moved to position 25 x with criteria'); +is($floatcon[0]->{rect}->{y}, 30, 'moved to position 30 y with criteria'); + +is($floatcon[1]->{rect}->{x}, 35, 'moved to position 35 x with criteria'); +is($floatcon[1]->{rect}->{y}, 40, 'moved to position 40 y with criteria'); + done_testing; diff --git a/testcases/t/139-ws-numbers.t b/testcases/t/139-ws-numbers.t index 6829a147..f76ee04b 100644 --- a/testcases/t/139-ws-numbers.t +++ b/testcases/t/139-ws-numbers.t @@ -24,7 +24,7 @@ sub check_order { my ($msg) = @_; my @ws = @{$i3->get_workspaces->recv}; - my @nums = map { $_->{num} } grep { defined($_->{num}) } @ws; + my @nums = map { $_->{num} } grep { $_->{num} != -1 } @ws; my @sorted = sort @nums; is_deeply(\@nums, \@sorted, $msg); diff --git a/testcases/t/158-wm_take_focus.t b/testcases/t/158-wm_take_focus.t index ba03913a..050e1162 100644 --- a/testcases/t/158-wm_take_focus.t +++ b/testcases/t/158-wm_take_focus.t @@ -59,6 +59,18 @@ subtest 'Window without WM_TAKE_FOCUS', sub { done_testing; }; +# http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.7 +# > Clients using the Globally Active model can only use a SetInputFocus request +# > to acquire the input focus when they do not already have it on receipt of one +# > of the following events: +# > * ButtonPress +# > * ButtonRelease +# > * Passive-grabbed KeyPress +# > * Passive-grabbed KeyRelease +# +# Since managing a window happens on a MapNotify (which is absent from this +# list), the window cannot accept input focus, so we should not try to focus +# the window at all. subtest 'Window with WM_TAKE_FOCUS and without InputHint', sub { fresh_workspace; @@ -74,7 +86,7 @@ subtest 'Window with WM_TAKE_FOCUS and without InputHint', sub { $window->map; - ok(recv_take_focus($window), 'got ClientMessage with WM_TAKE_FOCUS atom'); + ok(!recv_take_focus($window), 'did not receive ClientMessage'); done_testing; }; diff --git a/testcases/t/172-start-on-named-ws.t b/testcases/t/172-start-on-named-ws.t index 09b708be..8b00abac 100644 --- a/testcases/t/172-start-on-named-ws.t +++ b/testcases/t/172-start-on-named-ws.t @@ -88,4 +88,22 @@ is_deeply(\@names, [ '3' ], 'i3 starts on workspace 3 without whitespace'); exit_gracefully($pid); +################################################################################ +# 5: now with a binding that contains multiple commands +################################################################################ + +$config = <$tmp|; diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index 51192f55..9643aa42 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -50,9 +50,9 @@ EOT my $expected = <<'EOT'; cfg_enter_mode(meh) -cfg_mode_binding(bindsym, Mod1,Shift, x, (null), resize grow) -cfg_mode_binding(bindcode, Mod1, 44, (null), resize shrink) -cfg_mode_binding(bindsym, Mod1, x, --release, exec foo) +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) EOT is(parser_calls($config), @@ -620,7 +620,7 @@ EOT $expected = <<'EOT'; cfg_enter_mode(yo) -cfg_mode_binding(bindsym, (null), x, (null), resize shrink left) +cfg_mode_binding(bindsym, (null), x, (null), (null), resize shrink left) ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', 'bind', '}' ERROR: CONFIG: (in file ) ERROR: CONFIG: Line 1: mode "yo" { @@ -647,7 +647,7 @@ EOT $expected = <<'EOT'; cfg_bar_output(LVDS-1) -ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'position', 'output', 'tray_output', 'font', 'binding_mode_indicator', 'workspace_buttons', 'strip_workspace_numbers', 'verbose', 'colors', '}' +ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'position', 'output', 'tray_output', 'font', 'binding_mode_indicator', 'workspace_buttons', 'strip_workspace_numbers', 'verbose', 'colors', '}' ERROR: CONFIG: (in file ) ERROR: CONFIG: Line 1: bar { ERROR: CONFIG: Line 2: output LVDS-1 diff --git a/testcases/t/217-NET_CURRENT_DESKTOP.t b/testcases/t/217-NET_CURRENT_DESKTOP.t index fe2ea675..7ca6fbc2 100644 --- a/testcases/t/217-NET_CURRENT_DESKTOP.t +++ b/testcases/t/217-NET_CURRENT_DESKTOP.t @@ -71,6 +71,40 @@ is(current_desktop_index, 1, "Open on 0 and view 1"); cmd 'workspace 2'; is(current_desktop_index, 2, "Open and view empty"); +######################################################### +# Test the _NET_CURRENT_DESKTOP client request +# This request is sent by pagers and bars to switch the current desktop (which +# is like an ersatz workspace) to the given index +######################################################### + +sub send_current_desktop_request { + my ($idx) = @_; + + my $msg = pack "CCSLLLLLL", + X11::XCB::CLIENT_MESSAGE, # response_type + 32, # format + 0, # sequence + 0, + $_NET_CURRENT_DESKTOP, + $idx, # data32[0] (the desktop index) + 0, # data32[1] (can be a timestamp) + 0, # data32[2] + 0, # data32[3] + 0; # data32[4] + + $x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg); +} + +send_current_desktop_request(1); +is(current_desktop_index, 1, 'current desktop request switched to desktop 1'); +# note that _NET_CURRENT_DESKTOP is an index and that in this case, workspace 1 +# is at index 1 as a convenience for the test +is(focused_ws, '1', 'current desktop request switched to workspace 1'); + +send_current_desktop_request(0); +is(current_desktop_index, 0, 'current desktop request switched to desktop 0'); +is(focused_ws, '0', 'current desktop request switched to workspace 0'); + exit_gracefully($pid); done_testing; diff --git a/testcases/t/227-ipc-workspace-empty.t b/testcases/t/227-ipc-workspace-empty.t index 185910e8..0c67423e 100644 --- a/testcases/t/227-ipc-workspace-empty.t +++ b/testcases/t/227-ipc-workspace-empty.t @@ -50,6 +50,7 @@ subtest 'Workspace empty event upon switch', sub { my $event = $cond->recv; is($event->{change}, 'empty', '"Empty" event received upon workspace switch'); + is($event->{current}->{name}, $ws1, '"current" property should be set to the workspace con'); }; ################################################################################ @@ -116,6 +117,7 @@ subtest 'Workspace empty event upon window close', sub { my $event = $cond->recv; is($event->{change}, 'empty', '"Empty" event received upon window close'); + is($event->{current}->{name}, $ws1, '"current" property should be set to the workspace con'); }; } diff --git a/testcases/t/228-border-widths.t b/testcases/t/228-border-widths.t index e236fe60..be1e7f85 100644 --- a/testcases/t/228-border-widths.t +++ b/testcases/t/228-border-widths.t @@ -47,10 +47,12 @@ my $wscontent = get_ws($tmp); my @tiled = @{$wscontent->{nodes}}; ok(@tiled == 1, 'one tiled container opened'); +is($tiled[0]->{current_border_width}, 5, 'tiled current border width set to 5'); is($tilewindow->rect->width, $tiled[0]->{rect}->{width} - 2*5, 'tiled border width 5'); my @floating = @{$wscontent->{floating_nodes}}; ok(@floating == 1, 'one floating container opened'); +is($floating[0]->{nodes}[0]->{current_border_width}, 10, 'floating current border width set to 10'); is($floatwindow->rect->width, $floating[0]->{rect}->{width} - 2*10, 'floating border width 10'); exit_gracefully($pid); @@ -80,12 +82,49 @@ $wscontent = get_ws($tmp); @tiled = @{$wscontent->{nodes}}; ok(@tiled == 1, 'one tiled container opened'); +is($tiled[0]->{current_border_width}, 3, 'tiled current border width set to 3'); is($tilewindow->rect->width, $tiled[0]->{rect}->{width} - 2*3, 'tiled border width 3'); @floating = @{$wscontent->{floating_nodes}}; ok(@floating == 1, 'one floating container opened'); +is($floating[0]->{nodes}[0]->{current_border_width}, 7, 'floating current border width set to 7'); is($floatwindow->rect->width, $floating[0]->{rect}->{width} - 2*7, 'floating border width 7'); exit_gracefully($pid); +##################################################################### +# 3: make sure normal border widths work as well +##################################################################### + +$config = <{nodes}}; +ok(@tiled == 1, 'one tiled container opened'); +is($tiled[0]->{current_border_width}, 4, 'tiled current border width set to 4'); +is($tilewindow->rect->width, $tiled[0]->{rect}->{width} - 2*4, 'tiled border width 4'); + +@floating = @{$wscontent->{floating_nodes}}; +ok(@floating == 1, 'one floating container opened'); +is($floating[0]->{nodes}[0]->{current_border_width}, 6, 'floating current border width set to 6'); +is($floatwindow->rect->width, $floating[0]->{rect}->{width} - 2*6, 'floating border width 6'); + +exit_gracefully($pid); + done_testing; diff --git a/testcases/t/231-ipc-floating-event.t b/testcases/t/231-ipc-floating-event.t new file mode 100644 index 00000000..c2de64e4 --- /dev/null +++ b/testcases/t/231-ipc-floating-event.t @@ -0,0 +1,59 @@ +#!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 that the window::floating event works correctly. This event should be +# emitted when a window transitions to or from the floating state. +# Bug still in: 4.8-7-gf4a8253 +use i3test; + +my $i3 = i3(get_socket_path()); +$i3->connect->recv; + +my $cv = AnyEvent->condvar; + +$i3->subscribe({ + window => sub { + my ($event) = @_; + $cv->send($event) if $event->{change} eq 'floating'; + } + })->recv; + +my $t; +$t = AnyEvent->timer( + after => 0.5, + cb => sub { + $cv->send(0); + } +); + +my $win = open_window(); + +cmd '[id="' . $win->{id} . '"] floating enable'; +my $e = $cv->recv; + +isnt($e, 0, 'floating a container should send an ipc window event'); +is($e->{container}->{window}, $win->{id}, 'the event should contain information about the window'); +is($e->{container}->{floating}, 'user_on', 'the container should be floating'); + +$cv = AnyEvent->condvar; +cmd '[id="' . $win->{id} . '"] floating disable'; +my $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'); +is($e->{container}->{floating}, 'user_off', 'the container should not be floating'); + +done_testing; diff --git a/testcases/t/231-ipc-window-close.t b/testcases/t/231-ipc-window-close.t new file mode 100644 index 00000000..3483cf42 --- /dev/null +++ b/testcases/t/231-ipc-window-close.t @@ -0,0 +1,52 @@ +#!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 the ipc close event works properly +# +# Bug still in: 4.8-7-gf4a8253 +use i3test; + +my $i3 = i3(get_socket_path()); +$i3->connect()->recv; + +my $cv; +my $t; + +sub reset_test { + $cv = AE::cv; + $t = AE::timer(0.5, 0, sub { $cv->send(0); }); +} + +reset_test; + +$i3->subscribe({ + window => sub { + my ($e) = @_; + if ($e->{change} eq 'close') { + $cv->send($e->{container}); + } + }, + })->recv; + +my $window = open_window; + +cmd 'kill'; +my $con = $cv->recv; + +ok($con, 'closing a window should send the window::close event'); +is($con->{window}, $window->{id}, 'the event should contain information about the window'); + +done_testing; diff --git a/testcases/t/231-ipc-window-move.t b/testcases/t/231-ipc-window-move.t new file mode 100644 index 00000000..117d27fb --- /dev/null +++ b/testcases/t/231-ipc-window-move.t @@ -0,0 +1,61 @@ +#!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 the ipc window::move event works properly +# +# Bug still in: 4.8-7-gf4a8253 +use i3test; + +my $i3 = i3(get_socket_path()); +$i3->connect()->recv; + +my $cv; +my $t; + +sub reset_test { + $cv = AE::cv; + $t = AE::timer(0.5, 0, sub { $cv->send(0); }); +} + +reset_test; + +$i3->subscribe({ + window => sub { + my ($e) = @_; + if ($e->{change} eq 'move') { + $cv->send($e->{container}); + } + }, + })->recv; + +my $dummy_window = open_window; +my $window = open_window; + +cmd 'move right'; +my $con = $cv->recv; + +ok($con, 'moving a window should emit the window::move event'); +is($con->{window}, $window->{id}, 'the event should contain info about the window'); + +reset_test; + +cmd 'move to workspace ws_new'; +$con = $cv->recv; + +ok($con, 'moving a window to a different workspace should emit the window::move event'); +is($con->{window}, $window->{id}, 'the event should contain info about the window'); + +done_testing; diff --git a/testcases/t/231-wm-change-state.t b/testcases/t/231-wm-change-state.t new file mode 100644 index 00000000..92992b45 --- /dev/null +++ b/testcases/t/231-wm-change-state.t @@ -0,0 +1,49 @@ +#!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) +# +# Correctly handle WM_CHANGE_STATE requests for the iconic state +# See http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.4 +# Ticket: #1279 +# Bug still in: 4.8-7-gf4a8253 +use i3test; + +sub send_iconic_state_request { + my ($win) = @_; + + my $msg = pack "CCSLLLLLL", + X11::XCB::CLIENT_MESSAGE, # response_type + 32, # format + 0, # sequence + $win->id, # window + $x->atom(name => 'WM_CHANGE_STATE')->id, # message type + 3, # data32[0] + 0, # 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); +} + +my $ws = fresh_workspace; +my $win = open_window; + +send_iconic_state_request($win); +sync_with_i3; + +is(@{get_ws($ws)->{nodes}}, 0, 'When a window requests the iconic state, the container should be closed'); + +done_testing; diff --git a/testcases/t/232-cmd-move-criteria.t b/testcases/t/232-cmd-move-criteria.t new file mode 100644 index 00000000..22a2eb4e --- /dev/null +++ b/testcases/t/232-cmd-move-criteria.t @@ -0,0 +1,37 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Test that the `move [direction]` command works with criteria +# Bug still in: 4.8-16-g6888a1f +use i3test; + +my $ws = fresh_workspace; + +my $win1 = open_window; +my $win2 = open_window; +my $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); + +# 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'); + +done_testing; diff --git a/testcases/t/232-cmd-workspace-number-selection.t b/testcases/t/232-cmd-workspace-number-selection.t new file mode 100644 index 00000000..bda05643 --- /dev/null +++ b/testcases/t/232-cmd-workspace-number-selection.t @@ -0,0 +1,52 @@ +#!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 that `workspace {N}` acts like `workspace number {N}` when N is a plain +# digit, and likewise for `move to workspace {N}`. +# Ticket: #1238 +# Bug still in: 4.8-16-g3f5a0f0 +use i3test; + +cmd 'workspace 5:foo'; +open_window; +fresh_workspace; +cmd 'workspace 5'; + +is(focused_ws, '5:foo', + 'a command to switch to a workspace with a bare number should switch to a workspace of that number'); + +fresh_workspace; +my $win = open_window; +cmd '[id="' . $win->{id} . '"] move to workspace 5'; + +is(@{get_ws('5:foo')->{nodes}}, 2, + 'a command to move a container to a workspace with a bare number should move that container to a workspace of that number'); + +fresh_workspace; +cmd 'workspace 7'; +open_window; +cmd 'workspace 7:foo'; +$win = open_window; + +cmd 'workspace 7'; +is(focused_ws, '7', + 'a workspace with a name that is a matching plain number should be preferred when switching'); + +cmd '[id="' . $win->{id} . '"] move to workspace 7'; +is(@{get_ws('7')->{nodes}}, 2, + 'a workspace with a name that is a matching plain number should be preferred when moving'); + +done_testing; diff --git a/testcases/t/232-ipc-window-urgent.t b/testcases/t/232-ipc-window-urgent.t new file mode 100644 index 00000000..2ac9ecbb --- /dev/null +++ b/testcases/t/232-ipc-window-urgent.t @@ -0,0 +1,68 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Test that the window::urgent event works correctly. The window::urgent event +# should be emitted when a window becomes urgent or loses its urgent status. +# +use i3test; + +my $config = <connect()->recv; + +my $cv; +$i3->subscribe({ + window => sub { + my ($event) = @_; + $cv->send($event) if $event->{change} eq 'urgent'; + } +})->recv; + +my $t; +$t = AnyEvent->timer( + after => 0.5, + cb => sub { + $cv->send(0); + } +); + +$cv = AnyEvent->condvar; +fresh_workspace; +my $win = open_window; +my $dummy_win = open_window; + +$win->add_hint('urgency'); +my $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'); +is($event->{container}->{urgent}, 1, 'the container should be urgent'); + +$cv = AnyEvent->condvar; +$win->delete_hint('urgency'); +my $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'); +is($event->{container}->{urgent}, 0, 'the container should not be urgent'); + +done_testing; diff --git a/testcases/t/234-ewmh-desktop-names.t b/testcases/t/234-ewmh-desktop-names.t new file mode 100644 index 00000000..d95965c4 --- /dev/null +++ b/testcases/t/234-ewmh-desktop-names.t @@ -0,0 +1,75 @@ +#!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 that the EWMH specified property _NET_DESKTOP_NAMES is updated properly +# on the root window. We interpret this as a list of the open workspace names. +# Ticket: #1241 +use i3test; + +sub get_desktop_names { + # Make sure that i3 pushed its changes to X11 before querying. + sync_with_i3; + + my $cookie = $x->get_property( + 0, + $x->get_root_window(), + $x->atom(name => '_NET_DESKTOP_NAMES')->id, + $x->atom(name => 'UTF8_STRING')->id, + 0, + 4096, + ); + + my $reply = $x->get_property_reply($cookie->{sequence}); + + return 0 if $reply->{value_len} == 0; + + # the property is a null-delimited list of utf8 strings ;; + return split /\0/, $reply->{value}; +} + +cmd 'workspace foo'; + +my @expected_names = ('foo'); +my @desktop_names = get_desktop_names; + +is_deeply(\@desktop_names, \@expected_names, '_NET_DESKTOP_NAMES should be an array of the workspace names'); + +# open a new workspace and see that the property is updated correctly +open_window; +cmd 'workspace bar'; + +@desktop_names = get_desktop_names; +@expected_names = ('foo', 'bar'); + +is_deeply(\@desktop_names, \@expected_names, 'it should be updated when a new workspace appears'); + +# rename the workspace and see that the property is updated correctly +cmd 'rename workspace bar to baz'; + +@desktop_names = get_desktop_names; +@expected_names = ('foo', 'baz'); + +is_deeply(\@desktop_names, \@expected_names, 'it should be updated when a workspace is renamed'); + +# empty a workspace and see that the property is updated correctly +cmd 'workspace foo'; + +@desktop_names = get_desktop_names; +@expected_names = ('foo'); + +is_deeply(\@desktop_names, \@expected_names, 'it should be updated when a workspace is emptied'); + +done_testing; diff --git a/testcases/t/234-layout-restore-output.t b/testcases/t/234-layout-restore-output.t index d407289a..bc90131d 100644 --- a/testcases/t/234-layout-restore-output.t +++ b/testcases/t/234-layout-restore-output.t @@ -19,7 +19,6 @@ # Bug still in: 4.8-26-gf96ec19 use i3test; use File::Temp qw(tempfile); -use List::MoreUtils qw(uniq); use IO::Handle; my $ws = fresh_workspace; diff --git a/testcases/t/234-regress-default-floating-border.t b/testcases/t/234-regress-default-floating-border.t new file mode 100644 index 00000000..d5994f58 --- /dev/null +++ b/testcases/t/234-regress-default-floating-border.t @@ -0,0 +1,43 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# This is a regression test for a bug where a normal floating default border is +# not applied when the default tiling border is set to a pixel value. +# Ticket: #1305 +# Bug still in: 4.8-62-g7381b50 +use i3test i3_autostart => 0; + +my $config = <{floating_nodes}}; + +is($floating[0]->{nodes}[0]->{border}, 'normal', 'default floating border is `normal`'); + +exit_gracefully($pid); + +done_testing; diff --git a/testcases/t/235-check-config-no-x.t b/testcases/t/235-check-config-no-x.t new file mode 100644 index 00000000..614d6b3a --- /dev/null +++ b/testcases/t/235-check-config-no-x.t @@ -0,0 +1,60 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Check whether the -C option works without a display and doesn't +# accidentally start the nagbar. +# +use i3test i3_autostart => 0; +use File::Temp qw(tempfile); + +sub check_config { + my ($config) = @_; + my ($fh, $tmpfile) = tempfile(UNLINK => 1); + print $fh $config; + my $output = qx(DISPLAY= ../i3 -C -c $tmpfile 2>&1); + my $retval = $?; + $fh->flush; + close($fh); + return ($retval >> 8, $output); +} + +################################################################################ +# 1: test with a bogus configuration file +################################################################################ + +my $cfg = < 0; +use X11::XCB qw(PROP_MODE_REPLACE); + +my $config = <atom(name => 'WM_CLASS'); + my $atomtype = $x->atom(name => 'STRING'); + $x->change_property( + PROP_MODE_REPLACE, + $window->id, + $atomname->id, + $atomtype->id, + 8, + length($class) + 1, + $class + ); + sync_with_i3; +} + +my $ws = fresh_workspace; + +my $win = open_window; + +change_window_class($win, "special\0Special"); + +my $con = @{get_ws_content($ws)}[0]; + +is($con->{window_properties}->{class}, 'Special', + 'The container class should be updated when a window changes class'); + +is($con->{window_properties}->{instance}, 'special', + 'The container instance should be updated when a window changes instance'); + +# The mark `special_class_mark` is added in a `for_window` assignment in the +# config for testing purposes +is($con->{mark}, 'special_class_mark', + 'A `for_window` assignment should run for a match when the window changes class'); + +exit_gracefully($pid); + +done_testing; diff --git a/testcases/t/236-floating-focus-raise.t b/testcases/t/236-floating-focus-raise.t new file mode 100644 index 00000000..4be87137 --- /dev/null +++ b/testcases/t/236-floating-focus-raise.t @@ -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) +# +# Test that focusing floating windows with the command `focus [direction]` +# promotes the focused window to the top of the rendering stack. +# Ticket: #1322 +# Bug still in: 4.8-88-gcc09348 +use i3test; + +my $ws = fresh_workspace; + +my $win1 = open_floating_window; +my $win2 = open_floating_window; +my $win3 = open_floating_window; + +# it's a good idea to do this a few times because of the implementation +for my $i (1 .. 3) { + cmd 'focus left'; + my $ws_con = get_ws($ws); + is($ws_con->{floating_nodes}[-1]->{nodes}[0]->{id}, get_focused($ws), + "focus left put the focused window on top of the floating windows (try $i)"); +} + +for my $i (1 .. 3) { + cmd 'focus right'; + my $ws_con = get_ws($ws); + is($ws_con->{floating_nodes}[-1]->{nodes}[0]->{id}, get_focused($ws), + "focus right put the focused window on top of the floating windows (try $i)"); +} + +done_testing; diff --git a/testcases/t/238-ipc-binding-event.t b/testcases/t/238-ipc-binding-event.t new file mode 100644 index 00000000..6931fe20 --- /dev/null +++ b/testcases/t/238-ipc-binding-event.t @@ -0,0 +1,88 @@ +#!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 that the binding event works properly +# Ticket: #1210 +use i3test i3_autostart => 0; + +my $keysym = 't'; +my $command = 'nop'; +my @mods = ('Shift', 'Ctrl'); +my $binding_symbol = join("+", @mods) . "+$keysym"; + +my $config = < /dev/null); + + skip 'xdotool is required to test the binding event. `[apt-get install|pacman -S] xdotool`', 1 if $?; + + skip "AnyEvent::I3 too old (need >= 0.16)", 1 if $AnyEvent::I3::VERSION < 0.16; + + my $pid = launch_with_config($config); + + my $i3 = i3(get_socket_path()); + $i3->connect->recv; + + my $cv = AE::cv; + my $timer = AE::timer 0.5, 0, sub { $cv->send(0); }; + + $i3->subscribe({ + binding => sub { + $cv->send(shift); + } + })->recv; + + qx(xdotool key $binding_symbol); + + my $e = $cv->recv; + + does_i3_live; + + diag "Event:\n", Dumper($e); + + ok($e, + 'the binding event should emit when user input triggers an i3 binding event'); + + is($e->{change}, 'run', + 'the `change` field should indicate this binding has run'); + + ok($e->{binding}, + 'the `binding` field should be a hash that contains information about the binding'); + + is($e->{binding}->{input_type}, 'keyboard', + 'the input_type field should be the input type of the binding (keyboard or mouse)'); + + note 'the `mods` field should contain the symbols for the modifiers of the binding'; + foreach (@mods) { + ok(grep(/$_/i, @{$e->{binding}->{mods}}), "`mods` contains the modifier $_"); + } + + is($e->{binding}->{command}, $command, + 'the `command` field should contain the command the binding ran'); + + is($e->{binding}->{input_code}, 0, + 'the input_code should be the specified code if the key was bound with bindcode, and otherwise zero'); + + exit_gracefully($pid); + +} +done_testing; diff --git a/testcases/t/238-regress-reload-bindsym.t b/testcases/t/238-regress-reload-bindsym.t new file mode 100644 index 00000000..6d5d12c3 --- /dev/null +++ b/testcases/t/238-regress-reload-bindsym.t @@ -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 that the binding event works properly +# Ticket: #1210 +use i3test i3_autostart => 0; + +my $config = < /dev/null); + + skip 'xdotool is required to test the binding event. `[apt-get install|pacman -S] xdotool`', 1 if $?; + + my $pid = launch_with_config($config); + + my $i3 = i3(get_socket_path()); + $i3->connect->recv; + + qx(xdotool key r); + + does_i3_live; + + exit_gracefully($pid); + +} +done_testing; diff --git a/testcases/t/239-net-close-window-request.t b/testcases/t/239-net-close-window-request.t new file mode 100644 index 00000000..20c3f843 --- /dev/null +++ b/testcases/t/239-net-close-window-request.t @@ -0,0 +1,49 @@ +#!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 _NET_CLOSE_WINDOW requests to close a window. +# See http://standards.freedesktop.org/wm-spec/wm-spec-latest.html#idm140200472668896 +# Ticket: #1396 +# Bug still in: 4.8-116-gbb1f857 +use i3test; + +sub send_close_window_request { + my ($win) = @_; + + my $msg = pack "CCSLLLLLL", + X11::XCB::CLIENT_MESSAGE, # response_type + 32, # format + 0, # sequence + $win->id, # window + $x->atom(name => '_NET_CLOSE_WINDOW')->id, # message type + 0, # data32[0] + 0, # 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); +} + +my $ws = fresh_workspace; +my $win = open_window; + +send_close_window_request($win); +sync_with_i3; + +is(@{get_ws($ws)->{nodes}}, 0, 'When a pager sends a _NET_CLOSE_WINDOW request for a window, the container should be closed'); + +done_testing; diff --git a/testcases/t/520-regress-focus-direction-floating.t b/testcases/t/520-regress-focus-direction-floating.t new file mode 100644 index 00000000..ccef49e7 --- /dev/null +++ b/testcases/t/520-regress-focus-direction-floating.t @@ -0,0 +1,48 @@ +#!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 `focus [direction]` will focus an existing floating con when no +# tiling con exists on the output in [direction] when focusing across outputs +# Bug still in: 4.7.2-204-g893dbae +use i3test i3_autostart => 0; + +my $config = <input_focus, $win->id, + 'Focusing across outputs with `focus [direction]` should focus an existing floating con when no tiling con exists on the output in [direction].'); + +exit_gracefully($pid); + +done_testing; diff --git a/testcases/t/521-ewmh-desktop-viewport.t b/testcases/t/521-ewmh-desktop-viewport.t new file mode 100644 index 00000000..9e36090c --- /dev/null +++ b/testcases/t/521-ewmh-desktop-viewport.t @@ -0,0 +1,95 @@ +#!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 that the EWMH specified property _NET_DESKTOP_VIEWPORT is updated +# properly on the root window. We interpret this as a list of x/y coordinate +# pairs for the upper left corner of the respective outputs of the workspaces +# Ticket: #1241 +use i3test i3_autostart => 0; + +my $config = <get_property( + 0, + $x->get_root_window(), + $x->atom(name => '_NET_DESKTOP_VIEWPORT')->id, + $x->atom(name => 'CARDINAL')->id, + 0, + 4096 + ); + + my $reply = $x->get_property_reply($cookie->{sequence}); + + return 0 if $reply->{value_len} == 0; + + my $len = $reply->{length}; + + return unpack ("L$len", $reply->{value}); +} + +# initialize the workspaces +cmd 'workspace 1'; +cmd 'workspace 0'; + +my @expected_viewport = (0, 0, 1024, 0); +my @desktop_viewport = get_desktop_viewport; + +is_deeply(\@desktop_viewport, \@expected_viewport, + '_NET_DESKTOP_VIEWPORT should be an array of x/y coordinate pairs for the upper left corner of the respective outputs of the workspaces'); + +cmd 'workspace 0'; +open_window; +cmd 'workspace 3'; + +@expected_viewport = (0, 0, 0, 0, 1024, 0); +@desktop_viewport = get_desktop_viewport; + +is_deeply(\@desktop_viewport, \@expected_viewport, + 'it should be updated when a new workspace appears'); + +cmd 'rename workspace 3 to 2'; + +@expected_viewport = (0, 0, 0, 0, 1024, 0); +@desktop_viewport = get_desktop_viewport; + +is_deeply(\@desktop_viewport, \@expected_viewport, + 'it should stay up to date when a workspace is renamed'); + +cmd 'workspace 0'; + +@expected_viewport = (0, 0, 1024, 0); +@desktop_viewport = get_desktop_viewport; + +is_deeply(\@desktop_viewport, \@expected_viewport, + 'it should be updated when a workspace is emptied'); + +exit_gracefully($pid); + +done_testing; diff --git a/tests/queue.h b/tests/queue.h index cc129da7..0b3a9c0b 100644 --- a/tests/queue.h +++ b/tests/queue.h @@ -90,435 +90,454 @@ /* * Singly-linked List definitions. */ -#define SLIST_HEAD(name, type) \ -struct name { \ - struct type *slh_first; /* first element */ \ -} +#define SLIST_HEAD(name, type) \ + struct name { \ + struct type *slh_first; /* first element */ \ + } -#define SLIST_HEAD_INITIALIZER(head) \ - { NULL } +#define SLIST_HEAD_INITIALIZER(head) \ + { NULL } -#define SLIST_ENTRY(type) \ -struct { \ - struct type *sle_next; /* next element */ \ -} +#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_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(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)) +#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_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_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_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_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(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) +#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(name, type) \ + struct name { \ + struct type *lh_first; /* first element */ \ + } -#define LIST_HEAD_INITIALIZER(head) \ - { NULL } +#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 */ \ -} +#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_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)) +#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_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_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_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_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_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) +#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(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_HEAD_INITIALIZER(head) \ + { NULL, &(head).sqh_first } -#define SIMPLEQ_ENTRY(type) \ -struct { \ - struct type *sqe_next; /* next element */ \ -} +#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_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)) +#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_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_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_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_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) +#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(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_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 */ \ -} +#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)) +#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_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(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)) +#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_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_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_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_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_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_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) +#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(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_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 */ \ -} +#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_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(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)) +#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_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_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_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_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_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_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) +#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 index d6bf9a24..abc3b3b5 100644 --- a/tests/swap.c +++ b/tests/swap.c @@ -24,11 +24,11 @@ void dump() { printf("first: %d\n", e->abc); e = TAILQ_LAST(&head, objhead); printf("last: %d\n", e->abc); - TAILQ_FOREACH (e, &head, entry) { + TAILQ_FOREACH(e, &head, entry) { printf(" %d\n", e->abc); } printf("again, but reverse:\n"); - TAILQ_FOREACH_REVERSE (e, &head, objhead, entry) { + TAILQ_FOREACH_REVERSE(e, &head, objhead, entry) { printf(" %d\n", e->abc); } printf("done\n\n");