pax_global_header00006660000000000000000000000064145303427110014512gustar00rootroot0000000000000052 comment=df5f576c4897578564b91452c07ba324524d5f25 spectrwm-SPECTRWM_3_5_1/000077500000000000000000000000001453034271100150525ustar00rootroot00000000000000spectrwm-SPECTRWM_3_5_1/CHANGELOG.md000066400000000000000000000623461453034271100166760ustar00rootroot00000000000000spectrwm 3.5.1 ============== Released on Nov 25, 2023 Fixes NetBSD support and a few minor issues. * Fix `bar_at_bottom` bottom gap when `bar_border_width` > 0. * Fix maximize new windows in max layout when `max_layout_maximize = 1`. * Fix `autorun` option and `WS` quirk should accept a value of `-1` as stated in the manual. * Fix `bar_color_free` and `bar_font_color_free` options. * Fix bar urgency hint (+U) workspaces should begin at 1. * Fix iconified windows should uniconify on MapRequest. * Fix focus fallback issue when iconifying windows. * Improve handling when programs try to position their own windows. * Fix NetBSD build issues. * Add NetBSD to list of OSes that have the XCB XInput Extension. * Fix build failure when building without the XCB XInput Extension. * Fix possible segfault at startup. * Add SWMHACK section to manual. * Fix typos in manual. spectrwm 3.5.0 ============== Released on Oct 22, 2023 Includes a bunch of major new features and improvements, such as dock/panel support, an always mapped window mode, floating workspace layout, transparent color support, tons of fixes, and more! * Add *free* window mode. - *free* windows are floating windows that are not in a workspace. They remain mapped and may be resized, positioned and stacked anywhere. When iconified, they appear at the end of the uniconify menu. Note that free windows can be stacked above/below workspace windows but must be put into a workspace and unfloated to be part of its tiling layout. `float_toggle` is convenient for this purpose. - Add `free_toggle` action (default: `M-S-grave`). Toggle focused window between workspace mode and free mode. - Add `focus_free` action (default: `M-grave`). Switch focus to/from windows in free mode, if any. - Add related color and focus mark options. * Improve EWMH (Extended Window Manager Hints) support. - Add support for docks/panels and desktop managers. - Add strut support for windows (e.g. panels) to automatically reserve screen real estate. - Add support for applications to initiate move/resize operations. - Add *demands attention* support to urgency features to include windows that request focus but are denied. - Add support for *below* state to keep windows stacked below others. - Improve _NET_ACTIVE_WINDOW handling. - Fix _NET_DESKTOP_VIEWPORT should update on workspace and region changes. * Improve window stacking. - Overhaul window stacking for improved reliability and flexibility required for new features/fixes. Windows are now stacked as a whole instead of per region/workspace. - Add `click_to_raise` option (default: `1` (enabled)). Raises stacking priority when clicking on a window. - Add `below_toggle` action (default: `M-S-t`). Toggles *below* state on a focused window to keep it below other windows. `raise` can be used to temporarily bring a window above all others. - Fix `raise` and `always_raise` stacking issues. - Fix follow mode stacking issues. - Fix stacking order issues. - Restore stacking order after leaving fullscreen/maximized state. * Workaround application issues related to ICCCM 6.3 button grabs. - If X Input Extension >= 2.1 is available, handle button bindings with the `REPLAY` flag passively, without grabs. For other button bindings, establish grabs on root. - Otherwise, for compatibility, establish all button binding grabs directly on client windows. * Add alpha transparent color support for use with compositing managers. Colors can now be specified with an alpha component via the format `rbga:rr/gg/bb/aa` (values in hex.) * Improve bar fonts. - Fallback to a "fail-safe" font if the default/user `bar_font` fails to load. - Add fallback handling for missing glyphs when using multiple fonts with Xft. - Add supplementary private-use code points to `bar_font_pua`. - Fix `$bar_font` program variable substitution should not include fallbacks. * Improve window mapping. - Add `maximize_hide_other` and `fullscreen_hide_other` options. When a maximized/fullscreen window is focused, hide unrelated windows on the same workspace. Useful for transparent windows. - Fix window mapping issue when handling simultaneous screen changes. - Improve reliability. * Improve (re)start handling. - Set intial focus more reliably. - Focus on fullscreen/maximized windows before main. - Restore window floating geometry on shutdown. * Improve focus handling. - Add `prior` setting to `focus_close`. When the focused window is closed, fallback to the last focused window in the workspace. - Add `focus_prior` action. Focus last focused window on workspace. (Default binding: `M-S-a`.) - Improve previous focus fallback. - Fix iconified window focus issue. - Fix input focus fallback. - Fix setting focus with EWMH should unmaximize other windows. - Fix move/resize operation should abort on focus loss. - Fix `focus_main` issue with iconified/floating windows. - Fix max layout focus issue when closing transients. - Fix `warp_pointer` issues. * Improve focus follow mode. - Fix handling of ConfigureWindow and EWMH requests. - Fix workspace switching issues. * Improve status bar. - Add character sequence for workspace list indicator (+L). - Add workspace mark options for the workspace indicator (+L). - Add stack mark options for the stacking indicator (+S). - Add focus mark options for the focus status indicator (+F). - Add character sequence for number of windows in workspace (+w) (lowercase). - Add unfocused options to color bar text and background. - Add color options for when a window in free mode is focused. - Fix `bar_action` piping deadlock issue. - Fix `name_workspace` should clear on empty string. - Fix refresh bar on `name_workspace`. - Set WM_CLASS, WM_NAME and _NET_WM_NAME on the bar window. * Add `floating` workspace layout stacking mode. - In floating layout, windows are not tiled and may be freely moved around and resized. - Add `stack_mark_floating` option for the stacking indicator (default:` '[~]'`). - Add `layout_floating` action (default: unbound). Directly switch to floating layout. - Add `floating` `stack_mode` to the `layout` option. * Improve max layout. - Allow windows to be unmaximized/floated in max layout. - Add `max_layout_maximize` option to configure default maximized state. - Allow floating windows to remain floating when dragged between regions into a max layout workspace. * Improve window handling. - Add *snap* behavior when dragging tiled/maximized windows. Prevents accidentally floating tiled windows. - Add `snap_range` option (default 25). Sets the pixel distance a tiled/maximized window must be dragged (with the pointer) to make it float and move freely. Set to 0 to unsnap/float immediately. - Add `maximized_unfocus` and `fullscreen_unfocus` options. Configures handling of maximized/fullscreen windows that lose focus. - Add support for ICCCM `WM_CHANGE_STATE` ClientMessage. Enables applications to iconify their own windows. - Add support for window gravity. Improves floating window positioning by applications. - Disable border on maximized windows when `disable_border = always`. - Add window titles to `search_win`. - Fix maximize handling. - Fix handling when a window is lost immediately after ReparentWindow. - Fix Java workaround. * Improve workspace handling. - Add `workspace_autorotate` option. When switching workspaces between regions, automatically "rotate" vertical/horizontal layouts based on RandR rotation data. - Add `prior_layout` action. Switch to the last used layout. (Unbound by default.) - Add optional rotation argument to `region` option. - Fix ws cycle actions should skip visible workspaces. - Add `cycle_visible` option to the man page and example conf. * Improve debugging. - Add `-d` command-line option to enable debug mode. Enables debug mode actions and logging to *stderr* without the need to rebuild with `-DSWM_DEBUG`. - Add multi-line support to `debug_toggle` overlay (default: M-d). - Add atom name cache to avoid redundant requests/syncs when printing output. * Fix X connection error handling to exit on a failed connection. * Fix build issues. - Fix compile error when building against musl. - Fix build with clang 16 on Linux. * Improve OpenBSD `pledge(2)` support. - Add "wpath" pledge for sparc64 support - Simplify usage. * Improve Linux Makefile. * Improve manual and examples. - Add details to `modkey` option in man page. - Add stack modes and window states to man page. - Fix incorrect key binding for `ws_6` in spectrwm_fr.conf. - Fix man page `wmctrl(1)` examples. - Fix `iostat(8)` issue in example baraction.sh script for OpenBSD. - Update man page note regarding `dmenu(1)` Xft support. - Update example spectrwm.conf. - Update `keyboard_mapping` example configuration files. - Update html manual. spectrwm 3.4.1 ============== Released on Jun 25, 2020 * Fix always_raise mapping issue. * Fix _NET_CURRENT_DESKTOP should be updated on ws_next_move/ws_prev_move. * Fix focus redirect for transient windows that are about to map. * Fix manual focus should not be affected by pointer on (un)grab. * Add java detection for JetBrains windows. * Remove _NET_WM_STATE on withdrawn windows as advised by EWMH spec. * Add information to man page about program call execution. spectrwm 3.4.0 ============== Released on Jun 17, 2020 * Add optional startup parameters: - -c file - Specify a configuration file to load instead of scanning for one. - -v - Print version and exit. * Add new `restart_of_day` action. (Unbound by default.) (Same as restart but configuration file is loaded in full.) * Improve startup error handling. * Fix input focus issues. * Fix max layout 'flickering' issue when `focus_mode = follow`. * Fix `ws_next_move` and `ws_prev_move`. * Fix withdrawn window handling. * Fix focus issue when moving transient (and related) windows between workspaces. * Fix maximized windows sometimes unmaximize on workspace switch. * Fix `SIGHUP` restart. * Fix transient window crossing issue on focus/swap next/prev actions. * Fix border color issue when clicking to focus a window on an unfocused region. * Fix `keyboard_mapping` fallback issue. * Fix width calculation of Xft glyphs. (Fixes the (dis)appearing space when switching workspaces.) * Increase bar hard limits to better accomodate complex markup sequences. * Add workaround to man page for OSs ignoring `LD_PRELOAD`. * Add some notes to man page and fix a warning. * Add missing options to example spectrwm.conf. * Update spectrwm_fr.conf * linux: Add example baraction.sh script. * linux: Accept user-provided pkg-config command. * linux: Install examples. spectrwm 3.3.0 ============== Released on Dec 19, 2019 * Add new bar text markup sequences for multiple colors/fonts/sections. * Add new `bar_font_pua` option to assign a font (such as an icon font) to the Unicode Private Use Area (U+E000 -> U+F8FF). * Extend `disable_border` option with `always`. * Add support for XDG Base Directory Specification. * Add OpenBSD pledge(2) support. * Enable xinput2 on OpenBSD. * Enable travis. * Fix keysym binding issue with multiple keyboard layouts. * Fix buffer overflow in `bar_strlcat_esc`. * Fix infinite loop due to unsigned integer overflow. * Fix cygwin compile issues. * Fix NetBSD Makefile. * Bunch of statical analyzer fixes. * Bunch of minor fixes. spectrwm 3.2.0 ============== Released on Sep 7, 2018 * Add new '+L' bar_format sequence to add a workspace list indicator to the bar. * Add new 'workspace_indicator' option to configure the workspace indicator. * Add new 'layout_vertical','layout_horizontal' and 'layout_max' actions. (Unbound by default.) * Add new 'ws_empty_move' action. (Unbound by default.) * Add support for high screen depth when creating frame/bar windows. (Compositing manager alpha transparency now works!) * Add check to adapt move/resize update rate to the refresh rate of the active display(s). * Add 'max' alias for the layout option value 'fullscreen' for consistency. * Add CHANGELOG.md * Fix warp_pointer issue when Coordinate Transformation Matrix is used. (Currently available on Linux and FreeBSD only.) * Fix focus bar color on (re)start/screenchange. * Fix libswmhack causing issues such as deadlocks with some programs. * Fix config file parsing on musl libc. * Fix slight pointer jump on move action. * Fix segfault with missing FontSet charsets. * Fix mdoc compliance. spectrwm 3.1.0 ============== Released on Oct 3, 2017 Major changes: * Add +R for region index to bar formatting. * Add new bar_color_selected and bar_font_color_selected options. * Add new 'ws_empty' action. * Enable padding in the bar_format using '_' character * Handle MappingNotify during startup. * Reset SIGPIPE before execvp(). * Correct size for WM_STATE This release also fixes a bunch of bugs, linux build and man page nits. spectrwm 3.0.2 ============== Released on May 23, 2016 Quick patch release that addresses some fallout from going full reparenting. * Ensure iconic windows stay iconic when reparenting. * Fix workspace cleanup on RandR screenchange. Fixes [#127](https://github.com/conformal/spectrwm/issues/127) and [#120](https://github.com/conformal/spectrwm/issues/120). spectrwm 3.0.1 ============== Released on May 5, 2016 * Fix makefile for non-Bitrig OS' * Redraw the focus window frame when changing regions with the pointer [#126](https://github.com/conformal/spectrwm/issues/126) * Prepend SWM_LIB to LD_PRELOAD instead of clobbering [#124](https://github.com/conformal/spectrwm/issues/124) spectrwm 3.0.0 ============== Released on May 2, 2016 We are proud to release spectrwm 3.0.0. Only one major new feature was added this release that should make spectrwm less quirky when using poorly written, old X11 and java applications. With the addition of reparenting spectrwm is now all grown up! In addition, spectrwm is now nearly ICCCM and EWMH compliant. Minor features/enhancements: * Add [Online manual](https://htmlpreview.github.io/?https://github.com/conformal/spectrwm/blob/master/spectrwm.html) * Add fullscreen_toggle action (_NET_WM_STATE_FULLSCREEN) * Send window to next/previous regions workspace * Add support for _NET_REQUEST_FRAME_EXTENTS As usual, a bunch of little, and not always obvious, fixes went in as well. See commit logs for details. Enjoy! Team spectrwm spectrwm 2.7.2 ============== Released on May 26, 2015 spectrwm 2.7.1 ============== Released on May 24, 2015 spectrwm 2.7.0 ============== Released on May 22, 2015 spectrwm 2.6.2 ============== Released on Jan 27, 2015 spectrwm 2.6.1 ============== Released on Oct 26, 2014 * Fix urgency indicator issue * Fix stacking issue on (re)start when managing unma * Fix xscreensaver-command example * Update Italian man page * Man page fixes, mostly spacing related * Reorder LDFLAGS in Linux Makefile to work with --a * Fix warp_pointer centering * Add note to man page regarding autorun and LD_PRELOAD * Honour correctly "disable_border" in max_stack * Fix focus_urgent spectrwm 2.6.0 ============== Released on Aug 22, 2014 * Improve English man page. * Improve Linux Makefile. * Fix typo s/fallowing/following/ * Fix bug in baraction.sh that causes bar text to flicker every 20s. * Fix man page to use escape codes for aring å and pi p. * Fix stacking issue at (re)start when spawn_position = first or prev. * Convert all booleans to stdbool.h bool. * Add new quirk IGNOREPID. * Add new quirk IGNORESPAWNWS. * Add new option: warp_pointer. * Add new quirk: WS[n] * Add new option: urgent_collapse spectrwm 2.5.1 ============== Released on May 8, 2014 * Improve stacking for windows with multiple transients. * Add clarification for the 'name' option to man page. * Add default maximize_toggle binding to man page. * Set stacking order when setting up a new status bar. * Fix segfault in fullscreen layout when a window with transient(s) unmap. * Fix segfault when loading "layout" with non-zero parameters. spectrwm 2.5.0 ============== Released on Feb 26, 2014 * Add new maximize_toggle action (Default bind: M-e) Toggles maximization of the focused window. * Change floating indicator in bar to also show 'm' for maximized state. * Add color_focus_maximized and color_unfocus_maximized config options. Sets border colors on maximized windows. Defaults to the value of color_focus and color_unfocus, respectively. * Add 'name' configuration option. Set name of workspace at start-of-day. * Improve support for Extended Window Manager Hints (EWMH): - Add support for NETCURRENT DESKTOP. - Add support for NETDESKTOP_NAMES. - Add support for NETNUMBER_OF_DESKTOPS. - Add support for NETCLIENT_LIST. Windows are sorted according to NETCLIENT_LIST at start. - Add support for NETDESKTOP_GEOMETRY and NETDESKTOP_VIEWPORT. - Add support for NETRESTACK_WINDOW. - Add support for NETWM_DESKTOP client message. - Improve handling of NETWM_STATE_FULLSCREEN. - Fix support for NETWM_NAME. - Change iconify to use NETWM_STATE_HIDDEN instead of SWMICONIC. - Add NETWM_FULL_PLACEMENT to NETSUPPORTED. * Add new reorder stack action for floating windows. Reorder floating windows by using swap_next/prev without affecting tiling order. * Deny NETACTIVE_WINDOW ClientMessages with a source type of 'normal'. Focus change requests that are not a result of direct user action are ignored. Requests from applications that use the old EWMH specification such as wmctrl(1) are still accepted. * Add new OBEYAPPFOCUSREQ quirk. When an application requests focus on the window via a NETACTIVE_WINDOW client message (source indication of 1), comply with the request. * Fix text rendering issue in search_win. * Fix floating windows remaining borderless after being fullscreen. * Fix window border colors when moving windows to hidden workspaces. * Fix segfault when attempting to set a color on a non-existent screen. Show error instead of exiting when screen index is invalid. * Fix configurerequest resize on transients. * Fix move floater to max_stack. * Fix focus issues when a window maps/unmaps on an unfocused region. * Fix stacking issues. * Fix 'bind[] = ...' not unbinding as expected. * Fix quirk matching of windows missing WM_CLASS. * Ignore EnterNotify when entering from an inferior window. * Disable floating_toggle on fullscreen layout. * Disable swapwin on fullscreen layout. * Ignore key press events while moving/resizing. * Fix LD_PRELOAD error on Linux. Note: On 64-bit Linux systems, if LD_PRELOAD isn't a relative/absolute pathname to libswmhack.so, then ld.so attempts to load a 32-bit version for 32-bit programs. This produces an error message. The solution is to either build and install a 32-bit libswmhack.so.0.0 or use an absolute/relative path so that ld.so only loads libswmhack.so for 64-bit binaries. * Update OSX Makefile. spectrwm 2.4.0 ============== Released on Nov 15, 2013 spectrwm 2.3.0 ============== Released on Apr 29, 2013 * Added ability to move/resize floating windows beyond region boundaries. * Added 'soft boundary' behavior to region boundaries. When moving a window past the region boundary, the window will 'snap' to the region boundary if it is less than boundary_width distance beyond the edge. * Added new boundary_width configuration option. Disable the 'soft boundary' behavior by setting this option to 0. * Added ability to set tile_gap to negative values. This makes it possible for tiled windows to overlap. Set to the opposite of border_width to collapse borders. * Fixed floating window stacking order issue on multiple-region setups. * Fixed crash on maprequest when WM_CLASS name and/or instance isn't set. * Fixed positioning issue on flipped layouts with a multi-column/row stack. * Fixed focus when switching to an inactive workspace with a new window. * Fixed symlinks in Linux Makefile. spectrwm 2.2.0 ============== Released on Mar 23, 2013 * Change validation of default 'optional' programs to only occur when the respective config entry is overridden, not when the binding is overridden. * Added details to the man page and spectrwm.conf on how to disable/override the default programs. * Change key grabbing to only grab ws binds within workspace_limit. * New QUIRKS: - NOFOCUSONMAP: Don't change focus to the window when it gets mapped on the screen. - FOCUSONMAP_SINGLE: When the window is mapped, change focus if it is the only mapped window on the workspace using the quirk entry. * New ws_next_move and ws_prev_move bindings to send a window to the next/prev workspace and switch to the workspace. * Fix fullscreen layout stacking issue when running with multiple regions. * Fix input focus issue with multiple regions when changing region focus with the keyboard. * Fix manual focus mode; pointer motion over an empty region no longer changes region focus. * Fix building on OSX. * Fix expansion of ~ for the keyboard_mapping path. * Fix segfault that can occur when the XCB server connection is lost. * Remove path from Linux spectrwm.desktop. spectrwm 2.1.1 ============== Released on Nov 28, 2012 Quite a few little fixes but they add up. * avoid a free on an uninitialized variable by setting optval to NULL. * Fix fparseln flags to remove escape characters in the result. * Fix issue where rapid window crossing events might get ignored. * Validate bound spawn programs after conf is loaded. * Fix move/resize to bail if the window gets destroyed. * Fix bar clock not getting updated during periods of inactivity. spectrwm 2.1.0 ============== Released on Oct 30, 2012 * New configuration options: - tile_gap: adjust spacing between tiled windows. - region_padding: adjust spacing between tiled windows and the region border. - bar_border_unfocus[x]: border color of the bar on unfocused regions of screen x. - bar_enabled_ws[x]: set the default state for bar_toggle_ws on workspace x. * New bindings: - rg_ - focus on region n, default is MOD-Keypad_<1-9> - mvrg_ - move window to region n, default is MOD-Shift-Keypad_<1-9> - bar_toggle_ws - toggle the status bar on current workspace, default is MOD-Shift-b. * New argument variables for program spawn: - $region_index - $workspace_index * Improved bar_action handling to eliminate the need for bar_delay. * Renamed screen_* bindings to rg_*; config files using the old bindings are still supported. * Fixed handling of region focus with empty workspaces. * Fixed toggle_bar not working on empty workspaces. * Fixed issue where multiple key actions might be handled simultaneously. * Fixed focus behavior when iconified windows exist in the ws. * Fixed windows not being unmapped on single-monitor fullscreen layout. * Fixed mouse and keyboard binds to work regardless of caps/numlock state. * Fixed a couple segfaults. * Fixed a couple memleaks. * Kill bar_action script oAn an unclean exit. * Add startup exception mechanism that display error messages in the bar. * Add config file check that uses startup exceptions to tell user if the file has issues. * Add runtime dependency checker that uses startup exceptions to tell user if something is missing. spectrwm 2.0.2 ============== Released on Aug 27, 2012 This is an emergency patch release for folks that end up with a blank screen after starting spectrwm. No need to updated unless you see this issue. Release notes: * Fix scan_xrandr to fallback when a scan results in no new regions. * Add tilde ~ expansion to autorun command in the config. spectrwm 2.0.1 ============== Released on Aug 26, 2012 * Added support for Xcursor. * Fixed several fullscreen layout issues. * Improved focus handling so related windows are raised when appropriate. * Fixed several focus issues. * Fixed several issues that could cause segfaults. * Fixed startup issue where certain windows might not get managed. * Fixed delay when moving/resizing windows via keyboard. spectrwm 2.0.0 ============== Released on Aug 22, 2012 * complete rewrite using xcb * 100% backwards compatible * way more responsive and snappy * Tons of warts fixed * cygwin works again * xft fonts And many other things. spectrwm 1.2.0 ============== Released on Jul 31, 2012 spectrwm 1.1.2 ============== Released on Jul 17, 2012 * Fix issue where a window/icon could not be clicked or otherwise be manipulated (skype, thunderbird etc). * Fix an issue where on some Intel graphics cards when exiting the screen turned garbled and would blink really badly. * Bonus fix: spawn_position to actually do what it is supposed to do. spectrwm 1.1.1 ============== Released on Jul 3, 2012 * Add backwards compatibility for the spawn_term binding * Add clarification to man page that default workspace_limit is 10. spectrwm 1.1.0 ============== Released on Jul 2, 2012 * Fix status bar flicker by double-buffering the output. * Add horizontal_flip and vertical_flip layout options. * Kill references before focusing on a new window. * Add new options to change focus behavior on window open and close. * Increase workspace hard limit to 22. * Tons of wart removals spectrwm 1.0.0 ============== Released on Feb 17, 2012 * Fixed all clang static analyze bugs/issues * Remain name and config file compatible with scrotwm * Fix OSX version again * Print proper version with M-S-v on linux * Add flip_layout binding to all keyboard layout examples * Fix setting of window property strings * Clear status-bar when iconifying the last window * Use a red-black tree for key bindings spectrwm-SPECTRWM_3_5_1/LICENSE.md000066400000000000000000000024751453034271100164660ustar00rootroot00000000000000Copyright (c) 2009-2016 Marco Peereboom Copyright (c) 2009-2011 Ryan McBride Copyright (c) 2009 Darrin Chandler Copyright (c) 2009 Pierre-Yves Ritschard Copyright (c) 2010 Tuukka Kataja Copyright (c) 2011 Jason L. Wright Copyright (c) 2011-2016 Reginald Kennedy Copyright (c) 2011-2012 Lawrence Teo Copyright (c) 2011-2012 Tiago Cunha Copyright (c) 2012-2015 David Hill Copyright (c) 2014-2016 Yuri D'Elia Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. spectrwm-SPECTRWM_3_5_1/Makefile000066400000000000000000000015441453034271100165160ustar00rootroot00000000000000.include PREFIX?=/usr/local BINDIR=${PREFIX}/bin SUBDIR= lib PROG=spectrwm MAN=spectrwm.1 CFLAGS+=-std=c99 -Wmissing-prototypes -Wall -Wextra -Wshadow -Wno-uninitialized -g # Uncomment define below to disallow user settable clock format string #CFLAGS+=-DSWM_DENY_CLOCK_FORMAT CPPFLAGS+= -I${X11BASE}/include -I${X11BASE}/include/freetype2 LDADD+=-lutil -L${X11BASE}/lib -lX11 -lX11-xcb -lxcb -lxcb-util -lxcb-icccm -lxcb-keysyms -lxcb-randr -lxcb-xinput -lxcb-xtest -lfontconfig -lXft -lXcursor BUILDVERSION != sh "${.CURDIR}/buildver.sh" .if !${BUILDVERSION} == "" CPPFLAGS+= -DSPECTRWM_BUILDSTR=\"$(BUILDVERSION)\" .endif MANDIR= ${PREFIX}/man/man obj: _xenocara_obj beforeinstall: ln -sf ${PROG} ${BINDIR}/scrotwm spectrwm.html: spectrwm.1 mandoc -Thtml ${.CURDIR}/spectrwm.1 > spectrwm.html .include .include spectrwm-SPECTRWM_3_5_1/README.md000066400000000000000000000065271453034271100163430ustar00rootroot00000000000000spectrwm ======== spectrwm is a small, dynamic tiling and reparenting window manager for X11. It tries to stay out of the way so that valuable screen real estate can be used for much more important stuff. It has sane defaults, and it does not require one to learn a language to do any configuration. spectrwm is written by hackers for hackers, and it strives to be small, compact, and fast. spectrwm was largely inspired by [xmonad](http://xmonad.org) and [dwm](http://dwm.suckless.org). Both are fine products, but they suffer from things like: crazy-unportable-language syndrome, silly defaults, asymmetrical window layout, the 'how hard can it be?' (to code efficiently) problem, and good old NIH. Nevertheless, [dwm](http://dwm.suckless.org) was a phenomenal resource, and good ideas and code were borrowed from it. On the other hand, [xmonad](http://xmonad.org) has great defaults and key bindings, plus xinerama support, but it is crippled by not being written in C. spectrwm is a beautiful pearl! For it, too, was created by grinding irritation. Nothing is a bigger waste of time either than moving windows around until they are the right size-ish or having just about any relevant key combination be eaten by some task one never performs. The path of agony is too long to quote, and, in classic [OpenBSD](http://www.openbsd.org) fashion (put up, or hack up), a brand new window manager was whooped up to serve no other purpose than to obey its masters. spectrwm is released under the ISC license. Patches can be accepted, provided that they are also licensed with ISC. ## Building and installation [Click here for current installation guide](https://github.com/conformal/spectrwm/wiki/Installation) ## Feedback and questions You can and come chat with us on IRC. We use the [OFTC](https://www.oftc.net) channel #spectrwm. ## Major features * Dynamic RandR support (multi-head) * Navigation anywhere on all screens with either the keyboard or mouse * Customizable status bar * Human readable configuration file * Restartable without losing state * Quick launch menu * Many screen layouts possible with a few simple key strokes * Windows can be added or removed from master area * Windows can be moved to any workspace or within a region * Resizable master area * Move/resize floating windows * Drag-to-float * Extended Window Manager Hints (EWMH) Support * Configureable tiling * Adjustable tile gap allows for a true one pixel border. * Customizable colors and border width. * User definable regions * User definable modkey & key bindings * User definable quirk bindings * User definable key bindings to launch applications * Multi OS support (*BSD, Linux, OSX, Windows/cygwin) * Reparenting window manager ## Documentation [Click here for current man page](https://htmlpreview.github.io/?https://github.com/conformal/spectrwm/blob/master/spectrwm.html) ## License spectrwm is ISC licensed unless otherwise specified in individual files. ## Screenshots ![Vertical stack](https://github.com/conformal/spectrwm/wiki/Scrotwm1.png) ![Horizontal stack](https://github.com/conformal/spectrwm/wiki/Scrotwm2.png) ![Horizontal stack](https://github.com/conformal/spectrwm/wiki/Scrotwm3.png) ![Vertical stack with floater and extra window in master area](https://github.com/conformal/spectrwm/wiki/Scrotwm4.png) ![mplayer, resized and moved](https://github.com/conformal/spectrwm/wiki/Scrotwm5.png) spectrwm-SPECTRWM_3_5_1/baraction.sh000066400000000000000000000036251453034271100173560ustar00rootroot00000000000000#!/bin/sh # Example Bar Action Script for OpenBSD-current. # print_date() { # The date is printed to the status bar by default. # To print the date through this script, set clock_enabled to 0 # in spectrwm.conf. Uncomment "print_date" below. FORMAT="%a %b %d %R %Z %Y" DATE=`date "+${FORMAT}"` echo -n "${DATE} " } print_mem() { MEM=`/usr/bin/top | grep Free: | cut -d " " -f6` echo -n "Free mem: $MEM " } _print_cpu() { printf "CPU: %3d%% User %3d%% Nice %3d%% Sys %3d%% Spin %3d%% Int %3d%% Idle " $1 $2 $3 $4 $5 $6 } print_cpu() { OUT="" # iostat prints each column justified to 3 chars, so if one counter # is 100, it jams up agains the preceeding one. sort this out. while [ "${1}x" != "x" ]; do if [ ${1} -gt 99 ]; then OUT="$OUT ${1%100} 100" else OUT="$OUT ${1}" fi shift; done _print_cpu $OUT } print_cpuspeed() { CPU_SPEED=`/sbin/sysctl hw.cpuspeed | cut -d "=" -f2` printf "CPU speed: %4d MHz " $CPU_SPEED } print_bat() { BAT_STATUS=$1 BAT_LEVEL=$2 AC_STATUS=$3 if [ $AC_STATUS -ne 255 -o $BAT_STATUS -lt 4 ]; then if [ $AC_STATUS -eq 0 ]; then echo -n "on battery (${BAT_LEVEL}%)" else case $AC_STATUS in 1) AC_STRING="on AC: " ;; 2) AC_STRING="on backup AC: " ;; *) AC_STRING="" ;; esac; case $BAT_STATUS in 4) BAT_STRING="(no battery)" ;; [0-3]) BAT_STRING="(battery ${BAT_LEVEL}%)" ;; *) BAT_STRING="(battery unknown)" ;; esac; FULL="${AC_STRING}${BAT_STRING}" if [ "$FULL" != "" ]; then echo -n "$FULL" fi fi fi } # cache the output of apm(8), no need to call that every second. APM_DATA="" I=0 while :; do IOSTAT_DATA=`/usr/sbin/iostat -C -c 2 | tail -n 1 | grep '[0-9]$'` if [ $I -eq 0 ]; then APM_DATA=`/usr/sbin/apm -alb` fi # print_date print_mem print_cpu $IOSTAT_DATA print_cpuspeed print_bat $APM_DATA echo "" I=$(( ( ${I} + 1 ) % 11 )) sleep 1 done spectrwm-SPECTRWM_3_5_1/buildver.sh000066400000000000000000000001541453034271100172220ustar00rootroot00000000000000#!/bin/sh CURDIR=$(dirname $0) if [ -d "$CURDIR/.git" ]; then cd "$CURDIR" echo $(git rev-parse HEAD) fi spectrwm-SPECTRWM_3_5_1/freebsd/000077500000000000000000000000001453034271100164645ustar00rootroot00000000000000spectrwm-SPECTRWM_3_5_1/freebsd/Makefile000066400000000000000000000032071453034271100201260ustar00rootroot00000000000000.sinclude PREFIX?= /usr/local LOCALBASE?= /usr/local SWM_BINDIR?= $(PREFIX)/bin SWM_LIBDIR?= $(PREFIX)/lib SWM_MANDIR?= $(PREFIX)/man CC?= cc LVERS!= awk -F = '/major/ { printf( "%s.", $$2 ) } /minor/ { printf( "%s", $$2 ) }' ${.CURDIR}/../lib/shlib_version BUILDVERSION!= sh "${.CURDIR}/../buildver.sh" .if !${BUILDVERSION} == "" CFLAGS+= -DSPECTRWM_BUILDSTR=\"$(BUILDVERSION)\" .endif CFLAGS+= -std=c99 -Wmissing-prototypes -Wall -Wextra -Wshadow -Wno-uninitialized -g CFLAGS+= -I. -I${LOCALBASE}/include -I${LOCALBASE}/include/freetype2 CFLAGS+= -DSWM_LIB=\"$(SWM_LIBDIR)/libswmhack.so.$(LVERS)\" LDADD+= -lutil -L${LOCALBASE}/lib -lX11 -lX11-xcb -lxcb \ -lxcb-icccm -lxcb-keysyms -lxcb-randr -lxcb-util -lxcb-xinput -lxcb-xtest -lfontconfig -lXft -lXcursor all: spectrwm libswmhack.so.$(LVERS) spectrwm.c: ln -sf ../spectrwm.c ln -sf ../version.h ln -sf ../linux/queue_compat.h swm_hack.c: ln -sf ../lib/swm_hack.c spectrwm: spectrwm.o $(CC) $(LDFLAGS) $(LDADD) -o ${.TARGET} ${.ALLSRC} swm_hack.so: swm_hack.c $(CC) $(CFLAGS) -c -shared -fpic -DPIC -o ${.TARGET} ${.ALLSRC} libswmhack.so.$(LVERS): swm_hack.so $(CC) $(LDFLAGS) -shared -fpic -o ${.TARGET} ${.ALLSRC} install: all install -m 755 -d $(SWM_BINDIR) install -m 755 -d $(SWM_LIBDIR) install -m 755 -d $(SWM_MANDIR)/man1 install -m 755 spectrwm $(SWM_BINDIR) install -m 755 libswmhack.so.$(LVERS) $(SWM_LIBDIR) install -m 644 ../spectrwm.1 $(SWM_MANDIR)/man1/spectrwm.1 ln -sf spectrwm $(SWM_BINDIR)/scrotwm clean: rm -f spectrwm *.o *.so libswmhack.so.* spectrwm.c swm_hack.c version.h queue_compat.h .PHONY: all install clean .sinclude spectrwm-SPECTRWM_3_5_1/initscreen.sh000066400000000000000000000001661453034271100175540ustar00rootroot00000000000000#!/bin/sh # # Example xrandr multiscreen init xrandr --output LVDS --auto xrandr --output VGA --auto --right-of LVDS spectrwm-SPECTRWM_3_5_1/lib/000077500000000000000000000000001453034271100156205ustar00rootroot00000000000000spectrwm-SPECTRWM_3_5_1/lib/Makefile000066400000000000000000000007661453034271100172710ustar00rootroot00000000000000.include PREFIX?=/usr/local LIB= swmhack NOMAN= yes SRCS= swm_hack.c LIBDIR= ${X11BASE}/lib CC?= cc DEBUGLIBS= no NOPROFILE= yes CFLAGS+= -std=c99 -Wmissing-prototypes -Wall -Wextra -Wshadow -Wno-uninitialized -g CFLAGS+= -fPIC CFLAGS+= -I${X11BASE}/include LDADD+= -L${X11BASE}/lib -lX11 install: ${INSTALL} ${INSTALL_COPY} -o ${LIBOWN} -g ${LIBGRP} -m ${LIBMODE} \ lib${LIB}.so.${SHLIB_MAJOR}.${SHLIB_MINOR} \ ${PREFIX}/lib/ .include .include spectrwm-SPECTRWM_3_5_1/lib/shlib_version000066400000000000000000000000201453034271100204010ustar00rootroot00000000000000major=0 minor=0 spectrwm-SPECTRWM_3_5_1/lib/swm_hack.c000066400000000000000000000205421453034271100175630ustar00rootroot00000000000000/* * Copyright (c) 2009 Marco Peereboom * Copyright (c) 2009 Ryan McBride * Copyright (c) 2011-2018 Reginald Kennedy * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Copyright (C) 2005-2007 Carsten Haitzler * Copyright (C) 2006-2007 Kim Woelders * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies of the Software, its documentation and marketing & publicity * materials, and acknowledgment shall be given in the documentation, materials * and software packages that this Software was used. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * Basic hack mechanism (dlopen etc.) taken from e_hack.c in e17. */ #include #include #include #include #include #include #include #include #include /* dlopened libs so we can find the symbols in the real one to call them */ static void *lib_xlib = NULL; static void *lib_xtlib = NULL; static bool xterm = false; static Display *display = NULL; static Atom swmws = None, swmpid = None; void set_property(Display *, Window, Atom, char *); Atom get_atom_from_string(Display *, char *); #if defined(_GNU_SOURCE) && !defined(__CYGWIN__) #define DLOPEN(s) RTLD_NEXT #else #define DLOPEN(s) dlopen((s), RTLD_GLOBAL | RTLD_LAZY) #endif typedef Atom (XIA)(Display *_display, char *atom_name, Bool only_if_exists); Atom get_atom_from_string(Display *dpy, char *name) { Atom atom = None; static XIA *xia = NULL; if (lib_xlib == NULL) lib_xlib = DLOPEN("libX11.so"); if (lib_xlib) { if (xia == NULL) xia = (XIA *) dlsym(lib_xlib, "XInternAtom"); } if (xia == NULL) { fprintf(stderr, "libswmhack.so: ERROR: %s\n", dlerror()); return (atom); } atom = (*xia)(dpy, name, False); return (atom); } typedef int (XCP)(Display *_display, Window w, Atom property, Atom type, int format, int mode, unsigned char *data, int nelements); #define SWM_PROPLEN (16) void set_property(Display *dpy, Window id, Atom atom, char *val) { char prop[SWM_PROPLEN]; static XCP *xcp = NULL; if (lib_xlib == NULL) lib_xlib = DLOPEN("libX11.so"); if (lib_xlib) { if (xcp == NULL) xcp = (XCP *) dlsym(lib_xlib, "XChangeProperty"); } if (xcp == NULL) { fprintf(stderr, "libswmhack.so: ERROR: %s\n", dlerror()); return; } /* Try to update the window's workspace property */ if (atom) if (snprintf(prop, SWM_PROPLEN, "%s", val) < SWM_PROPLEN) (*xcp)(dpy, id, atom, XA_STRING, 8, PropModeReplace, (unsigned char *)prop, strlen((char *)prop)); } typedef Display *(ODF)(register _Xconst char *_display); /* XOpenDisplay intercept hack */ Display * XOpenDisplay(register _Xconst char *_display) { static ODF *func = NULL; if (lib_xlib == NULL) lib_xlib = DLOPEN("libX11.so"); if (lib_xlib && func == NULL) func = (ODF *) dlsym(lib_xlib, "XOpenDisplay"); if (func == NULL) { fprintf(stderr, "libswmhack.so: ERROR: %s\n", dlerror()); return (None); } display = (*func) (_display); if (display) { /* Preload atoms to prevent deadlock. */ if (swmws == None) swmws = get_atom_from_string(display, "_SWM_WS"); if (swmpid == None) swmpid = get_atom_from_string(display, "_SWM_PID"); } return (display); } typedef Window (CWF)(Display * _display, Window _parent, int _x, int _y, unsigned int _width, unsigned int _height, unsigned int _border_width, int _depth, unsigned int _class, Visual * _visual, unsigned long _valuemask, XSetWindowAttributes * _attributes); /* XCreateWindow intercept hack */ Window XCreateWindow(Display *dpy, Window parent, int x, int y, unsigned int width, unsigned int height, unsigned int border_width, int depth, unsigned int clss, Visual * visual, unsigned long valuemask, XSetWindowAttributes * attributes) { static CWF *func = NULL; char *env; Window id; if (lib_xlib == NULL) lib_xlib = DLOPEN("libX11.so"); if (lib_xlib && func == NULL) func = (CWF *) dlsym(lib_xlib, "XCreateWindow"); if (func == NULL) { fprintf(stderr, "libswmhack.so: ERROR: %s\n", dlerror()); return (None); } id = (*func) (dpy, parent, x, y, width, height, border_width, depth, clss, visual, valuemask, attributes); if (id) { if ((env = getenv("_SWM_WS")) != NULL) set_property(dpy, id, swmws, env); if ((env = getenv("_SWM_PID")) != NULL) set_property(dpy, id, swmpid, env); if (getenv("_SWM_XTERM_FONTADJ") != NULL) { unsetenv("_SWM_XTERM_FONTADJ"); xterm = true; } } return (id); } typedef Window (CSWF)(Display * _display, Window _parent, int _x, int _y, unsigned int _width, unsigned int _height, unsigned int _border_width, unsigned long _border, unsigned long _background); /* XCreateSimpleWindow intercept hack */ Window XCreateSimpleWindow(Display *dpy, Window parent, int x, int y, unsigned int width, unsigned int height, unsigned int border_width, unsigned long border, unsigned long background) { static CSWF *func = NULL; char *env; Window id; if (lib_xlib == NULL) lib_xlib = DLOPEN("libX11.so"); if (lib_xlib && func == NULL) func = (CSWF *) dlsym(lib_xlib, "XCreateSimpleWindow"); if (func == NULL) { fprintf(stderr, "libswmhack.so: ERROR: %s\n", dlerror()); return (None); } id = (*func) (dpy, parent, x, y, width, height, border_width, border, background); if (id) { if ((env = getenv("_SWM_WS")) != NULL) set_property(dpy, id, swmws, env); if ((env = getenv("_SWM_PID")) != NULL) set_property(dpy, id, swmpid, env); if (getenv("_SWM_XTERM_FONTADJ") != NULL) { unsetenv("_SWM_XTERM_FONTADJ"); xterm = true; } } return (id); } typedef void (ANEF)(XtAppContext app_context, XEvent *event_return); /* * XtAppNextEvent Intercept Hack * Normally xterm rejects "synthetic" (XSendEvent) events to prevent spoofing. * We don't want to disable this completely, it's insecure. But hook here * and allow these mostly harmless ones that we use to adjust fonts. */ void XtAppNextEvent(XtAppContext app_context, XEvent *event_return) { static ANEF *func = NULL; static KeyCode kp_add = 0, kp_subtract = 0; if (lib_xtlib == NULL) lib_xtlib = DLOPEN("libXt.so"); if (lib_xtlib && func == NULL) { func = (ANEF *) dlsym(lib_xtlib, "XtAppNextEvent"); if (display) { kp_add = XKeysymToKeycode(display, XK_KP_Add); kp_subtract = XKeysymToKeycode(display, XK_KP_Subtract); } } if (func == NULL) { fprintf(stderr, "libswmhack.so: ERROR: %s\n", dlerror()); return; } (*func) (app_context, event_return); /* Return here if it's not an Xterm. */ if (!xterm) return; /* Allow spoofing of font change keystrokes. */ if ((event_return->type == KeyPress || event_return->type == KeyRelease) && event_return->xkey.state == ShiftMask && (event_return->xkey.keycode == kp_add || event_return->xkey.keycode == kp_subtract)) event_return->xkey.send_event = 0; } spectrwm-SPECTRWM_3_5_1/linux/000077500000000000000000000000001453034271100162115ustar00rootroot00000000000000spectrwm-SPECTRWM_3_5_1/linux/Makefile000066400000000000000000000100211453034271100176430ustar00rootroot00000000000000PREFIX ?= /usr/local BINDIR ?= $(PREFIX)/bin LIBDIR ?= $(PREFIX)/lib SYSCONFDIR ?= $(PREFIX)/etc DATAROOTDIR ?= $(PREFIX)/share MANDIR ?= $(DATAROOTDIR)/man DOCDIR ?= $(DATAROOTDIR)/doc/spectrwm XSESSIONSDIR ?= $(DATAROOTDIR)/xsessions PKG_CONFIG ?= pkg-config BUILDVERSION := $(shell sh $(CURDIR)/../buildver.sh) LIBVERSION := $(shell . $(CURDIR)/../lib/shlib_version; echo $$major.$$minor) LIBMAJORVERSION := $(shell . $(CURDIR)/../lib/shlib_version; echo $$major) MAINT_CFLAGS := -std=c99 -Wmissing-prototypes -Wall -Wextra -Wshadow -g MAINT_LDFLAGS := -Wl,--as-needed MAINT_CPPFLAGS := -I. -D_GNU_SOURCE -DSWM_LIB=\"$(LIBDIR)/libswmhack.so.$(LIBVERSION)\" ifneq ("${BUILDVERSION}", "") MAINT_CPPFLAGS += -DSPECTRWM_BUILDSTR=\"$(BUILDVERSION)\" endif BIN_CFLAGS := -fPIE BIN_LDFLAGS := -fPIE -pie BIN_CPPFLAGS := $(shell $(PKG_CONFIG) --cflags x11 x11-xcb xcb-icccm xcb-keysyms xcb-randr xcb-util xcb-xinput xcb-xtest xcursor fontconfig xft) BIN_LDLIBS := $(shell $(PKG_CONFIG) --libs x11 x11-xcb xcb-icccm xcb-keysyms xcb-randr xcb-util xcb-xinput xcb-xtest xcursor fontconfig xft) LIB_CFLAGS := -fPIC LIB_LDFLAGS := -fPIC -shared LIB_CPPFLAGS := $(shell $(PKG_CONFIG) --cflags x11) LIB_LDLIBS := $(shell $(PKG_CONFIG) --libs x11) -ldl all: spectrwm libswmhack.so.$(LIBVERSION) spectrwm: spectrwm.o linux.o $(CC) $(MAINT_LDFLAGS) $(BIN_LDFLAGS) $(LDFLAGS) -o $@ $+ $(BIN_LDLIBS) $(LDLIBS) spectrwm.o: ../spectrwm.c ../version.h tree.h util.h queue_compat.h $(CC) $(MAINT_CFLAGS) $(BIN_CFLAGS) $(CFLAGS) $(MAINT_CPPFLAGS) $(BIN_CPPFLAGS) $(CPPFLAGS) -c -o $@ $< linux.o: linux.c util.h queue_compat.h $(CC) $(MAINT_CFLAGS) $(BIN_CFLAGS) $(CFLAGS) $(MAINT_CPPFLAGS) $(BIN_CPPFLAGS) $(CPPFLAGS) -c -o $@ $< libswmhack.so.$(LIBVERSION): swm_hack.so $(CC) $(MAINT_LDFLAGS) $(LIB_LDFLAGS) $(LDFLAGS) -Wl,-soname,$@ -o $@ $+ $(LIB_LDLIBS) $(LDLIBS) swm_hack.so: ../lib/swm_hack.c $(CC) $(MAINT_CFLAGS) $(LIB_CFLAGS) $(CFLAGS) $(MAINT_CPPFLAGS) $(LIB_CPPFLAGS) $(CPPFLAGS) -c -o $@ $< clean: rm -f spectrwm *.o libswmhack.so.* *.so install: all install -m 755 -d $(DESTDIR)$(BINDIR) install -m 755 -d $(DESTDIR)$(LIBDIR) install -m 755 -d $(DESTDIR)$(SYSCONFDIR) install -m 755 -d $(DESTDIR)$(MANDIR)/man1 install -m 755 -d $(DESTDIR)$(DOCDIR) install -m 755 -d $(DESTDIR)$(DOCDIR)/examples install -m 755 -d $(DESTDIR)$(XSESSIONSDIR) install -m 755 spectrwm $(DESTDIR)$(BINDIR) ln -sf spectrwm $(DESTDIR)$(BINDIR)/scrotwm install -m 755 libswmhack.so.$(LIBVERSION) $(DESTDIR)$(LIBDIR) ln -sf libswmhack.so.$(LIBVERSION) $(DESTDIR)$(LIBDIR)/libswmhack.so.$(LIBMAJORVERSION) ln -sf libswmhack.so.$(LIBVERSION) $(DESTDIR)$(LIBDIR)/libswmhack.so install -m 644 ../spectrwm.conf $(DESTDIR)$(SYSCONFDIR) install -m 644 ../spectrwm.1 $(DESTDIR)$(MANDIR)/man1 install -m 644 ../CHANGELOG.md $(DESTDIR)$(DOCDIR) install -m 644 ../LICENSE.md $(DESTDIR)$(DOCDIR) install -m 644 baraction.sh $(DESTDIR)$(DOCDIR)/examples install -m 644 ../initscreen.sh $(DESTDIR)$(DOCDIR)/examples install -m 644 ../screenshot.sh $(DESTDIR)$(DOCDIR)/examples install -m 644 ../spectrwm_*.conf $(DESTDIR)$(DOCDIR)/examples install -m 644 spectrwm.desktop $(DESTDIR)$(XSESSIONSDIR) uninstall: rm -f $(DESTDIR)$(BINDIR)/spectrwm rm -f $(DESTDIR)$(BINDIR)/scrotwm rm -f $(DESTDIR)$(LIBDIR)/libswmhack.so.$(LIBVERSION) rm -f $(DESTDIR)$(LIBDIR)/libswmhack.so.$(LIBMAJORVERSION) rm -f $(DESTDIR)$(LIBDIR)/libswmhack.so rm -f $(DESTDIR)$(SYSCONFDIR)/spectrwm.conf rm -f $(DESTDIR)$(MANDIR)/man1/spectrwm.1 rm -f $(DESTDIR)$(DOCDIR)/CHANGELOG.md rm -f $(DESTDIR)$(DOCDIR)/LICENSE.md rm -f $(DESTDIR)$(DOCDIR)/examples/baraction.sh rm -f $(DESTDIR)$(DOCDIR)/examples/initscreen.sh rm -f $(DESTDIR)$(DOCDIR)/examples/screenshot.sh rm -f $(DESTDIR)$(DOCDIR)/examples/spectrwm_*.conf rm -f $(DESTDIR)$(XSESSIONSDIR)/spectrwm.desktop .PHONY: all clean install uninstall spectrwm-SPECTRWM_3_5_1/linux/baraction.sh000066400000000000000000000041461453034271100205140ustar00rootroot00000000000000#!/bin/sh # Example Bar Action Script for Linux. # Requires: acpi, iostat. # Tested on: Debian 10, Fedora 31. # print_date() { # The date is printed to the status bar by default. # To print the date through this script, set clock_enabled to 0 # in spectrwm.conf. Uncomment "print_date" below. FORMAT="%a %b %d %R %Z %Y" DATE=`date "+${FORMAT}"` echo -n "${DATE} " } print_mem() { MEM=`/usr/bin/free -m | grep ^Mem: | sed -E 's/ +/ /g' | cut -d ' ' -f4` echo -n "Free mem: ${MEM}M " } _print_cpu() { printf "CPU: %3d%% User %3d%% Nice %3d%% Sys %3d%% Idle " $1 $2 $3 $6 } print_cpu() { OUT="" # Remove the decimal part from all the percentages. while [ "${1}x" != "x" ]; do OUT="$OUT `echo "${1}" | cut -d '.' -f1`" shift; done _print_cpu $OUT } print_cpuspeed() { CPU_SPEED=`/usr/bin/lscpu | grep '^CPU MHz:' | sed -E 's/ +/ /g' | cut -d ' ' -f3 | cut -d '.' -f1` printf "CPU speed: %4d MHz " $CPU_SPEED } print_bat() { AC_STATUS="$3" BAT_STATUS="$6" # Most battery statuses fit into a single word, except "Not charging" # for which we need to have special handling. if [ "$BAT_STATUS" = "Not" ]; then BAT_STATUS="$BAT_STATUS $7" shift fi BAT_LEVEL="`echo "$7" | tr -d ','`" if [ "$AC_STATUS" != "" -o "$BAT_STATUS" != "" ]; then if [ "$BAT_STATUS" = "Discharging," ]; then echo -n "on battery ($BAT_LEVEL)" else case "$AC_STATUS" in on-line) AC_STRING="on AC: " ;; *) AC_STRING="" ;; esac case "$BAT_STATUS" in "") BAT_STRING="(no battery)" ;; *harging,|Full,) BAT_STRING="(battery $BAT_LEVEL)" ;; *) BAT_STRING="(battery unknown)" ;; esac FULL="${AC_STRING}${BAT_STRING}" if [ "$FULL" != "" ]; then echo -n "$FULL" fi fi fi } # Cache the output of acpi(8), no need to call that every second. ACPI_DATA="" I=0 while :; do IOSTAT_DATA=`/usr/bin/iostat -c | grep '[0-9]$'` if [ $I -eq 0 ]; then ACPI_DATA=`/usr/bin/acpi -a 2>/dev/null; /usr/bin/acpi -b 2>/dev/null` fi # print_date print_mem print_cpu $IOSTAT_DATA print_cpuspeed print_bat $ACPI_DATA echo "" I=$(( ( ${I} + 1 ) % 11 )) sleep 1 done spectrwm-SPECTRWM_3_5_1/linux/linux.c000066400000000000000000000305631453034271100175230ustar00rootroot00000000000000#include #include #include #include #include #include #include "util.h" /* * All the workarounds for glibc stupidity are piled into this file... */ /* --------------------------------------------------------------------------- */ /* $OpenBSD: strlcpy.c,v 1.10 2005/08/08 08:05:37 espie Exp $ */ /* * Copyright (c) 1998 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Copy src to string dst of size siz. At most siz-1 characters * will be copied. Always NUL terminates (unless siz == 0). * Returns strlen(src); if retval >= siz, truncation occurred. */ size_t strlcpy(char *dst, const char *src, size_t siz) { char *d = dst; const char *s = src; size_t n = siz; /* Copy as many bytes as will fit */ if (n != 0 && --n != 0) { do { if ((*d++ = *s++) == 0) break; } while (--n != 0); } /* Not enough room in dst, add NUL and traverse rest of src */ if (n == 0) { if (siz != 0) *d = '\0'; /* NUL-terminate dst */ while (*s++) ; } return(s - src - 1); /* count does not include NUL */ } /* --------------------------------------------------------------------------- */ /* $OpenBSD: strlcat.c,v 1.13 2005/08/08 08:05:37 espie Exp $ */ /* * Copyright (c) 1998 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Appends src to string dst of size siz (unlike strncat, siz is the * full size of dst, not space left). At most siz-1 characters * will be copied. Always NUL terminates (unless siz <= strlen(dst)). * Returns strlen(src) + MIN(siz, strlen(initial dst)). * If retval >= siz, truncation occurred. */ size_t strlcat(char *dst, const char *src, size_t siz) { char *d = dst; const char *s = src; size_t n = siz; size_t dlen; /* Find the end of dst and adjust bytes left but don't go past end */ while (n-- != 0 && *d != '\0') d++; dlen = d - dst; n = siz - dlen; if (n == 0) return(dlen + strlen(s)); while (*s != '\0') { if (n != 1) { *d++ = *s; n--; } s++; } *d = '\0'; return(dlen + (s - src)); /* count does not include NUL */ } /* --------------------------------------------------------------------------- */ /* $NetBSD: fgetln.c,v 1.3 2007/08/07 02:06:58 lukem Exp $ */ /*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Christos Zoulas. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ char * fgetln(FILE *fp, size_t *len) { static char *buf = NULL; static size_t bufsiz = 0; char *ptr; if (buf == NULL) { bufsiz = BUFSIZ; if ((buf = malloc(bufsiz)) == NULL) return NULL; } if (fgets(buf, bufsiz, fp) == NULL) return NULL; *len = 0; while ((ptr = strchr(&buf[*len], '\n')) == NULL) { size_t nbufsiz = bufsiz + BUFSIZ; char *nbuf = realloc(buf, nbufsiz); if (nbuf == NULL) { int oerrno = errno; free(buf); errno = oerrno; buf = NULL; return NULL; } else buf = nbuf; *len = bufsiz; if (fgets(&buf[bufsiz], BUFSIZ, fp) == NULL) return buf; bufsiz = nbufsiz; } *len = (ptr - buf) + 1; return buf; } /* --------------------------------------------------------------------------- */ /* $OpenBSD: fparseln.c,v 1.6 2005/08/02 21:46:23 espie Exp $ */ /* $NetBSD: fparseln.c,v 1.7 1999/07/02 15:49:12 simonb Exp $ */ /* * Copyright (c) 1997 Christos Zoulas. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Christos Zoulas. * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define FPARSELN_UNESCESC 0x01 #define FPARSELN_UNESCCONT 0x02 #define FPARSELN_UNESCCOMM 0x04 #define FPARSELN_UNESCREST 0x08 #define FPARSELN_UNESCALL 0x0f static int isescaped(const char *, const char *, int); /* isescaped(): * Return true if the character in *p that belongs to a string * that starts in *sp, is escaped by the escape character esc. */ static int isescaped(const char *sp, const char *p, int esc) { const char *cp; size_t ne; /* No escape character */ if (esc == '\0') return 1; /* Count the number of escape characters that precede ours */ for (ne = 0, cp = p; --cp >= sp && *cp == esc; ne++) continue; /* Return true if odd number of escape characters */ return (ne & 1) != 0; } /* fparseln(): * Read a line from a file parsing continuations ending in \ * and eliminating trailing newlines, or comments starting with * the comment char. */ char * fparseln(FILE *fp, size_t *size, size_t *lineno, const char str[3], int flags) { static const char dstr[3] = { '\\', '\\', '#' }; char *buf = NULL, *ptr, *cp, esc, con, nl, com; size_t s, len = 0; int cnt = 1; if (str == NULL) str = dstr; esc = str[0]; con = str[1]; com = str[2]; /* * XXX: it would be cool to be able to specify the newline character, * but unfortunately, fgetln does not let us */ nl = '\n'; while (cnt) { cnt = 0; if (lineno) (*lineno)++; if ((ptr = fgetln(fp, &s)) == NULL) break; if (s && com) { /* Check and eliminate comments */ for (cp = ptr; cp < ptr + s; cp++) if (*cp == com && !isescaped(ptr, cp, esc)) { s = cp - ptr; cnt = s == 0 && buf == NULL; break; } } if (s && nl) { /* Check and eliminate newlines */ cp = &ptr[s - 1]; if (*cp == nl) s--; /* forget newline */ } if (s && con) { /* Check and eliminate continuations */ cp = &ptr[s - 1]; if (*cp == con && !isescaped(ptr, cp, esc)) { s--; /* forget escape */ cnt = 1; } } if (s == 0 && buf != NULL) continue; if ((cp = realloc(buf, len + s + 1)) == NULL) { free(buf); return NULL; } buf = cp; (void) memcpy(buf + len, ptr, s); len += s; buf[len] = '\0'; } if ((flags & FPARSELN_UNESCALL) != 0 && esc && buf != NULL && strchr(buf, esc) != NULL) { ptr = cp = buf; while (cp[0] != '\0') { int skipesc; while (cp[0] != '\0' && cp[0] != esc) *ptr++ = *cp++; if (cp[0] == '\0' || cp[1] == '\0') break; skipesc = 0; if (cp[1] == com) skipesc += (flags & FPARSELN_UNESCCOMM); if (cp[1] == con) skipesc += (flags & FPARSELN_UNESCCONT); if (cp[1] == esc) skipesc += (flags & FPARSELN_UNESCESC); if (cp[1] != com && cp[1] != con && cp[1] != esc) skipesc = (flags & FPARSELN_UNESCREST); if (skipesc) cp++; else *ptr++ = *cp++; *ptr++ = *cp++; } *ptr = '\0'; len = strlen(buf); } if (size) *size = len; return buf; } /* --------------------------------------------------------------------------- */ /* $OpenBSD: strtonum.c,v 1.6 2004/08/03 19:38:01 millert Exp $ */ /* * Copyright (c) 2004 Ted Unangst and Todd Miller * All rights reserved. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define INVALID 1 #define TOOSMALL 2 #define TOOLARGE 3 long long strtonum(const char *numstr, long long minval, long long maxval, const char **errstrp) { long long ll = 0; char *ep; int error = 0; struct errval { const char *errstr; int err; } ev[4] = { { NULL, 0 }, { "invalid", EINVAL }, { "too small", ERANGE }, { "too large", ERANGE }, }; ev[0].err = errno; errno = 0; if (minval > maxval) error = INVALID; else { ll = strtoll(numstr, &ep, 10); if (numstr == ep || *ep != '\0') error = INVALID; else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) error = TOOSMALL; else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) error = TOOLARGE; } if (errstrp != NULL) *errstrp = ev[error].errstr; errno = ev[error].err; if (error) ll = 0; return (ll); } spectrwm-SPECTRWM_3_5_1/linux/queue_compat.h000066400000000000000000000004441453034271100210530ustar00rootroot00000000000000#ifndef TAILQ_END #define TAILQ_END(head) NULL #endif #ifndef TAILQ_FOREACH_SAFE #define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = TAILQ_FIRST(head); \ (var) != TAILQ_END(head) && \ ((tvar) = TAILQ_NEXT(var, field), 1); \ (var) = (tvar)) #endif spectrwm-SPECTRWM_3_5_1/linux/spectrwm.desktop000066400000000000000000000002251453034271100214470ustar00rootroot00000000000000[Desktop Entry] Name=spectrwm Comment=The spectrwm window manager Type=Application Exec=spectrwm TryExec=spectrwm Keywords=tiling;window;manager;wm; spectrwm-SPECTRWM_3_5_1/linux/tree.h000066400000000000000000000606451453034271100173340ustar00rootroot00000000000000/* $xxxterm$ */ /* $OpenBSD: tree.h,v 1.12 2009/03/02 09:42:55 mikeb Exp $ */ /* * Copyright 2002 Niels Provos * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef _SYS_TREE_H_ #define _SYS_TREE_H_ /* * This file defines data structures for different types of trees: * splay trees and red-black trees. * * A splay tree is a self-organizing data structure. Every operation * on the tree causes a splay to happen. The splay moves the requested * node to the root of the tree and partly rebalances it. * * This has the benefit that request locality causes faster lookups as * the requested nodes move to the top of the tree. On the other hand, * every lookup causes memory writes. * * The Balance Theorem bounds the total access time for m operations * and n inserts on an initially empty tree as O((m + n)lg n). The * amortized cost for a sequence of m accesses to a splay tree is O(lg n); * * A red-black tree is a binary search tree with the node color as an * extra attribute. It fulfills a set of conditions: * - every search path from the root to a leaf consists of the * same number of black nodes, * - each red node (except for the root) has a black parent, * - each leaf node is black. * * Every operation on a red-black tree is bounded as O(lg n). * The maximum height of a red-black tree is 2lg (n+1). */ #define SPLAY_HEAD(name, type) \ struct name { \ struct type *sph_root; /* root of the tree */ \ } #define SPLAY_INITIALIZER(root) \ { NULL } #define SPLAY_INIT(root) do { \ (root)->sph_root = NULL; \ } while (0) #define SPLAY_ENTRY(type) \ struct { \ struct type *spe_left; /* left element */ \ struct type *spe_right; /* right element */ \ } #define SPLAY_LEFT(elm, field) (elm)->field.spe_left #define SPLAY_RIGHT(elm, field) (elm)->field.spe_right #define SPLAY_ROOT(head) (head)->sph_root #define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL) /* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */ #define SPLAY_ROTATE_RIGHT(head, tmp, field) do { \ SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \ SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ (head)->sph_root = tmp; \ } while (0) #define SPLAY_ROTATE_LEFT(head, tmp, field) do { \ SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \ SPLAY_LEFT(tmp, field) = (head)->sph_root; \ (head)->sph_root = tmp; \ } while (0) #define SPLAY_LINKLEFT(head, tmp, field) do { \ SPLAY_LEFT(tmp, field) = (head)->sph_root; \ tmp = (head)->sph_root; \ (head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \ } while (0) #define SPLAY_LINKRIGHT(head, tmp, field) do { \ SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ tmp = (head)->sph_root; \ (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \ } while (0) #define SPLAY_ASSEMBLE(head, node, left, right, field) do { \ SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \ SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\ SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \ SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \ } while (0) /* Generates prototypes and inline functions */ #define SPLAY_PROTOTYPE(name, type, field, cmp) \ void name##_SPLAY(struct name *, struct type *); \ void name##_SPLAY_MINMAX(struct name *, int); \ struct type *name##_SPLAY_INSERT(struct name *, struct type *); \ struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \ \ /* Finds the node with the same key as elm */ \ static __inline struct type * \ name##_SPLAY_FIND(struct name *head, struct type *elm) \ { \ if (SPLAY_EMPTY(head)) \ return(NULL); \ name##_SPLAY(head, elm); \ if ((cmp)(elm, (head)->sph_root) == 0) \ return (head->sph_root); \ return (NULL); \ } \ \ static __inline struct type * \ name##_SPLAY_NEXT(struct name *head, struct type *elm) \ { \ name##_SPLAY(head, elm); \ if (SPLAY_RIGHT(elm, field) != NULL) { \ elm = SPLAY_RIGHT(elm, field); \ while (SPLAY_LEFT(elm, field) != NULL) { \ elm = SPLAY_LEFT(elm, field); \ } \ } else \ elm = NULL; \ return (elm); \ } \ \ static __inline struct type * \ name##_SPLAY_MIN_MAX(struct name *head, int val) \ { \ name##_SPLAY_MINMAX(head, val); \ return (SPLAY_ROOT(head)); \ } /* Main splay operation. * Moves node close to the key of elm to top */ #define SPLAY_GENERATE(name, type, field, cmp) \ struct type * \ name##_SPLAY_INSERT(struct name *head, struct type *elm) \ { \ if (SPLAY_EMPTY(head)) { \ SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL; \ } else { \ int __comp; \ name##_SPLAY(head, elm); \ __comp = (cmp)(elm, (head)->sph_root); \ if(__comp < 0) { \ SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\ SPLAY_RIGHT(elm, field) = (head)->sph_root; \ SPLAY_LEFT((head)->sph_root, field) = NULL; \ } else if (__comp > 0) { \ SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\ SPLAY_LEFT(elm, field) = (head)->sph_root; \ SPLAY_RIGHT((head)->sph_root, field) = NULL; \ } else \ return ((head)->sph_root); \ } \ (head)->sph_root = (elm); \ return (NULL); \ } \ \ struct type * \ name##_SPLAY_REMOVE(struct name *head, struct type *elm) \ { \ struct type *__tmp; \ if (SPLAY_EMPTY(head)) \ return (NULL); \ name##_SPLAY(head, elm); \ if ((cmp)(elm, (head)->sph_root) == 0) { \ if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \ (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\ } else { \ __tmp = SPLAY_RIGHT((head)->sph_root, field); \ (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\ name##_SPLAY(head, elm); \ SPLAY_RIGHT((head)->sph_root, field) = __tmp; \ } \ return (elm); \ } \ return (NULL); \ } \ \ void \ name##_SPLAY(struct name *head, struct type *elm) \ { \ struct type __node, *__left, *__right, *__tmp; \ int __comp; \ \ SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ __left = __right = &__node; \ \ while ((__comp = (cmp)(elm, (head)->sph_root))) { \ if (__comp < 0) { \ __tmp = SPLAY_LEFT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if ((cmp)(elm, __tmp) < 0){ \ SPLAY_ROTATE_RIGHT(head, __tmp, field); \ if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ break; \ } \ SPLAY_LINKLEFT(head, __right, field); \ } else if (__comp > 0) { \ __tmp = SPLAY_RIGHT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if ((cmp)(elm, __tmp) > 0){ \ SPLAY_ROTATE_LEFT(head, __tmp, field); \ if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ break; \ } \ SPLAY_LINKRIGHT(head, __left, field); \ } \ } \ SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ } \ \ /* Splay with either the minimum or the maximum element \ * Used to find minimum or maximum element in tree. \ */ \ void name##_SPLAY_MINMAX(struct name *head, int __comp) \ { \ struct type __node, *__left, *__right, *__tmp; \ \ SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ __left = __right = &__node; \ \ while (1) { \ if (__comp < 0) { \ __tmp = SPLAY_LEFT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if (__comp < 0){ \ SPLAY_ROTATE_RIGHT(head, __tmp, field); \ if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ break; \ } \ SPLAY_LINKLEFT(head, __right, field); \ } else if (__comp > 0) { \ __tmp = SPLAY_RIGHT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if (__comp > 0) { \ SPLAY_ROTATE_LEFT(head, __tmp, field); \ if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ break; \ } \ SPLAY_LINKRIGHT(head, __left, field); \ } \ } \ SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ } #define SPLAY_NEGINF -1 #define SPLAY_INF 1 #define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y) #define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y) #define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y) #define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y) #define SPLAY_MIN(name, x) (SPLAY_EMPTY(x) ? NULL \ : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF)) #define SPLAY_MAX(name, x) (SPLAY_EMPTY(x) ? NULL \ : name##_SPLAY_MIN_MAX(x, SPLAY_INF)) #define SPLAY_FOREACH(x, name, head) \ for ((x) = SPLAY_MIN(name, head); \ (x) != NULL; \ (x) = SPLAY_NEXT(name, head, x)) /* Macros that define a red-black tree */ #define RB_HEAD(name, type) \ struct name { \ struct type *rbh_root; /* root of the tree */ \ } #define RB_INITIALIZER(root) \ { NULL } #define RB_INIT(root) do { \ (root)->rbh_root = NULL; \ } while (0) #define RB_BLACK 0 #define RB_RED 1 #define RB_ENTRY(type) \ struct { \ struct type *rbe_left; /* left element */ \ struct type *rbe_right; /* right element */ \ struct type *rbe_parent; /* parent element */ \ int rbe_color; /* node color */ \ } #define RB_LEFT(elm, field) (elm)->field.rbe_left #define RB_RIGHT(elm, field) (elm)->field.rbe_right #define RB_PARENT(elm, field) (elm)->field.rbe_parent #define RB_COLOR(elm, field) (elm)->field.rbe_color #define RB_ROOT(head) (head)->rbh_root #define RB_EMPTY(head) (RB_ROOT(head) == NULL) #define RB_SET(elm, parent, field) do { \ RB_PARENT(elm, field) = parent; \ RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \ RB_COLOR(elm, field) = RB_RED; \ } while (0) #define RB_SET_BLACKRED(black, red, field) do { \ RB_COLOR(black, field) = RB_BLACK; \ RB_COLOR(red, field) = RB_RED; \ } while (0) #ifndef RB_AUGMENT #define RB_AUGMENT(x) do {} while (0) #endif #define RB_ROTATE_LEFT(head, elm, tmp, field) do { \ (tmp) = RB_RIGHT(elm, field); \ if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field))) { \ RB_PARENT(RB_LEFT(tmp, field), field) = (elm); \ } \ RB_AUGMENT(elm); \ if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) { \ if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ else \ RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ } else \ (head)->rbh_root = (tmp); \ RB_LEFT(tmp, field) = (elm); \ RB_PARENT(elm, field) = (tmp); \ RB_AUGMENT(tmp); \ if ((RB_PARENT(tmp, field))) \ RB_AUGMENT(RB_PARENT(tmp, field)); \ } while (0) #define RB_ROTATE_RIGHT(head, elm, tmp, field) do { \ (tmp) = RB_LEFT(elm, field); \ if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field))) { \ RB_PARENT(RB_RIGHT(tmp, field), field) = (elm); \ } \ RB_AUGMENT(elm); \ if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) { \ if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ else \ RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ } else \ (head)->rbh_root = (tmp); \ RB_RIGHT(tmp, field) = (elm); \ RB_PARENT(elm, field) = (tmp); \ RB_AUGMENT(tmp); \ if ((RB_PARENT(tmp, field))) \ RB_AUGMENT(RB_PARENT(tmp, field)); \ } while (0) /* Generates prototypes and inline functions */ #define RB_PROTOTYPE(name, type, field, cmp) \ RB_PROTOTYPE_INTERNAL(name, type, field, cmp,) #define RB_PROTOTYPE_STATIC(name, type, field, cmp) \ RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static) #define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \ attr void name##_RB_INSERT_COLOR(struct name *, struct type *); \ attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\ attr struct type *name##_RB_REMOVE(struct name *, struct type *); \ attr struct type *name##_RB_INSERT(struct name *, struct type *); \ attr struct type *name##_RB_FIND(struct name *, struct type *); \ attr struct type *name##_RB_NFIND(struct name *, struct type *); \ attr struct type *name##_RB_NEXT(struct type *); \ attr struct type *name##_RB_PREV(struct type *); \ attr struct type *name##_RB_MINMAX(struct name *, int); \ \ /* Main rb operation. * Moves node close to the key of elm to top */ #define RB_GENERATE(name, type, field, cmp) \ RB_GENERATE_INTERNAL(name, type, field, cmp,) #define RB_GENERATE_STATIC(name, type, field, cmp) \ RB_GENERATE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static) #define RB_GENERATE_INTERNAL(name, type, field, cmp, attr) \ attr void \ name##_RB_INSERT_COLOR(struct name *head, struct type *elm) \ { \ struct type *parent, *gparent, *tmp; \ while ((parent = RB_PARENT(elm, field)) && \ RB_COLOR(parent, field) == RB_RED) { \ gparent = RB_PARENT(parent, field); \ if (parent == RB_LEFT(gparent, field)) { \ tmp = RB_RIGHT(gparent, field); \ if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ RB_COLOR(tmp, field) = RB_BLACK; \ RB_SET_BLACKRED(parent, gparent, field);\ elm = gparent; \ continue; \ } \ if (RB_RIGHT(parent, field) == elm) { \ RB_ROTATE_LEFT(head, parent, tmp, field);\ tmp = parent; \ parent = elm; \ elm = tmp; \ } \ RB_SET_BLACKRED(parent, gparent, field); \ RB_ROTATE_RIGHT(head, gparent, tmp, field); \ } else { \ tmp = RB_LEFT(gparent, field); \ if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ RB_COLOR(tmp, field) = RB_BLACK; \ RB_SET_BLACKRED(parent, gparent, field);\ elm = gparent; \ continue; \ } \ if (RB_LEFT(parent, field) == elm) { \ RB_ROTATE_RIGHT(head, parent, tmp, field);\ tmp = parent; \ parent = elm; \ elm = tmp; \ } \ RB_SET_BLACKRED(parent, gparent, field); \ RB_ROTATE_LEFT(head, gparent, tmp, field); \ } \ } \ RB_COLOR(head->rbh_root, field) = RB_BLACK; \ } \ \ attr void \ name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \ { \ struct type *tmp; \ while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) && \ elm != RB_ROOT(head)) { \ if (RB_LEFT(parent, field) == elm) { \ tmp = RB_RIGHT(parent, field); \ if (RB_COLOR(tmp, field) == RB_RED) { \ RB_SET_BLACKRED(tmp, parent, field); \ RB_ROTATE_LEFT(head, parent, tmp, field);\ tmp = RB_RIGHT(parent, field); \ } \ if ((RB_LEFT(tmp, field) == NULL || \ RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ (RB_RIGHT(tmp, field) == NULL || \ RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ RB_COLOR(tmp, field) = RB_RED; \ elm = parent; \ parent = RB_PARENT(elm, field); \ } else { \ if (RB_RIGHT(tmp, field) == NULL || \ RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\ struct type *oleft; \ if ((oleft = RB_LEFT(tmp, field)))\ RB_COLOR(oleft, field) = RB_BLACK;\ RB_COLOR(tmp, field) = RB_RED; \ RB_ROTATE_RIGHT(head, tmp, oleft, field);\ tmp = RB_RIGHT(parent, field); \ } \ RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ RB_COLOR(parent, field) = RB_BLACK; \ if (RB_RIGHT(tmp, field)) \ RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\ RB_ROTATE_LEFT(head, parent, tmp, field);\ elm = RB_ROOT(head); \ break; \ } \ } else { \ tmp = RB_LEFT(parent, field); \ if (RB_COLOR(tmp, field) == RB_RED) { \ RB_SET_BLACKRED(tmp, parent, field); \ RB_ROTATE_RIGHT(head, parent, tmp, field);\ tmp = RB_LEFT(parent, field); \ } \ if ((RB_LEFT(tmp, field) == NULL || \ RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ (RB_RIGHT(tmp, field) == NULL || \ RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ RB_COLOR(tmp, field) = RB_RED; \ elm = parent; \ parent = RB_PARENT(elm, field); \ } else { \ if (RB_LEFT(tmp, field) == NULL || \ RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\ struct type *oright; \ if ((oright = RB_RIGHT(tmp, field)))\ RB_COLOR(oright, field) = RB_BLACK;\ RB_COLOR(tmp, field) = RB_RED; \ RB_ROTATE_LEFT(head, tmp, oright, field);\ tmp = RB_LEFT(parent, field); \ } \ RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ RB_COLOR(parent, field) = RB_BLACK; \ if (RB_LEFT(tmp, field)) \ RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\ RB_ROTATE_RIGHT(head, parent, tmp, field);\ elm = RB_ROOT(head); \ break; \ } \ } \ } \ if (elm) \ RB_COLOR(elm, field) = RB_BLACK; \ } \ \ attr struct type * \ name##_RB_REMOVE(struct name *head, struct type *elm) \ { \ struct type *child, *parent, *old = elm; \ int color; \ if (RB_LEFT(elm, field) == NULL) \ child = RB_RIGHT(elm, field); \ else if (RB_RIGHT(elm, field) == NULL) \ child = RB_LEFT(elm, field); \ else { \ struct type *left; \ elm = RB_RIGHT(elm, field); \ while ((left = RB_LEFT(elm, field))) \ elm = left; \ child = RB_RIGHT(elm, field); \ parent = RB_PARENT(elm, field); \ color = RB_COLOR(elm, field); \ if (child) \ RB_PARENT(child, field) = parent; \ if (parent) { \ if (RB_LEFT(parent, field) == elm) \ RB_LEFT(parent, field) = child; \ else \ RB_RIGHT(parent, field) = child; \ RB_AUGMENT(parent); \ } else \ RB_ROOT(head) = child; \ if (RB_PARENT(elm, field) == old) \ parent = elm; \ (elm)->field = (old)->field; \ if (RB_PARENT(old, field)) { \ if (RB_LEFT(RB_PARENT(old, field), field) == old)\ RB_LEFT(RB_PARENT(old, field), field) = elm;\ else \ RB_RIGHT(RB_PARENT(old, field), field) = elm;\ RB_AUGMENT(RB_PARENT(old, field)); \ } else \ RB_ROOT(head) = elm; \ RB_PARENT(RB_LEFT(old, field), field) = elm; \ if (RB_RIGHT(old, field)) \ RB_PARENT(RB_RIGHT(old, field), field) = elm; \ if (parent) { \ left = parent; \ do { \ RB_AUGMENT(left); \ } while ((left = RB_PARENT(left, field))); \ } \ goto color; \ } \ parent = RB_PARENT(elm, field); \ color = RB_COLOR(elm, field); \ if (child) \ RB_PARENT(child, field) = parent; \ if (parent) { \ if (RB_LEFT(parent, field) == elm) \ RB_LEFT(parent, field) = child; \ else \ RB_RIGHT(parent, field) = child; \ RB_AUGMENT(parent); \ } else \ RB_ROOT(head) = child; \ color: \ if (color == RB_BLACK) \ name##_RB_REMOVE_COLOR(head, parent, child); \ return (old); \ } \ \ /* Inserts a node into the RB tree */ \ attr struct type * \ name##_RB_INSERT(struct name *head, struct type *elm) \ { \ struct type *tmp; \ struct type *parent = NULL; \ int comp = 0; \ tmp = RB_ROOT(head); \ while (tmp) { \ parent = tmp; \ comp = (cmp)(elm, parent); \ if (comp < 0) \ tmp = RB_LEFT(tmp, field); \ else if (comp > 0) \ tmp = RB_RIGHT(tmp, field); \ else \ return (tmp); \ } \ RB_SET(elm, parent, field); \ if (parent != NULL) { \ if (comp < 0) \ RB_LEFT(parent, field) = elm; \ else \ RB_RIGHT(parent, field) = elm; \ RB_AUGMENT(parent); \ } else \ RB_ROOT(head) = elm; \ name##_RB_INSERT_COLOR(head, elm); \ return (NULL); \ } \ \ /* Finds the node with the same key as elm */ \ attr struct type * \ name##_RB_FIND(struct name *head, struct type *elm) \ { \ struct type *tmp = RB_ROOT(head); \ int comp; \ while (tmp) { \ comp = cmp(elm, tmp); \ if (comp < 0) \ tmp = RB_LEFT(tmp, field); \ else if (comp > 0) \ tmp = RB_RIGHT(tmp, field); \ else \ return (tmp); \ } \ return (NULL); \ } \ \ /* Finds the first node greater than or equal to the search key */ \ attr struct type * \ name##_RB_NFIND(struct name *head, struct type *elm) \ { \ struct type *tmp = RB_ROOT(head); \ struct type *res = NULL; \ int comp; \ while (tmp) { \ comp = cmp(elm, tmp); \ if (comp < 0) { \ res = tmp; \ tmp = RB_LEFT(tmp, field); \ } \ else if (comp > 0) \ tmp = RB_RIGHT(tmp, field); \ else \ return (tmp); \ } \ return (res); \ } \ \ /* ARGSUSED */ \ attr struct type * \ name##_RB_NEXT(struct type *elm) \ { \ if (RB_RIGHT(elm, field)) { \ elm = RB_RIGHT(elm, field); \ while (RB_LEFT(elm, field)) \ elm = RB_LEFT(elm, field); \ } else { \ if (RB_PARENT(elm, field) && \ (elm == RB_LEFT(RB_PARENT(elm, field), field))) \ elm = RB_PARENT(elm, field); \ else { \ while (RB_PARENT(elm, field) && \ (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\ elm = RB_PARENT(elm, field); \ elm = RB_PARENT(elm, field); \ } \ } \ return (elm); \ } \ \ /* ARGSUSED */ \ attr struct type * \ name##_RB_PREV(struct type *elm) \ { \ if (RB_LEFT(elm, field)) { \ elm = RB_LEFT(elm, field); \ while (RB_RIGHT(elm, field)) \ elm = RB_RIGHT(elm, field); \ } else { \ if (RB_PARENT(elm, field) && \ (elm == RB_RIGHT(RB_PARENT(elm, field), field))) \ elm = RB_PARENT(elm, field); \ else { \ while (RB_PARENT(elm, field) && \ (elm == RB_LEFT(RB_PARENT(elm, field), field)))\ elm = RB_PARENT(elm, field); \ elm = RB_PARENT(elm, field); \ } \ } \ return (elm); \ } \ \ attr struct type * \ name##_RB_MINMAX(struct name *head, int val) \ { \ struct type *tmp = RB_ROOT(head); \ struct type *parent = NULL; \ while (tmp) { \ parent = tmp; \ if (val < 0) \ tmp = RB_LEFT(tmp, field); \ else \ tmp = RB_RIGHT(tmp, field); \ } \ return (parent); \ } #define RB_NEGINF -1 #define RB_INF 1 #define RB_INSERT(name, x, y) name##_RB_INSERT(x, y) #define RB_REMOVE(name, x, y) name##_RB_REMOVE(x, y) #define RB_FIND(name, x, y) name##_RB_FIND(x, y) #define RB_NFIND(name, x, y) name##_RB_NFIND(x, y) #define RB_NEXT(name, x, y) name##_RB_NEXT(y) #define RB_PREV(name, x, y) name##_RB_PREV(y) #define RB_MIN(name, x) name##_RB_MINMAX(x, RB_NEGINF) #define RB_MAX(name, x) name##_RB_MINMAX(x, RB_INF) #define RB_FOREACH(x, name, head) \ for ((x) = RB_MIN(name, head); \ (x) != NULL; \ (x) = name##_RB_NEXT(x)) #define RB_FOREACH_SAFE(x, name, head, y) \ for ((x) = RB_MIN(name, head); \ ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \ (x) = (y)) #define RB_FOREACH_REVERSE(x, name, head) \ for ((x) = RB_MAX(name, head); \ (x) != NULL; \ (x) = name##_RB_PREV(x)) #endif /* _SYS_TREE_H_ */ spectrwm-SPECTRWM_3_5_1/linux/util.h000066400000000000000000000007311453034271100173400ustar00rootroot00000000000000#define FPARSELN_UNESCESC 0x01 #define FPARSELN_UNESCCONT 0x02 #define FPARSELN_UNESCCOMM 0x04 #define FPARSELN_UNESCREST 0x08 #define FPARSELN_UNESCALL 0x0f size_t strlcpy(char *, const char *, size_t); size_t strlcat(char *, const char *, size_t); char *fgetln(FILE *, size_t *); char *fparseln(FILE *, size_t *, size_t *, const char [3], int); long long strtonum(const char *, long long, long long, const char **); #ifndef WAIT_ANY #define WAIT_ANY (-1) #endif spectrwm-SPECTRWM_3_5_1/netbsd/000077500000000000000000000000001453034271100163315ustar00rootroot00000000000000spectrwm-SPECTRWM_3_5_1/netbsd/Makefile000066400000000000000000000033021453034271100177670ustar00rootroot00000000000000.sinclude PREFIX?= /usr/pkg LOCALBASE?= /usr/pkg SWM_BINDIR?= ${DESTDIR}$(PREFIX)/bin SWM_LIBDIR?= ${DESTDIR}$(PREFIX)/lib SWM_MANDIR?= ${DESTDIR}$(PREFIX)/${PKGMANDIR} LVERS!= awk -F = '/major/ { printf( "%s.", $$2 ) } /minor/ { printf( "%s", $$2 ) }' ${.CURDIR}/../lib/shlib_version BUILDVERSION!= sh "${.CURDIR}/../buildver.sh" .if !${BUILDVERSION} == "" CFLAGS+= -DSPECTRWM_BUILDSTR=\"$(BUILDVERSION)\" .endif CFLAGS+= -std=c99 -Wmissing-prototypes -Wall -Wextra -Wshadow -Wno-uninitialized -g CFLAGS+= -I. -I${LOCALBASE}/include -I${LOCALBASE}/include/freetype2 CFLAGS+= -DSWM_LIB=\"$(SWM_LIBDIR)/libswmhack.so.$(LVERS)\" LDADD+= -lutil -L${LOCALBASE}/lib -lX11 -lX11-xcb -lxcb \ -lxcb-icccm -lxcb-keysyms -lxcb-randr -lxcb-util -lxcb-xinput -lxcb-xtest -lfontconfig -lXft -lXcursor all: spectrwm libswmhack.so.$(LVERS) spectrwm.c: ln -sf ../spectrwm.c ln -sf ../version.h ln -sf ../linux/queue_compat.h swm_hack.c: ln -sf ../lib/swm_hack.c spectrwm: spectrwm.o $(CC) $(LDFLAGS) $(LDADD) -o ${.TARGET} ${.ALLSRC} swm_hack.so: swm_hack.c $(CC) $(CFLAGS) -c -shared -fpic -DPIC -o ${.TARGET} ${.ALLSRC} libswmhack.so.$(LVERS): swm_hack.so $(CC) $(LDFLAGS) -shared -fpic -o ${.TARGET} ${.ALLSRC} install: all $(BSD_INSTALL_PROGRAM_DIR) $(SWM_BINDIR) $(BSD_INSTALL_LIB_DIR) $(SWM_LIBDIR) $(BSD_INSTALL_MAN_DIR) $(SWM_MANDIR)/man1 $(BSD_INSTALL_PROGRAM) spectrwm $(SWM_BINDIR) $(BSD_INSTALL_LIB) libswmhack.so.$(LVERS) $(SWM_LIBDIR) $(BSD_INSTALL_MAN) ../spectrwm.1 $(SWM_MANDIR)/man1/spectrwm.1 ln -sf spectrwm $(SWM_BINDIR)/scrotwm clean: rm -f spectrwm *.o *.so libswmhack.so.* spectrwm.c swm_hack.c version.h queue_compat.h .PHONY: all install clean .sinclude spectrwm-SPECTRWM_3_5_1/osx/000077500000000000000000000000001453034271100156635ustar00rootroot00000000000000spectrwm-SPECTRWM_3_5_1/osx/Makefile000066400000000000000000000044101453034271100173220ustar00rootroot00000000000000PREFIX?= /usr/local BINDIR?= $(PREFIX)/bin LIBDIR?= $(PREFIX)/lib MANDIR?= $(PREFIX)/share/man BUILDVERSION = $(shell sh $(CURDIR)/../buildver.sh) # To use xquartz, uncomment the following three lines. INCFLAGS+= -I/opt/X11/include -I/opt/X11/include/freetype2/ LDADD+= -L/opt/X11/lib -lX11 -lXcursor -lfontconfig -lXft # To use homebrew, uncomment the following three lines. #INCFLAGS+= -I/opt/X11/include #LDADD+= -lX11 -lXcursor -lfontconfig -lXft -L/opt/X11/lib #INCFLAGS+= -I/usr/local/Cellar/freetype/2.5.0.1/include/freetype2 # To use homebrew , comment out the following two lines. #INCFLAGS+= -I/opt/local/include/freetype2 -I/opt/local/include #LDADD+= -L/opt/local/lib -lX11 -lXcursor -lfontconfig -lXft LDADD+= -lxcb-keysyms -lxcb-util -lxcb-randr -lX11-xcb -lxcb-xtest -lxcb -lxcb-icccm LVERS= $(shell . ../lib/shlib_version; echo $$major.$$minor) CFLAGS+= -std=c99 -Wmissing-prototypes -Wall -Wextra -Wshadow -Wno-uninitialized -g CFLAGS+= -O2 -D_GNU_SOURCE -D__OSX__ -I. -I.. ${INCFLAGS} CFLAGS+= -DSWM_LIB=\"$(LIBDIR)/libswmhack.so.$(LVERS)\" ifneq ("${BUILDVERSION}", "") CFLAGS+= -DSPECTRWM_BUILDSTR=\"$(BUILDVERSION)\" endif CC?= cc all: spectrwm libswmhack.so.$(LVERS) spectrwm.c: ln -sf ../linux/tree.h ln -sf ../spectrwm.c ln -sf ../version.h ln -sf ../linux/queue_compat.h swm_hack.c: ln -sf ../lib/swm_hack.c spectrwm: spectrwm.o osx.o $(CC) $(LDFLAGS) -o $@ $+ $(LDADD) %.so: %.c $(CC) $(CFLAGS) -c -fpic -DPIC $+ -o $@ libswmhack.so.$(LVERS): swm_hack.so $(CC) $(LDFLAGS) -shared -fpic -o libswmhack.so.$(LVERS) swm_hack.so $(LDADD) # replace above line with this for OSX 10.5 # $(CC) -shared -bundle -fpic -o libswmhack.so.$(LVERS) $(LDADD) swm_hack.so install: all install -m 755 -d $(DESTDIR)$(BINDIR) install -m 755 -d $(DESTDIR)$(LIBDIR) install -m 755 -d $(DESTDIR)$(MANDIR)/man1 install -m 755 spectrwm $(DESTDIR)$(BINDIR) install -m 755 libswmhack.so.$(LVERS) $(DESTDIR)$(LIBDIR) install -m 644 ../spectrwm.1 $(DESTDIR)$(MANDIR)/man1/spectrwm.1 ln -sf spectrwm $(DESTDIR)$(BINDIR)/scrotwm ln -sf libswmhack.so.0.0 $(DESTDIR)$(LIBDIR)/libswmhack.so.0 ln -sf libswmhack.so.0.0 $(DESTDIR)$(LIBDIR)/libswmhack.so clean: rm -f spectrwm *.o *.so libswmhack.so.* spectrwm.c swm_hack.c tree.h version.h queue_compat.h .PHONY: all install clean spectrwm-SPECTRWM_3_5_1/osx/osx.c000066400000000000000000000036431453034271100166460ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "osx.h" /* --------------------------------------------------------------------------- */ /* $OpenBSD: strtonum.c,v 1.6 2004/08/03 19:38:01 millert Exp $ */ /* * Copyright (c) 2004 Ted Unangst and Todd Miller * All rights reserved. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define INVALID 1 #define TOOSMALL 2 #define TOOLARGE 3 long long strtonum(const char *numstr, long long minval, long long maxval, const char **errstrp) { long long ll = 0; char *ep; int error = 0; struct errval { const char *errstr; int err; } ev[4] = { { NULL, 0 }, { "invalid", EINVAL }, { "too small", ERANGE }, { "too large", ERANGE }, }; ev[0].err = errno; errno = 0; if (minval > maxval) error = INVALID; else { ll = strtoll(numstr, &ep, 10); if (numstr == ep || *ep != '\0') error = INVALID; else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) error = TOOSMALL; else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) error = TOOLARGE; } if (errstrp != NULL) *errstrp = ev[error].errstr; errno = ev[error].err; if (error) ll = 0; return (ll); } spectrwm-SPECTRWM_3_5_1/osx/osx.h000066400000000000000000000001071453034271100166430ustar00rootroot00000000000000long long strtonum(const char *, long long, long long, const char **); spectrwm-SPECTRWM_3_5_1/outdated_man_pages/000077500000000000000000000000001453034271100206755ustar00rootroot00000000000000spectrwm-SPECTRWM_3_5_1/outdated_man_pages/spectrwm_es.1000066400000000000000000000470251453034271100233220ustar00rootroot00000000000000.\" Copyright (c) 2009 Marco Peereboom .\" Copyright (c) 2009 Darrin Chandler .\" .\" Permission to use, copy, modify, and distribute this software for any .\" purpose with or without fee is hereby granted, provided that the above .\" copyright notice and this permission notice appear in all copies. .\" .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .Dd $Mdocdate: February 15 2012 $ .Dt SPECTRWM 1 .Os .Sh NOMBRE .Nm spectrwm .Nd es un manejador de ventanas para X11 .Sh SYNOPSIS .Nm spectrwm .Sh DESCRIPCIN .Nm es un manejador de ventanas super minimalista para X11. Intenta no superponer las ventanas para que las mismas puedan usarse de manera eficiente y para cosas mas importantes. Tiene configuraciones normales y no requiere que sepas un lenguaje de programacion para configurarlo. Esta escrito por hackers para hackers y apunta a ser pequeo, compacto y rpido. .Pp Cuando .Nm inicia, lo primero que hace es leer el archivo de configuracion, .Pa spectrwm.conf . Ver .Sx ARCHIVOS DE CONFIGURACIN . .Pp La siguiente anotacion se usa a travs de esta pagina: .Pp .Bl -tag -width Ds -offset indent -compact .It Cm M Meta .It Cm S Shift .It Aq Cm Name Nombre de tecla .It Cm M1 Boton 1 del mouse .It Cm M3 Boton 3 del mouse .El .Pp .Nm es muy simple de usar. La mayoria de las acciones se hacen con los mapeos (bindings) de mouse o teclado. Ver la seccin de .Sx BINDINGS para las personalizaciones y configuraciones por defecto. .Sh ARCHIVOS DE CONFIGURACIN .Nm primero trata de abrir el archivo por defecto en el directorio del usuario, .Pa ~/.spectrwm.conf . Si ese archivo no esta disponible, luego trata de abrir el archivo global de configuracion .Pa /etc/spectrwm.conf . .Pp El formato del archivo es \*(Lttecla\*(Gt = \*(Ltconfiguracion\*(Gt. Por ejemplo: .Pp .Dl color_focus = red .Pp Habilitamos o deshabilitamos la opcin usando 1 o 0 respectivamente. .Pp El archivo soporta las siguientes palabras clave: .Pp .Bl -tag -width "title_class_enabledXXX" -offset indent -compact .It Cm autorun Inicia una aplicacion en un escritorio en particular al primer inicio. Definido por el formato ws[]:aplicacion, ej. ws[2]:xterm lanza xterm en el escritorio 2. .It Cm color_focus Color del borde de la ventana en foco. .It Cm color_unfocus Color del borde de la ventana fuera de foco. .It Cm bar_enabled Habilitar o deshabilitar la barra de estado. .It Cm bar_border Ns Bq Ar x Color del borde de la barra de estado en pantalla. .Ar x . .It Cm bar_border_width Setea el grosor de la barra de estado en pixels. Deshabilitado seteando 0. .It Cm bar_color Ns Bq Ar x Color de la ventana de la barra de estado en pantalla. .Ar x . .It Cm bar_font_color Ns Bq Ar x Color de la fuente en la barra de estado en pantalla. .Ar x . .It Cm bar_font Fuente de la barra de estado. .It Cm bar_action Scripts externos con populares agregados de informacin para la barra de estado, como la vida de la bateria. .It Cm bar_at_bottom Puedes posicionar la statusbar en la parte inferior de la pantalla. .It Cm stack_enabled Habilitar o deshabilitar mostrar el algoritmo de apilamiento en la barra de estado. .It Cm clock_enabled Habilitar o deshabilitar el reloj en la barra de estado, deshabilitado por defecto con un 0, para usar el reloj de la barra de estado (bar_action) .Pa bar_action script. .It Cm dialog_ratio Algunas aplicaciones tienen ventanas de dialogo muy pequeas como para ser usables. Este relacin (ratio) es el tamao de la pantalla, por ejemplo 0.6 es 60% del tamao fsico de la pantalla. .It Cm layout Selecciona una disposicion para usar en el primer inicio. Definido con el formato ws[idx]:master_grow:master_add:stack_inc:layout:always_raise:stack_mode, ej. ws[2]:-4:0:1:0:horizontal setea el escritorio 2 en horizontal, el stack principal y reduce 4 puntos agregando una ventana al stack, mientras mantiene el comportamiento de ventanas flotantes. Modos posible de stack_mode .Pa vertical , .Pa horizontal and .Pa fullscreen . .Pp Ver .Pa master_grow , .Pa master_shrink , .Pa master_add , .Pa master_del , .Pa stack_inc , .Pa stack_del , y .Pa always_raise para mas informacion. Tenga en cuenta que las opciones de stack son complicados y tienen efectos secundarios. Uno debe familiarizarse con estos comandos antes de experimentar con la opcion .Pa layout .Pp Esta opcion no necesita un reinicio. .It Cm region Acomodar una region personalizada, removiendo cualquier autodetecin de regiones que ocupe el espacio en la pantalla. Definiendo el formato screen[]:WIDTHxHEIGHT+X+Y, e.g.\& screen[1]:800x1200+0+0. .It Cm term_width Setear un ancho minimo preferido para la terminal. Si el valor es mayor que 0, .Nm intentar ajustar el tamao de la fuente de la terminal para mantener el ancho de la terminal por encima de este nmero cuando la ventana cambia de tamao. Actualmente solo es soportado por .Xr xterm 1 El binario de .Xr xterm 1 no debe ser un setuid o setgid, que no sea el que viene por defecto en la mayoria de los sistemas. Los usuarios pueden necesitar setear program[term] (ver la seccin .Sx PROGRAMAS ) para usar una copia alternativa del binario de .Xr xterm 1 sin el seteo del setgid. .It Cm title_class_enabled Habilitar o deshabilitar la clase de ventana en la barre de estado. Habilitado seteando 1 .It Cm title_name_enabled Habilitar o deshabilita el titulo de la ventana en la barra de estado. Habilitado seteando 1 .It Cm urgent_enabled Habilitar o deshabilitar el aviso de urgencia. Tenga en cuenta que muchos emuladores de terminal requieren de este parametro habilitado para que funcione. En xterm, por ejemplo, hay que agregar la siguiente linea .Pa xterm.urgentOnBell: true to .Pa .Xdefaults . .It Cm window_name_enabled Habilitar o deshabilita el nombre de la ventana en la barra de estado. Habilitado seteando 1 .It Cm verbose_layout Habilitar o deshabilita la notificacion del area principal y el stack en la barra de estado. Habilitado seteandolo a 1. .It Cm modkey Cambiar mod key. Mod1 generalmente es la tecla ALT y Mod4 la tecla de windows en una PC. .It Cm focus_mode Usando el valor de .Pa follow_cursor puedes hacer que el manejador de ventanas se enfoque en la ventana cuando el cursor pase por arriba de las mismas o bien cambiando de estacion de trabajo. .It Cm disable_border Remueve el borde de una sola ventana cuando la barra de estado esta desactivada. .It Cm border_width Setea el grosor del borde de la ventana en pixels. Deshabilitar todos los bordes seteandolo a 0. .It Cm program Ns Bq Ar p Definir una nueva accion para ejecutar un programa. .Ar p . Ver la seccin de .Sx PROGRAMAS .It Cm bind Ns Bq Ar x Combinacin de teclas para una accin .Ar x . Ver la seccin .Sx BINDINGS .It Cm quirk Ns Bq Ar c:n Agregar un "quirk" (o forzados) para ventanas con clase .Ar c y nombre .Ar n . Ver la seccin .Sx QUIRKS .El .Pp Los colores deben ser especificados por la especificacin .Xr XQueryColor 3 y las fuentes por la especificacin .Xr XQueryFont 3 .Sh PROGRAMAS .Nm te permite definir acciones personales para lanzar los programas que quieras y luego obligar a la misma con una funcin de acciones. Ver la seccin .Sx BINDINGS .Pp Los programas por defecto se describen ac: .Pp .Bl -tag -width "screenshot_wind" -offset indent -compact .It Cm term xterm .It Cm screenshot_all screenshot.sh completo .It Cm screenshot_wind screenshot.sh por ventana .It Cm lock xlock .It Cm initscr initscreen.sh .It Cm menu dmenu_run \-fn $bar_font \-nb $bar_color \-nf $bar_font_color \-sb $bar_border \-sf $bar_color .El .Pp Los programas en la configuracin personal, se especifican aca: .Pp .Dl program[] = [ [... ]] .Pp .Aq nombre es un identificador, no genera conflictos con ninguna accion o palabra clave, .Aq progpath es la ruta al programa, y .Aq arg es ninguno o mas de un argumento para el programa. .Pp Las siguientes variables de configuracion en .Nm (ver .Sx ARCHIVOS DE CONFIGURACIN ), y pueden ser usadas en los campos de .Aq arg como asi tambien sustituidas por valores al momento del inicio de un programa: .Pp .Bl -tag -width "$bar_font_color" -offset indent -compact .It Cm $bar_border .It Cm $bar_color .It Cm $bar_font .It Cm $bar_font_color .It Cm $color_focus .It Cm $color_unfocus .El .Pp Ejemplo: .Bd -literal -offset indent program[ff] = /usr/local/bin/firefox http://spectrwm.com.ar/ bind[ff] = Mod+f # Ahora Mod+F inicia firefox .Ed .Pp Para deshacer lo anterior: .Bd -literal -offset indent bind[] = Mod+f program[ff] = .Ed .Sh BINDINGS .Nm provee muchas funciones (o acciones) accesibles por medio de la asignacin (bindings) de teclas o el mouse. .Pp Las corrientes asignaciones (bindings) del mouse son: .Pp .Bl -tag -width "M-j, M-XXX" -offset indent -compact .It Cm M1 Enfoco una ventana .It Cm M-M1 Muevo una ventana .It Cm M-M3 Redimenciono una ventana .It Cm M-S-M3 Redimenciono una ventana hasta que quede centrada .El .Pp Las corrientes asignaciones (bindings) de teclas son: .Pp .Bl -tag -width "M-j, M-XXX" -offset indent -compact .It Cm M-S- Ns Aq Cm Return term .It Cm M-p menu .It Cm M-S-q quit .It Cm M-q restart .Nm .It Cm M- Ns Aq Cm Space cycle_layout .It Cm M-S- Ns Aq Cm Space reset_layout .It Cm M-h master_shrink .It Cm M-l master_grow .It Cm M-, master_add .It Cm M-. master_del .It Cm M-S-, stack_inc .It Cm M-S-. stack_del .It Cm M- Ns Aq Cm Return swap_main .It Xo .Cm M-j , .Cm M- Ns Aq Cm TAB .Xc focus_next .It Xo .Cm M-k , .Cm M-S- Ns Aq Cm TAB .Xc focus_prev .It Cm M-m focus_main .It Cm M-S-j swap_next .It Cm M-S-k swap_prev .It Cm M-b bar_toggle .It Cm M-x wind_del .It Cm M-S-x wind_kill .It Cm M- Ns Aq Ar n .Pf ws_ Ns Ar n .It Cm M-S- Ns Aq Ar n .Pf mvws_ Ns Ar n .It Cm M- Ns Aq Cm Right ws_next .It Cm M- Ns Aq Cm Left ws_prev .It Cm M-a ws_prior .It Cm M-S- Ns Aq Cm Right screen_next .It Cm M-S- Ns Aq Cm Left screen_prev .It Cm M-s screenshot_all .It Cm M-S-s screenshot_wind .It Cm M-S-v version .It Cm M-t float_toggle .It Cm M-S Aq Cm Delete lock .It Cm M-S-i initscr .It Cm M-w iconify .It Cm M-S-w uniconify .It Cm M-S-r always_raise .It Cm M-v button2 .It Cm M-- width_shrink .It Cm M-= width_grow .It Cm M-S- height_shrink .It Cm M-S-= height_grow .It Cm M-[ move_left .It Cm M-] move_right .It Cm M-S-[ move_up .It Cm M-S-] move_down .El .Pp El nombre de las accines descripta a continuacin: .Pp .Bl -tag -width "M-j, M-XXX" -offset indent -compact .It Cm term Ejecutar una terminal (ver .Sx PROGRAMAS ) .It Cm menu Menu (ver .Sx PROGRAMAS ) .It Cm quit Salir .Nm .It Cm restart Reiniciar .Nm .It Cm cycle_layout Disposicin de las ventanas .It Cm reset_layout Reiniciar la disposicin de las ventanas .It Cm master_shrink Achicar la region principal .It Cm master_grow Agrandar la region principal .It Cm master_add Agregar una ventana a la region principal .It Cm master_del Quitar una ventana de la region principal .It Cm stack_inc Agregar columnas/filas a las pilas .It Cm stack_del Quitar columnas/filas de las pilas .It Cm swap_main Mover la ventana corriente a la region principal .It Cm focus_next Enfocar la proxima ventana en la estacin de trabajo .It Cm focus_prev Enfocar la anterior ventana en la estacin de trabajo .It Cm focus_main Enfocar en la ventana principal de la estacin de trabajo .It Cm swap_next Ejecutar con la siguiente ventana en la estacin de trabajo .It Cm swap_prev Ejecutar con la anterior ventana en la estacin de trabajo .It Cm bar_toggle Cambiar la barra de estado en todas las estaciones de trabajo .It Cm wind_del Borrar la ventana corriente en la estacin de trabajo .It Cm wind_kill Destruir la ventana corriente en la estacin de trabajo .It Cm ws_ Ns Ar n Cambiar entre estaciones de trabajo .Ar n , donde .Ar n es 1 por 10 .It Cm mvws_ Ns Ar n Mover la ventana corriente a una estacin de trabajo .Ar n , donde .Ar n es 1 por 10 .It Cm ws_next Cambiar a la proxima estacin de trabajo con una ventana en ella .It Cm ws_prev Cambiar a la anterior estacin de trabajo con una ventana en ella .It Cm screen_next Mover el puntero a la proxima region .It Cm screen_prev Mover el puntero a la anterior region .It Cm screenshot_all Tomar una captura de pantalla de todo la pantalla (si esta habilitado) (ver .Sx PROGRAMAS ) .It Cm screenshot_wind Tomar una captura de pantalla de la ventana seleccionada (si esta habilitado) (ver .Sx PROGRAMAS ) .It Cm version Mostrar la version en la barra de estado .It Cm float_toggle Mostar la ventana en foco entre las flotantes y acomodadas .It Cm lock Bloquear pantalla (ver .Sx PROGRAMAS ) .It Cm initscr Reiniciar la pantalla (ver .Sx PROGRAMAS ) .It Cm iconify Minimiza (unmap) la ventana en foco. .It Cm uniconify Maximiza (map) la ventana seleccionada por dmenu. .It Cm always_raise Cuando se establece las ventanas en cascada se esconden las ventanas flotantes. .It Cm button2 Falsifica el boton del medio del mouse. .It Cm width_shrink Reducir el ancho de una ventana flotante. .It Cm width_grow Agranda el ancho de una ventana flotante. .It Cm height_shrink Reducir la altura de una ventana flotante. .It Cm height_grow Agranda la altura de una ventana flotante. .It Cm move_left Mueve la ventana flotante un paso a la izquierda. .It Cm move_right Mueve la ventana flotante un paso a la derecha. .It Cm move_up Mueve la ventana flotante un paso arriba. .It Cm move_down Mueve la ventana flotante un paso abajo. .El .Pp Personalizar mapeos (bindings) en el archivo de configuracin: .Pp .Dl bind[] = .Pp .Aq accion una de las acciones listadas (o ninguna) y .Aq teclas una o mas teclas modificadas (puede ser ninguna tambien) (MOD, Mod1, Shift, etc.) y una o mas teclas normales (b, barra espaciadora, etc.), separadas por un "+". Por ejemplo: .Bd -literal -offset indent bind[reset] = Mod4+q # combinacin Tecla de Windows + q reinicia bind[] = Mod1+q # des-hace la combinacin Alt + q .Ed .Pp Multiples combinaciones de teclas pueden hacer lo mismo. .Sh QUIRKS .Nm te da "quirks" (o forzados) ventanas que tienen que ser tratas de manera especial, como por ejemplo, popups, aplicaciones de pantalla completa, etc. .Pp Los "quirks" (o forzados) por defecto son: .Pp .Bl -tag -width "OpenOffice.org N.M:VCLSalFrameXXX" -offset indent -compact .It Firefox\-bin:firefox\-bin TRANSSZ .It Firefox:Dialog FLOAT .It Gimp:gimp FLOAT + ANYWHERE .It MPlayer:xv FLOAT + FULLSCREEN .It OpenOffice.org 2.4:VCLSalFrame FLOAT .It OpenOffice.org 3.1:VCLSalFrame FLOAT .It pcb:pcb FLOAT .It xine:Xine Window FLOAT + ANYWHERE .It xine:xine Panel FLOAT + ANYWHERE .It xine:xine Video Fullscreen Window FULLSCREEN + FLOAT .It Xitk:Xitk Combo FLOAT + ANYWHERE .It Xitk:Xine Window FLOAT + ANYWHERE .It XTerm:xterm XTERM_FONTADJ .El .Pp Los "quirks" (o forzados) se describen a continuacin: .Pp .Bl -tag -width "XTERM_FONTADJXXX" -offset indent -compact .It FLOAT Esta ventana no tiene que ser acomodada, pero le permitimos flotar libremente. .It TRANSSZ Ajusta el tamao de las ventanas transitorias que son demasiado pequeas utilizando dialog_ratio (ver .Sx ARCHIVOS DE CONFIGURACIN ) . .It ANYWHERE Permite que la ventana se ponga donde quiera. .It XTERM_FONTADJ Ajusta las fuentes de xterm cuando se redimenciona. .It FULLSCREEN Quita el borde para permitir las ventanas en pantalla completa. .It FOCUSPREV El enfoque de salida fuerza la solicitud de aplicacisn que anteriormente se centraba en la aplicacion anterior del stack. .El .Pp Las configuraciones de "quirks" (o forzados) en el archivo de configuracin se ven a continuacin: .Pp .Dl quirk[:] = [ + ... ] .Pp .Aq clases y .Aq nombre especifica la ventana en la cual el "quirk(s)" (o forzados) se aplica, y .Aq quirk es uno de los "quirks" (o forzados) de la lista. Por ejemplo: .Bd -literal -offset indent quirk[MPlayer:xv] = FLOAT + FULLSCREEN # dejamos que mplayer funcione libremente quirk[pcb:pcb] = NONE # borramos el quirk existente .Ed .Pp Podes obtener .Aq clases y .Aq nombre corriendo el programa xprop(1) y luego clickear en la ventana que quieras. En el proximo ejemplo, podremos verlo en accin con una ventana de Firefox: .Bd -literal -offset indent $ xprop | grep WM_CLASS WM_CLASS(STRING) = "Navigator", "Firefox" .Ed .Sh EWMH .Nm parcialmente implementa los Consejos de ventana extendido Manager (EWMH) especificacion. Esto permite el control de las ventanas, asi como .Nm si a partir de scripts y programas externos. Esto se logra mediante .Nm responder a ciertos eventos ClientMessage. Desde la terminal de estos eventos se puede enviar facilmente el uso de herramientas tales como .Xr wmctrl 1 y .Xr xdotool 1 . para el formato real de estos eventos ClientMessage, consulte la especificacion EWMH. .Pp La Identificacion de la ventana actualmente enfocada se almacena en el _NET_ACTIVE_WINDOW propiedad de la ventana raiz. Esto puede ser usado por ejemplo para recuperar el titulo de la ventana activa con .Xr xprop 1 y .Xr grep 1 : .Bd -literal -offset indent $ WINDOWID=`xprop \-root _NET_ACTIVE_WINDOW | grep \-o "0x.*"` $ xprop \-id $WINDOWID WM_NAME | grep \-o "\\".*\\"" .Ed .Pp Una ventana se puede enfocar mediante el envio de un mensaje del cliente _NET_ACTIVE_WINDOW a la ventana principal. Por ejemplo, usando .Xr wmctrl 1 para enviar el mensaje (suponiendo que 0x4a0000b es el ID de la ventana para ser especifico): .Bd -literal -offset indent $ wmctrl \-i \-a 0x4a0000b .Ed .Pp Ventanas se pueden cerrar mediante el envmo de un mensaje del cliente _NET_CLOSE_WINDOW a la ventana principal. Por ejemplo, usando .Xr wmctrl 1 para enviar el mensaje (suponiendo que 0x4a0000b es el ID de la ventana se cierre): .Bd -literal -offset indent $ wmctrl \-i \-c 0x4a0000b .Ed .Pp Las ventanas se pueden flotar y flotar sin-mediante la adicion o eliminacion de la _NET_WM_STATE_ABOVE atom desde _NET_WM_STATE la propiedad de la ventana Esto se puede lograr mediante el envio de un mensaje a los clientes _NET_WM_STATE raiz de la ventana. Por ejemplo, el siguiente cambia el estado de la flota. .Xr wmctrl 1 para enviar el mensaje (suponiendo que 0x4a0000b es el ID de la ventana flotante o no-flotante): .Bd -literal -offset indent $ wmctrl \-i \-r 0x4a0000b \-b toggle,_NET_WM_STATE_ABOVE .Ed .Pp Ventanas flotantes tambien se puede cambiar el tamano y movido por el envio de un _NET_MOVERESIZE_WINDOW Mensaje del cliente de la ventana raiz. Por ejemplo, uso .Xr wmctrl 1 para enviar el mensaje (suponiendo que 0x4a0000b es el ID de la ventana a redimensionar / mover): .Bd -literal -offset indent $ wmctrl \-i \-r 0x4a0000b \-e 0,100,50,640,480 .Ed .Pp Esto mueve la ventana de (100,50) y cambia el tamaqo a 640x480. .Pp Todos los eventos _NET_MOVERESIZE_WINDOW recibido por las ventanas apiladas se ignoran. .Sh SIGNALS Enviando .Nm una senal de HUP reinicia spectrwm. .Sh ARCHIVOS .Bl -tag -width "/etc/spectrwm.confXXX" -compact .It Pa ~/.spectrwm.conf .Nm archivo de configuracin especifico del usuario. .It Pa /etc/spectrwm.conf .Nm configuraciones globales. .El .Sh HISTORIA .Nm fue inspirado en xmonad y dwm. .Sh AUTORES .An -nosplit .Nm fue escrito por .Pp .Bl -tag -width "Ryan Thomas McBride Aq mcbride@countersiege.com " -offset indent -compact .It Cm Marco Peereboom Aq marco@peereboom.us .It Cm Ryan Thomas McBride Aq mcbride@countersiege.com .It Cm Darrin Chandler Aq dwchandler@stilyagin.com .It Cm Pierre-Yves Ritschard Aq pyr@spootnik.org .It Cm Tuukka Kataja Aq stuge@xor.fi .It Cm Jason L. Wright Aq jason@thought.net .It Cm Reginald Kennedy Aq rk@rejii.com .It Cm Lawrence Teo Aq lteo@lteo.net .It Cm Tiago Cunha Aq tcunha@gmx.com .El spectrwm-SPECTRWM_3_5_1/outdated_man_pages/spectrwm_it.1000066400000000000000000001110441453034271100233200ustar00rootroot00000000000000.\" Copyright (c) 2009-2012 Marco Peereboom .\" Copyright (c) 2009 Darrin Chandler .\" Copyright (c) 2011-2014 Reginald Kennedy .\" Copyright (c) 2011-2012 Lawrence Teo .\" Copyright (c) 2011-2012 Tiago Cunha .\" Copyright (c) 2012 David Hill .\" .\" Permission to use, copy, modify, and distribute this software for any .\" purpose with or without fee is hereby granted, provided that the above .\" copyright notice and this permission notice appear in all copies. .\" .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .Dd $Mdocdate: February 15 2012 $ .Dt SPECTRWM 1 .Os .Sh NOME .Nm spectrwm .Nd gestore di finestre per X11 .Sh SINTASSI .Nm spectrwm .Sh DESCRIZIONE .Nm \[`e] un gestore di finestre minimale che cerca di stare in disparte, in modo che il prezioso spazio sullo schermo possa essere usato per cose molto pi\[`u] importanti. Ha delle impostazioni predefinite ragionevoli e non richiede all'utente di imparare un nuovo linguaggio per modificarne la configurazione. \[`E] stato scritto dagli hacker per gli hacker, ed ha come obiettivo quello di essere piccolo, compatto e veloce. .Pp All'avvio, .Nm legge le impostazioni presenti nel suo file di configurazione, .Pa spectrwm.conf . Vedere la sezione .Sx FILE DI CONFIGURAZIONE pi\[`u] sotto. .Pp Le seguenti notazioni verranno utilizzate all'interno di questa pagina: .Pp .Bl -tag -width Ds -offset indent -compact .It Cm M Meta .It Cm S Shift .It Aq Cm Name Tasto Name .It Cm M1 Tasto 1 (sinistro) del mouse .It Cm M3 Tasto 3 (destro) del mouse .El .Pp .Nm \[`e] molto semplice da usare. La maggior parte delle azioni vengono svolte utilizzando combinazioni di tasti sulla tastiera o sul mouse. Vedere la sezione .Sx SCORCIATOIE pi\[`u] sotto per informazioni sulle impostazioni predefinite e sulle possibilit\[`a] di personalizzazione. .Sh FILE DI CONFIGURAZIONE .Nm da precedenza al file di configurazione specifico dell'utente, .Pa ~/.spectrwm.conf . Se quel file non \[`e] disponibile, tenta di utilizzare il file di configurazione globale .Pa /etc/spectrwm.conf . .Pp Il formato del file \[`e] .Pp .Dl Ar keyword Li = Ar setting .Pp Ad esempio: .Pp .Dl color_focus = red .Pp Le impostazioni di tipo binario possono essere abilitate e disabilitate utilizzando rispettivamente i valori 1 e 0. .Pp I colori devono essere specificati usando il formato usato da .Xr XQueryColor 3 . .Pp I commenti iniziano con #. Qualora fosse necessario usare usare .Ql # come parte di un'opzione, questo dovr\[`a] essere preceduto da un backslash, eg. \e#. .Pp Il file di configurazione supporta le seguenti impostazioni: .Bl -tag -width 2m .It Ic autorun All'avvio, esegue un'applicazione nell'area di lavoro specificata. Il formato da utilizzare \[`e] .Li ws Ns Bo Ar idx Bc : Ns Ar application , eg. ws[2]:xterm esegue .Xr xterm 1 nell'area di lavoro 2. .It Ic bar_action Script esterno che produce informazioni aggiuntive, come ad esempio quelle sullo stato di carica della batteria, da inserire nella barra di stato. .It Ic bar_at_bottom Posiziona la barra di stato in fondo ad ogni regione anzich\['e] in cima. .It Ic bar_borders Ns Bq Ar x Colore del bordo della barra di stato nello schermo .Ar x . .It Ic bar_border_unfocus Ns Bq Ar x Colore del bordo della barra di stato nelle regioni dello schermo .Ar x che non hanno il focus. .It Ic bar_border_width Imposta lo spessore del bordo della barra di stato, in pixel. Il bordo pu\[`o] essere disabilitato usando il valore 0. .It Ic bar_color Ns Bq Ar x Colore di sfondo della barra di stato nello schermo .Ar x . .It Ic bar_enabled Imposta il valore predefinito di .Ic bar_toggle ; il valore predefinito per questa opzione \[`e] 1. .It Ic bar_enabled_ws Ns Bq Ar x Imposta il valore predefinito di .Ic bar_toggle_ws per l'area di lavoro .Ar x ; il valore predefinito per questa opzione \[`e] 1. .It Ic bar_font Font da usare per la barra di stato. Il font pu\[`o] essere specificato usando Xft o X Logical Font Description (XLFD). \[`E] possibile specificare dei font secondari separandoli tra loro con una virgola. Se tutte le specifiche sono nel formato XLFD verranno usati i gruppi di font; se almeno una delle specifiche \[`e] in formato Xft, verr\[`a] usato Xft. Quando Xft \[`e] in uso, verr\[`a] usato solo il primo font caricato con successo anche nel caso in cui non dovesse comprendere tutti i glifi necessari. L'impostazione predefinita prevede l'uso dei gruppi di font; \[`e] bene tenere conto del fatto che .Xr dmenu 1 non supporta Xft. .Pp Esempio con Xft: .Bd -literal -offset indent bar_font = Terminus:style=Regular:pixelsize=14:antialias=true bar_font = -*-profont-medium-*-*-*-11-*-*-*-*-*-*-*,Terminus:pixelsize=14,\ -*-clean-medium-*-*-*-12-*-*-*-*-*-*-* .Ed .Pp Esempio con gruppi di font: .Bd -literal -offset indent bar_font = -*-terminus-medium-*-*-*-14-*-*-*-*-*-*-* bar_font = -*-profont-medium-*-*-*-11-*-*-*-*-*-*-*,\ -*-terminus-medium-*-*-*-14-*-*-*-*-*-*-*,\ -*-clean-medium-*-*-*-12-*-*-*-*-*-*-* .Ed .Pp Per ottenere un elenco dei font disponibili sul proprio sistema, usare .Xr fc-list 1 o .Xr xlsfonts 1 . L'applicazione .Xr xfontsel 1 pu\[`o] essere d'aiuto per il formato XLFD. .It Ic bar_font_color Ns Bq Ar x Colore del testo nella barra di stato nello schermo .Ar x . .It Ic bar_format Imposta il formato da utilizzare per la barra di stato, sovrascrivendo .Ic clock_format e tutte le opzioni che terminano con .Ic enabled . Il formato viene passato a .Xr strftime 3 prima di essere usato, e pu\[`o] contenere le seguenti sequenze di caratteri: .Bl -column "Sequenza di caratteri" "Sostituita con" -offset indent .It Sy "Sequenza di caratteri" Ta Sy "Sostituita con" .It Li "+<" Ta "Lascia uno spazio" .It Li "+A" Ta "Output dello script esterno" .It Li "+C" Ta "Classe della finestra corrente (da WM_CLASS)" .It Li "+D" Ta "Nome dell'area di lavoro corrente" .It Li "+F" Ta "Indicatore di float" .It Li "+I" Ta "Indice dell'area di lavoro corrente" .It Li "+M" Ta "Numero di finestre minimizzate nell'area di lavoro corrente" .It Li "+N" Ta "Numero dello schermo corrente" .It Li "+P" Ta "Classe e istanza della finestra corrente, separate da due punti" .It Li "+S" Ta "Algoritmo di gestione delle finestre in uso" .It Li "+T" Ta "Istanza della finestra corrente (da WM_CLASS)" .It Li "+U" Ta "Indicatore di urgenza" .It Li "+V" Ta "Versione del programma" .It Li "+W" Ta "Nome della finestra corrente (da _NET_WM_NAME o WM_NAME)" .It Li "++" Ta "Il carattere" Ql + .El .Pp Tutte le sequenze di caratteri possono limitare il numero di caratteri massimo utilizzato, eg. +64A. Il testo che non contribuisce a formare una di queste sequenze di caratteri viene copiato senza subire modifiche. .It Ic bar_justify Allinea il test all'interno della barra di stato. I valori possibili sono .Ar left (sinistra), .Ar center (centro) e .Ar right (destra). .Pp Se il valore scelto non \[`e] .Ar left , il testo potrebbe non risultare allineato correttamente. Vedere .Ic bar_format per maggiori informazioni. .It Ic bind Ns Bq Ar x Associa una combinazione di tasti all'azione .Ar x . Vedere la sezione .Sx SCORCIATOIE pi\[`u] sotto. .It Ic border_width Imposta lo spessore del bordo delle finestre, in pixel. Il bordo pu\[`o] essere disabilitato usando il valore 0. .It Ic boundary_width Imposta la larghezza dell'area di contenimento delle regioni, in pixel. Questo valore permette di controllare di quanto una finestra debba essere trascinata o ridimensionata oltre il limite di una regione prima che venga considerata al di fuori di essa. Questa impostazione non viene presa in considerazione quando le finestre vengono manipolate utilizzando la tastiera. L'area di contenimento pu\[`o] essere disabilitata usando il valore 0. .It Ic clock_enabled Abilita o disabilita l'orologio nella barra di stato. Se viene usato il valore 0, \[`e] possibile inserire un orologio personalizzato nello script esterno definito in .Ic bar action . .It Ic iconic_enabled Visualizza nella barra di stato il numero di finestre minimizzate. Questo indicatore pu\[`o] essere abilitato usando il valore 1. .It Ic color_focus Colore del bordo della finestra che detiene il focus. Il colore predefinito \[`e] il rosso. .It Ic color_focus_maximized Colore del bordo della finestra che detiene il focus, se massimizzata. Il colore predefinito \[`e] quello usato per .Ic color_focus . .It Ic color_unfocus Colore del bordo delle finestre che non detengono il focus. Il colore predefinito \[`e] rgb:88/88/88. .It Ic color_unfocus_maximized Colore del bordo delle finestre che non detengono il focus, se massimizzate. Il colore predefinito \[`e] quello usato per .Ic color_unfocus . .It Ic dialog_ratio Alcune applicazioni creano finestre di dialogo troppo piccole per essere utilizzate. Questa opzione indica la percentuale dello schermo da utilizzare per le finestre di dialogo: ad esempio, il valore 0.6 indica che dovr\[`a] essere usato il 60% dello spazio disponibile. .It Ic disable_border Non mostrare i bordi quando la barra di stato \[`e] nascosta e c'\[`e] una sola finestra nella regione. .It Ic focus_close Imposta quale finestra ricever\[`a] il focus in seguito alla chiusura della finestra che lo detiene al momento. I valori possibili sono .Ar first (prima), .Ar next (successiva), .Ar previous (precedente, impostazione predefinita) e .Ar last (ultima). .Ar next e .Ar previous sono intese relativamente alla finestra che \[`e] stata chiusa. .It Ic focus_close_wrap Se abilitata, l'ultima finestra ricever\[`a] il focus quando l'ultima viene chiusa, e viceversa. Questa opzione pu\[`o] essere disabilitata usando il valore 0. .It Ic focus_default Finestra che deve ricevere il focus quando nessun'altra finestra lo detiene. I valori possibili sono .Ar first (prima) e .Ar last (ultima, impostazione predefinita). .It Ic focus_mode Comportamento del focus in relazione al cursore del mouse. I valori possibili sono: .Pp .Bl -tag -width "default" -offset indent -compact .It Ar default Modifica il focus quando viene attraversato un bordo in seguito ad un movimento del cursore o all'interazione con una finestra. .It Ar follow Modifica il focus ogni volta che il cursore attraversa un bordo, anche se questo avviene in seguito al passaggio ad un'area di lavoro diversa o ad un cambio di layout. .It Ar manual Modifica il focus solo quando si interagisce con una finestra. .El .It Ic java_workaround Evita alcuni problemi di rendering nelle GUI Java impersonando il window manager LG3D, scritto da Sun. Il valore predefinito \[`e] 1. .It Ic keyboard_mapping Rimuove tutte le scorciatoie da tastiera esistenti e carica le nuove scorciatoie dal file specificato. Questo permette di caricare scorciatoie specifiche del proprio layout di tastiera. Vedere la sezione .Sx MAPPE DI TASTIERA pi\[`u] sotto per un elenco dei file che vengono forniti. .It Ic layout Imposta il layout da utilizzare all'avvio. Definito nel formato .Li ws Ns Bo Ar idx Bc : Ns Ar master_grow : Ns Ar master_add : Ns Ar stack_inc : Ns Ar always_raise : Ns Ar stack_mode , eg. ws[2]:-4:0:1:0:horizontal configura l'area di lavoro 2 per utilizzare un algoritmo di stacking orizzontale, riduce l'area principale di 4 unit\[`a] e aggiunge una finestra all'area di stacking, mantenendo il comportamento predefinito per le finestre floating. I valori possibili per .Ar stack_mode sono .Ar vertical , .Ar vertical_flip , .Ar horizontal , .Ar horizontal_flip and .Ar fullscreen . Vedere .Ic master_grow , .Ic master_shrink , .Ic master_add , .Ic master_del , .Ic stack_inc , .Ic stack_dec , e .Ic always_raise per maggiori informazioni. Le opzioni che controllano gli algoritmi di gestione delle finestre sono complesse e possono influenzare altre opzioni, quindi \[`e] opportuno prendere confidenza con esse prima di utilizzare l'opzione .Ic layout . .Pp Questa impostazione non viene applicata al riavvio. .It Ic modkey Cambia il modificatore. Solitamente Mod1 \[`e] il tasto Alt e Mod4 il tasto Windows su un PC. .It Ic name Imposta il nome di un'area di lavoro all'avvio. Definito nel formato .Li ws Ns Bo Ar idx Bc : Ns Ar name , eg. ws[1]:Console assegna il nome .Dq Console all'area di lavoro 1. .It Ic program Ns Bq Ar p Definisce la nuova azione .Ar p , corrispondente all'esecuzione di un programma. Vedere la sezione .Sx PROGRAMMI pi\[`u] sotto. .It Ic quirk Ns Bq Ar c Ns Li : Ns Ar i Ns Li : Ns Ar n Aggiunge un "quirk" per le finestre di classe .Ar c , istanza .Ar i e nome .Ar n . Vedere la sezione .Sx QUIRK pi\[`u] sotto. .It Ic region Definisce una regione personalizzata, rimuovendo tutte le regioni create in automatico che occupano la stessa parte dello schermo. Definita nel formato .Li screen Ns Bo Ar idx Ns Bc : Ns Ar width Ns x Ns Ar height Ns + Ns Ar x Ns + Ns Ar y , eg. screen[1]:800x1200+0+0. .Pp Per fare s\[`i] che una regione copra pi\[`u] monitor, \[`e] sufficiente definirla in modo che li occupi tutti, eg. screen[1]:2048x768+0+0 definisce una regione che copre due monitor con risoluzione 1024x768 posizionati uno di fianco all'altro. .It Ic region_padding Larghezza, in pixel, dello spazio lasciato vuoto all'interno di una regione. Questa opzione pu\[`o] essere disabilitata usando il valore 0. .It Ic spawn_position Posizione da assegnare alle finestre al momento della loro creazione. I valori possibili sono .Ar first (prima), .Ar next (successiva), .Ar previous (precedente) e .Ar last (ultima, predefinito). .Ar next e .Ar previous sono intese relativamente alla finestra che detiene il focus. .It Ic stack_enabled Abilita o disabilita la visualizzazione dell'algoritmo di stacking in uso all'interno della barra di stato. .It Ic term_width Imposta la larghezza minima desiderata per i terminali. Se il valore \[`e] maggiore di 0, .Nm cercher\[`a] di regolare la dimensione del font usato dal terminale per fare s\[`i] che la larghezza rimanga superiore ad esso mentre la finestra viene ridimensionata. Solo .Xr xterm 1 \[`e] supportato al momento. L'eseguibile di .Xr xterm 1 non deve avere i bit setuid o setgid abilitati, contrariamente a quanto avviene nella maggior parte dei sistemi. L'utente potrebbe dover creare una copia dell'eseguibile di .Xr xterm 1 , priva dei bit setuid e setgid, e modificare program[term] (vedere la sezione .Sx PROGRAMMI pi\[`u] sotto) in modo che punti a questa copia. .It Ic title_gap Larghezza, in pixel, dello spazio lasciato vuoto tra una finestra e l'altra. L'utilizzo di un valore negativo fa s\[`i] che le finestre si sovrappongano. Se il valore impostato \[`e] l'opposto di .Ic border_width , non verr\[`a] visualizzato alcun bordo tra le finestre. Questa opzione pu\[`o] essere disabilitata usando il valore 0. .It Ic urgent_collapse Disabilita la visualizzazione di un testo sostitutivo per le aree di lavoro che non contengono finestre urgenti. Questa opzione pu\[`o] essere abilitata usando il valore 1. .It Ic urgent_enabled Abilita o disabilita la visualizzazione dell'indicatore di urgenza all'interno della barra di stato. Molti emulatore di terminale devono essere configurati esplicitamente per fare s\[`i] che il carattere "bell" causi la modifica dello stato di urgenza della finestra. Ad esempio, in .Xr xterm 1 , \[`e] necessario aggiungere al file .Pa .Xdefaults la seguente riga: .Bd -literal -offset indent xterm.bellIsUrgent: true .Ed .It Ic verbose_layout Abilita o disabilita la visualizzazione del numero di finestre nell'area principale e del numero di righe (o colonne) nell'area di stacking. Questa opzione pu\[`o] essere disabilitata usando il valore 1. Vedre .Ar master_add , .Ar master_del , .Ar stack_inc e .Ar stack_dec per maggiori informazioni. .It Ic window_class_enabled Abilita o disabilita la visualizzazione del nome della classe (da WM_CLASS) all'interno della barra di stato. Questa opzione pu\[`o] essere abilitata usando il valore 1. .It Ic window_instance_enabled Abilita o disabilita la visualizzazione del nome dell'istanza (da WM_CLASS) all'interno della barra di stato. Questa opzione pu\[`o] essere abilitata usando il valore 1. .It Ic window_name_enabled Abilita o disabilita la visualizzazione del titolo della finestra (da _NET_WM_NAME o WM_NAME) all'interno della barra di stato. Questa opzione pu\[`o] essere abilitata usando il valore 1. .Pp Per impedire che titoli di finestra troppo lunghi impediscano di visualizzare altre informazioni, lo spazio dedicato al titolo \[`e] limitato a 64 caratteri. Vedere .Ic bar_format per maggiori informazioni. .It Ic warp_pointer Posiziona il cursore del mouse al centro della finestra che ha il focus quando vengono utilizzate scorciatoie da tastiera per modificare il focus, cambiare area di lavoro, cambiare regione, etc. Questa opzione pu\[`o] essere abilitata usando il valore 1. .It Ic workspace_limit Imposta il numero di aree di lavoro disponibili. Il valore minimo \[`e] 1, quello massimo \[`e] 22, quello predefinito \[`e] 10. .El .Sh PROGRAMMI .Nm consente di definire azioni personalizzate per l'esecuzione di programmi, e di assegnare queste azioni a scorciatoie da tastiera come \[`e] possibile per quelle predefinite. Vedere la sezione .Sx SCORCIATOIE pi\[`u] sotto. .Pp I programmi vengono definiti come segue: .Pp .Dl program Ns Bo Ar action Bc = Ar progpath Op Ar arg Op Ar arg ... .Pp .Ar action \[`e] qualsiasi identificatore che non vada in conflitto con una delle azioni predefinite, .Ar progpath \[`e] il percorso del programma da eseguire e .Ar arg sono gli argomenti (uno o pi\[`u]) da passare al programma. .Pp Se il percorso o gli argomenti comprendono il carattere .Ql # , questo dovr\[`a] essere preceduto da un carattere di escape, diventando \e#. .Pp I seguenti argomenti verranno sostituiti, al momento dell'esecuzione, con il valore assegnato all'opzione corrispondente: .Pp .Bl -tag -width "$bar_font_color" -offset indent -compact .It Cm $bar_border .It Cm $bar_color .It Cm $bar_font .It Cm $bar_font_color .It Cm $color_focus .It Cm $color_unfocus .It Cm $dmenu_bottom \-b se .Ic bar_at_bottom \[`e] abilitato .It Cm $region_index .It Cm $workspace_index .El .Pp Esempio: .Bd -literal -offset indent program[ff] = /usr/local/bin/firefox http://spectrwm.org/ bind[ff] = MOD+Shift+b # Ora M-S-b esegue Firefox .Ed .Pp Per eliminare la scorciatoia appena definita: .Bd -literal -offset indent bind[] = MOD+Shift+b .Ed .Pp Programmi predefiniti: .Pp .Bl -tag -width "screenshot_wind" -offset indent -compact .It Cm lock xlock .It Cm menu dmenu_run $dmenu_bottom \-fn $bar_font \-nb $bar_color \-nf $bar_font_color \-sb $bar_border \-sf $bar_color .It Cm term xterm .It Cm initscr initscreen.sh # opzionale .It Cm screenshot_all screenshot.sh full # opzionale .It Cm screenshot_wind screenshot.sh window # opzionale .El .Pp I programmi opzionali non verranno verificati a meno di non essere ridefiniti dall'utente. Se uno dei programmi predefiniti fallisce la verifica, \[`e] possibile risolvere l'errore installando il programma corrispondente, modificando il percorso del programma o disabilitando la scorciatoia relativa. .Pp Ad esempio, per ridefinire .Ic lock : .Bd -literal -offset indent program[lock] = xscreensaver\-command \-lock .Ed .Pp Per disabilitare la scorciatoia assegnata a .Ic lock ed impedirne la verifica: .Bd -literal -offset indent bind[] = MOD+Shift+Delete .Ed .Sh SCORCIATOIE .Nm fornisce numerose azioni che possono essere attivate usando combinazioni di tasti sulla tastiera o sul mouse. .Pp Le combinazioni di tasti che coinvolgono il mouse sono le seguenti: .Pp .Bl -tag -width "M-j, M-XXX" -offset indent -compact .It Cm M1 Assegna il focus ad una finestra .It Cm M-M1 Sposta una finestra .It Cm M-M3 Ridimensiona una finestra .It Cm M-S-M3 Ridimensiona una finestra, mantenendola centrata .El .Pp Le scorciatoie da tastiera predefinite sono le seguenti: .Pp .Bl -tag -width "M-j, M-XXXXXX" -offset indent -compact .It Cm M-S- Ns Aq Cm Return term .It Cm M-p menu .It Cm M-S-q quit .It Cm M-q restart .It Cm M- Ns Aq Cm Space cycle_layout .It Cm M-S-\e flip_layout .It Cm M-S- Ns Aq Cm Space stack_reset .It Cm M-h master_shrink .It Cm M-l master_grow .It Cm M-, master_add .It Cm M-. master_del .It Cm M-S-, stack_inc .It Cm M-S-. stack_dec .It Cm M- Ns Aq Cm Return swap_main .It Xo .Cm M-j , .Cm M- Ns Aq Cm TAB .Xc focus_next .It Xo .Cm M-k , .Cm M-S- Ns Aq Cm TAB .Xc focus_prev .It Cm M-m focus_main .It Cm M-u focus_urgent .It Cm M-S-j swap_next .It Cm M-S-k swap_prev .It Cm M-b bar_toggle .It Cm M-S-b bar_toggle_ws .It Cm M-x wind_del .It Cm M-S-x wind_kill .It Cm M- Ns Aq Ar 1-9,0,F1-F12 .Pf ws_ Aq Ar 1-22 .It Cm M-S- Ns Aq Ar 1-9,0,F1-F12 .Pf mvws_ Ns Aq Ar 1-22 .It Cm M- Ns Aq Ar Keypad 1-9 .Pf rg_ Aq Ar 1-9 .It Cm M-S- Ns Aq Ar Keypad 1-9 .Pf mvrg_ Aq Ar 1-9 .It Cm M- Ns Aq Cm Right ws_next .It Cm M- Ns Aq Cm Left ws_prev .It Cm M- Ns Aq Cm Up ws_next_all .It Cm M- Ns Aq Cm Down ws_prev_all .It Cm M-a ws_next_move .It Cm M-S- Ns Aq Cm Left ws_prev_move .It Cm M-S- Ns Aq Cm Up ws_prior .It Cm M-S- Ns Aq Cm Right rg_next .It Cm M-S- Ns Aq Cm Left rg_prev .It Cm M-s screenshot_all .It Cm M-S-s screenshot_wind .It Cm M-S-v version .It Cm M-t float_toggle .It Cm M-S- Ns Aq Cm Delete lock .It Cm M-S-i initscr .It Cm M-w iconify .It Cm M-S-w uniconify .It Cm M-e maximize_toggle .It Cm M-S-r always_raise .It Cm M-v button2 .It Cm M-- width_shrink .It Cm M-= width_grow .It Cm M-S-- height_shrink .It Cm M-S-= height_grow .It Cm M-[ move_left .It Cm M-] move_right .It Cm M-S-[ move_up .It Cm M-S-] move_down .It Cm M-S-/ name_workspace .It Cm M-/ search_workspace .It Cm M-f search_win .El .Pp Le azioni predefinite disponibili sono le seguenti: .Pp .Bl -tag -width "M-j, M-XXXX" -offset indent -compact .It Cm term Esegue un terminale (vedere la sezione .Sx PROGRAMMI pi\[`u] in alto). .It Cm menu Mostra il menu (vedere la sezione .Sx PROGRAMMI pi\[`u] in alto). .It Cm quit Chiude .Nm . .It Cm restart Riavvia .Nm . .It Cm cycle_layout Passa al layout successivo. .It Cm flip_layout Inverte l'area principale e quella di stacking. .It Cm stack_reset Riporta il layout al suo stato iniziale. .It Cm master_shrink Riduce la dimensione dell'area principale. .It Cm master_grow Aumenta la dimensione dell'area principale. .It Cm master_add Aggiunge una finestra all'area principale. .It Cm master_del Rimuove una finestra dall'area principale. .It Cm stack_inc Aggiunge una riga (o colonna) all'area di stacking. .It Cm stack_dec Rimuove una riga (o colonna) dall'area di stacking. .It Cm swap_main Sposta la finestra corrente nell'area principale. .It Cm focus_next Assegna il focus alla finestra successiva. .It Cm focus_prev Assegna il focus alla finestra precedente. .It Cm focus_main Assegna il focus alla finestra principale dell'area di lavoro. .It Cm focus_urgent Assegna il focus alla finestra urgente successiva. Verr\[`a] effettuato, se necessario, il passaggio ad un'altra area di lavoro. .It Cm swap_next Inverte la finestra corrente con quella successiva. .It Cm swap_prev Inverte la finestra corrente con quella precedente. .It Cm bar_toggle Modifica la visibilit\[`a] della barra di stato a livello globale. .It Cm bar_toggle_ws Modifica la visibilit\[`a] della barra di stato nell'area di lavoro corrente. .It Cm wind_del Chiude la finestra corrente. .It Cm wind_kill Distrugge la finestra corrente. .It Cm ws_ Ns Ar n Passa all'area di lavoro .Ar n , dove .Ar n \[`e] un valore compreso tra 1 e .Ic workspace_limit . .It Cm mvws_ Ns Ar n Sposta la finestra corrente nell'area di lavoro .Ar n , dove .Ar n \[`e] un numero compreso tra 1 e .Ic workspace_limit . .It Cm rg_ Ns Ar n Assegna il focus alla regione .Ar n , dove .Ar n \[`e] un numero compreso tra 1 e 9. .It Cm mvrg_ Ns Ar n Sposta la finestra corrente nella regione .Ar n , dove .Ar n \[`e] un numero compreso tra 1 e 9. .It Cm ws_next Passa all'area di lavoro non vuota successiva. .It Cm ws_prev Passa all'area di lavoro non vuota precedente. .It Cm ws_next_all Passa all'area di lavoro successiva. .It Cm ws_prev_all Passa all'area di lavoro precedente. .It Cm ws_next_move Passa all'area di lavoro successiva, spostando allo stesso tempo la finestra corrente. .It Cm ws_prev_move Passa all'area di lavoro precedente, spostando allo stesso tempo la finestra corrente. .It Cm ws_prior Passa all'ultima area di lavoro visitata. .It Cm rg_next Passa alla regione successiva. .It Cm rg_prev Passa alla regione precedente. .It Cm screenshot_all Cattura l'intera schermata chiamando l'apposito script (vedere la sezione .Sx PROGRAMMI pi\[`u] in alto). .It Cm screenshot_wind Cattura una singola finestra chiamando l'apposito script (vedere la sezione .Sx PROGRAMMI pi\[`u] in alto). .It Cm version Modifica la visibilit\[`a] del numero di versione all'interno della barra di stato. .It Cm float_toggle Modifica la finestra che detiene il focus, portandola da floating a tiled e viceversa. .It Cm lock Blocca lo schermo (vedere la sezione .Sx PROGRAMMI pi\[`u] in alto). .It Cm initscr Inizializza nuovamente tutti gli schermi (vedere la sezione .Sx PROGRAMMI pi\[`u] in alto). .It Cm iconify Minimizza la finestra che detiene il focus. .It Cm uniconify Ripristina la finestra selezionata tramite .Xr dmenu 1 . .It Cm maximize_toggle Modifica lo stato di massimizzazione della finestra che detiene il focus. .It Cm always_raise Se impostato, le finestre tiled possono oscurare le finestre floating. .It Cm button2 Simula la pressione del tasto centrale del mouse. .It Cm width_shrink Riduce la larghezza di una finestra floating. .It Cm width_grow Aumenta la larghezza di una finestra floating. .It Cm height_shrink Riduce l'altezza di una finestra floating. .It Cm height_grow Aumenta l'altezza di una finestra floating. .It Cm move_left Sposta una finestra floating verso sinistra di un'unit\[`a]. .It Cm move_right Sposta una finestra floating verso destra di un'unit\[`a]. .It Cm move_up Sposta una finestra floating verso l'alto di un'unit\[`a]. .It Cm move_down Sposta una finestra floating verso il basso di un'unit\[`a]. .It Cm name_workspace Assegna un nome all'area di lavoro corrente. .It Cm search_workspace Cerca un'area di lavoro. .It Cm search_win Cerca una finestra all'interno dell'area di lavoro corrente. .El .Pp Le scorciatoie da tastiera personalizzate vengono definite come segue: .Pp .Dl bind Ns Bo Ar action Bc = Ar keys .Pp .Ar action \[`e] una delle azioni predefinite descritte sopra, oppure la stringa vuota (per disabilitare la scorciatoia). .Ar keys \[`e] composta da uno o pi\[`u] tasti modificatore (eg. MOD, Mod1, Shift, etc.) e uno o pi\[`u] tasti normali (eg. b, Space, etc.) separati da .Ql + . .Pp Esempio: .Bd -literal -offset indent bind[reset] = Mod4+q # Assegna l'azione reset alla scorciatoia Win+q bind[] = Mod1+q # disabilita la scorciatoia predefinita Alt+q .Ed .Pp Per usare il valore dell'opzione .Ic modkey in una scorciatoia, specificare MOD come modificatore. .Pp Pi\[`u] scorciatoie possono essere assegnate alla stessa azione. .Pp Per usare dei caratteri non latini, come \[oa] o \[*p], all'interno di una scorciatoia, \[`e] necessario specificare il nome xkb del carattere anzich\['e] il carattere stesso. Eseguendo .Xr xev 1 e premendo un tasto mentre la finestra del programma detiene il focus, \[`e] possibile leggere il nome xkb corrispondente al tasto premuto. Ad esempio, per \[oa]: .Bd -literal -offset indent KeyPress event, serial 41, synthetic NO, window 0x2600001, root 0x15a, subw 0x0, time 106213808, (11,5), root:(359,823), state 0x0, keycode 24 (keysym 0xe5, aring), same_screen YES, XLookupString gives 2 bytes: (c3 a5) "\[oa]" XmbLookupString gives 2 bytes: (c3 a5) "\[oa]" XFilterEvent returns: False .Ed .Pp Il nome xkb \[`e] aring. Quindi, all'interno di .Pa spectrwm.conf sar\[`a] possibile aggiungere la seguente riga: .Bd -literal -offset indent bind[program] = MOD+aring .Ed .Sh MAPPE DI TASTIERA \[`E] possibile caricare le scorciatoie da un file di configurazione separato tramite l'opzione .Ic keyboard_mapping : questo consente di utilizzare scorciatoie specifiche del proprio layout di tastiera. .Pp Vengono forniti i seguenti file: .Pp .Bl -tag -width "spectrwm_XX.confXXX" -offset indent -compact .It Cm spectrwm_cz.conf Layout per tastiere ceche .It Cm spectrwm_es.conf Layout per tastiere spagnole .It Cm spectrwm_fr.conf Layout per tastiere francesi .It Cm spectrwm_fr_ch.conf Layout per tastiere francesi (Svizzera) .It Cm spectrwm_se.conf Layout per tastiere svedesi .It Cm spectrwm_us.conf Layout per tastiere americane .El .Sh QUIRK \[`E] possibile specificare "quirk" da applicare a quelle finestre (ad esempio applicazioni a schermo intero e finestre di dialogo) che richiedono un comportamento speciale da parte di un gestore di finestre tiling come .Nm . .Pp La configurazione predefinita, per quanto riguarda i quirk, \[`e] la seguente: .Pp .Bl -tag -width "OpenOffice.org N.M:VCLSalFrameXXX" -offset indent \ -compact .It Firefox\-bin:firefox\-bin TRANSSZ .It Firefox:Dialog FLOAT .It Gimp:gimp FLOAT + ANYWHERE .It MPlayer:xv FLOAT + FULLSCREEN + FOCUSPREV .It OpenOffice.org 2.4:VCLSalFrame FLOAT .It OpenOffice.org 3.1:VCLSalFrame FLOAT .It pcb:pcb FLOAT .It xine:Xine Window FLOAT + ANYWHERE .It xine:xine Panel FLOAT + ANYWHERE .It xine:xine Video Fullscreen Window FULLSCREEN + FLOAT .It Xitk:Xitk Combo FLOAT + ANYWHERE .It Xitk:Xine Window FLOAT + ANYWHERE .It XTerm:xterm XTERM_FONTADJ .El .Pp I quirk disponibili sono i seguenti: .Pp .Bl -tag -width "XTERM_FONTADJXXX" -offset indent -compact .It FLOAT Le finestre saranno sempre floating. .It TRANSSZ Modifica la dimensione delle finestre di dialogo in base al valore di .Ic dialog_ratio (vedere la sezione .Sx FILE DI CONFIGURAZIONE pi\[`u] in alto). .It ANYWHERE Consenti alle finestre di decidere la propria posizione. .It XTERM_FONTADJ Regola la dimensione dei font di .Xr xterm 1 quando la dimensione delle finestre viene modificata. .It FULLSCREEN Non mostrare il bordo. .It FOCUSPREV Alla chiusura di una finestra, il focus verr\[`a] assegnato alla finestra che lo deteneva in precedenza e non alla finestra precedente nello stack. .It NOFOCUSONMAP Non assegnare il focus alle finestre quando vengono create. Questo quirk viene ignorato se il valore di .Ic focus_mode \[`e] .Ar follow . .It FOCUSONMAP_SINGLE Assegna il focus alle finestre quando vengono create solo se non sono gi\[`a] presenti delle finestre con la stessa classe e istanza nell'area di lavoro. Questo quirk viene ignorato se il valore di .Ic focus_mode \[`e] .Ar follow . .It OBEYAPPFOCUSREQ Assegna il focus alle finestre quando viene richiesto tramite un messaggio di tipo _NET_ACTIVE_WINDOW con sorgente 1. Se la sorgente \[`e] 0 (non specificato) o 2 (pager), la richiesta viene sempre accolta. .It IGNOREPID Ignora il PID nella scelta dell'area di lavoro iniziale per le nuove finestre. Molto utile per le applicazioni (eg. terminali) che creano pi\[`u] finestre all'interno dello stesso processo. .It IGNORESPAWNWS Ignora l'area di lavoro in cui \[`e] stata eseguita la scorciatoia da tastiera nella scelta dell'area di lavoro iniziale per le nuove finestre. .It WS Ns Bq Ar n Obbliga le nuove finestre ad essere assegnate all'area di lavoro .Ar n . .El .Pp I quirk personalizzati vengono definiti come segue: .Pp .Dl quirk Ns Bo Ar class Ns Bo : Ns Ar instance Ns Bo : Ns Ar name Bc Bc Bc = Ar quirk Op + Ar quirk ... .Pp .Ar class , .Ar instance (opzionale) e .Ar name (opzionale) sono dei pattern che vengono usati per determinare a quali finestre i quirk debbano essere applicati e .Ar quirk \[`e] uno dei quirk descritti in precedenza. .Pp I pattern vengono interpretati come espressioni regolari estese POSIX. I simboli ':', '[' e ']' devono essere preceduti da '\\' per essere considerati letteralmente. Vedere .Xr regex 7 per ulteriori informazioni sulle espressioni regolari estese POSIX. .Pp Ad esempio: .Bd -literal -offset indent quirk[MPlayer] = FLOAT + FULLSCREEN + FOCUSPREV # Le finestre con \ classe 'MPlayer' sono floating quirk[.*] = FLOAT # Le finestre sono floating quirk[.*:.*:.*] = FLOAT # Come sopra quirk[Firefox:Navigator] = FLOAT # Le finestre di navigazione di \ Firefox sono floating quirk[::Console] = FLOAT # Le finestre la cui classe non \[`e] impostata \ e il cui nome \[`e] 'Console' sono floating quirk[\\[0-9\\].*:.*:\\[\\[\\:alnum\\:\\]\\]*] = FLOAT # Le finestre la \ cui classe inizia con un numero e il cui nome \[`e] non definito o \ contiene solo caratteri alfanumerici, senza spazi, sono floating quirk[pcb:pcb] = NONE # Rimuove i quirk predefiniti .Ed \[`E] possibile ottenere .Ar class , .Ar instance e .Ar name eseguendo .Xr xprop 1 e selezionando la finestra desiderata. In questo esempio, \[`e] stata selezionata la finestra principale di Firefox: .Bd -literal -offset indent $ xprop | grep \-E "^(WM_CLASS|_NET_WM_NAME|WM_NAME)" WM_CLASS(STRING) = "Navigator", "Firefox" WM_NAME(STRING) = "spectrwm - ConformalOpenSource" _NET_WM_NAME(UTF8_STRING) = "spectrwm - ConformalOpenSource" .Ed .Pp Il comando .Xr xprop 1 visualizza WM_CLASS nel seguente formato: .Bd -literal -offset indent WM_CLASS(STRING) = "", "" .Ed .Pp In questo caso, bisognerebbe aggiungere al file di configurazione la seguente riga: .Bd -literal -offset indent quirk[Firefox:Navigator] = FLOAT .Ed .Pp .Nm assegna alcuni quirk in automatico, basandosi sul valore della propriet\[`a] _NET_WM_WINDOW_TYPE, nel seguente modo: .Pp .Bl -tag -width "_NET_WM_WINDOW_TYPE_TOOLBARXXX" -offset indent -compact .It _NET_WM_WINDOW_TYPE_DOCK FLOAT + ANYWHERE .It _NET_WM_WINDOW_TYPE_TOOLBAR FLOAT + ANYWHERE .It _NET_WM_WINDOW_TYPE_UTILITY FLOAT + ANYWHERE .It _NET_WM_WINDOW_TYPE_SPLASH FLOAT .It _NET_WM_WINDOW_TYPE_DIALOG FLOAT .El .Pp In tutti gli altri casi gli unici quirk assegnati alle finestre saranno quelli predefiniti o, con precedenza maggiore, quelli specificati nel file di configurazione. .Sh EWMH .Nm implementa in maniera parziale la specifica EWMH, Extended Window Manager Hints: questo consente di controllare le finestre, oltre che .Nm stesso, da script e programmi esterni. Per sfruttare questo supporto, .Nm dovr\[`a] ricevere degli eventi di tipo ClientMessage; questo tipo di messaggio pu\[`o] essere inviato dalla riga di comando usando tool come .Xr wmctrl 1 e .Xr xdotool 1 . Il formato dei messaggi \[`e] definito nella specifica EWMH. .Pp L'identificativo della finestra che detiene il focus pu\[`o] essere ottenuto leggendo il valore della propriet\[`a] _NET_ACTIVE_WINDOWS della root window. Ad esempio, per visualizzare il titolo della finestra: .Bd -literal -offset indent $ WINDOWID=`xprop \-root _NET_ACTIVE_WINDOW | grep \-o "0x.*"` $ xprop \-id $WINDOWID _NET_WM_NAME | grep \-o "\\".*\\"" .Ed .Pp Il focus pu\[`o] essere assegnato ad una finestra inviando un messaggio di tipo _NET_ACTIVE_WINDOW alla root window, ad esempio: .Bd -literal -offset indent $ wmctrl \-i \-a 0x4a0000b .Ed .Pp Le finestre possono essere chiuse inviando un messaggio di tipo _NET_CLOSE_WINDOW, ad esempio: .Bd -literal -offset indent $ wmctrl \-i \-c 0x4a0000b .Ed .Pp Le finestre possono essere portate da floating a tiled, e viceversa, aggiungendo o rimuovendo l'atomo _NET_WM_STATE_ABOVE dalla propriet\[`a] _NET_WM_STATE della finestra. Per fare ci\[`o], \[`e] necessario inviare un messaggio di tipo _NET_WM_STATE, ad esempio: .Bd -literal -offset indent $ wmctrl \-i \-r 0x4a0000b \-b toggle,_NET_WM_STATE_ABOVE .Ed .Pp Le finestre possono essere minimizzate e ripristinate sostituendo _NET_WM_STATE_HIDDEN a _NET_WM_STATE_ABOVE nell'esempio precedente: .Bd -literal -offset indent $ wmctrl \-i \-r 0x4a0000b \-b toggle,_NET_WM_STATE_HIDDEN .Ed .Pp Le finestre floating possono essere ridimensionate e spostate tramite l'invio di un messaggio di tipo _NET_MOVERESIZE_WINDOW, ad esempio: .Bd -literal -offset indent $ wmctrl \-i \-r 0x4a0000b \-e 0,100,50,640,480 .Ed In questo caso, la con id 0x4a0000b finestra viene spostata in (100,50) e la sua dimensione diventa 640x480. .Pp I messaggi di tipo _NET_MOVERESIZE_WINDOW che fanno riferimento a finestre tiled verranno ignorati. .Sh SEGNALI Il segnale HUP fa riavviare .Nm . .Sh FILE .Bl -tag -width "/etc/spectrwm.confXXX" -compact .It Pa ~/.spectrwm.conf File di configurazione specifico dell'utente. .Nm . .It Pa /etc/spectrwm.conf File di configurazione globale. .El .Sh ORIGINE .Nm \[`e] ispirato a xmonad & dwm. .Sh AUTORI .An -nosplit .Nm \[`e] stato scritto da: .Pp .Bl -tag -width "Ryan Thomas McBride Aq mcbride@countersiege.com " -offset \ indent -compact .It Cm Marco Peereboom Aq marco@peereboom.us .It Cm Ryan Thomas McBride Aq mcbride@countersiege.com .It Cm Darrin Chandler Aq dwchandler@stilyagin.com .It Cm Pierre-Yves Ritschard Aq pyr@spootnik.org .It Cm Tuukka Kataja Aq stuge@xor.fi .It Cm Jason L. Wright Aq jason@thought.net .It Cm Reginald Kennedy Aq rk@rejii.com .It Cm Lawrence Teo Aq lteo@lteo.net .It Cm Tiago Cunha Aq tcunha@gmx.com .It Cm David Hill Aq dhill@mindcry.org .El spectrwm-SPECTRWM_3_5_1/outdated_man_pages/spectrwm_pt.1000066400000000000000000000360631453034271100233360ustar00rootroot00000000000000.\" Copyright (c) 2009 Marco Peereboom .\" Copyright (c) 2009 Darrin Chandler .\" .\" Permission to use, copy, modify, and distribute this software for any .\" purpose with or without fee is hereby granted, provided that the above .\" copyright notice and this permission notice appear in all copies. .\" .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .Dd $Mdocdate$ .Dt SPECTRWM 1 .Os .Sh NOME .Nm spectrwm .Nd gerenciador de janela para o X11 .Sh SINOPSE .Nm spectrwm .Sh DESCRI\(,C\(~AO .Nm \('e um gerenciador de janela minimalista que tenta n\(~ao atrapalhar a valorosa forma real da tela para que essa possa ser usada para coisas muito mais importantes. Tem sensatos defaults e n\(~ao requer que algu\('em aprenda uma linguagem de programa\(,c\(~ao para fazer qualquer configura\(,c\(~ao. Ele foi escrito por hackers para hackers e esfor\(,ca-se em ser pequeno, compacto e r\('apido. .Pp Quando o .Nm inicia, ele l\(^e as configura\(,c\(~oes do seu arquivo de configura\(,c\(~ao, .Pa spectrwm.conf . Veja a se\(,c\(~ao .Sx ARQUIVOS DE CONFIGURA\(,C\(~AO logo abaixo. .Pp A seguinte nota\(,c\(~ao \('e usada por toda essa p\('agina: .Pp .Bl -tag -width Ds -offset indent -compact .It Cm M Meta .It Cm S Shift .It Aq Cm Name Nome da tecla .It Cm M1 Bot\(~ao 1 do mouse .It Cm M3 Bot\(~ao 3 do mouse .El .Pp .Nm \('e muito simples de usar. Muitas das a\(,c\(~oes s\(~ao iniciadas por atalhos do mouse ou do teclado. Veja a se\(,c\(~ao .Sx ATALHOS logo abaixo para os defaults e as personaliza\(,c\(~oes. .Sh ARQUIVOS DE CONFIGURA\(,C\(~AO .Nm primeiro tenta abrir o arquivo de configura\(,c\(~ao no diret\('orio do usu\('ario, .Pa ~/.spectrwm.conf . Se o arquivo n\(~ao estiver dispon\('ivel, ent\(~ao tenta abrir o arquivo de configura\(,c\(~ao global .Pa /etc/spectrwm.conf . .Pp Assim \('e o formato do arquivo:\*(Ltpalavra-chave\*(Gt = \*(Ltconfigura\(,c\(~ao\*(Gt. Por exemplo: .Pp .Dl color_focus = red .Pp Para habilitar ou desabilitar uma op\(,c\(~ao usa-se o 1 ou 0, respectivamente. .Pp O arquivo suporta as seguintes palavras-chave: .Pp .Bl -tag -width "title_class_enabledXXX" -offset indent -compact .It Cm color_focus Cor da borda da janela atualmente focada. .It Cm color_unfocus Cor da borda das janelas fora de foco. .It Cm bar_enabled Habilita ou desabilita a barra de status. .It Cm bar_border Ns Bq Ar x Cor da borda da barra de status na tela .Ar x . .It Cm bar_color Ns Bq Ar x Cor da janela da barra de status na tela .Ar x . .It Cm bar_font_color Ns Bq Ar x Cor da fonte na barra de status na tela .Ar x . .It Cm bar_font Fonte da barra de status. .It Cm bar_action Script externo que preenche a barra de status com informa\(,c\(~oes adicionais, como tempo de vida da bateria. .It Cm bar_at_bottom Coloca a barra de status na parte inferior de cada regi\(~ao, ao inv\('es da parte superior. .It Cm stack_enabled Habilita ou desabilita mostrar o atual algor\('itmo de empilhamento na barra de status. .It Cm clock_enabled Habilita ou desabilita mostrar o rel\('ogio na barra de status. Desabilite configurando para 0, ent\(~ao um rel\('ogio personalizado pode ser usado no script bar_action. .It Cm dialog_ratio Algumas aplica\(,c\(~oes tem janelas de di\('alogo que s\(~ao muito pequenas para serem \('uteis. Essa taxa \('e o tamanho da tela para o qual elas ser\(~ao redimencionadas. Por exemplo, 0.6 equivale a 60% do tamanho da tela f\('isica. .It Cm region Aloca uma regi\(~ao personalizada, removendo qualquer regi\(~ao automaticamente detectada que ocupe o mesmo espa\(,co na tela. Definido no formato screen[]:WIDTHxHEIGHT+X+Y, e.g.\& screen[1]:800x1200+0+0. .It Cm term_width Configura a largura m\('inima preferida para o terminal Se esse valor for maior do que 0, .Nm vai tentar ajustar os tamanhos da fonte no terminal para manter a largura do terminal acima desse n\('umero enquanto a janela \('e redimencionada. Apenas o .Xr xterm 1 \('e suportado atualmente. O bin\('ario do .Xr xterm 1 n\(~ao deve ser setuid ou setgid, que \('e o default em muitos sistemas. Os usu\('arios podem precisar de configurar program[term] (veja a se\(,c\(~ao .Sx PROGRAMAS ) para usar uma c\('opia alternativa do bin\('ario do .Xr xterm 1 sem o bit setgid ativado. .It Cm title_class_enabled Habilita ou desabilita mostrar a classe da janela na barra de status. Habilite configurando para 1. .It Cm title_name_enabled Habilita ou desabilita mostrar o t\('itulo da janela na barra de status. Habilite configurando para 1. .It Cm window_name_enabled Habilita ou desabilita mostrar a nome da janela na barra de status. Habilite configurando para 1. .It Cm modkey Muda a tecla de modifica\(,c\(~ao. Mod1 \('e geralmente a tecla ALT e Mod4 \('e a tecla windows em um PC. .It Cm focus_mode Usar um valor de follow_cursor vai fazer o gerenciador de janela focar a janela sob o mouse quando trocando \('areas de trabalho e criando janelas. .It Cm disable_border Remove a borda quando a barra estiver desabilitada e houver apenas uma janela na tela. .It Cm program Ns Bq Ar p Define uma nova a\(,c\(~ao para executar um programa .Ar p . Veja a se\(,c\(~ao .Sx PROGRAMAS logo abaixo. .It Cm bind Ns Bq Ar x Cria uma combina\(,c\(~ao de teclas de atalho para a a\(,c\(~ao .Ar x . Veja a se\(,c\(~ao .Sx ATALHOS logo abaixo. .It Cm quirk Ns Bq Ar c:n Adicione "quirk" para janelas com classe .Ar c e nome .Ar n . Veja a se\(,c\(~ao .Sx QUIRKS logo abaixo. .El .Pp Cores precisam ser especificadas pela especifica\(,c\(~ao .Xr XQueryColor 3 e fontes pela especifica\(,c\(~ao .Xr XQueryFont 3 . .Pp Para listar as fontes dispon\('iveis em seu sistema veja o manual do .Xr fc-list 1 ou do .Xr xlsfonts 1 . A aplica\(,c\(~ao .Xr xfontsel 1 pode te ajudar a mostrar a X Logical Font Description ("XLFD") usada na configura\(,c\(~ao da palavra-chave bar_font. .Sh PROGRAMAS .Nm te permite definir a\(,c\(~oes personalizadas para executar programas de sua escolha e ent\(~ao criar um atalho para elas da mesma forma que as a\(,c\(~oes embutidas. Veja a se\(,c\(~ao .Sx ATALHOS logo abaixo. .Pp Os programas default s\(~ao descritos abaixo: .Pp .Bl -tag -width "screenshot_wind" -offset indent -compact .It Cm term xterm .It Cm screenshot_all screenshot.sh full .It Cm screenshot_wind screenshot.sh window .It Cm lock xlock .It Cm initscr initscreen.sh .It Cm menu dmenu_run \-fn $bar_font \-nb $bar_color \-nf $bar_font_color \-sb $bar_border \-sf $bar_color .El .Pp Programas personalizados no arquivo de configura\(,c\(~ao s\(~ao especificados da seguinte maneira: .Pp .Dl program[] = [ [... ]] .Pp .Aq name \('e um identificador qualquer que n\(~ao conflite com uma a\(,c\(~ao ou palavra-chave embutida, .Aq progpath \('e o programa desejado, e .Aq arg \('e zero ou mais argumentos para o programa. .Pp As seguintes vari\('aveis representam valores configur\('aveis no .Nm (veja a se\(,c\(~ao .Sx ARQUIVOS DE CONFIGURA\(,C\(~AO logo acima), e podem ser usadas nos campos .Aq arg e ser\(~ao substitu\('idas pelos valores na hora em que o programa for executado: .Pp .Bl -tag -width "$bar_font_color" -offset indent -compact .It Cm $bar_border .It Cm $bar_color .It Cm $bar_font .It Cm $bar_font_color .It Cm $color_focus .It Cm $color_unfocus .El .Pp Exemplo: .Bd -literal -offset indent program[ff] = /usr/local/bin/firefox http://spectrwm.org/ bind[ff] = Mod+f # Agora Mod+F executa o firefox .Ed .Pp Para desfazer a configura\(,c\(~ao anterior: .Bd -literal -offset indent bind[] = Mod+f program[ff] = .Ed .Pp .Sh ATALHOS .Nm prov\(^e muitas fun\(,c\(~oes (ou a\(,ces) acessadas pelos atalhos do teclado ou do mouse. .Pp Os atuais atalhos do mouse s\(~ao descritos abaixo: .Pp .Bl -tag -width "M-j, M-XXX" -offset indent -compact .It Cm M1 Foca a janela .It Cm M-M1 Move a janela .It Cm M-M3 Redimenciona a janela .It Cm M-S-M3 Redimenciona a janela enquanto a mant\('em centralizada .El .Pp Os atalhos default do teclado s\(~ao descritos abaixo: .Pp .Bl -tag -width "M-j, M-XXX" -offset indent -compact .It Cm M-S- Ns Aq Cm Return term .It Cm M-p menu .It Cm M-S-q quit .It Cm M-q restart .Nm .It Cm M- Ns Aq Cm Space cycle_layout .It Cm M-S- Ns Aq Cm Space reset_layout .It Cm M-h master_shrink .It Cm M-l master_grow .It Cm M-, master_add .It Cm M-. master_del .It Cm M-S-, stack_inc .It Cm M-S-. stack_del .It Cm M- Ns Aq Cm Return swap_main .It Xo .Cm M-j , .Cm M- Ns Aq Cm TAB .Xc focus_next .It Xo .Cm M-k , .Cm M-S- Ns Aq Cm TAB .Xc focus_prev .It Cm M-m focus_main .It Cm M-S-j swap_next .It Cm M-S-k swap_prev .It Cm M-b bar_toggle .It Cm M-x wind_del .It Cm M-S-x wind_kill .It Cm M- Ns Aq Ar n .Ns ws_ Ns Ar n .It Cm M-S- Ns Aq Ar n .Ns mvws_ Ns Ar n .It Cm M- Ns Aq Cm Right ws_next .It Cm M- Ns Aq Cm Left ws_prev .It Cm M-a ws_prior .It Cm M-S- Ns Aq Cm Right screen_next .It Cm M-S- Ns Aq Cm Left screen_prev .It Cm M-s screenshot_all .It Cm M-S-s screenshot_wind .It Cm M-S-v version .It Cm M-t float_toggle .It Cm M-S Aq Cm Delete lock .It Cm M-S-i initscr .El .Pp Os nomes das a\(,c\(~oes e suas descri\(,ces est\(~ao listados abaixo: .Pp .Bl -tag -width "M-j, M-XXX" -offset indent -compact .It Cm term Executa um novo terminal (veja a se\(,c\(~ao .Sx PROGRAMAS logo acima) .It Cm menu Menu (veja a se\(,c\(~ao .Sx PROGRAMAS logo acima) .It Cm quit Sair .Nm .It Cm restart Reiniciar .Nm .It Cm cycle_layout Circula entre os poss\('iveis layouts .It Cm reset_layout Reinicia o layout .It Cm master_shrink Encolhe a \('area mestre .It Cm master_grow Aumenta a \('area mestre .It Cm master_add Adiciona janelas na \('area mestre .It Cm master_del Remove janelas da \('area mestre .It Cm stack_inc Adiciona colunas/linhas para a \('area de empilhamento .It Cm stack_del Remove colunas/linhas da \('area de empilhamento .It Cm swap_main Move a janela atual para a \('area mestre .It Cm focus_next Foca a pr\('oxima janela da \('area de trabalho .It Cm focus_prev Foca a janela anterior da \('area de trabalho .It Cm focus_main Foca a janela principal da \('area de trabalho .It Cm swap_next Troca com a pr\('oxima janela da \('area de trabalho .It Cm swap_prev Troca com a janela anterior da \('area de trabalho .It Cm bar_toggle Ativa/desativa a barra de status em todas as \('areas de trabalho .It Cm wind_del Apaga a janela atual da \('area de trabalho .It Cm wind_kill Destr\('oi a janela atual da \('area de trabalho .It Cm ws_ Ns Ar n Troca para a \('area de trabalho .Ar n , onde .Ar n vai de 1 at\('e 10 .It Cm mvws_ Ns Ar n Move a janela atual para a \('area de trabalho .Ar n , onde .Ar n vai de 1 at\('e 10 .It Cm ws_next Troca para a pr\('oxima \('area de trabalho que possua uma janela .It Cm ws_prev Troca para a \('area de trabalho anterior que possua uma janela .It Cm ws_prior Troca para a \('ultima \('area de trabalho visitada .It Cm screen_next Move o ponteiro para a pr\('oxima regi\(~ao .It Cm screen_prev Move o ponteiro para a regi\(~ao anterior .It Cm screenshot_all Tira screenshot da tela inteira (se habilitado) (veja a se\(,c\(~ao .Sx PROGRAMAS logo acima) .It Cm screenshot_wind Tira screenshot da janela selecionada (se habilitado) (veja a se\(,c\(~ao .Sx PROGRAMAS logo acima) .It Cm version Ativa/desativa a vers\(~ao na barras de status .It Cm float_toggle Troca o estado da janela focada entre flutuante e tiled .It Cm lock Trava a tela (veja a se\(,c\(~ao .Sx PROGRAMAS logo acima) .It Cm initscr Reinicializa as telas f\('isicas (veja a se\(,c\(~ao .Sx PROGRAMAS logo acima) .El .Pp Atalhos personalizados no arquivo de configura\(,c\(~ao s\(~ao especificados da seguinte maneira: .Pp .Dl bind[] = .Pp .Aq action \('e uma das a\(,c\(~oes listadas acima (ou vazio) e .Aq keys est\('a na forma de zero ou mais teclas de modifica\(,c\(~ao (MOD, Mod1, Shift, etc.) e uma ou mais teclas normais (b, space, etc.), separadas pelo "+". Por exemplo: .Bd -literal -offset indent bind[reset] = Mod4+q # combina a tecla Windows + q para reiniciar bind[] = Mod1+q # desfaz a combina\(,c\(~ao Alt + q .Ed .Pp M\('ultiplas combina\(,c\(~oes de teclas podem ser usadas para a mesma a\(,c\(~ao. .Sh QUIRKS .Nm prov\(^e "quirks" que manipulam janelas que devem ser tratadas especialmente em um gerenciador de janela "tiling", tal como algumas aplica\(,c\(~oes de di\('alogos e tela cheia. .Pp Os quirks default est\(~ao descritos abaixo: .Pp .Bl -tag -width "OpenOffice.org N.M:VCLSalFrameXXX" -offset indent -compact .It Firefox\-bin:firefox\-bin TRANSSZ .It Firefox:Dialog FLOAT .It Gimp:gimp FLOAT + ANYWHERE .It MPlayer:xv FLOAT + FULLSCREEN .It OpenOffice.org 2.4:VCLSalFrame FLOAT .It OpenOffice.org 3.1:VCLSalFrame FLOAT .It pcb:pcb FLOAT .It xine:Xine Window FLOAT + ANYWHERE .It xine:xine Panel FLOAT + ANYWHERE .It xine:xine Video Fullscreen Window FULLSCREEN + FLOAT .It Xitk:Xitk Combo FLOAT + ANYWHERE .It Xitk:Xine Window FLOAT + ANYWHERE .It XTerm:xterm XTERM_FONTADJ .El .Pp Os quirks em si est\(~ao descritos abaixo: .Pp .Bl -tag -width "XTERM_FONTADJXXX" -offset indent -compact .It FLOAT Esta janela n\(~ao deve ser "tiled", mas permitida a flutuar livremente. .It TRANSSZ Ajusta o tamanho das janelas transit\('orias que sejam muito pequenas usando dialog_ratio (veja a se\(,c\(~ao .Sx ARQUIVOS DE CONFIGURA\(,C\(~AO ) . .It ANYWHERE Permite que a janela posicione a si mesma, n\(~ao-centrada. .It XTERM_FONTADJ Ajusta as fontes do xterm quando redimencionando. .It FULLSCREEN Remove a borda para permitir a janela usar todo o tamanho da tela. .El .Pp Quirks personalizados no arquivo de configura\(,c\(~ao s\(~ao especificados da seguinte maneira: .Pp .Dl quirk[:] = [ + ... ] .Pp .Aq class e .Aq name especificam a janela ao qual o quirk se aplica, e .Aq quirk \('e um dos quirks da lista acima. Por exemplo: .Bd -literal -offset indent quirk[MPlayer:xv] = FLOAT + FULLSCREEN # faz o mplayer tocar livremente quirk[pcb:pcb] = NONE # remove quirk existente .Ed .Pp Voc\(^e pode obter .Aq class e .Aq name executando o xprop(1) e ent\(~ao clicando na janela desejada. No seguinte exemplo a jenela principal do Firefox foi clicada: .Bd -literal -offset indent $ xprop | grep WM_CLASS WM_CLASS(STRING) = "Navigator", "Firefox" .Ed .Pp Note que usando o grep(1) para WM_CLASS voc\(^e obt\('em class e name. No exemplo acima a configura\(,c\(~ao do quirk poderia ser: .Bd -literal -offset indent quirk[Firefox:Navigator] = FLOAT .Ed .Sh SINAIS Enviar ao .Nm um sinal HUP far\('a com que o mesmo seja reiniciado. .Sh ARQUIVOS .Bl -tag -width "/etc/spectrwm.confXXX" -compact .It Pa ~/.spectrwm.conf Configura\(,c\(~oes espec\('ificas do usu\('ario. .It Pa /etc/spectrwm.conf Configura\(,c\(~oes globais. .El .Sh HIST\('ORIA .Nm foi inspirado pelo xmonad & dwm. .Sh AUTORES .An -nosplit .Pp .Nm foi escrito por .An Marco Peereboom Aq marco@peereboom.us , .An Ryan Thomas McBride Aq mcbride@countersiege.com e .An Darrin Chandler Aq dwchandler@stilyagin.com . .Sh BUGS Atualmente o menu, invocado com .Cm M-p , depende do dmenu. spectrwm-SPECTRWM_3_5_1/outdated_man_pages/spectrwm_ru.1000066400000000000000000000430771453034271100233440ustar00rootroot00000000000000.\" Copyright (c) 2009 Marco Peereboom .\" Copyright (c) 2009 Darrin Chandler .\" .\" Permission to use, copy, modify, and distribute this software for any .\" purpose with or without fee is hereby granted, provided that the above .\" copyright notice and this permission notice appear in all copies. .\" .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .Dd $Mdocdate$ .Dt SPECTRWM 1 .Os .Sh НАЗВАНИЕ .Nm spectrwm .Nd Оконный менеджер для X11 .Sh ИСПОЛЬЗОВАНИЕ .Nm spectrwm .Sh ОПИСАНИЕ .Nm это минималистичный менеджер окон, ставящий своей целью не мешать вам и не занимать ценное пространство экрана. Его настройки по-умолчанию разумны и, кроме того, он не требует знания языков программирования для работы с конфигурационным файлом. Он написан хакерами для хакеров и старается быть легким, компактным и быстрым. .Pp Когда .Nm запускается, он читает настройки из своего конфигурационного файла, .Pa spectrwm.conf . Смотрите секцию .Sx КОНФИГУРАЦИОННЫЕ ФАЙЛЫ ниже. .Pp На этой странице используются следующие обозначения: .Pp .Bl -tag -width Ds -offset indent -compact .It Cm M Мета-клавиша .It Cm S Shift .It Aq Cm Name Имя клавиши .It Cm M1 Кнопка мыши 1 .It Cm M3 Кнопка мыши 3 .El .Pp .Nm должен быть понятным и очевидным. Большинство действий выполняется комбинациями клавиш. Смотрите секцию .Sx ПРИВЯЗКИ ниже, чтобы узнать о стандартных настройках. .Sh КОНФИГУРАЦИОННЫЕ ФАЙЛЫ .Nm пытается прочитать файл в домашнем каталоге, .Pa ~/.spectrwm.conf . В случае, если он недоступен, происходит обращение к глобальному файлу настроек, .Pa /etc/spectrwm.conf . .Pp Формат файла следующий: \*(Ltключ\*(Gt = \*(Ltзначение\*(Gt. Например: .Pp .Dl color_focus = red .Pp Однозначное включение и выключение задается значениями 1 и 0. .Pp Поддерживаются следующие ключевые слова: .Pp .Bl -tag -width "title_class_enabledXXX" -offset indent -compact .It Cm color_focus Цвет рамки окна в фокусе. .It Cm color_unfocus Цвет рамки окон не в фокусе. .It Cm bar_enabled Включение статусной строки. .It Cm bar_border Ns Bq Ar x Цвет рамки статусной строки .Ar x . .It Cm bar_color Ns Bq Ar x Цвет статусной строки .Ar x . .It Cm bar_font_color Ns Bq Ar x Цвет шрифта статусной строки .Ar x . .It Cm bar_font Тип шрифта статусной строки. .It Cm bar_action Внешний файл скрипта для статусной строки, выводящий туда информацию, например, уровень заряда батарей. .It Cm stack_enabled Включить отображение способа укладки окон в статусной строке. .It Cm clock_enabled Включить часы в статусной строке. Можно отключить, установив 0, и Вы сможете использовать собственные часы из внешнего скрипта. .It Cm dialog_ratio Ряд приложений имеет слишком маленькие диалоговые окна. Это значение - доля размера экрана, к которой они будут приведены. Например, значение 0.6 будет соответствовать 60% от реального размера экрана. .It Cm region Выделяет область экрана на Ваше усмотрение, уничтожает все перекрытые области экрана, определенные автоматически. Формат: screen[]:WIDTHxHEIGHT+X+Y, например\& screen[1]:1280x800+0+0. .It Cm term_width Установить минимальную допустимую ширину эмулятора терминала. Если это значение больше 0, .Nm попытается отмасштабировать шрифты в терминале, чтобы ширина была больше этого значения . Поодерживается только .Xr xterm 1 . Также .Xr xterm 1 не может быть с setuid или setgid, хотя это так на многих системах. Возможно необходимо задать program[term] (Смотрите секцию .Sx ПРОГРАММЫ ) чтобы использовалась другая копия .Xr xterm 1 без заданного бита setgid. .It Cm title_class_enabled Отображать класс окна в статусной строке. Обычно выключено .It Cm title_name_enabled Отображать заголовок окна в статусной строке. Обычно выключено .It Cm modkey Назначить Мета-клавишу, клавишу-модификатор. Mod1 соответствует клавише ALT, а Mod4 соответствует клавише WIN на PC. .It Cm program Ns Bq Ar p Добавить пользовательскую программу для назначения привязки .Ar p . Смотрите секцию .Sx ПРОГРАММЫ ниже. .It Cm bind Ns Bq Ar x Назначить привязку на действие .Ar x . Смотрите секцию .Sx ПРИВЯЗКИ ниже. .It Cm quirk Ns Bq Ar c:n Добавить костыль для окон с классом .Ar c и именем .Ar n . Смотрите секцию .Sx КОСТЫЛИ ниже. .El .Pp Цвета задаются с помощью .Xr XQueryColor 3 А шрифты задаются с использованием .Xr XQueryFont 3 . .Sh ПРОГРАММЫ .Nm позволяет Вам добавлять Ваши собственные действия для запуска программ и делать к ним привязки как ко всем остальным действиям Смотрите секцию .Sx ПРИВЯЗКИ ниже. .Pp Стандартные программы: .Pp .Bl -tag -width "screenshot_wind" -offset indent -compact .It Cm term xterm .It Cm screenshot_all screenshot.sh full .It Cm screenshot_wind screenshot.sh window .It Cm lock xlock .It Cm initscr initscreen.sh .It Cm menu dmenu_run \-fn $bar_font \-nb $bar_color \-nf $bar_font_color \-sb $bar_border \-sf $bar_color .El .Pp Ваши собственные программы задаются следующим образом: .Pp .Dl program[] = [ [... ]] .Pp .Aq name это любой идентификатор, не мешающийся с уже существующими, .Aq progpath это собственно путь к программе, .Aq arg это список передаваемых аргументов или оставьте пустым. .Pp Следующие переменные можно получать из .Nm (Смотрите секцию .Sx КОНФИГУРАЦИОННЫЕ ФАЙЛЫ выше), и их можно использовать как .Aq arg (в момент запуска программы будет выполнена подстановка значений): .Pp .Bl -tag -width "$bar_font_color" -offset indent -compact .It Cm $bar_border .It Cm $bar_color .It Cm $bar_font .It Cm $bar_font_color .It Cm $color_focus .It Cm $color_unfocus .El .Pp Например: .Bd -literal -offset indent program[ff] = /usr/local/bin/firefox http://spectrwm.org/ bind[ff] = Mod+f # Значит Mod+F запускает firefox .Ed .Pp Чтобы отменить назначение: .Bd -literal -offset indent bind[] = Mod+f program[ff] = .Ed .Pp .Sh ПРИВЯЗКИ .Nm предоставляет доступ к действиям с помощью клавиатурных комбинаций. .Pp Установленные привязки для мыши: .Pp .Bl -tag -width "M-j, M-XXX" -offset indent -compact .It Cm M1 Сфокусироваться на окне .It Cm M-M1 Переместить окно .It Cm M-M3 Изменить размер окна .It Cm M-S-M3 Изменить размер окна, удерживая его в центре .El .Pp Стандартные клавиатурные привязки: .Pp .Bl -tag -width "M-j, M-XXX" -offset indent -compact .It Cm M-S- Ns Aq Cm Return term .It Cm M-p menu .It Cm M-S-q quit .It Cm M-q restart .Nm .It Cm M- Ns Aq Cm Space cycle_layout .It Cm M-S- Ns Aq Cm Space reset_layout .It Cm M-h master_shrink .It Cm M-l master_grow .It Cm M-, master_add .It Cm M-. master_del .It Cm M-S-, stack_inc .It Cm M-S-. stack_del .It Cm M- Ns Aq Cm Return swap_main .It Xo .Cm M-j , .Cm M- Ns Aq Cm TAB .Xc focus_next .It Xo .Cm M-k , .Cm M-S- Ns Aq Cm TAB .Xc focus_prev .It Cm M-m focus_main .It Cm M-S-j swap_next .It Cm M-S-k swap_prev .It Cm M-b bar_toggle .It Cm M-x wind_del .It Cm M-S-x wind_kill .It Cm M- Ns Aq Ar n .Ns ws_ Ns Ar n .It Cm M-S- Ns Aq Ar n .Ns mvws_ Ns Ar n .It Cm M- Ns Aq Cm Right ws_next .It Cm M- Ns Aq Cm Left ws_prev .It Cm M-S- Ns Aq Cm Right screen_next .It Cm M-S- Ns Aq Cm Left screen_prev .It Cm M-s screenshot_all .It Cm M-S-s screenshot_wind .It Cm M-S-v version .It Cm M-t float_toggle .It Cm M-S Aq Cm Delete lock .It Cm M-S-i initscr .El .Pp Описания действий перечислены ниже: .Pp .Bl -tag -width "M-j, M-XXX" -offset indent -compact .It Cm term Запустить эмулятор терминала (Смотрите секцию .Sx ПРОГРАММЫ выше) .It Cm menu Меню (Смотрите секцию .Sx ПРОГРАММЫ выше) .It Cm quit Выйти .Nm .It Cm restart Перезапустить .Nm .It Cm cycle_layout Менять укладку окон .It Cm reset_layout Стандартная укладка .It Cm master_shrink Сжать область главного окна .It Cm master_grow Расширить область главного окна .It Cm master_add Добавить окна в главную область .It Cm master_del Убрать окна из главной области .It Cm stack_inc Увеличить число столбцов или рядов в текущей укладке .It Cm stack_del Уменьшить число столбцов или рядов в текущей укладке .It Cm swap_main Отправить текущее окно в главную область, сделать главным .It Cm focus_next Фокусироваться на следующем окне .It Cm focus_prev Фокусироваться на предыдущем окне .It Cm focus_main Фокусироваться на главном окне .It Cm swap_next Поменять со следующим окном .It Cm swap_prev Поменять со предыдущим окном .It Cm bar_toggle Выключить статусную строку на всех рабочих столах .It Cm wind_del Закрыть фокусированное окно .It Cm wind_kill Грохнуть фокусированное окно .It Cm ws_ Ns Ar n Переключиться на рабочий стол .Ar n , где .Ar n от 1 до 10 .It Cm mvws_ Ns Ar n Переместить фокусированное окно в рабочий стол .Ar n , где .Ar n от 1 до 10 .It Cm ws_next Перейти к следующему не пустому рабочему столу .It Cm ws_prev Перейти к следующему не пустому рабочему столу .It Cm screen_next Переместить указатель в следующую область .It Cm screen_prev Переместить указатель в следующую область .It Cm screenshot_all Сделать снимок всего экрана (если возможно) (Смотрите секцию .Sx ПРОГРАММЫ выше) .It Cm screenshot_wind Сделать снимок окна (если возможно) (Смотрите секцию .Sx ПРОГРАММЫ выше) .It Cm version Показать версию в статусной строке .It Cm float_toggle Переключить окно в фокусе в плавающий режим, float .It Cm lock Заблокировать экран (Смотрите секцию .Sx ПРОГРАММЫ выше) .It Cm initscr Инициализировать экран еще раз (Смотрите секцию .Sx ПРОГРАММЫ выше) .El .Pp Собственные привязки назначаются следующим образом: .Pp .Dl bind[] = .Pp .Aq action это действие из списка программ .Aq keys это не более одной клавиши-модификатора (MOD, Mod1, Shift, и.т.п.) и обычные клавиши (b, space, и.т.п.), разделенные "+". Например: .Bd -literal -offset indent bind[reset] = Mod4+q # назначить WIN + q на действие reset bind[] = Mod1+q # снять все действия с Alt + q .Ed .Pp На одно действие можно назначить несколько комбинаций. .Sh КОСТЫЛИ .Nm позволяет настроить костыли, нужные для специальной работы spectrwm с рядом приложений, который вы определяете сами. То есть, Вы можете принудительно установить способ тайлинга для какого-нибудь приложения .Pp Список стандартных костылей: .Pp .Bl -tag -width "OpenOffice.org N.M:VCLSalFrameXXX" -offset indent -compact .It Firefox\-bin:firefox\-bin TRANSSZ .It Firefox:Dialog FLOAT .It Gimp:gimp FLOAT + ANYWHERE .It MPlayer:xv FLOAT + FULLSCREEN .It OpenOffice.org 2.4:VCLSalFrame FLOAT .It OpenOffice.org 3.1:VCLSalFrame FLOAT .It pcb:pcb FLOAT .It xine:Xine Window FLOAT + ANYWHERE .It xine:xine Panel FLOAT + ANYWHERE .It xine:xine Video Fullscreen Window FULLSCREEN + FLOAT .It Xitk:Xitk Combo FLOAT + ANYWHERE .It Xitk:Xine Window FLOAT + ANYWHERE .It XTerm:xterm XTERM_FONTADJ .El .Pp Описание: .Pp .Bl -tag -width "XTERM_FONTADJXXX" -offset indent -compact .It FLOAT Такое окно не нужно тайлить вообще, разрешить ему float .It TRANSSZ Тразиентое окно (Смотрите секцию .Sx КОНФИГУРАЦИОННЫЕ ФАЙЛЫ) . .It ANYWHERE Позволить окну самостоятельно выбрать местоположение .It XTERM_FONTADJ Изменять шрифты xterm при изменении размеров окна .It FULLSCREEN Позволить окну запускаться в полноэкранном режиме .El .Pp Назначать костыли можно следующим образом: .Pp .Dl quirk[:] = [ + ... ] .Pp .Aq class и .Aq name определяют к какому окну будет применяться костыль, а .Aq quirk один из вышеперечисленных способов. Например: .Bd -literal -offset indent quirk[MPlayer:xv] = FLOAT + FULLSCREEN # mplayer настроен quirk[pcb:pcb] = NONE # убрать существующий костыль .Ed .Pp Вы можете узнать .Aq class и .Aq name запустив xprop и нажав в интересующее окно. Вот как будет выглядеть вывод для Firefox: .Bd -literal -offset indent $ xprop | grep WM_CLASS WM_CLASS(STRING) = "Navigator", "Firefox" .Ed .Pp Обратите внимание, класс и имя меняются местами, правильный костыль будет выглядеть так: .Bd -literal -offset indent quirk[Firefox:Navigator] = FLOAT .Ed .Sh ФАЙЛЫ .Bl -tag -width "/etc/spectrwm.confXXX" -compact .It Pa ~/.spectrwm.conf .Nm Личные настройки пользователя. .It Pa /etc/spectrwm.conf .Nm Глобавльные настройки. .El .Sh ИСТОРИЯ .Nm идейно основан на dwm и xmonad .Sh АВТОРЫ .An -nosplit .Pp .Nm написан: .An Marco Peereboom Aq marco@peereboom.us , .An Ryan Thomas McBride Aq mcbride@countersiege.com and .An Darrin Chandler Aq dwchandler@stilyagin.com . .Sh БАГИ При вызове меню с помощью .Cm M-p , необходима корректная работа dmenu. spectrwm-SPECTRWM_3_5_1/screenshot.sh000066400000000000000000000002031453034271100175560ustar00rootroot00000000000000#!/bin/sh # screenshot() { case $1 in full) scrot -m ;; window) sleep 1 scrot -s ;; *) ;; esac; } screenshot $1 spectrwm-SPECTRWM_3_5_1/spectrwm.1000066400000000000000000001535251453034271100170130ustar00rootroot00000000000000.\" Copyright (c) 2009-2012 Marco Peereboom .\" Copyright (c) 2009 Darrin Chandler .\" Copyright (c) 2011-2023 Reginald Kennedy .\" Copyright (c) 2011-2012 Lawrence Teo .\" Copyright (c) 2011-2012 Tiago Cunha .\" Copyright (c) 2012 David Hill .\" .\" Permission to use, copy, modify, and distribute this software for any .\" purpose with or without fee is hereby granted, provided that the above .\" copyright notice and this permission notice appear in all copies. .\" .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .Dd $Mdocdate: November 25 2023 $ .Dt SPECTRWM 1 .Os .Sh NAME .Nm spectrwm .Nd window manager for X11 .Sh SYNOPSIS .Nm spectrwm .Op Fl c Ar file .Op Fl v .Sh OPTIONS .Bl -tag -width Ds .It Fl c Ar file Specify a configuration file to load instead of scanning for one. .It Fl d Enable debug mode and logging to stderr. .It Fl v Print version and exit. .El .Sh DESCRIPTION .Nm is a minimalistic window manager that tries to stay out of the way so that valuable screen real estate can be used for much more important stuff. It has sane defaults and does not require one to learn a language to do any configuration. It was written by hackers for hackers and it strives to be small, compact and fast. .Pp When .Nm starts up, it reads settings from its configuration file, .Pa spectrwm.conf . See the .Sx CONFIGURATION FILES section below. .Pp The following notation is used throughout this page: .Pp .Bl -tag -width Ds -offset indent -compact .It Cm M Meta .It Cm S Shift .It Aq Cm Name Named key or button .El .Pp .Nm is very simple in its use. Most of the actions are initiated via key or pointer button bindings. See the .Sx BINDINGS section below for defaults and customizations. .Sh CONFIGURATION FILES .Nm looks for the user-configuration file in the following order: .Pp .Bl -enum -offset indent -compact .It .Pa $XDG_CONFIG_HOME/spectrwm/spectrwm.conf .It .Pa ~/.config/spectrwm/spectrwm.conf (if .Pa $XDG_CONFIG_HOME is either not set or empty) .It .Pa ~/.spectrwm.conf . .El .Pp If the user-configuration file is not found, .Nm then looks for the global configuration file in the following order: .Pp .Bl -enum -offset indent -compact .It .Pa $XDG_CONFIG_DIRS/spectrwm/spectrwm.conf (each colon-separated directory in .Pa $XDG_CONFIG_DIRS ) .It .Pa /etc/xdg/spectrwm/spectrwm.conf (if .Pa $XDG_CONFIG_DIRS is either not set or empty) .It .Pa /etc/spectrwm.conf .El .Pp The format of the file is .Pp .Dl Ar keyword Li = Ar setting .Pp For example: .Pp .Dl color_focus = red .Pp Enabling or disabling an option is done by using 1 or 0 respectively. .Pp Colors need to be specified per the .Xr XQueryColor 3 specification. In addition, alpha transparency may be specified via the format .Li rbga : Ns Ar red Ns / Ns Ar green Ns / Ns Ar blue Ns / Ns Ar alpha (8-bit hex values) For example, to specify a 50% transparent blue status bar background: .Pp .Dl bar_color = rgba:00/00/ff/7f .Pp Note that a compositing manager is required for alpha transparency. .Pp Mark option values may be wrapped in single/double quotes to prevent whitespace trimming, specify empty strings, etc. Literal quote/backslash characters can be escaped with a backslash .Sq \e , when needed. .Pp Comments begin with a #. When a literal .Ql # is desired in an option, then it must be escaped with a backslash, i.e. \e# .Pp The file supports the following keywords: .Bl -tag -width 2m .It Ic autorun Launch an application in a specified workspace at start-of-day. Defined in the format .Li ws Ns Bo Ar idx Bc : Ns Ar application , e.g. ws[2]:xterm launches an .Xr xterm 1 in workspace 2. Specify .Sq ws[-1] to launch applications such as desktop managers and panels in free mode to keep them always mapped. .Pp Note that .Pa libswmhack.so is required for "spawn-in-workspace" behavior. See the .Sx SWMHACK section below for more information, tips, and workarounds if a program fails to spawn in the specified workspace. .It Ic bar_action External script that populates additional information in the status bar, such as battery life. .It Ic bar_action_expand Process .Ic bar_format character sequences in .Ic bar_action output; default is 0. .It Ic bar_at_bottom Place the statusbar at the bottom of each region instead of the top. Default is 0. .It Ic bar_border Ns Bq Ar x Border color of status bar(s) on screen number .Ar x . Default is rgb:00/80/80. .It Ic bar_border_free Ns Bq Ar x Border color of a status bar for a focused region on screen number .Ar x when a workspace-free window is focused. Default is rgb:80/80/00. .It Ic bar_border_unfocus Ns Bq Ar x Border color of status bar(s) for unfocused region(s) on screen number .Ar x . Default is rgb:00/40/40. .It Ic bar_border_width Set status bar border thickness in pixels. Disable border by setting to 0. .It Ic bar_color Ns Bq Ar x Background color of status bar(s) on screen number .Ar x . .Pp A comma separated list of up to 10 colors can be specified. The first value is used as the default background color. Any of these colors can then be selected as a background color in the status bar through the use of the markup sequence .Ic +@bg=n;\& where n is between 0 and 9. .It Ic bar_color_free Ns Bq Ar x Background color of a status bar for a focused region on screen number .Ar x when a workspace-free window is focused. .Pp A comma separated list of up to 10 colors can be specified, with the same syntax and behavior as .Ic bar_color . Default is rgb:40/40/00. .It Ic bar_color_selected Ns Bq Ar x Background color for selected .Cm menu items on screen number .Ar x . Defaults to the value of .Ic bar_border . .It Ic bar_color_unfocus Ns Bq Ar x Background color of status bar(s) for unfocused region(s) on screen number .Ar x . .Pp A comma separated list of up to 10 colors can be specified, with the same syntax and behavior as .Ic bar_color for unfocused bar(s). Defaults to the value of .Ic bar_color . .It Ic bar_enabled Set default .Ic bar_toggle state; default is 1. .It Ic bar_enabled_ws Ns Bq Ar x Set default .Ic bar_toggle_ws state on workspace .Ar x ; default is 1. .It Ic bar_font Fonts used in the status bar. Either Xft or X Logical Font Description (XLFD) may be used to specify fonts. Fallback fonts may be specified by separating each font with a comma. If all entries are in XLFD syntax, font set will be used. If at least one entry is Xft, Xft will be used. .Pp The default is to use font set. .Pp If Xft is used, a comma-separated list of up to 10 fonts can be specified. The first entry is the default font. Any font defined here can then be selected in the status bar through the use of the markup sequence .Ic +@fn=n;\& where n is between 0 and 9. .Pp Also note that .Xr dmenu 1 prior to 4.6 does not support Xft fonts. .Pp Xft examples: .Bd -literal -offset indent bar_font = Terminus:style=Regular:pixelsize=14:antialias=true bar_font = -*-profont-medium-*-*-*-11-*-*-*-*-*-*-*,Terminus:pixelsize=14,\ -*-clean-medium-*-*-*-12-*-*-*-*-*-*-* .Ed .Pp Font set examples: .Bd -literal -offset indent bar_font = -*-terminus-medium-*-*-*-14-*-*-*-*-*-*-* bar_font = -*-profont-medium-*-*-*-11-*-*-*-*-*-*-*,\ -*-terminus-medium-*-*-*-14-*-*-*-*-*-*-*,\ -*-clean-medium-*-*-*-12-*-*-*-*-*-*-* .Ed .Pp To list the available fonts in your system see .Xr fc-list 1 or .Xr xlsfonts 1 manpages. The .Xr xfontsel 1 application can help with the XLFD setting. .It Ic bar_font_color Ns Bq Ar x Foreground color of the status bar(s) on screen number .Ar x . .Pp A comma separated list of up to 10 colors can be specified. The first value is used as the default foreground color. Any of these colors can then be selected as a foreground color in the status bar through the use of the markup sequence .Ic +@fg=n;\& where n is between 0 and 9. .It Ic bar_font_color_unfocus Ns Bq Ar x Foreground color of status bar(s) for unfocused region(s) on screen number .Ar x . .Pp A comma separated list of up to 10 colors can be specified, with the same syntax and behavior as .Ic bar_font_color for unfocused bar(s). Defaults to the value of .Ic bar_font_color . .It Ic bar_font_color_selected Ns Bq Ar x Foreground color for selected .Cm menu items on screen number .Ar x . Defaults to the value of .Ic bar_color . .It Ic bar_font_pua Specify a font to override the Unicode Private Use Area code points (U+E000 -> U+F8FF, U+F0000 -> U+FFFFD, U+100000 -> U+10FFFD). Some fonts use these code points to provide special icon glyphs. Available only with Xft fonts. .It Ic bar_format Set the bar format string, overriding .Ic clock_format and all of the .Ic enabled options. The format is passed through .Xr strftime 3 before being used. It may contain the following character sequences: .Bl -column "Character sequence" "Replaced with" -offset indent .It Sy "Character sequence" Ta Sy "Replaced with" .It Li "+<" Ta "Pad with a space" .It Li "+A" Ta "Output of the external script" .It Li "+C" Ta "Window class (from WM_CLASS)" .It Li "+D" Ta "Workspace name" .It Li "+F" Ta "Focus status indicator" .It Li "+I" Ta "Workspace index" .It Li "+L" Ta "Workspace list indicator" .It Li "+M" Ta "Number of iconic (minimized) windows in workspace" .It Li "+N" Ta "Screen number" .It Li "+P" Ta "Window class and instance separated by a colon" .It Li "+R" Ta "Region index" .It Li "+S" Ta "Stacking algorithm" .It Li "+T" Ta "Window instance (from WM_CLASS)" .It Li "+U" Ta "Urgency hint" .It Li "+V" Ta "Program version" .It Li "+w" Ta "Number of windows in workspace" .It Li "+W" Ta "Window name (from _NET_WM_NAME/WM_NAME)" .It Li "+|[weight][justify]" Ta Begin new section and reset markup sequence effects. .Pp .Ic weight is a positive integer used to allocate horizontal space between 'L', 'C' and 'R' sections (see justify). The default weight is 1. .Pp .Ic justify can have the value L, C, R or T. L, C, R are for left, center and right justified sections respectively. A 'T' section will limit its space usage to fit to the text. If no value is specified for a given section, the setting from .Ic bar_justify is used. .It Li "++" Ta "A literal" Ql + .It Li "+@" Ta "Prefix for text markup sequences" .El .Pp The currently recognized text markup sequences are: .Bl -column "Character sequence" "Action" -offset indent .It Sy "Character sequence" Ta Sy "Action" .It Li "+@fn=n;\&" Ta Selects font n (from 0 to 9) from .Ic bar_font . .It Li "+@fg=n;\&" Ta Selects foreground color n (from 0 to 9) from .Ic bar_font_color . .It Li "+@bg=n;\&" Ta Selects background color n (from 0 to 9) from .Ic bar_color . .It Li "+@stp;\&" Ta Stops the interpretation of markup sequences. Any markup sequence found after +@stp will appear as normal characters in the status bar. .El .Pp Note that markup sequences in .Ic bar_action script output will only be processed if .Ic bar_action_expand is enabled. .Pp All character sequences may limit its output to a specific length, for example +64A. By default, no padding/alignment is done in case the length of the replaced string is less than the specified length (64 in the example). The padding/alignment can be enabled using a '_' character in the sequence. For example: +_64W, +64_W and +_64_W enable padding before (right alignment), after (left alignment), and both before and after (center alignment) window name, respectively. Any characters that do not match the specification are copied as-is. .It Ic bar_justify Justify the status bar text. Possible values are .Ar left , .Ar center , and .Ar right . .Pp Note that if the output is not left justified, it may not be properly aligned in some circumstances, due to the white-spaces in the default static format. See the .Ic bar_format option for more details. .It Ic bind Ns Bq Ar x Bind key or button combo to action .Ar x . See the .Sx BINDINGS section below. .It Ic border_width Set window border thickness in pixels. Disable all borders by setting to 0. .It Ic boundary_width Set region containment boundary width in pixels. This is how far a window must be dragged/resized (with the pointer) beyond the region edge before it is allowed outside the region. Disable the window containment effect by setting to 0. .It Ic cancelkey Change the key used as an alternative means of terminating move/resize operations. Default is Escape. .Pp See the .Sx BINDINGS section below for details on how to find key names. .It Ic click_to_raise Enable or disable raising stacking priority when clicking on a window. Default is 1. .It Ic clock_enabled Enable or disable displaying the clock in the status bar. Disable by setting to 0 so a custom clock could be used in the .Ic bar_action script. .It Ic color_focus_free Border color of the currently focused window that is in free mode. Default is yellow. .It Ic color_focus_maximized_free Border color of the currently focused maximized window that is in free mode. Defaults to the value of .Ic color_focus_free . .It Ic color_unfocus_free Border color of unfocused windows that are in free mode, default is rgb:88/88/00. .It Ic color_unfocus_maximized_free Border color of unfocused maximized windows that are in free mode. Defaults to the value of .Ic color_unfocus_free . .It Ic color_focus Border color of the currently focused window. Default is red. .It Ic color_focus_maximized Border color of the currently focused, maximized window. Defaults to the value of .Ic color_focus . .It Ic color_unfocus Border color of unfocused windows, default is rgb:88/88/88. .It Ic color_unfocus_maximized Border color of unfocused, maximized windows. Defaults to the value of .Ic color_unfocus . .It Ic cycle_visible Include workspaces that are mapped when switching with .Ic ws_next , .Ic ws_prev , .Ic ws_next_all , .Ic ws_prev_all , .Ic ws_next_move , or .Ic ws_prev_move . Enable by setting to 1. .Pp Note that mapped workspaces will be swapped unless .Ic workspace_clamp is enabled. If .Ic warp_focus is also enabled, focus will go to the region where the workspace is mapped. .It Ic dialog_ratio Some applications have dialogue windows that are too small to be useful. This ratio adjusts the window/region size ratio for transient windows having the TRANSSZ quirk. For example, 0.6 is 60% of the the monitor size if the current region spans the monitor. .It Ic disable_border Remove border when bar is disabled and there is only one window on the region. Enable by setting to 1. Setting this to .Ar always removes the border regardless of the bar being enabled/disabled. Defaults to 0. .It Ic focus_close Window to put focus when the focused window is closed. Possible values are .Ar first , .Ar next , .Ar previous (default), .Ar last and .Ar prior . .Ar next and .Ar previous are relative to the window that is closed. .Ar prior is the last focused window in the workspace. .It Ic focus_close_wrap Whether to allow the focus to jump to the last window when the first window is closed or vice versa. Disable by setting to 0. .It Ic focus_default Window to put focus when no window has been focused. Possible values are .Ar first and .Ar last (default). .It Ic focus_mark_none Set the .Ic bar_format focus status indicator (+F) string to substitute when no window is focused. Default is ''. .It Ic focus_mark_normal Set the .Ic bar_format focus status indicator (+F) string to substitute when a normal (not floating, maximized or free) window is focused. Default is ''. .It Ic focus_mark_floating Set the .Ic bar_format focus status indicator (+F) string to substitute when a floating window is focused. Default is '(f)'. .It Ic focus_mark_free Set the .Ic bar_format focus status indicator (+F) string to substitute when a window that is in free mode is focused. Default is '(*)'. .It Ic focus_mark_maximized Set the .Ic bar_format focus status indicator (+F) string to substitute when a maximized window is focused. Default is '(m)'. .It Ic focus_mode Window focus behavior with respect to the pointer. Possible values: .Pp .Bl -tag -width "default" -offset indent -compact .It Ar default Set window focus on border crossings caused by cursor motion and window interaction. .It Ar follow Set window focus on all cursor border crossings, including workspace switches and changes to layout. .It Ar manual Set window focus on window interaction only. .El .It Ic fullscreen_hide_other When a fullscreen window is focused and not in .Ic below state, hide unrelated windows in the same workspace. Useful for transparent windows. Defaults to 0. .It Ic fullscreen_unfocus Set handling when a fullscreen window loses focus. Possible values: .Pp .Bl -tag -width "quick_belowXXX" -offset indent -compact .It Ar none Leave window fullscreen. (default) .It Ar restore Exit fullscreen. .It Ar iconify Minimize/hide the window. .It Ar float Exit fullscreen and float window. .It Ar below Set .Ic below state on the window. .It Ar quick_below Set .Ic below state on the window, unset when refocused. .El .Pp Note that this option is ignored in max layout. .It Ic iconic_enabled Display the number of iconic (minimized) windows in the status bar. Enable by setting to 1. .It Ic keyboard_mapping Clear all key bindings (not button bindings) and load new bindings from the specified file. This allows you to load pre-defined key bindings for your keyboard layout. See the .Sx KEYBOARD MAPPING FILES section below for a list of keyboard mapping files that have been provided for several keyboard layouts. .Pp Note that .Pa /dev/null can be specified if you only want to clear bindings. .It Ic layout Select layout to use at start-of-day. Defined in the format .Li ws Ns Bo Ar idx Bc : Ns Ar master_grow : Ns Ar master_add : Ns Ar \ stack_inc : Ns Ar always_raise : Ns Ar stack_mode , e.g. ws[2]:-4:0:1:0:horizontal sets workspace 2 to the horizontal stack mode, shrinks the master area by 4 ticks and adds one window to the stack, while maintaining default floating window behavior. Possible .Ar stack_mode values are .Ar vertical , .Ar vertical_flip , .Ar horizontal , .Ar horizontal_flip , .Ar max and .Ar floating . .Pp See .Ic master_grow , .Ic master_shrink , .Ic master_add , .Ic master_del , .Ic stack_inc , .Ic stack_dec , .Ic stack_balance , and .Ic always_raise for more information. Note that the stacking options are complicated and have side-effects. One should familiarize oneself with these commands before experimenting with the .Ic layout option. .Pp This setting is not retained at restart. .It Ic max_layout_maximize Automatically maximize windows in max layout. Note that automatic maximize behavior is disabled for windows that are unmaximized in max layout. Maximizing the window or resetting the layout with .Ic stack_reset enables it again. Enabled by default. Disable by setting to 0. .It Ic maximize_hide_bar When set to 1, .Ic maximize_toggle will also hide/restore the bar visibility of the affected workspace. Defaults to 0. .It Ic maximize_hide_other When a maximized window is focused and not in .Ic below state, hide unrelated windows in the same workspace. Useful for transparent windows. Defaults to 0. .It Ic maximized_unfocus Set handling when a maximized window loses focus. Possible values: .Pp .Bl -tag -width "quick_belowXXX" -offset indent -compact .It Ar none Leave window maximized. .It Ar restore Unmaximize window. (default) .It Ar iconify Minimize/hide the window. .It Ar float Unmaximize and float window. .It Ar below Set .Ic below state on the window. .It Ar quick_below Set .Ic below state on the window, unset when refocused. .El .Pp Note that this option is ignored in max layout. .It Ic modkey Change the current modifier value of .Ic MOD in .Ic bind entries that come later in the configuration file. For existing bindings, the new value is substituted for the previous value. Possible values are .Ar Mod1 (default), .Ar Mod2 , .Ar Mod3 , .Ar Mod4 and .Ar Mod5 . .Pp .Ar Mod1 is generally the Alt key, .Ar Mod2 is the Command key on macOS and .Ar Mod4 is the Windows key on a PC. The current modifier key mapping can be found by running xmodmap(1). .It Ic name Set the name of a workspace at start-of-day. Defined in the format .Li ws Ns Bo Ar idx Bc : Ns Ar name , e.g. ws[1]:Console sets the name of workspace 1 to .Dq Console . .It Ic program Ns Bq Ar p Define new action to spawn a program .Ar p . See the .Sx PROGRAMS section below. .It Ic quirk Ns Bq Ar c Ns Bq : Ns Ar i Ns Bq : Ns Ar n Add "quirk" for windows with class .Ar c , instance .Ar i (optional) and name .Ar n (optional). See the .Sx QUIRKS section below. .It Ic region Allocates a custom region, removing any autodetected regions that occupy the same space on the specified logical X screen number. Defined in the format .Li screen Ns Bo Ar idx Ns Bc : Ns Ar width Ns x Ns Ar height Ns + Ns Ar x Ns \ + Ns Ar y Ns Bo , Ns Ar rotation Bc , e.g. screen[1]:800x1200+0+0 or screen[1]:800x1200+0+0,inverted (with optional rotation). .Pp To make a region span multiple monitors, create a region big enough to cover them all, e.g. screen[1]:2048x768+0+0 makes the region span two monitors with 1024x768 resolution sitting one next to the other. .Pp Possible values for the optional rotation argument are .Ar normal (default), .Ar left , .Ar inverted and .Ar right . Note that rotation is used by .Ic workspace_autorotate . .It Ic region_padding Pixel width of empty space within region borders. Disable by setting to 0. .It Ic snap_range Set the distance in pixels a tiled/maximized window must be moved (with the pointer) to unsnap and float freely. Set to 0 to unsnap immediately. Default is 25. .It Ic spawn_position Position in stack to place newly spawned windows. Possible values are .Ar first , .Ar next , .Ar previous and .Ar last (default). .Ar next and .Ar previous are relative to the focused window. .It Ic stack_enabled Enable or disable displaying the current stacking algorithm in the status bar. .It Ic stack_mark_floating Set the .Ar floating layout mark for the .Ic bar_format stacking indicator (+S). Default is '[~]'. .It Ic stack_mark_horizontal Set the .Ar horizontal layout mark for the .Ic bar_format stacking indicator (+S). Default is '[-]'. .It Ic stack_mark_horizontal_flip Set the .Ar horizontal_flip layout mark for the .Ic bar_format stacking indicator (+S). Default is '[v]'. .It Ic stack_mark_max Set the .Ar max layout mark for the .Ic bar_format stacking indicator (+S). Default is '[ ]'. .It Ic stack_mark_vertical Set the .Ar vertical layout mark for the .Ic bar_format stacking indicator (+S). Default is '[|]'. .It Ic stack_mark_vertical_flip Set the .Ar vertical_flip layout mark for the .Ic bar_format stacking indicator (+S). Default is '[>]'. .It Ic term_width Set a preferred minimum width for the terminal. If this value is greater than 0, .Nm will attempt to adjust the font sizes in the terminal to keep the terminal width above this number as the window is resized. Only .Xr xterm 1 is currently supported. The .Xr xterm 1 binary must not be setuid or setgid, which it is by default on most systems. Users may need to set program[term] (see the .Sx PROGRAMS section) to use an alternate copy of the .Xr xterm 1 binary without the setgid bit set. .It Ic tile_gap Pixel width of empty space between tiled windows. Negative values cause overlap. Set this to the opposite of .Ic border_width to collapse the border between tiles. Disable by setting to 0. .It Ic urgent_collapse Minimizes the space consumed by the urgency hint indicator by removing the placeholders for non-urgent workspaces, the trailing space when there are urgent windows and the default leading space. Enable by setting to 1. .It Ic urgent_enabled Enable or disable the urgency hint indicator in the status bar. Note that many terminal emulators require an explicit setting for the bell character to trigger urgency on the window. In .Xr xterm 1 , for example, one needs to add the following line to .Pa .Xdefaults : .Bd -literal -offset indent xterm.bellIsUrgent: true .Ed .It Ic verbose_layout Enable or disable displaying the current master window count and stack column/row count in the status bar. Enable by setting to 1. See .Ar master_add , .Ar master_del , .Ar stack_inc and .Ar stack_dec for more information. .It Ic warp_focus Focus on the target window/workspace/region when clamped. For example, when attempting to switch to a workspace that is mapped on another region and .Ar workspace_clamp is enabled, focus on the region with the target workspace. Enable by setting to 1. .It Ic warp_pointer Centers the pointer on the focused window when using bindings to change focus, switch workspaces, change regions, etc. Enable by setting to 1. .It Ic window_class_enabled Enable or disable displaying the window class name (from WM_CLASS) in the status bar. Enable by setting to 1. .It Ic window_instance_enabled Enable or disable displaying the window instance name (from WM_CLASS) in the status bar. Enable by setting to 1. .It Ic window_name_enabled Enable or disable displaying the window display name (from _NET_WM_NAME/WM_NAME) in the status bar. Enable by setting to 1. .Pp To prevent excessively large window names from pushing the remaining text off the bar, it is limited to 64 characters, by default. See the .Ic bar_format option for more details. .It Ic workspace_autorotate When moving workspaces across regions, auto-rotate vertical/horizontal layouts based on rotation data from .Xr xrandr 1 . Enable by setting to 1. .It Ic workspace_clamp Prevents workspaces from being swapped when attempting to switch to a workspace that is mapped to another region. Use .Ar warp_focus if you want to focus on the region containing the workspace and .Ar warp_pointer if you want to also send the pointer. Enable by setting to 1. .It Ic workspace_indicator Configure the status bar workspace indicator. One or more of the following options may be specified in a comma-separated list: .Pp .Bl -tag -width "markcurrentXXX" -offset indent -compact .It Ar listcurrent Include the current workspace. .It Ar listactive Include workspaces with windows. .It Ar listempty Include empty workspaces. .It Ar listnamed Include named workspaces. .It Ar listurgent Include workspaces with urgent window(s). .It Ar listall Include all workspaces. .It Ar hidecurrent Always exclude the current workspace from the list. .It Ar markcurrent Indicate the current workspace if it is in the list. .It Ar markactive Indicate workspaces in the list that are active. .It Ar markempty Indicate workspaces in the list that are empty. .It Ar markurgent Indicate workspaces in the list that contain urgent window(s). .It Ar printnames Display the names of named workspaces in the list. .It Ar noindexes Hide the index of the workspaces. .El .Pp The default is .Ar listcurrent , Ns Ar listactive , Ns Ar markcurrent , Ns Ar printnames .Pp Note that markup sequences can be used to style the workspace indicator. For example, to change the color of the current workspace: .Bd -literal -offset indent workspace_mark_current = '+@fg=1;' workspace_mark_current_suffix = '+@fg=0;' .Ed .It Ic workspace_limit Set the total number of workspaces available. Minimum is 1, maximum is 22, default is 10. .It Ic workspace_mark_active Set the string inserted before active workspaces in the .Ic workspace_indicator . Default is '^'. .It Ic workspace_mark_active_suffix Set the string inserted after active workspaces in the .Ic workspace_indicator . Default is '' (empty string). .It Ic workspace_mark_current Set the string inserted before the current workspace in the .Ic workspace_indicator . Default is '*'. .It Ic workspace_mark_current_suffix Set the string inserted after the current workspace in the .Ic workspace_indicator . Default is '' (empty string). .It Ic workspace_mark_empty Set the string inserted before empty workspaces in the .Ic workspace_indicator . Default is '-'. .It Ic workspace_mark_empty_suffix Set the string inserted after empty workspaces in the .Ic workspace_indicator . Default is '' (empty string). .It Ic workspace_mark_urgent Set the string inserted before urgent workspaces in the .Ic workspace_indicator . Default is '!'. .It Ic workspace_mark_urgent_suffix Set the string inserted after urgent workspaces in the .Ic workspace_indicator . Default is '' (empty string). .El .Sh STACK MODES .Bl -tag -width "horizontal flipped" .It Ic vertical Master area is on the left and stack area is on the right. Additional windows are vertically tiled in stack area. .It Ic vertical flipped Same as above but stack and master areas are swapped. .It Ic horizontal Master area is on the top and stack area is on the bottom. Additional windows are horizontally tiled in stack area. .It Ic horizontal flipped Same as above but stack and master areas are swapped. .It Ic max The focused window occupies the whole region, except for the bar (if enabled). .It Ic floating Windows are untiled and can be resized and positioned. .El .Sh WINDOW STATES These can be set/unset by the corresponding .Ic toggle actions listed in the .Sx BINDINGS section below. .Bl -tag -width "fullscreen" .It Ic floating The window is stacked above others and is not in a tile; it may be freely resized and positioned. .It Ic below The window is floating, but stacked below others. .It Ic maximized The window occupies the work area of the region (area that excludes space reserved by the bar, docks/panels, etc.) By default, focusing another window removes the maximized state of the window. See .Ic maximized_unfocus to configure unfocused behavior. .It Ic fullscreen The window occupies the whole region. By default, focusing another window does not remove the fullscreen state of the window. See .Ic fullscreen_unfocus to configure unfocused behavior. .It Ic free The window is floating, but not bound by regions, workspaces or their layouts. It is always mapped, unless iconified, and may be resized and positioned anywhere. .El .Sh PROGRAMS .Nm allows you to define custom actions to launch programs of your choice and then bind them the same as with built-in actions. See the .Sx BINDINGS section below. .Pp Custom programs in the configuration file are specified as follows: .Pp .Dl program Ns Bo Ar action Bc = Ar progpath Op Ar arg Op Ar arg ... .Pp .Ar action is any identifier that does not conflict with a built-in action or keyword, .Ar progpath is the desired program, and .Ar arg is zero or more arguments to the program. .Pp With the exception of '~' expansion, program calls are executed as-is without any interpretation. A shell can be called to execute shell commands. (e.g. sh -c 'command string'). .Pp Remember that when using .Ql # in your program call, it must be escaped with a backslash, i.e. \e# .Pp The following argument variables are replaced with values at the time the program is spawned: .Pp .Bl -tag -width "$bar_font_color" -offset indent -compact .It Cm $bar_border .It Cm $bar_color .It Cm $bar_color_selected .It Cm $bar_font .It Cm $bar_font_color .It Cm $bar_font_color_selected .It Cm $color_focus .It Cm $color_unfocus .It Cm $dmenu_bottom \-b if .Ic bar_at_bottom is enabled. .It Cm $region_index .It Cm $workspace_index .El .Pp Example: .Bd -literal -offset indent program[ff] = /usr/local/bin/firefox http://spectrwm.org/ bind[ff] = MOD+Shift+b # Now M-S-b launches firefox .Ed .Pp To cancel the previous, unbind it: .Bd -literal -offset indent bind[] = MOD+Shift+b .Ed .Pp Default programs: .Bl -tag -width "screenshot_wind" -offset indent -compact .It Cm term xterm .It Cm lock xlock .It Cm menu dmenu_run $dmenu_bottom \-fn $bar_font \-nb $bar_color \-nf $bar_font_color \-sb $bar_color_selected \-sf $bar_font_color_selected .It Cm search dmenu $dmenu_bottom \-i \-fn $bar_font \-nb $bar_color \-nf $bar_font_color \-sb $bar_color_selected \-sf $bar_font_color_selected .It Cm name_workspace dmenu $dmenu_bottom \-p Workspace \-fn $bar_font \-nb $bar_color \-nf $bar_font_color \-sb $bar_color_selected \-sf $bar_font_color_selected .It Cm initscr initscreen.sh # optional .It Cm screenshot_all screenshot.sh full # optional .It Cm screenshot_wind screenshot.sh window # optional .El .Pp Note that optional default programs will not be validated unless overridden. If a default program fails validation, you can resolve the exception by installing the program, modifying the program call or disabling the program by freeing the respective binding. .Pp For example, to override .Ic lock : .Bd -literal -offset indent program[lock] = xscreensaver\-command \-lock .Ed .Pp To unbind .Ic lock and prevent it from being validated: .Bd -literal -offset indent bind[] = MOD+Shift+Delete .Ed .Pp Note that when a program is spawned, .Nm aims to place its windows in its spawn workspace. See the .Sx SWMHACK section below for more information, tips, and workarounds if a program fails to spawn in the correct workspace. .Sh BINDINGS .Nm provides many functions (or actions) accessed via key or pointer button bindings. .Pp The default bindings are listed below: .Pp .Bl -tag -width "M-j, M-XXXXXX" -offset indent -compact .It Aq Cm Button1 focus .It Cm M- Ns Aq Cm Button1 move .It Cm M- Ns Aq Cm Button3 resize .It Cm M-S- Ns Aq Cm Button3 resize_centered .It Cm M-S- Ns Aq Cm Return term .It Cm M-p menu .It Cm M-S-q quit .It Cm M-q restart .It Aq Ar unbound restart_of_day .It Cm M- Ns Aq Cm Space cycle_layout .It Cm M-S-\e flip_layout .It Aq Ar unbound prior_layout .It Aq Ar unbound layout_vertical .It Aq Ar unbound layout_horizontal .It Aq Ar unbound layout_max .It Aq Ar unbound layout_floating .It Cm M-S- Ns Aq Cm Space stack_reset .It Aq Ar unbound stack_balance .It Cm M-h master_shrink .It Cm M-l master_grow .It Cm M-,\& master_add .It Cm M-.\& master_del .It Cm M-S-,\& stack_inc .It Cm M-S-.\& stack_dec .It Cm M- Ns Aq Cm Return swap_main .It Xo .Cm M-j , .Cm M- Ns Aq Cm TAB .Xc focus_next .It Xo .Cm M-k , .Cm M-S- Ns Aq Cm TAB .Xc focus_prev .It Cm M-m focus_main .It Cm M-\(ga focus_free .It Cm M-S-a focus_prior .It Cm M-u focus_urgent .It Cm M-S-j swap_next .It Cm M-S-k swap_prev .It Cm M-b bar_toggle .It Cm M-S-b bar_toggle_ws .It Cm M-x wind_del .It Cm M-S-x wind_kill .It Cm M- Ns Aq Ar 1-9,0,F1-F12 .Pf ws_ Aq Ar 1-22 .It Cm M-S- Ns Aq Ar 1-9,0,F1-F12 .Pf mvws_ Ns Aq Ar 1-22 .It Cm M- Ns Aq Ar Keypad 1-9 .Pf rg_ Aq Ar 1-9 .It Cm M-S- Ns Aq Ar Keypad 1-9 .Pf mvrg_ Aq Ar 1-9 .It Aq Ar unbound mvrg_next .It Aq Ar unbound mvrg_prev .It Aq Ar unbound ws_empty .It Aq Ar unbound ws_empty_move .It Cm M- Ns Aq Cm Right ws_next .It Cm M- Ns Aq Cm Left ws_prev .It Cm M- Ns Aq Cm Up ws_next_all .It Cm M- Ns Aq Cm Down ws_prev_all .It Cm M-a ws_prior .It Cm M-S- Ns Aq Cm Down ws_prev_move .It Cm M-S- Ns Aq Cm Up ws_next_move .It Cm M-S- Ns Aq Cm Right rg_next .It Cm M-S- Ns Aq Cm Left rg_prev .It Aq Ar unbound rg_move_next .It Aq Ar unbound rg_move_prev .It Cm M-s screenshot_all .It Cm M-S-s screenshot_wind .It Cm M-S-v version .It Cm M-t float_toggle .It Cm M-S-t below_toggle .It Cm M-S-\(ga free_toggle .It Cm M-S- Ns Aq Cm Delete lock .It Cm M-S-i initscr .It Cm M-w iconify .It Cm M-S-w uniconify .It Cm M-e maximize_toggle .It Cm M-S-e fullscreen_toggle .It Cm M-r raise .It Cm M-S-r always_raise .It Cm M-v button2 .It Cm M-- width_shrink .It Cm M-= width_grow .It Cm M-S-- height_shrink .It Cm M-S-= height_grow .It Cm M-[ move_left .It Cm M-]\& move_right .It Cm M-S-[ move_up .It Cm M-S-]\& move_down .It Cm M-S-/ name_workspace .It Cm M-/ search_workspace .It Cm M-f search_win .It Cm M-d debug_toggle (debug mode only) .It Cm M-S-d dumpwins (debug mode only) .El .Pp The action names and descriptions are listed below: .Pp .Bl -tag -width "layout_horizontalX" -offset indent -compact .It Cm focus Focus window/region under pointer. .It Cm move Move window with pointer while binding is pressed. .It Cm resize Resize window with pointer while binding is pressed. .It Cm resize_centered Same as .Ic resize but keep window centered. .It Cm term Spawn a new terminal (see .Sx PROGRAMS above). .It Cm menu Menu (see .Sx PROGRAMS above). .It Cm quit Quit .Nm . .It Cm restart Restart .Nm . .It Cm restart_of_day Same as .Ic restart but configuration file is loaded in full. .It Cm cycle_layout Switch to the next layout. .It Cm flip_layout Swap the master and stacking areas. .It Cm prior_layout Switch to the last used layout. .It Cm layout_vertical Switch to vertical layout. .It Cm layout_horizontal Switch to horizontal layout. .It Cm layout_max Switch to max layout. .It Cm layout_floating Switch to floating layout. .It Cm stack_reset Reset layout. .It Cm stack_balance Balance master/stacking area. .It Cm master_shrink Shrink master area. .It Cm master_grow Grow master area. .It Cm master_add Add windows to master area. .It Cm master_del Remove windows from master area. .It Cm stack_inc Add columns/rows to stacking area. .It Cm stack_dec Remove columns/rows from stacking area. .It Cm swap_main Move current window to master area. .It Cm focus_next Focus next window in workspace. .It Cm focus_prev Focus previous window in workspace. .It Cm focus_main Focus on main window in workspace. .It Cm focus_prior Focus last focused window in workspace. .It Cm focus_free Focus on a window in free mode, if any. .It Cm focus_urgent Focus on next window with the urgency hint flag set. The workspace is switched if needed. .It Cm swap_next Swap with next window in workspace. .It Cm swap_prev Swap with previous window in workspace. .It Cm bar_toggle Toggle overall visibility of status bars. .It Cm bar_toggle_ws Toggle status bar on current workspace. .It Cm wind_del Delete current window. .It Cm wind_kill Kill the program that created the current window. .It Cm ws_ Ns Ar n Switch to workspace .Ar n , where .Ar n is 1 through .Ic workspace_limit . .It Cm mvws_ Ns Ar n Move current window to workspace .Ar n , where .Ar n is 1 through .Ic workspace_limit . .It Cm rg_ Ns Ar n Focus on region .Ar n , where .Ar n is 1 through 9. .It Cm mvrg_ Ns Ar n Move current window to region .Ar n , where .Ar n is 1 through 9. .It Cm mvrg_next Move current window to workspace in next region. .It Cm mvrg_prev Move current window to workspace in previous region. .It Cm ws_empty Switch to the first empty workspace. .It Cm ws_empty_move Switch to the first empty workspace and move current window. .It Cm ws_next Switch to next workspace with a window in it. .It Cm ws_prev Switch to previous workspace with a window in it. .It Cm ws_next_all Switch to next workspace. .It Cm ws_prev_all Switch to previous workspace. .It Cm ws_next_move Switch to next workspace with the current window. .It Cm ws_prev_move Switch to previous workspace with the current window. .It Cm ws_prior Switch to last visited workspace. .It Cm rg_next Switch to next region. .It Cm rg_prev Switch to previous region. .It Cm rg_move_next Switch to next region and move current workspace. .It Cm rg_move_prev Switch to previous region and move current workspace. .It Cm screenshot_all Take screenshot of entire screen (if enabled) (see .Sx PROGRAMS above). .It Cm screenshot_wind Take screenshot of selected window (if enabled) (see .Sx PROGRAMS above). .It Cm version Toggle version in status bar. .It Cm float_toggle Toggle focused window between tiled and floating. .It Cm below_toggle Toggle .Ic below state on current window. .It Cm free_toggle Toggle focused window between workspace mode and free mode. .It Cm lock Lock screen (see .Sx PROGRAMS above). .It Cm initscr Reinitialize physical screens (see .Sx PROGRAMS above). .It Cm iconify Minimize (unmap) currently focused window. .It Cm uniconify Restore (map) window returned by .Xr dmenu 1 selection. .It Cm maximize_toggle Toggle maximization of focused window. .It Cm fullscreen_toggle Toggle fullscreen state of focused window. .It Cm raise Raise the current window. .It Cm always_raise When set tiled windows are allowed to obscure floating windows. .It Cm button2 Fake a middle mouse button click (Button2). .It Cm width_shrink Shrink the width of a floating window. .It Cm width_grow Grow the width of a floating window. .It Cm height_shrink Shrink the height of a floating window. .It Cm height_grow Grow the height of a floating window. .It Cm move_left Move a floating window a step to the left. .It Cm move_right Move a floating window a step to the right. .It Cm move_up Move a floating window a step upwards. .It Cm move_down Move a floating window a step downwards. .It Cm name_workspace Name the current workspace. .It Cm search_workspace Search for a workspace. .It Cm search_win Search the windows in the current workspace. .It Cm debug_toggle Toggles debug overlay. (debug mode only) .It Cm dumpwins Dump current window/focus/stacking state to debug log. (debug mode only) .El .Pp Custom bindings in the configuration file are specified as follows: .Pp .Dl bind Ns Bo Ar action Bc = Ar combo .Pp .Ar action is one of the actions listed above (or empty to unbind) and .Ar combo is in the form of zero or more modifier keys and/or special arguments (Mod1, Shift, Control, MOD, etc.) and a normal key (b, Space, etc) or a button (Button1 .. Button255), separated by .Ql + . Multiple key/button combinations may be bound to the same action. .Pp Special arguments: .Bl -tag -width "anymodxxxx" -offset indent -compact .It Cm MOD Substituted for the currently defined .Ic modkey . .It Cm ANYMOD Select all modifier combinations not handled by another binding. .It Cm REPLAY Reprocess binding press/release events for other programs to handle. Unavailable for .Ic move , .Ic resize and .Ic resize_centered . .El .Pp .Cm MOD example: .Bd -literal -offset indent bind[reset] = Mod4+q # bind Windows-key + q to reset bind[] = Mod1+q # unbind Alt + q bind[move] = MOD+Button3 # Bind move to M-Button3 bind[] = MOD+Button1 # Unbind default move binding. .Ed .Pp .Cm ANYMOD example: .Bd -literal -offset indent bind[focus] = ANYMOD+Button3 bind[move] = MOD+Button3 .Ed .Pp In the above example, .Cm M- Ns Aq Cm Button3 initiates .Ic move and .Aq Cm Button3 pressed with any other combination of modifiers sets focus to the window/region under the pointer. .Pp .Cm REPLAY example: .Bd -literal -offset indent bind[focus] = REPLAY+Button3 .Ed .Pp In the above example, when .Aq Cm Button3 is pressed without any modifier(s), focus is set to the window under the pointer and the button press is passed to the window. .Pp To bind non-latin characters such as \[oa] or \[*p] you must enter the xkb character name instead of the character itself. Run .Xr xev 1 , focus the window and press the specific key and in the terminal output read the symbol name. In the following example for \[oa]: .Bd -literal -offset indent KeyPress event, serial 41, synthetic NO, window 0x2600001, root 0x15a, subw 0x0, time 106213808, (11,5), root:(359,823), state 0x0, keycode 24 (keysym 0xe5, aring), same_screen YES, XLookupString gives 2 bytes: (c3 a5) "\[oa]" XmbLookupString gives 2 bytes: (c3 a5) "\[oa]" XFilterEvent returns: False .Ed .Pp The xkb name is aring. In other words, in .Pa spectrwm.conf add: .Bd -literal -offset indent bind[program] = MOD+aring .Ed .Pp To clear all default keyboard bindings and specify your own, see the .Ic keyboard_mapping option. .Sh KEYBOARD MAPPING FILES Keyboard mapping files for several keyboard layouts are listed below. These files can be used with the .Ic keyboard_mapping setting to load pre-defined key bindings for the specified keyboard layout. .Pp .Bl -tag -width "spectrwm_XX.confXXX" -offset indent -compact .It Cm spectrwm_cz.conf Czech Republic keyboard layout .It Cm spectrwm_es.conf Spanish keyboard layout .It Cm spectrwm_fr.conf French keyboard layout .It Cm spectrwm_fr_ch.conf Swiss French keyboard layout .It Cm spectrwm_se.conf Swedish keyboard layout .It Cm spectrwm_us.conf United States keyboard layout .El .Sh QUIRKS .Nm provides "quirks" which handle windows that must be treated specially in a tiling window manager, such as some dialogs and fullscreen apps. .Pp The default quirks are described below: .Pp .Bl -tag -width "OpenOffice.org N.M:VCLSalFrameXXX" -offset indent \ -compact .It Firefox\-bin:firefox\-bin TRANSSZ .It Firefox:Dialog FLOAT .It Gimp:gimp FLOAT + ANYWHERE .It MPlayer:xv FLOAT + FULLSCREEN + FOCUSPREV .It OpenOffice.org 2.4:VCLSalFrame FLOAT .It OpenOffice.org 3.1:VCLSalFrame FLOAT .It pcb:pcb FLOAT .It xine:Xine Window FLOAT + ANYWHERE .It xine:xine Panel FLOAT + ANYWHERE .It xine:xine Video Fullscreen Window FULLSCREEN + FLOAT .It Xitk:Xitk Combo FLOAT + ANYWHERE .It Xitk:Xine Window FLOAT + ANYWHERE .It XTerm:xterm XTERM_FONTADJ .El .Pp The quirks themselves are described below: .Pp .Bl -tag -width "XTERM_FONTADJXXX" -offset indent -compact .It ANYWHERE Allow window to position itself, uncentered. .It FLOAT This window should not be tiled, but allowed to float freely. .It FOCUSONMAP_SINGLE When the window first appears on the screen, change focus to the window if there are no other windows on the workspace with the same WM_CLASS class/instance value. Has no effect when .Ic focus_mode is set to .Ar follow . .It FOCUSPREV On exit force focus on previously focused application not previous application in the stack. .It FULLSCREEN Remove border to allow window to use full region size. .It IGNOREPID Ignore the PID when determining the initial workspace for a new window. Especially useful for terminal windows that share a process. .It IGNORESPAWNWS Ignore the spawn workspace when determining the initial workspace for a new window. .It MINIMALBORDER Remove border when window is unfocused and floating. .It NOFOCUSCYCLE Remove from normal focus cycle (focus_prev or focus_next). The window can still be focused using search_win. .It NOFOCUSONMAP Do not change focus to the window when it first appears on the screen. Has no effect when .Ic focus_mode is set to .Ar follow . .It OBEYAPPFOCUSREQ When an application requests focus on the window via a _NET_ACTIVE_WINDOW client message (source indication of 1), comply with the request. Note that a source indication of 0 (unspecified) or 2 (pager) are always obeyed. .It TRANSSZ Adjusts size on transient windows that are too small using .Ic dialog_ratio (see .Sx CONFIGURATION FILES ) . .It WS Ns Bq Ar n Force a new window to appear on workspace .Ar n . Specify -1 to put the window into free mode so that it is mapped independent of workspaces and regions. .It XTERM_FONTADJ Adjust .Xr xterm 1 fonts when resizing. .El .Pp Custom quirks in the configuration file are specified as follows: .Pp .Dl quirk Ns Bo Ar class Ns Bo : Ns Ar instance Ns Bo : Ns Ar name Bc Bc Bc \ = Ar quirk Op + Ar quirk ... .Pp .Ar class , .Ar instance (optional) and .Ar name (optional) are patterns used to determine which window(s) the quirk(s) apply to and .Ar quirk is one of the quirks from the list above. .Pp Note that patterns are interpreted as POSIX Extended Regular Expressions. Any ':', '[' or ']' must be escaped with '\e'. See .Xr regex 7 for more information on POSIX Extended Regular Expressions. .Pp For example: .Bd -literal -offset indent quirk[MPlayer] = FLOAT + FULLSCREEN + FOCUSPREV # Float all windows having a \ class of 'MPlayer' quirk[.*] = FLOAT # Float all windows by default. quirk[.*:.*:.*] = FLOAT # Same as above. quirk[Firefox:Navigator] = FLOAT # Float all Firefox browser windows. quirk[::Console] = FLOAT # Float windows with WM_CLASS not set and a \ window name of 'Console'. quirk[\e[0-9\e].*:.*:\e[\e[\e:alnum\e:\e]\e]*] = FLOAT # Float windows with \ WM_CLASS class beginning with a number, any WM_CLASS instance and a \ _NET_WM_NAME/WM_NAME either blank or containing alphanumeric characters \ without spaces. quirk[pcb:pcb] = NONE # remove existing quirk .Ed .Pp You can obtain .Ar class , .Ar instance and .Ar name by running .Xr xprop 1 and then clicking on the desired window. In the following example the main window of Firefox was clicked: .Bd -literal -offset indent $ xprop | grep \-E "^(WM_CLASS|_NET_WM_NAME|WM_NAME)" WM_CLASS(STRING) = "Navigator", "Firefox" WM_NAME(STRING) = "spectrwm - ConformalOpenSource" _NET_WM_NAME(UTF8_STRING) = "spectrwm - ConformalOpenSource" .Ed .Pp Note that .Xr xprop 1 displays WM_CLASS as: .Bd -literal -offset indent WM_CLASS(STRING) = "", "" .Ed .Pp In the example above the quirk entry would be: .Bd -literal -offset indent quirk[Firefox:Navigator] = FLOAT .Ed .Pp .Nm also automatically assigns quirks to windows based on the value of the window's _NET_WM_WINDOW_TYPE property as follows: .Pp .Bl -tag -width "_NET_WM_WINDOW_TYPE_TOOLBARXXX" -offset indent -compact .It _NET_WM_WINDOW_TYPE_TOOLBAR FLOAT + ANYWHERE .It _NET_WM_WINDOW_TYPE_UTILITY FLOAT + ANYWHERE .It _NET_WM_WINDOW_TYPE_SPLASH FLOAT .It _NET_WM_WINDOW_TYPE_DIALOG FLOAT .El .Pp In all other cases, no automatic quirks are assigned to the window. Quirks specified in the configuration file override the automatic quirks. .Sh EWMH .Nm partially implements the Extended Window Manager Hints (EWMH) specification. This enables controlling windows as well as .Nm itself from external scripts and programs. This is achieved by .Nm responding to certain ClientMessage events. From the terminal these events can be conveniently sent using tools such as .Xr wmctrl 1 and .Xr xdotool 1 . For the actual format of these ClientMessage events, see the EWMH specification. .Pp The id of the currently focused window is stored in the _NET_ACTIVE_WINDOW property of the root window. This can be used for example to retrieve the title of the currently active window with .Xr xprop 1 and .Xr grep 1 : .Bd -literal -offset indent $ WINDOWID=`xprop \-root _NET_ACTIVE_WINDOW | grep \-o "0x.*"` $ xprop \-id $WINDOWID _NET_WM_NAME | grep \-o "\e".*\e"" .Ed .Pp A window can be focused by sending a _NET_ACTIVE_WINDOW client message to the root window. For example, using .Xr wmctrl 1 to send the message (assuming 0x4a0000b is the id of the window to be focused): .Bd -literal -offset indent $ wmctrl \-i \-a 0x4a0000b .Ed .Pp Windows can be closed by sending a _NET_CLOSE_WINDOW client message to the root window. For example, using .Xr wmctrl 1 to send the message (assuming 0x4a0000b is the id of the window to be closed): .Bd -literal -offset indent $ wmctrl \-i \-c 0x4a0000b .Ed .Pp Windows can be floated and un-floated by adding or removing the _NET_WM_STATE_ABOVE atom from the _NET_WM_STATE property of the window. This can be achieved by sending a _NET_WM_STATE client message to the root window. For example, the following toggles the floating state of a window using .Xr wmctrl 1 to send the message (assuming 0x4a0000b is the id of the window to be floated or un-floated): .Bd -literal -offset indent $ wmctrl \-i \-r 0x4a0000b \-b toggle,above .Ed .Pp Windows can also be iconified and un-iconified by substituting _NET_WM_STATE_HIDDEN for _NET_WM_STATE_ABOVE in the previous example: .Bd -literal -offset indent $ wmctrl \-i \-r 0x4a0000b \-b toggle,hidden .Ed .Pp Floating windows can also be resized and moved by sending a _NET_MOVERESIZE_WINDOW client message to the root window. For example, using .Xr wmctrl 1 to send the message (assuming 0x4a0000b is the id of the window to be resize/moved): .Bd -literal -offset indent $ wmctrl \-i \-r 0x4a0000b \-e 0,100,50,640,480 .Ed .Pp This moves the window to (100,50) and resizes it to 640x480. .Pp Any _NET_MOVERESIZE_WINDOW events received for stacked windows are ignored. .Sh SWMHACK When spawning a program via .Ic autorun or a binding, .Nm aims to place the program's windows (if any) in its spawn workspace. To accomplish this "spawn-in-workspace" behavior, .Nm must determine the intended spawn workspace when managing a new window. Since it cannot be done with X11 alone, .Pa libswmhack.so is included to make this feature possible. .Pp When a program is spawned, .Nm automatically sets .Pa LD_PRELOAD and .Pa _SWM_WS in the program's spawn environment to enable .Pa libswmhack.so when it is executed. Note that .Pa LD_PRELOAD is the path to .Pa libswmhack.so and .Pa _SWM_WS is the spawn workspace for any windows created by the program. .Pp When running programs from terminals, scripts, etc, the inherited environment may need to be configured. It is possible to override the spawn workspace by setting .Pa _SWM_WS to a different value. Alternatively, .Pa _SWM_WS can be .Xr unset 1 or set to a blank value to disable "spawn-in-workspace" behavior. Note that workspaces are counted from 0. .Sq -1 can be specified to put windows into workspace-free mode. .Pp For example, to play a video with .Xr mpv 1 on workspace 10 without changing the spawn workspace in the environment: .Bd -literal -offset indent $ _SWM_WS=9 mpv video.mkv .Ed .Pp Play the video in free mode so that it remains mapped when switching workspaces. .Bd -literal -offset indent $ _SWM_WS=-1 mpv video.mkv .Ed .Pp Disable "spawn-in-workspace" in the environment so that new windows map on whichever workspace happens to be focused. .Bd -literal -offset indent $ unset _SWM_WS .Ed .Pp Change the environment to spawn programs in free mode. .Bd -literal -offset indent $ export _SWM_WS=-1 .Ed .Pp When spawning a program that creates windows via a daemon, ensure the daemon is started with the correct .Pa LD_PRELOAD in its environment. .Pp For example, when starting .Xr urxvtd 1 via .Xr xinit 1 , .Pa LD_PRELOAD must be specified. .Bd -literal -offset indent LD_PRELOAD=/usr/lib/libswmhack.so.0.0 urxvtd -q -o -f .Ed .Pp Note that some operating systems may ignore .Pa LD_PRELOAD if certain conditions are not met. It is advised to check the man page of .Pa ld.so . .Pp In situations where .Pa libswmhack.so cannot be used, it is possible to use a quirk to spawn a program in a specific workspace. .Pp e.g. launch an xterm(1) in workspace 2 on startup: .Bd -literal -offset indent autorun = ws[2]:xterm -name ws2 quirk[XTerm:ws2] = WS[2] .Ed .Pp Note that XCB programs are currently unsupported by .Pa libswmhack.so . .Sh SIGNALS Sending .Nm a HUP signal will restart it. .Sh FILES .Bl -tag -width "/etc/spectrwm.confXXX" -compact .It Pa ~/.spectrwm.conf .Nm user specific settings. .It Pa /etc/spectrwm.conf .Nm global settings. .El .Sh HISTORY .Nm was inspired by xmonad & dwm. .Sh AUTHORS .An -nosplit .Nm was written by: .Pp .Bl -tag -width "Ryan Thomas McBride Aq mcbride@countersiege.com " -offset \ indent -compact .It An Marco Peereboom Aq Mt marco@peereboom.us .It An Ryan Thomas McBride Aq Mt mcbride@countersiege.com .It An Darrin Chandler Aq Mt dwchandler@stilyagin.com .It An Pierre-Yves Ritschard Aq Mt pyr@spootnik.org .It An Tuukka Kataja Aq Mt stuge@xor.fi .It An Jason L. Wright Aq Mt jason@thought.net .It An Reginald Kennedy Aq Mt rk@rejii.com .It An Lawrence Teo Aq Mt lteo@lteo.net .It An Tiago Cunha Aq Mt tcunha@gmx.com .It An David Hill Aq Mt dhill@mindcry.org .El spectrwm-SPECTRWM_3_5_1/spectrwm.c000066400000000000000000015152561453034271100171010ustar00rootroot00000000000000/* * Copyright (c) 2009-2019 Marco Peereboom * Copyright (c) 2009-2011 Ryan McBride * Copyright (c) 2009 Darrin Chandler * Copyright (c) 2009 Pierre-Yves Ritschard * Copyright (c) 2010 Tuukka Kataja * Copyright (c) 2011 Jason L. Wright * Copyright (c) 2011-2023 Reginald Kennedy * Copyright (c) 2011-2012 Lawrence Teo * Copyright (c) 2011-2012 Tiago Cunha * Copyright (c) 2012-2015 David Hill * Copyright (c) 2014-2015 Yuri D'Elia * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* kernel includes */ #include #include #include #include #include #if !defined(__OpenBSD__) #include "queue_compat.h" #endif #include #include #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) #include #else #include "tree.h" #endif /* /usr/includes */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__FreeBSD__) #include #else #include #endif #if defined(__NetBSD__) #include #endif #include #include #include #include #include #if !defined(SWM_XCB_HAS_XINPUT) && (defined(__linux__) || defined(__FreeBSD__) \ || defined(__OpenBSD__) || defined(__NetBSD__)) #define SWM_XCB_HAS_XINPUT #endif #include #include #include #include #include #include #ifdef SWM_XCB_HAS_XINPUT #include #endif #include #include /* local includes */ #include "version.h" #ifdef __OSX__ #include #endif /* singly-linked tail queue macros appeared in OpenBSD 6.9 */ #ifndef STAILQ_HEAD #define STAILQ_HEAD SIMPLEQ_HEAD #define STAILQ_HEAD_INITIALIZER SIMPLEQ_HEAD_INITIALIZER #define STAILQ_ENTRY SIMPLEQ_ENTRY #define STAILQ_FIRST SIMPLEQ_FIRST #define STAILQ_END SIMPLEQ_END #define STAILQ_EMPTY SIMPLEQ_EMPTY #define STAILQ_NEXT SIMPLEQ_NEXT #define STAILQ_FOREACH SIMPLEQ_FOREACH #define STAILQ_FOREACH_SAFE SIMPLEQ_FOREACH_SAFE #define STAILQ_INIT SIMPLEQ_INIT #define STAILQ_INSERT_HEAD SIMPLEQ_INSERT_HEAD #define STAILQ_INSERT_TAIL SIMPLEQ_INSERT_TAIL #define STAILQ_INSERT_AFTER SIMPLEQ_INSERT_AFTER #define STAILQ_REMOVE_HEAD SIMPLEQ_REMOVE_HEAD #define STAILQ_REMOVE_AFTER SIMPLEQ_REMOVE_AFTER /*#define STAILQ_REMOVE*/ #define STAILQ_CONCAT SIMPLEQ_CONCAT /*#define STAILQ_LAST*/ #endif #ifdef SPECTRWM_BUILDSTR static const char *buildstr = SPECTRWM_BUILDSTR; #else static const char *buildstr = SPECTRWM_VERSION; #endif #if !defined(__CYGWIN__) /* cygwin chokes on randr stuff */ # if RANDR_MAJOR < 1 # error RandR versions less than 1.0 are not supported #endif # if RANDR_MAJOR >= 1 # if RANDR_MINOR >= 2 # define SWM_XRR_HAS_CRTC # endif # endif #endif /* __CYGWIN__ */ #ifndef XCB_ICCCM_NUM_WM_HINTS_ELEMENTS #define XCB_ICCCM_SIZE_HINT_P_MIN_SIZE XCB_SIZE_HINT_P_MIN_SIZE #define XCB_ICCCM_SIZE_HINT_P_MAX_SIZE XCB_SIZE_HINT_P_MAX_SIZE #define XCB_ICCCM_SIZE_HINT_P_RESIZE_INC XCB_SIZE_HINT_P_RESIZE_INC #define XCB_ICCCM_WM_HINT_INPUT XCB_WM_HINT_INPUT #define XCB_ICCCM_WM_HINT_X_URGENCY XCB_WM_HINT_X_URGENCY #define XCB_ICCCM_WM_STATE_ICONIC XCB_WM_STATE_ICONIC #define XCB_ICCCM_WM_STATE_WITHDRAWN XCB_WM_STATE_WITHDRAWN #define XCB_ICCCM_WM_STATE_NORMAL XCB_WM_STATE_NORMAL #define xcb_icccm_get_text_property_reply_t xcb_get_text_property_reply_t #define xcb_icccm_get_text_property_reply_wipe xcb_get_text_property_reply_wipe #define xcb_icccm_get_wm_class xcb_get_wm_class #define xcb_icccm_get_wm_class_reply xcb_get_wm_class_reply #define xcb_icccm_get_wm_class_reply_t xcb_get_wm_class_reply_t #define xcb_icccm_get_wm_class_reply_wipe xcb_get_wm_class_reply_wipe #define xcb_icccm_get_wm_hints xcb_get_wm_hints #define xcb_icccm_wm_hints_get_urgency xcb_wm_hints_get_urgency #define xcb_icccm_get_wm_hints_reply xcb_get_wm_hints_reply #define xcb_icccm_get_wm_name xcb_get_wm_name #define xcb_icccm_get_wm_name_reply xcb_get_wm_name_reply #define xcb_icccm_get_wm_normal_hints xcb_get_wm_normal_hints #define xcb_icccm_get_wm_normal_hints_reply xcb_get_wm_normal_hints_reply #define xcb_icccm_get_wm_protocols xcb_get_wm_protocols #define xcb_icccm_get_wm_protocols_reply xcb_get_wm_protocols_reply #define xcb_icccm_get_wm_protocols_reply_t xcb_get_wm_protocols_reply_t #define xcb_icccm_get_wm_protocols_reply_wipe xcb_get_wm_protocols_reply_wipe #define xcb_icccm_get_wm_transient_for xcb_get_wm_transient_for #define xcb_icccm_get_wm_transient_for_reply xcb_get_wm_transient_for_reply #define xcb_icccm_wm_hints_t xcb_wm_hints_t #endif /* Enable to turn on debug output by default. */ /*#define SWM_DEBUG*/ #define DPRINTF(x...) do { \ if (swm_debug) \ fprintf(stderr, x); \ } while (0) #define DNPRINTF(n, fmt, args...) do { \ if (swm_debug & n) \ fprintf(stderr, "%ld %s: " fmt, \ (long)(time(NULL) - time_started), __func__, ## args); \ } while (0) #define YESNO(x) ((x) ? "yes" : "no") #define SWM_D_MISC (0x00001) #define SWM_D_EVENT (0x00002) #define SWM_D_WS (0x00004) #define SWM_D_FOCUS (0x00008) #define SWM_D_MOVE (0x00010) #define SWM_D_STACK (0x00020) #define SWM_D_MOUSE (0x00040) #define SWM_D_PROP (0x00080) #define SWM_D_CLASS (0x00100) #define SWM_D_KEY (0x00200) #define SWM_D_QUIRK (0x00400) #define SWM_D_SPAWN (0x00800) #define SWM_D_EVENTQ (0x01000) #define SWM_D_CONF (0x02000) #define SWM_D_BAR (0x04000) #define SWM_D_INIT (0x08000) #define SWM_D_ATOM (0x10000) #define SWM_D_ALL (0x1ffff) /* Debug output is disabled by default unless SWM_DEBUG is set. */ uint32_t swm_debug = 0 #ifdef SWM_DEBUG | SWM_D_MISC | SWM_D_EVENT | SWM_D_WS | SWM_D_FOCUS | SWM_D_MOVE | SWM_D_STACK | SWM_D_MOUSE | SWM_D_PROP | SWM_D_CLASS | SWM_D_KEY | SWM_D_QUIRK | SWM_D_SPAWN | SWM_D_EVENTQ | SWM_D_CONF | SWM_D_BAR | SWM_D_INIT | SWM_D_ATOM #endif /* SWM_DEBUG */ ; #define ALLOCSTR(s, x...) do { \ if (s && asprintf(s, x) == -1) \ err(1, "asprintf"); \ } while (0) /* _NET_WM_STATE flags */ #define EWMH_F_MAXIMIZED_VERT (1 << 0) #define EWMH_F_MAXIMIZED_HORZ (1 << 1) #define EWMH_F_SKIP_TASKBAR (1 << 2) #define EWMH_F_SKIP_PAGER (1 << 3) #define EWMH_F_HIDDEN (1 << 4) #define EWMH_F_FULLSCREEN (1 << 5) #define EWMH_F_ABOVE (1 << 6) #define EWMH_F_BELOW (1 << 7) #define EWMH_F_DEMANDS_ATTENTION (1 << 8) #define SWM_F_MANUAL (1 << 9) #define SWM_EWMH_ACTION_COUNT_MAX (10) #define EWMH_F_MAXIMIZED (EWMH_F_MAXIMIZED_VERT | EWMH_F_MAXIMIZED_HORZ) #define EWMH_F_UNTILED (EWMH_F_ABOVE | EWMH_F_FULLSCREEN | \ EWMH_F_MAXIMIZED) #define EWMH_WINDOW_TYPE_DESKTOP (1 << 0) #define EWMH_WINDOW_TYPE_DOCK (1 << 1) #define EWMH_WINDOW_TYPE_TOOLBAR (1 << 2) #define EWMH_WINDOW_TYPE_MENU (1 << 3) #define EWMH_WINDOW_TYPE_UTILITY (1 << 4) #define EWMH_WINDOW_TYPE_SPLASH (1 << 5) #define EWMH_WINDOW_TYPE_DIALOG (1 << 6) #define EWMH_WINDOW_TYPE_DROPDOWN_MENU (1 << 7) #define EWMH_WINDOW_TYPE_POPUP_MENU (1 << 8) #define EWMH_WINDOW_TYPE_TOOLTIP (1 << 9) #define EWMH_WINDOW_TYPE_NOTIFICATION (1 << 10) #define EWMH_WINDOW_TYPE_COMBO (1 << 11) #define EWMH_WINDOW_TYPE_DND (1 << 12) #define EWMH_WINDOW_TYPE_NORMAL (1 << 13) #define WINDESKTOP(w) ((w)->type & EWMH_WINDOW_TYPE_DESKTOP) #define WINDOCK(w) ((w)->type & EWMH_WINDOW_TYPE_DOCK) #define WINTOOLBAR(w) ((w)->type & EWMH_WINDOW_TYPE_TOOLBAR) #define WINUTILITY(w) ((w)->type & EWMH_WINDOW_TYPE_UTILITY) #define WINSPLASH(w) ((w)->type & EWMH_WINDOW_TYPE_SPLASH) #define WINDIALOG(w) ((w)->type & EWMH_WINDOW_TYPE_DIALOG) #define EWMH_ALL_DESKTOPS (0xffffffff) /* convert 8-bit to 16-bit */ #define RGB_8_TO_16(col) (((col) << 8) + (col)) #define SWM_COLOR_TO_XRENDERCOLOR(sc, xrc) do { \ xrc.red = sc.r; \ xrc.green = sc.g; \ xrc.blue = sc.b; \ xrc.alpha = sc.a; \ } while (0); #define LENGTH(x) (int)(sizeof (x) / sizeof (x)[0]) #define MODKEY XCB_MOD_MASK_1 #define ANYMOD XCB_MOD_MASK_ANY #define CLEANMASK(mask) ((mask) & (XCB_KEY_BUT_MASK_SHIFT | \ XCB_KEY_BUT_MASK_CONTROL | XCB_KEY_BUT_MASK_MOD_1 | \ XCB_KEY_BUT_MASK_MOD_2 | XCB_KEY_BUT_MASK_MOD_3 | \ XCB_KEY_BUT_MASK_MOD_4 | XCB_KEY_BUT_MASK_MOD_5) & ~(numlockmask)) #define BUTTONMASK (XCB_EVENT_MASK_BUTTON_PRESS | \ XCB_EVENT_MASK_BUTTON_RELEASE) #define MOUSEMASK (BUTTONMASK|XCB_EVENT_MASK_POINTER_MOTION) #define CANCELKEY XK_Escape #define SWM_PROPLEN (16) #define SWM_FUNCNAME_LEN (32) #define SWM_QUIRK_LEN (64) #define SWM_MAX_FONT_STEPS (3) /* For SWM_Q_XTERM_FONTADJ */ /* For ws_win, swm_region and swm_bar. */ #define WINID(w) ((w) ? (w)->id : XCB_WINDOW_NONE) #define X(r) ((r)->g.x) #define Y(r) ((r)->g.y) #define WIDTH(r) ((r)->g.w) #define HEIGHT(r) ((r)->g.h) #define ROTATION(rr) ((rr)->g.r) #define MAX_X(r) (X(r) + WIDTH(r)) #define MAX_Y(r) (Y(r) + HEIGHT(r)) #define SH_POS(w) ((w)->sh.flags & XCB_ICCCM_SIZE_HINT_P_POSITION) #define SH_UPOS(w) ((w)->sh.flags & XCB_ICCCM_SIZE_HINT_US_POSITION) #define SH_MIN(w) ((w)->sh.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) #define SH_MIN_W(w) ((w)->sh.min_width) #define SH_MIN_H(w) ((w)->sh.min_height) #define SH_MAX(w) ((w)->sh.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE) #define SH_MAX_W(w) ((w)->sh.max_width) #define SH_MAX_H(w) ((w)->sh.max_height) #define SH_INC(w) ((w)->sh.flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC) #define SH_INC_W(w) ((w)->sh.width_inc) #define SH_INC_H(w) ((w)->sh.height_inc) #define SH_GRAVITY(w) ((w)->sh.flags & XCB_ICCCM_SIZE_HINT_P_WIN_GRAVITY) #define MAXIMIZED_VERT(w) ((w)->ewmh_flags & EWMH_F_MAXIMIZED_VERT) #define MAXIMIZED_HORZ(w) ((w)->ewmh_flags & EWMH_F_MAXIMIZED_HORZ) #define SKIP_TASKBAR(w) ((w)->ewmh_flags & EWMH_F_SKIP_TASKBAR) #define SKIP_PAGER(w) ((w)->ewmh_flags & EWMH_F_SKIP_PAGER) #define HIDDEN(w) ((w)->ewmh_flags & EWMH_F_HIDDEN) #define FULLSCREEN(w) ((w)->ewmh_flags & EWMH_F_FULLSCREEN) #define ABOVE(w) ((w)->ewmh_flags & EWMH_F_ABOVE) #define BELOW(w) ((w)->ewmh_flags & EWMH_F_BELOW) #define DEMANDS_ATTENTION(w) ((w)->ewmh_flags & EWMH_F_DEMANDS_ATTENTION) #define MANUAL(w) ((w)->ewmh_flags & SWM_F_MANUAL) #define MAXIMIZED(w) ((w)->ewmh_flags & EWMH_F_MAXIMIZED) /* Constrain Window flags */ #define SWM_CW_RESIZABLE (0x01) #define SWM_CW_SOFTBOUNDARY (0x02) #define SWM_CW_HARDBOUNDARY (0x04) #define SWM_CW_RIGHT (0x10) #define SWM_CW_LEFT (0x20) #define SWM_CW_BOTTOM (0x40) #define SWM_CW_TOP (0x80) #define SWM_CW_ALLSIDES (0xf0) #define SWM_FOCUS_DEFAULT (0) #define SWM_FOCUS_FOLLOW (1) #define SWM_FOCUS_MANUAL (2) #define SWM_COUNT_TILED (1 << 0) #define SWM_COUNT_FLOATING (1 << 1) #define SWM_COUNT_ICONIC (1 << 2) #define SWM_COUNT_DESKTOP (1 << 3) #define SWM_COUNT_NORMAL (SWM_COUNT_TILED | SWM_COUNT_FLOATING | \ SWM_COUNT_DESKTOP) #define SWM_COUNT_ALL (SWM_COUNT_NORMAL | SWM_COUNT_ICONIC) #define SWM_WIN_UNFOCUS (1 << 0) #define SWM_WIN_NOUNMAP (1 << 1) #define SWM_WSI_LISTCURRENT (0x001) #define SWM_WSI_LISTACTIVE (0x002) #define SWM_WSI_LISTEMPTY (0x004) #define SWM_WSI_LISTNAMED (0x008) #define SWM_WSI_LISTURGENT (0x010) #define SWM_WSI_LISTALL (0x0ff) #define SWM_WSI_HIDECURRENT (0x100) #define SWM_WSI_MARKCURRENT (0x200) #define SWM_WSI_MARKURGENT (0x400) #define SWM_WSI_MARKACTIVE (0x800) #define SWM_WSI_MARKEMPTY (0x1000) #define SWM_WSI_PRINTNAMES (0x2000) #define SWM_WSI_NOINDEXES (0x4000) #define SWM_WSI_DEFAULT (SWM_WSI_LISTCURRENT | SWM_WSI_LISTACTIVE | \ SWM_WSI_MARKCURRENT | SWM_WSI_PRINTNAMES) #define SWM_WM_CLASS_INSTANCE "spectrwm" #define SWM_WM_CLASS_BAR "panel" #define SWM_CONF_DEFAULT (0) #define SWM_CONF_KEYMAPPING (1) #define SWM_CONF_DELIMLIST "," #define SWM_CONF_WHITESPACE " \t\n" #ifndef SWM_LIB #define SWM_LIB "/usr/local/lib/libswmhack.so" #endif char **start_argv; xcb_atom_t a_state; xcb_atom_t a_change_state; xcb_atom_t a_prot; xcb_atom_t a_delete; xcb_atom_t a_net_frame_extents; xcb_atom_t a_net_wm_check; xcb_atom_t a_net_wm_pid; xcb_atom_t a_net_supported; xcb_atom_t a_takefocus; xcb_atom_t a_utf8_string; xcb_atom_t a_swm_pid; xcb_atom_t a_swm_ws; volatile sig_atomic_t running = 1; volatile sig_atomic_t restart_wm = 0; xcb_timestamp_t event_time = 0; int outputs = 0; xcb_window_t pointer_window = XCB_WINDOW_NONE; bool randr_support = false; int randr_eventbase; unsigned int numlockmask = 0; bool xinput2_support = false; int xinput2_opcode; bool xinput2_raw = false; Display *display; xcb_connection_t *conn; xcb_key_symbols_t *syms; int boundary_width = 50; int snap_range = 25; bool cycle_empty = false; bool cycle_visible = false; int term_width = 0; int font_adjusted = 0; uint16_t mod_key = MODKEY; xcb_keysym_t cancel_key = CANCELKEY; xcb_keycode_t cancel_keycode = XCB_NO_SYMBOL; bool warp_focus = false; bool warp_pointer = false; bool workspace_autorotate = false; bool workspace_clamp = false; /* dmenu search */ struct swm_region *search_r; int select_list_pipe[2]; int select_resp_pipe[2]; pid_t searchpid; volatile sig_atomic_t search_resp; int search_resp_action; struct search_window { TAILQ_ENTRY(search_window) entry; int idx; struct ws_win *win; xcb_gcontext_t gc; xcb_window_t indicator; }; TAILQ_HEAD(search_winlist, search_window) search_wl = TAILQ_HEAD_INITIALIZER(search_wl); /* search actions */ enum { SWM_SEARCH_NONE, SWM_SEARCH_UNICONIFY, SWM_SEARCH_NAME_WORKSPACE, SWM_SEARCH_SEARCH_WORKSPACE, SWM_SEARCH_SEARCH_WINDOW }; #define SWM_STACK_TOP (0) #define SWM_STACK_BOTTOM (1) #define SWM_STACK_ABOVE (2) #define SWM_STACK_BELOW (3) #define SWM_STACK_PRIOR (4) /* dialog windows */ double dialog_ratio = 0.6; /* status bar */ #define SWM_BAR_MAX (1024) #define SWM_BAR_JUSTIFY_LEFT (0) #define SWM_BAR_JUSTIFY_CENTER (1) #define SWM_BAR_JUSTIFY_RIGHT (2) #define SWM_BAR_OFFSET (4) #define SWM_BAR_FONTS "-*-terminus-medium-*-*-*-12-*-*-*-*-*-*-*," \ "-*-profont-*-*-*-*-12-*-*-*-*-*-*-*," \ "-*-times-medium-r-*-*-12-*-*-*-*-*-*-*," \ "-misc-fixed-medium-r-*-*-12-*-*-*-*-*-*-*," \ "-*-*-*-r-*-*-*-*-*-*-*-*-*-*" #define SWM_BAR_FONTS_FALLBACK "-*-fixed-*-r-*-*-*-*-*-*-*-*-*-*," \ "-*-*-*-*-*-r-*-*-*-*-*-*-*-*," \ "-*-*-*-*-*-*-*-*-*-*-*-*-*-*" #define SWM_BAR_MAX_FONTS (10) #define SWM_BAR_MAX_COLORS (10) #ifdef X_HAVE_UTF8_STRING #define DRAWSTRING(x...) Xutf8DrawString(x) #define TEXTEXTENTS(x...) Xutf8TextExtents(x) #else #define DRAWSTRING(x...) XmbDrawString(x) #define TEXTEXTENTS(x...) XmbTextExtents(x) #endif enum { SWM_UNFOCUS_NONE, SWM_UNFOCUS_RESTORE, SWM_UNFOCUS_ICONIFY, SWM_UNFOCUS_FLOAT, SWM_UNFOCUS_BELOW, SWM_UNFOCUS_QUICK_BELOW, }; char *bar_argv[] = { NULL, NULL }; char bar_ext[SWM_BAR_MAX]; char bar_ext_buf[SWM_BAR_MAX]; char bar_vertext[SWM_BAR_MAX]; bool bar_version = false; bool bar_enabled = true; int bar_border_width = 1; bool bar_at_bottom = false; bool bar_extra = false; int bar_height = 0; int bar_justify = SWM_BAR_JUSTIFY_LEFT; char *bar_format = NULL; bool bar_action_expand = false; bool stack_enabled = true; bool clock_enabled = true; bool iconic_enabled = false; int fullscreen_unfocus = SWM_UNFOCUS_NONE; bool fullscreen_hide_other = false; int maximized_unfocus = SWM_UNFOCUS_RESTORE; bool maximize_hide_bar = false; bool maximize_hide_other = false; bool max_layout_maximize = true; bool urgent_enabled = false; bool urgent_collapse = false; char *clock_format = NULL; bool window_class_enabled = false; bool window_instance_enabled = false; bool window_name_enabled = false; bool click_to_raise = true; uint32_t workspace_indicator = SWM_WSI_DEFAULT; int focus_mode = SWM_FOCUS_DEFAULT; int focus_close = SWM_STACK_BELOW; bool focus_close_wrap = true; int focus_default = SWM_STACK_TOP; int spawn_position = SWM_STACK_TOP; bool disable_border = false; bool disable_border_always = false; int border_width = 1; int region_padding = 0; int tile_gap = 0; bool verbose_layout = false; bool debug_enabled; time_t time_started; pid_t bar_pid; XFontSet bar_fs = NULL; XFontSetExtents *bar_fs_extents; char *bar_fontnames[SWM_BAR_MAX_FONTS]; int num_xftfonts = 0; char *bar_fontname_pua = NULL; int font_pua_index; bool bar_font_legacy = true; char *bar_fonts = NULL; XftColor bar_fg_colors[SWM_BAR_MAX_COLORS]; XftColor bar_fg_colors_free[SWM_BAR_MAX_COLORS]; XftColor bar_fg_colors_unfocus[SWM_BAR_MAX_COLORS]; int num_fg_colors = 1; int num_bg_colors = 1; XftColor search_font_color; char *startup_exception = NULL; unsigned int nr_exceptions = 0; char *workspace_mark_current = NULL; char *workspace_mark_current_suffix = NULL; char *workspace_mark_urgent = NULL; char *workspace_mark_urgent_suffix = NULL; char *workspace_mark_active = NULL; char *workspace_mark_active_suffix = NULL; char *workspace_mark_empty = NULL; char *workspace_mark_empty_suffix = NULL; char *focus_mark_none = NULL; char *focus_mark_normal = NULL; char *focus_mark_floating = NULL; char *focus_mark_free = NULL; char *focus_mark_maximized = NULL; char *stack_mark_floating = NULL; char *stack_mark_max = NULL; char *stack_mark_vertical = NULL; char *stack_mark_vertical_flip = NULL; char *stack_mark_horizontal = NULL; char *stack_mark_horizontal_flip = NULL; size_t stack_mark_maxlen = 0; #define ROTATION_DEFAULT (XCB_RANDR_ROTATION_ROTATE_0) #define ROTATION_VERT (XCB_RANDR_ROTATION_ROTATE_0 | \ XCB_RANDR_ROTATION_ROTATE_180) /* layout manager data */ struct swm_geometry { int16_t x; int16_t y; uint16_t w; uint16_t h; uint16_t r; /* RandR rotation. */ }; struct swm_screen; struct workspace; struct swm_stackable { SLIST_ENTRY(swm_stackable) entry; enum stackable { STACKABLE_WIN, STACKABLE_BAR, STACKABLE_REGION, STACKABLE_INVALID } type; union { struct ws_win *win; struct swm_bar *bar; struct swm_region *region; }; enum swm_layer { SWM_LAYER_REGION, SWM_LAYER_DESKTOP, SWM_LAYER_BELOW, SWM_LAYER_TILED, SWM_LAYER_DOCK, SWM_LAYER_BAR, SWM_LAYER_ABOVE, SWM_LAYER_MAXIMIZED, SWM_LAYER_FULLSCREEN, SWM_LAYER_RAISED, SWM_LAYER_INVALID } layer; struct swm_screen *s; /* always valid, never changes */ }; SLIST_HEAD(swm_stack_list, swm_stackable); struct swm_bar { struct swm_stackable *st; /* Always valid, never changes. */ xcb_window_t id; struct swm_geometry g; struct swm_region *r; /* Associated region. */ bool disabled; xcb_pixmap_t buffer; }; /* virtual "screens" */ struct swm_region { TAILQ_ENTRY(swm_region) entry; struct swm_stackable *st; /* Always valid, never changes. */ xcb_window_t id; struct swm_geometry g; struct swm_geometry g_usable; struct workspace *ws; /* current workspace on this region */ struct workspace *ws_prior; /* prior workspace on this region */ struct swm_screen *s; /* screen idx */ struct swm_bar *bar; }; TAILQ_HEAD(swm_region_list, swm_region); struct swm_strut { SLIST_ENTRY(swm_strut) entry; struct ws_win *win; /* _NET_WM_STRUT_PARTIAL: CARDINAL[12]/32 */ uint32_t left; uint32_t right; uint32_t top; uint32_t bottom; uint32_t left_start_y; uint32_t left_end_y; uint32_t right_start_y; uint32_t right_end_y; uint32_t top_start_x; uint32_t top_end_x; uint32_t bottom_start_x; uint32_t bottom_end_x; }; SLIST_HEAD(swm_strut_list, swm_strut); struct ws_win { TAILQ_ENTRY(ws_win) entry; TAILQ_ENTRY(ws_win) manage_entry; TAILQ_ENTRY(ws_win) focus_entry; TAILQ_ENTRY(ws_win) priority_entry; struct swm_stackable *st; /* Always valid, never changes */ xcb_window_t id; xcb_window_t frame; xcb_window_t transient_for; /* WM_TRANSIENT_FOR (WINDOW). */ xcb_visualid_t visual; struct ws_win *main; /* Always valid. */ struct ws_win *parent; /* WM_TRANSIENT_FOR ws_win. */ struct ws_win *focus_redirect;/* focus on transient */ struct swm_geometry g; /* current geometry */ struct swm_geometry g_grav; /* win-gravity reference. */ struct swm_geometry g_float; /* root coordinates */ struct swm_geometry g_floatref; /* reference coordinates */ bool g_floatref_root; bool g_float_xy_valid; uint8_t gravity; bool mapped; uint32_t mapping; /* # of pending operations */ uint32_t unmapping; /* # of pending operations */ uint32_t state; /* current ICCCM WM_STATE */ bool normalmax; bool maxstackmax; bool bordered; uint32_t type; /* _NET_WM_WINDOW_TYPE */ uint32_t ewmh_flags; int font_size_boundary[SWM_MAX_FONT_STEPS]; int font_steps; int last_inc; bool can_delete; bool take_focus; uint32_t quirks; struct workspace *ws; /* always valid */ struct swm_screen *s; /* always valid, never changes */ xcb_size_hints_t sh; xcb_icccm_get_wm_class_reply_t ch; xcb_icccm_wm_hints_t hints; struct swm_strut *strut; xcb_window_t debug; /* Debug overlay window. */ }; TAILQ_HEAD(ws_win_list, ws_win); TAILQ_HEAD(ws_win_focus, ws_win); TAILQ_HEAD(ws_win_managed, ws_win); TAILQ_HEAD(ws_win_priority, ws_win); /* pid goo */ struct pid_e { TAILQ_ENTRY(pid_e) entry; pid_t pid; int ws; }; TAILQ_HEAD(pid_list, pid_e) pidlist = TAILQ_HEAD_INITIALIZER(pidlist); /* layout handlers */ void stack(struct swm_region *); void vertical_config(struct workspace *, int); void vertical_stack(struct workspace *, struct swm_geometry *); void horizontal_config(struct workspace *, int); void horizontal_stack(struct workspace *, struct swm_geometry *); void max_config(struct workspace *, int); void max_stack(struct workspace *, struct swm_geometry *); void floating_stack(struct workspace *, struct swm_geometry *); void plain_stacker(struct workspace *); void fancy_stacker(struct workspace *); struct layout { void (*l_stack)(struct workspace *, struct swm_geometry *); void (*l_config)(struct workspace *, int); uint32_t flags; #define SWM_L_FOCUSPREV (1 << 0) #define SWM_L_MAPONFOCUS (1 << 1) #define SWM_L_NOTILE (1 << 2) void (*l_string)(struct workspace *); } layouts[] = { /* stack, configure */ { vertical_stack, vertical_config, 0, plain_stacker }, { horizontal_stack, horizontal_config, 0, plain_stacker }, { max_stack, max_config, SWM_L_MAPONFOCUS | SWM_L_FOCUSPREV, plain_stacker }, { floating_stack, NULL, SWM_L_NOTILE, plain_stacker }, { NULL, NULL, 0, NULL }, }; /* position of max_stack mode in the layouts array, index into layouts! */ #define SWM_V_STACK (0) #define SWM_H_STACK (1) #define SWM_MAX_STACK (2) #define SWM_FLOATING_STACK (3) #define SWM_H_SLICE (32) #define SWM_V_SLICE (32) #define SWM_FANCY_MAXLEN 8 /* Includes null byte. */ /* define work spaces */ struct workspace { RB_ENTRY(workspace) entry; int idx; /* workspace index */ char *name; /* workspace name */ bool always_raise; /* raise windows on focus */ bool bar_enabled; /* bar visibility */ struct layout *cur_layout; /* current layout handlers */ struct layout *prev_layout; /* may be NULL */ struct ws_win *focus; /* may be NULL */ struct ws_win *focus_raise; /* may be NULL */ struct swm_screen *s; /* Always valid, never changes. */ struct swm_region *r; /* may be NULL */ struct swm_region *old_r; /* may be NULL */ struct ws_win_list winlist; /* list of windows in ws */ char *stacker; /* stack_mark buffer */ size_t stacker_len; uint16_t rotation; /* Layout reference. */ /* stacker state */ struct { int horizontal_msize; int horizontal_mwin; int horizontal_stacks; bool horizontal_flip; int vertical_msize; int vertical_mwin; int vertical_stacks; bool vertical_flip; } l_state; }; RB_HEAD(workspace_tree, workspace); enum { SWM_S_COLOR_BAR, SWM_S_COLOR_BAR1, SWM_S_COLOR_BAR2, SWM_S_COLOR_BAR3, SWM_S_COLOR_BAR4, SWM_S_COLOR_BAR5, SWM_S_COLOR_BAR6, SWM_S_COLOR_BAR7, SWM_S_COLOR_BAR8, SWM_S_COLOR_BAR9, SWM_S_COLOR_BAR_UNFOCUS, SWM_S_COLOR_BAR_UNFOCUS1, SWM_S_COLOR_BAR_UNFOCUS2, SWM_S_COLOR_BAR_UNFOCUS3, SWM_S_COLOR_BAR_UNFOCUS4, SWM_S_COLOR_BAR_UNFOCUS5, SWM_S_COLOR_BAR_UNFOCUS6, SWM_S_COLOR_BAR_UNFOCUS7, SWM_S_COLOR_BAR_UNFOCUS8, SWM_S_COLOR_BAR_UNFOCUS9, SWM_S_COLOR_BAR_FREE, SWM_S_COLOR_BAR_FREE1, SWM_S_COLOR_BAR_FREE2, SWM_S_COLOR_BAR_FREE3, SWM_S_COLOR_BAR_FREE4, SWM_S_COLOR_BAR_FREE5, SWM_S_COLOR_BAR_FREE6, SWM_S_COLOR_BAR_FREE7, SWM_S_COLOR_BAR_FREE8, SWM_S_COLOR_BAR_FREE9, SWM_S_COLOR_BAR_SELECTED, SWM_S_COLOR_BAR_BORDER, SWM_S_COLOR_BAR_BORDER_UNFOCUS, SWM_S_COLOR_BAR_BORDER_FREE, SWM_S_COLOR_BAR_FONT, SWM_S_COLOR_BAR_FONT1, SWM_S_COLOR_BAR_FONT2, SWM_S_COLOR_BAR_FONT3, SWM_S_COLOR_BAR_FONT4, SWM_S_COLOR_BAR_FONT5, SWM_S_COLOR_BAR_FONT6, SWM_S_COLOR_BAR_FONT7, SWM_S_COLOR_BAR_FONT8, SWM_S_COLOR_BAR_FONT9, SWM_S_COLOR_BAR_FONT_UNFOCUS, SWM_S_COLOR_BAR_FONT_UNFOCUS1, SWM_S_COLOR_BAR_FONT_UNFOCUS2, SWM_S_COLOR_BAR_FONT_UNFOCUS3, SWM_S_COLOR_BAR_FONT_UNFOCUS4, SWM_S_COLOR_BAR_FONT_UNFOCUS5, SWM_S_COLOR_BAR_FONT_UNFOCUS6, SWM_S_COLOR_BAR_FONT_UNFOCUS7, SWM_S_COLOR_BAR_FONT_UNFOCUS8, SWM_S_COLOR_BAR_FONT_UNFOCUS9, SWM_S_COLOR_BAR_FONT_FREE, SWM_S_COLOR_BAR_FONT_FREE1, SWM_S_COLOR_BAR_FONT_FREE2, SWM_S_COLOR_BAR_FONT_FREE3, SWM_S_COLOR_BAR_FONT_FREE4, SWM_S_COLOR_BAR_FONT_FREE5, SWM_S_COLOR_BAR_FONT_FREE6, SWM_S_COLOR_BAR_FONT_FREE7, SWM_S_COLOR_BAR_FONT_FREE8, SWM_S_COLOR_BAR_FONT_FREE9, SWM_S_COLOR_BAR_FONT_SELECTED, SWM_S_COLOR_FOCUS, SWM_S_COLOR_FOCUS_MAXIMIZED, SWM_S_COLOR_UNFOCUS, SWM_S_COLOR_UNFOCUS_MAXIMIZED, SWM_S_COLOR_FOCUS_FREE, SWM_S_COLOR_FOCUS_MAXIMIZED_FREE, SWM_S_COLOR_UNFOCUS_FREE, SWM_S_COLOR_UNFOCUS_MAXIMIZED_FREE, SWM_S_COLOR_MAX }; /* physical screen mapping */ #define SWM_WS_MAX (22) /* hard limit */ int workspace_limit = 10; /* soft limit */ #define SWM_RATE_DEFAULT (60) /* Default for swm_screen. */ struct swm_screen { int idx; /* screen index */ xcb_window_t root; struct swm_region *r; /* Root region is always valid. */ struct swm_region_list rl; /* Additional regions on this screen. */ struct swm_region_list orl; /* Old/unused regions on this screen. */ struct swm_region *r_focus; /* Current active region. */ xcb_window_t active_window; /* current _NET_ACTIVE_WINDOW */ xcb_window_t swmwin; /* ewmh wm check/default input */ struct workspace_tree workspaces; /* Dynamic workspaces. */ struct ws_win_priority priority; /* Window floating priority. */ struct swm_stack_list stack; /* Current stacking order. */ struct ws_win *focus; /* Currently focused window. */ struct ws_win_focus fl; /* Previous focus queue. */ struct ws_win_managed managed; /* All client windows. */ int managed_count; struct swm_strut_list struts; struct swm_color { uint16_t r; uint16_t g; uint16_t b; uint16_t a; uint16_t r_orig; uint16_t g_orig; uint16_t b_orig; uint32_t pixel; int manual; } c[SWM_S_COLOR_MAX]; uint8_t depth; xcb_timestamp_t rate; /* Max updates/sec for move and resize */ xcb_visualid_t visual; Visual *xvisual; /* Needed for Xft. */ xcb_colormap_t colormap; xcb_gcontext_t gc; XftFont *bar_xftfonts[SWM_BAR_MAX_FONTS + 1]; }; struct swm_screen *screens; /* args to functions */ union arg { int id; #define SWM_ARG_ID_FOCUSNEXT (0) #define SWM_ARG_ID_FOCUSPREV (1) #define SWM_ARG_ID_FOCUSMAIN (2) #define SWM_ARG_ID_FOCUSURGENT (3) #define SWM_ARG_ID_FOCUSPRIOR (4) #define SWM_ARG_ID_FOCUSFREE (5) #define SWM_ARG_ID_SWAPNEXT (10) #define SWM_ARG_ID_SWAPPREV (11) #define SWM_ARG_ID_SWAPMAIN (12) #define SWM_ARG_ID_MASTERSHRINK (20) #define SWM_ARG_ID_MASTERGROW (21) #define SWM_ARG_ID_MASTERADD (22) #define SWM_ARG_ID_MASTERDEL (23) #define SWM_ARG_ID_FLIPLAYOUT (24) #define SWM_ARG_ID_STACKRESET (30) #define SWM_ARG_ID_STACKINIT (31) #define SWM_ARG_ID_STACKBALANCE (32) #define SWM_ARG_ID_CYCLEWS_UP (40) #define SWM_ARG_ID_CYCLEWS_DOWN (41) #define SWM_ARG_ID_CYCLERG_UP (42) #define SWM_ARG_ID_CYCLERG_DOWN (43) #define SWM_ARG_ID_CYCLEWS_UP_ALL (44) #define SWM_ARG_ID_CYCLEWS_DOWN_ALL (45) #define SWM_ARG_ID_CYCLEWS_MOVE_UP (46) #define SWM_ARG_ID_CYCLEWS_MOVE_DOWN (47) #define SWM_ARG_ID_STACKINC (50) #define SWM_ARG_ID_STACKDEC (51) #define SWM_ARG_ID_CYCLE_LAYOUT (60) #define SWM_ARG_ID_LAYOUT_VERTICAL (61) #define SWM_ARG_ID_LAYOUT_HORIZONTAL (62) #define SWM_ARG_ID_LAYOUT_MAX (63) #define SWM_ARG_ID_PRIOR_LAYOUT (64) #define SWM_ARG_ID_LAYOUT_FLOATING (65) #define SWM_ARG_ID_DONTCENTER (70) #define SWM_ARG_ID_CENTER (71) #define SWM_ARG_ID_KILLWINDOW (80) #define SWM_ARG_ID_DELETEWINDOW (81) #define SWM_ARG_ID_WIDTHGROW (90) #define SWM_ARG_ID_WIDTHSHRINK (91) #define SWM_ARG_ID_HEIGHTGROW (92) #define SWM_ARG_ID_HEIGHTSHRINK (93) #define SWM_ARG_ID_MOVEUP (100) #define SWM_ARG_ID_MOVEDOWN (101) #define SWM_ARG_ID_MOVELEFT (102) #define SWM_ARG_ID_MOVERIGHT (103) #define SWM_ARG_ID_BAR_TOGGLE (110) #define SWM_ARG_ID_BAR_TOGGLE_WS (111) #define SWM_ARG_ID_CYCLERG_MOVE_UP (112) #define SWM_ARG_ID_CYCLERG_MOVE_DOWN (113) #define SWM_ARG_ID_WS_EMPTY (120) #define SWM_ARG_ID_WS_EMPTY_MOVE (121) #define SWM_ARG_ID_RESTARTOFDAY (130) char **argv; }; /* quirks */ struct quirk { TAILQ_ENTRY(quirk) entry; char *class; /* WM_CLASS:class */ char *instance; /* WM_CLASS:instance */ char *name; /* WM_NAME */ regex_t regex_class; regex_t regex_instance; regex_t regex_name; uint32_t quirk; int ws; /* Initial workspace. */ #define SWM_Q_FLOAT (1 << 0) /* Float this window. */ #define SWM_Q_TRANSSZ (1 << 1) /* Transient window size too small. */ #define SWM_Q_ANYWHERE (1 << 2) /* Don't position this window */ #define SWM_Q_XTERM_FONTADJ (1 << 3) /* Adjust xterm fonts when resizing. */ #define SWM_Q_FULLSCREEN (1 << 4) /* Remove border when fullscreen. */ #define SWM_Q_FOCUSPREV (1 << 5) /* Focus on caller. */ #define SWM_Q_NOFOCUSONMAP (1 << 6) /* Don't focus on window when mapped.*/ #define SWM_Q_FOCUSONMAP_SINGLE (1 << 7) /* Only focus if single win of type. */ #define SWM_Q_OBEYAPPFOCUSREQ (1 << 8) /* Focus when applications ask. */ #define SWM_Q_IGNOREPID (1 << 9) /* Ignore PID when determining ws. */ #define SWM_Q_IGNORESPAWNWS (1 << 10)/* Ignore _SWM_WS when managing win. */ #define SWM_Q_NOFOCUSCYCLE (1 << 11)/* Remove from normal focus cycle. */ #define SWM_Q_MINIMALBORDER (1 << 12)/* No border when floating/unfocused.*/ }; TAILQ_HEAD(quirk_list, quirk) quirks = TAILQ_HEAD_INITIALIZER(quirks); /* * Supported EWMH hints should be added to * both the enum and the ewmh array */ enum { _NET_ACTIVE_WINDOW, _NET_CLIENT_LIST, _NET_CLOSE_WINDOW, _NET_CURRENT_DESKTOP, _NET_DESKTOP_GEOMETRY, _NET_DESKTOP_NAMES, _NET_DESKTOP_VIEWPORT, _NET_MOVERESIZE_WINDOW, _NET_NUMBER_OF_DESKTOPS, _NET_REQUEST_FRAME_EXTENTS, _NET_RESTACK_WINDOW, _NET_WM_ACTION_ABOVE, _NET_WM_ACTION_CLOSE, _NET_WM_ACTION_FULLSCREEN, _NET_WM_ACTION_MOVE, _NET_WM_ACTION_RESIZE, _NET_WM_ALLOWED_ACTIONS, _NET_WM_DESKTOP, _NET_WM_FULL_PLACEMENT, _NET_WM_MOVERESIZE, _NET_WM_NAME, _NET_WM_STATE, _NET_WM_STATE_MAXIMIZED_VERT, _NET_WM_STATE_MAXIMIZED_HORZ, _NET_WM_STATE_SKIP_TASKBAR, _NET_WM_STATE_SKIP_PAGER, _NET_WM_STATE_HIDDEN, _NET_WM_STATE_FULLSCREEN, _NET_WM_STATE_ABOVE, _NET_WM_STATE_BELOW, _NET_WM_STATE_DEMANDS_ATTENTION, _NET_WM_STRUT, _NET_WM_STRUT_PARTIAL, _NET_WM_WINDOW_TYPE, _NET_WM_WINDOW_TYPE_DESKTOP, _NET_WM_WINDOW_TYPE_DOCK, _NET_WM_WINDOW_TYPE_TOOLBAR, _NET_WM_WINDOW_TYPE_MENU, _NET_WM_WINDOW_TYPE_UTILITY, _NET_WM_WINDOW_TYPE_SPLASH, _NET_WM_WINDOW_TYPE_DIALOG, _NET_WM_WINDOW_TYPE_DROPDOWN_MENU, _NET_WM_WINDOW_TYPE_POPUP_MENU, _NET_WM_WINDOW_TYPE_TOOLTIP, _NET_WM_WINDOW_TYPE_NOTIFICATION, _NET_WM_WINDOW_TYPE_COMBO, _NET_WM_WINDOW_TYPE_DND, _NET_WM_WINDOW_TYPE_NORMAL, _NET_WORKAREA, _SWM_WM_STATE_MANUAL, SWM_EWMH_HINT_MAX }; struct ewmh_hint { char *name; xcb_atom_t atom; } ewmh[SWM_EWMH_HINT_MAX] = { /* must be in same order as in the enum */ {"_NET_ACTIVE_WINDOW", XCB_ATOM_NONE}, {"_NET_CLIENT_LIST", XCB_ATOM_NONE}, {"_NET_CLOSE_WINDOW", XCB_ATOM_NONE}, {"_NET_CURRENT_DESKTOP", XCB_ATOM_NONE}, {"_NET_DESKTOP_GEOMETRY", XCB_ATOM_NONE}, {"_NET_DESKTOP_NAMES", XCB_ATOM_NONE}, {"_NET_DESKTOP_VIEWPORT", XCB_ATOM_NONE}, {"_NET_MOVERESIZE_WINDOW", XCB_ATOM_NONE}, {"_NET_NUMBER_OF_DESKTOPS", XCB_ATOM_NONE}, {"_NET_REQUEST_FRAME_EXTENTS", XCB_ATOM_NONE}, {"_NET_RESTACK_WINDOW", XCB_ATOM_NONE}, {"_NET_WM_ACTION_ABOVE", XCB_ATOM_NONE}, {"_NET_WM_ACTION_CLOSE", XCB_ATOM_NONE}, {"_NET_WM_ACTION_FULLSCREEN", XCB_ATOM_NONE}, {"_NET_WM_ACTION_MOVE", XCB_ATOM_NONE}, {"_NET_WM_ACTION_RESIZE", XCB_ATOM_NONE}, {"_NET_WM_ALLOWED_ACTIONS", XCB_ATOM_NONE}, {"_NET_WM_DESKTOP", XCB_ATOM_NONE}, {"_NET_WM_FULL_PLACEMENT", XCB_ATOM_NONE}, {"_NET_WM_MOVERESIZE", XCB_ATOM_NONE}, {"_NET_WM_NAME", XCB_ATOM_NONE}, {"_NET_WM_STATE", XCB_ATOM_NONE}, {"_NET_WM_STATE_MAXIMIZED_VERT", XCB_ATOM_NONE}, {"_NET_WM_STATE_MAXIMIZED_HORZ", XCB_ATOM_NONE}, {"_NET_WM_STATE_SKIP_TASKBAR", XCB_ATOM_NONE}, {"_NET_WM_STATE_SKIP_PAGER", XCB_ATOM_NONE}, {"_NET_WM_STATE_HIDDEN", XCB_ATOM_NONE}, {"_NET_WM_STATE_FULLSCREEN", XCB_ATOM_NONE}, {"_NET_WM_STATE_ABOVE", XCB_ATOM_NONE}, {"_NET_WM_STATE_BELOW", XCB_ATOM_NONE}, {"_NET_WM_STATE_DEMANDS_ATTENTION", XCB_ATOM_NONE}, {"_NET_WM_STRUT", XCB_ATOM_NONE}, {"_NET_WM_STRUT_PARTIAL", XCB_ATOM_NONE}, {"_NET_WM_WINDOW_TYPE", XCB_ATOM_NONE}, {"_NET_WM_WINDOW_TYPE_DESKTOP", XCB_ATOM_NONE}, {"_NET_WM_WINDOW_TYPE_DOCK", XCB_ATOM_NONE}, {"_NET_WM_WINDOW_TYPE_TOOLBAR", XCB_ATOM_NONE}, {"_NET_WM_WINDOW_TYPE_MENU", XCB_ATOM_NONE}, {"_NET_WM_WINDOW_TYPE_UTILITY", XCB_ATOM_NONE}, {"_NET_WM_WINDOW_TYPE_SPLASH", XCB_ATOM_NONE}, {"_NET_WM_WINDOW_TYPE_DIALOG", XCB_ATOM_NONE}, {"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", XCB_ATOM_NONE}, {"_NET_WM_WINDOW_TYPE_POPUP_MENU", XCB_ATOM_NONE}, {"_NET_WM_WINDOW_TYPE_TOOLTIP", XCB_ATOM_NONE}, {"_NET_WM_WINDOW_TYPE_NOTIFICATION", XCB_ATOM_NONE}, {"_NET_WM_WINDOW_TYPE_COMBO", XCB_ATOM_NONE}, {"_NET_WM_WINDOW_TYPE_DND", XCB_ATOM_NONE}, {"_NET_WM_WINDOW_TYPE_NORMAL", XCB_ATOM_NONE}, {"_NET_WORKAREA", XCB_ATOM_NONE}, {"_SWM_WM_STATE_MANUAL", XCB_ATOM_NONE}, }; /* EWMH source type */ enum { EWMH_SOURCE_TYPE_NONE = 0, EWMH_SOURCE_TYPE_NORMAL = 1, EWMH_SOURCE_TYPE_OTHER = 2, }; enum { EWMH_WM_MOVERESIZE_SIZE_TOPLEFT = 0, EWMH_WM_MOVERESIZE_SIZE_TOP = 1, EWMH_WM_MOVERESIZE_SIZE_TOPRIGHT = 2, EWMH_WM_MOVERESIZE_SIZE_RIGHT = 3, EWMH_WM_MOVERESIZE_SIZE_BOTTOMRIGHT = 4, EWMH_WM_MOVERESIZE_SIZE_BOTTOM = 5, EWMH_WM_MOVERESIZE_SIZE_BOTTOMLEFT = 6, EWMH_WM_MOVERESIZE_SIZE_LEFT = 7, EWMH_WM_MOVERESIZE_MOVE = 8, EWMH_WM_MOVERESIZE_SIZE_KEYBOARD = 9, EWMH_WM_MOVERESIZE_MOVE_KEYBOARD = 10, EWMH_WM_MOVERESIZE_CANCEL = 11, }; #define SWM_SIZE_VFLIP (0x1) #define SWM_SIZE_HFLIP (0x2) #define SWM_SIZE_VERT (0x4) #define SWM_SIZE_HORZ (0x8) #define SWM_SIZE_TOP (SWM_SIZE_VERT | SWM_SIZE_VFLIP) #define SWM_SIZE_BOTTOM (SWM_SIZE_VERT) #define SWM_SIZE_RIGHT (SWM_SIZE_HORZ) #define SWM_SIZE_LEFT (SWM_SIZE_HORZ | SWM_SIZE_HFLIP) #define SWM_SIZE_TOPLEFT (SWM_SIZE_TOP | SWM_SIZE_LEFT) #define SWM_SIZE_TOPRIGHT (SWM_SIZE_TOP | SWM_SIZE_RIGHT) #define SWM_SIZE_BOTTOMLEFT (SWM_SIZE_BOTTOM | SWM_SIZE_LEFT) #define SWM_SIZE_BOTTOMRIGHT (SWM_SIZE_BOTTOM | SWM_SIZE_RIGHT) /* Cursors */ enum { XC_FLEUR, XC_BOTTOM_LEFT_CORNER, XC_BOTTOM_RIGHT_CORNER, XC_BOTTOM_SIDE, XC_LEFT_PTR, XC_LEFT_SIDE, XC_RIGHT_SIDE, XC_SIZING, XC_TOP_LEFT_CORNER, XC_TOP_RIGHT_CORNER, XC_TOP_SIDE, XC_MAX }; struct cursors { char *name; /* Name used by Xcursor .*/ uint8_t cf_char; /* cursorfont index. */ xcb_cursor_t cid; } cursors[XC_MAX] = { {"fleur", XC_fleur, XCB_CURSOR_NONE}, {"bottom_left_corner", XC_bottom_left_corner, XCB_CURSOR_NONE}, {"bottom_right_corner", XC_bottom_right_corner, XCB_CURSOR_NONE}, {"bottom_side", XC_bottom_side, XCB_CURSOR_NONE}, {"left_ptr", XC_left_ptr, XCB_CURSOR_NONE}, {"left_side", XC_left_side, XCB_CURSOR_NONE}, {"right_side", XC_right_side, XCB_CURSOR_NONE}, {"sizing", XC_sizing, XCB_CURSOR_NONE}, {"top_left_corner", XC_top_left_corner, XCB_CURSOR_NONE}, {"top_right_corner", XC_top_right_corner, XCB_CURSOR_NONE}, {"top_side", XC_top_side, XCB_CURSOR_NONE}, }; #define SWM_TEXTFRAGS_MAX (SWM_BAR_MAX/4) struct text_fragment { char *text; int length; int font; int width; int fg; int bg; }; /* bar section */ struct bar_section { char fmtrep[SWM_BAR_MAX * 2]; char fmtsplit[SWM_BAR_MAX * 2]; struct text_fragment frag[SWM_TEXTFRAGS_MAX]; bool fit_to_text; int justify; int weight; int start; int width; int text_start; int text_width; int nfrags; /* Needed for legacy font */ int height; int ypos; }; struct bar_section *bsect = NULL; int maxsect = 0; int numsect; #define SWM_SPAWN_OPTIONAL 0x1 /* spawn */ struct spawn_prog { TAILQ_ENTRY(spawn_prog) entry; char *name; int argc; char **argv; int flags; }; TAILQ_HEAD(spawn_list, spawn_prog) spawns = TAILQ_HEAD_INITIALIZER(spawns); /* Action callback flags. */ #define FN_F_NOREPLAY (0x1) /* User callable function IDs. */ enum actionid { FN_FOCUS_FREE, FN_FREE_TOGGLE, FN_BAR_TOGGLE, FN_BAR_TOGGLE_WS, FN_BUTTON2, FN_CYCLE_LAYOUT, FN_FLIP_LAYOUT, FN_FLOAT_TOGGLE, FN_BELOW_TOGGLE, FN_FOCUS, FN_FOCUS_MAIN, FN_FOCUS_NEXT, FN_FOCUS_PREV, FN_FOCUS_PRIOR, FN_FOCUS_URGENT, FN_FULLSCREEN_TOGGLE, FN_MAXIMIZE_TOGGLE, FN_HEIGHT_GROW, FN_HEIGHT_SHRINK, FN_ICONIFY, FN_LAYOUT_VERTICAL, FN_LAYOUT_HORIZONTAL, FN_LAYOUT_MAX, FN_LAYOUT_FLOATING, FN_MASTER_SHRINK, FN_MASTER_GROW, FN_MASTER_ADD, FN_MASTER_DEL, FN_MOVE, FN_MOVE_DOWN, FN_MOVE_LEFT, FN_MOVE_RIGHT, FN_MOVE_UP, FN_MVRG_1, FN_MVRG_2, FN_MVRG_3, FN_MVRG_4, FN_MVRG_5, FN_MVRG_6, FN_MVRG_7, FN_MVRG_8, FN_MVRG_9, KF_MVRG_NEXT, KF_MVRG_PREV, FN_MVWS_1, FN_MVWS_2, FN_MVWS_3, FN_MVWS_4, FN_MVWS_5, FN_MVWS_6, FN_MVWS_7, FN_MVWS_8, FN_MVWS_9, FN_MVWS_10, FN_MVWS_11, FN_MVWS_12, FN_MVWS_13, FN_MVWS_14, FN_MVWS_15, FN_MVWS_16, FN_MVWS_17, FN_MVWS_18, FN_MVWS_19, FN_MVWS_20, FN_MVWS_21, FN_MVWS_22, FN_NAME_WORKSPACE, FN_PRIOR_LAYOUT, FN_QUIT, FN_RAISE, FN_RAISE_TOGGLE, FN_RESIZE, FN_RESIZE_CENTERED, FN_RESTART, FN_RESTART_OF_DAY, FN_RG_1, FN_RG_2, FN_RG_3, FN_RG_4, FN_RG_5, FN_RG_6, FN_RG_7, FN_RG_8, FN_RG_9, FN_RG_MOVE_NEXT, FN_RG_MOVE_PREV, FN_RG_NEXT, FN_RG_PREV, FN_SCREEN_NEXT, FN_SCREEN_PREV, FN_SEARCH_WIN, FN_SEARCH_WORKSPACE, FN_SPAWN_CUSTOM, FN_STACK_BALANCE, FN_STACK_INC, FN_STACK_DEC, FN_STACK_RESET, FN_SWAP_MAIN, FN_SWAP_NEXT, FN_SWAP_PREV, FN_UNICONIFY, FN_VERSION, FN_WIDTH_GROW, FN_WIDTH_SHRINK, FN_WIND_DEL, FN_WIND_KILL, FN_WS_1, FN_WS_2, FN_WS_3, FN_WS_4, FN_WS_5, FN_WS_6, FN_WS_7, FN_WS_8, FN_WS_9, FN_WS_10, FN_WS_11, FN_WS_12, FN_WS_13, FN_WS_14, FN_WS_15, FN_WS_16, FN_WS_17, FN_WS_18, FN_WS_19, FN_WS_20, FN_WS_21, FN_WS_22, FN_WS_EMPTY, FN_WS_EMPTY_MOVE, FN_WS_NEXT, FN_WS_NEXT_ALL, FN_WS_NEXT_MOVE, FN_WS_PREV, FN_WS_PREV_ALL, FN_WS_PREV_MOVE, FN_WS_PRIOR, FN_DEBUG_TOGGLE, FN_DUMPWINS, /* ALWAYS last: */ FN_INVALID }; enum binding_type { KEYBIND, BTNBIND }; #define BINDING_F_REPLAY (0x1) struct binding { RB_ENTRY(binding) entry; uint16_t mod; /* Modifier Mask. */ enum binding_type type; /* Key or Button. */ uint32_t value; /* KeySym or Button Index. */ enum actionid action; /* Action Identifier. */ uint32_t flags; char *spawn_name; }; RB_HEAD(binding_tree, binding) bindings = RB_INITIALIZER(&bindings); struct atom_name { RB_ENTRY(atom_name) entry; xcb_atom_t atom; char *name; }; RB_HEAD(atom_name_tree, atom_name) atom_names = RB_INITIALIZER(&atom_names); /* function prototypes */ void adjust_font(struct ws_win *); int apply_unfocus(struct workspace *, struct ws_win *); char *argsep(char **); int atom_name_cmp(struct atom_name *, struct atom_name *); void atom_name_insert(xcb_atom_t, char *); struct atom_name *atom_name_lookup(xcb_atom_t); void atom_name_remove(struct atom_name *); void bar_cleanup(struct swm_region *); void bar_draw(struct swm_bar *); void bar_extra_setup(void); void bar_extra_stop(void); int bar_extra_update(void); void bar_fmt(const char *, char *, struct swm_region *, size_t); void bar_fmt_expand(char *, size_t); void bar_parse_markup(struct swm_screen *s, struct bar_section *); void bar_print(struct swm_region *, const char *); void bar_print_layout(struct swm_region *); void bar_print_legacy(struct swm_region *, const char *); void bar_split_format(char *); void bar_strlcat_esc(char *, char *, size_t); void bar_replace(char *, char *, struct swm_region *, size_t); void bar_replace_action(char *, char *, struct swm_region *, size_t); void bar_replace_pad(char *, int *, size_t); char *bar_replace_seq(char *, char *, struct swm_region *, size_t *, size_t); void bar_setup(struct swm_region *); void bar_toggle(struct swm_screen *, struct binding *, union arg *); void bar_urgent(struct swm_screen *, char *, size_t); void bar_window_class(char *, size_t, struct ws_win *); void bar_window_class_instance(char *, size_t, struct ws_win *); void bar_window_instance(char *, size_t, struct ws_win *); void bar_window_name(char *, size_t, struct ws_win *); void bar_window_state(char *, size_t, struct ws_win *); void bar_workspace_indicator(char *, size_t, struct swm_region *); void bar_workspace_name(char *, size_t, struct workspace *); void below_toggle(struct swm_screen *, struct binding *, union arg *); int binding_cmp(struct binding *, struct binding *); void binding_insert(uint16_t, enum binding_type, uint32_t, enum actionid, uint32_t, const char *); struct binding *binding_lookup(uint16_t, enum binding_type, uint32_t); void binding_remove(struct binding *); static bool bounds_intersect(struct swm_geometry *, struct swm_geometry *); bool button_has_binding(uint32_t); void buttonpress(xcb_button_press_event_t *); void buttonrelease(xcb_button_release_event_t *); void center_pointer(struct swm_region *); void clear_atom_names(void); static void clear_attention(struct ws_win *); void clear_bindings(void); void clear_keybindings(void); void clear_quirks(void); void clear_spawns(void); void click_focus(struct swm_screen *, xcb_window_t, int, int); void client_msg(struct ws_win *, xcb_atom_t, xcb_timestamp_t); void clientmessage(xcb_client_message_event_t *); static char *color_to_rgb(struct swm_color *); int conf_load(const char *, int); void config_win(struct ws_win *, xcb_configure_request_event_t *); void configurenotify(xcb_configure_notify_event_t *); void configurerequest(xcb_configure_request_event_t *); static void constrain_window(struct ws_win *, struct swm_geometry *, uint32_t *); static bool contain_window(struct ws_win *, struct swm_geometry, int, uint32_t); int count_win(struct workspace *, uint32_t); int create_search_win(struct ws_win *, int); void cursors_cleanup(void); void cursors_load(void); void cyclerg(struct swm_screen *, struct binding *, union arg *); void cyclews(struct swm_screen *, struct binding *, union arg *); void debug_refresh(struct ws_win *); void debug_toggle(struct swm_screen *, struct binding *, union arg *); void destroynotify(xcb_destroy_notify_event_t *); void draw_frame(struct ws_win *); void dumpwins(struct swm_screen *, struct binding *, union arg *); void emptyws(struct swm_screen *, struct binding *, union arg *); int enable_wm(void); void enternotify(xcb_enter_notify_event_t *); void event_error(xcb_generic_error_t *); void event_handle(xcb_generic_event_t *); uint32_t ewmh_apply_flags(struct ws_win *, uint32_t); void ewmh_change_wm_state(struct ws_win *, xcb_atom_t, long); void ewmh_get_desktop_names(struct swm_screen *); void ewmh_get_strut(struct ws_win *); void ewmh_get_window_type(struct ws_win *); void ewmh_get_wm_state(struct ws_win *); void ewmh_print_window_type(uint32_t); void ewmh_update_actions(struct ws_win *); static void ewmh_update_active_window(struct swm_screen *); void ewmh_update_client_list(struct swm_screen *); void ewmh_update_current_desktop(struct swm_screen *); void ewmh_update_desktop_names(struct swm_screen *); void ewmh_update_desktops(struct swm_screen *); void ewmh_update_wm_state(struct ws_win *); void ewmh_update_workarea(struct swm_screen *); char *expand_tilde(const char *); void expose(xcb_expose_event_t *); void fake_keypress(struct ws_win *, xcb_keysym_t, uint16_t); struct swm_bar *find_bar(xcb_window_t); struct ws_win *find_main_window(struct ws_win *); struct pid_e *find_pid(pid_t); struct swm_region *find_region(xcb_window_t); struct swm_screen *find_screen(xcb_window_t); struct ws_win *find_window(xcb_window_t); void floating_toggle(struct swm_screen *, struct binding *, union arg *); void flush(void); void focus(struct swm_screen *, struct binding *, union arg *); void focus_follow(struct swm_screen *, struct swm_region *, struct ws_win *); void focus_pointer(struct swm_screen *, struct binding *, union arg *); void focus_region(struct swm_region *); void focus_win(struct swm_screen *s, struct ws_win *); void focus_win_input(struct ws_win *, bool); void focus_window(xcb_window_t); void focus_window_region(xcb_window_t); void focusin(xcb_focus_in_event_t *); static void focusout(xcb_focus_out_event_t *); void focusrg(struct swm_screen *, struct binding *, union arg *); static bool follow_pointer(struct swm_screen *); int fontset_init(void); void free_toggle(struct swm_screen *, struct binding *, union arg *); void free_window(struct ws_win *); void fullscreen_toggle(struct swm_screen *, struct binding *, union arg *); xcb_atom_t get_atom_from_string(const char *); const char *get_atom_label(xcb_atom_t); char *get_atom_name(xcb_atom_t); int get_character_font(struct swm_screen *, FcChar32, int); struct swm_geometry get_boundary(struct ws_win *); struct swm_region *get_current_region(struct swm_screen *); const char *get_event_label(xcb_generic_event_t *); struct ws_win *get_focus_magic(struct ws_win *); struct ws_win *get_focus_other(struct ws_win *); static const char *get_gravity_label(uint8_t); #ifdef SWM_XCB_HAS_XINPUT const char *get_input_event_label(xcb_ge_generic_event_t *); #endif xcb_window_t get_input_focus(void); xcb_atom_t get_intern_atom(const char *); xcb_keycode_t get_keysym_keycode(xcb_keysym_t); struct ws_win *get_main_window(struct workspace *); const char *get_mapping_notify_label(uint8_t); const char *get_moveresize_direction_label(uint32_t); xcb_generic_event_t *get_next_event(bool); const char *get_notify_detail_label(uint8_t); const char *get_notify_mode_label(uint8_t); struct swm_region *get_pointer_region(struct swm_screen *); struct ws_win *get_pointer_win(struct swm_screen *); const char *get_randr_event_label(xcb_generic_event_t *); const char *get_randr_rotation_label(int); struct swm_region *get_region(struct swm_screen *, int); int get_region_index(struct swm_region *); xcb_screen_t *get_screen(int); int get_screen_count(void); const struct xcb_setup_t *get_setup(void); const char *get_source_type_label(uint32_t); const char *get_stack_mode_label(uint8_t); const char *get_state_mask_label(uint16_t); xcb_keysym_t get_string_keysym(const char *); int32_t get_swm_ws(xcb_window_t); const char *get_win_input_model_label(struct ws_win *); char *get_win_name(xcb_window_t); uint32_t get_win_state(xcb_window_t); static void get_wm_hints(struct ws_win *); static void get_wm_normal_hints(struct ws_win *); void get_wm_protocols(struct ws_win *); const char *get_wm_state_label(uint32_t); static bool get_wm_transient_for(struct ws_win *); struct workspace *get_workspace(struct swm_screen *, int); struct ws_win *get_ws_focus(struct workspace *); struct ws_win *get_ws_focus_prev(struct workspace *); int get_ws_id(struct ws_win *); void grab_buttons_win(xcb_window_t); void grab_windows(void); void grabbuttons(void); void grabkeys(void); void iconify(struct swm_screen *, struct binding *, union arg *); bool is_valid_markup(char *, size_t *); bool isxlfd(char *); bool keybindreleased(struct binding *, xcb_key_release_event_t *); void keypress(xcb_key_press_event_t *); void keyrelease(xcb_key_release_event_t *); bool keyrepeating(xcb_key_release_event_t *); void kill_bar_extra_atexit(void); void kill_refs(struct ws_win *); void leavenotify(xcb_leave_notify_event_t *); void load_float_geom(struct ws_win *); struct ws_win *manage_window(xcb_window_t, int, bool); void map_window(struct ws_win *); void mapnotify(xcb_map_notify_event_t *); void mappingnotify(xcb_mapping_notify_event_t *); void maprequest(xcb_map_request_event_t *); void maximize_toggle(struct swm_screen *, struct binding *, union arg *); void motionnotify(xcb_motion_notify_event_t *); void move(struct swm_screen *, struct binding *, union arg *); void move_win(struct ws_win *, struct binding *, int); void move_win_pointer(struct ws_win *, struct binding *, uint32_t, uint32_t); void moveresize_win(struct ws_win *, xcb_client_message_event_t *); void name_workspace(struct swm_screen *, struct binding *, union arg *); void new_region(struct swm_screen *, int16_t, int16_t, uint16_t, uint16_t, uint16_t); int parse_color(struct swm_screen *, const char *, struct swm_color *); int parse_rgb(const char *, uint16_t *, uint16_t *, uint16_t *); int parse_rgba(const char *, uint16_t *, uint16_t *, uint16_t *, uint16_t *); int parse_workspace_indicator(const char *, uint32_t *, char **); int parsebinding(const char *, uint16_t *, enum binding_type *, uint32_t *, uint32_t *, char **); int parsequirks(const char *, uint32_t *, int *, char **); void pressbutton(struct swm_screen *, struct binding *, union arg *); void print_stackable(struct swm_stackable *); void print_stacking(struct swm_screen *); void print_strut(struct swm_strut *); void print_win_geom(xcb_window_t); void prioritize_window(struct ws_win *); void priorws(struct swm_screen *, struct binding *, union arg *); void propertynotify(xcb_property_notify_event_t *); void put_back_event(xcb_generic_event_t *); void quirk_free(struct quirk *); void quirk_insert(const char *, const char *, const char *, uint32_t, int); void quirk_remove(struct quirk *); void quirk_replace(struct quirk *, const char *, const char *, const char *, uint32_t, int); void quit(struct swm_screen *, struct binding *, union arg *); void raise_focus(struct swm_screen *, struct binding *, union arg *); void raise_toggle(struct swm_screen *, struct binding *, union arg *); #if defined(SWM_XCB_HAS_XINPUT) && defined(XCB_INPUT_RAW_BUTTON_PRESS) void rawbuttonpress(xcb_input_raw_button_press_event_t *); #endif void refresh_stack(struct swm_screen *); int refresh_strut(struct swm_screen *); struct swm_region *region_under(struct swm_screen *, int, int); void regionize(struct ws_win *, int, int); int reparent_window(struct ws_win *); void reparentnotify(xcb_reparent_notify_event_t *); void resize(struct swm_screen *, struct binding *, union arg *); void resize_win(struct ws_win *, struct binding *, int); void resize_win_pointer(struct ws_win *, struct binding *, uint32_t, uint32_t, uint32_t, bool); void restart(struct swm_screen *, struct binding *, union arg *); static bool rg_root(struct swm_region *); void rotatews(struct workspace *, uint16_t); void scan_config(void); void scan_randr(struct swm_screen *); void screenchange(xcb_randr_screen_change_notify_event_t *); void search_do_resp(void); void search_resp_name_workspace(const char *, size_t); void search_resp_search_window(const char *); void search_resp_search_workspace(const char *); void search_resp_uniconify(const char *, size_t); void search_win(struct swm_screen *, struct binding *, union arg *); void search_win_cleanup(void); void search_workspace(struct swm_screen *, struct binding *, union arg *); void send_to_rg(struct swm_screen *, struct binding *, union arg *); void send_to_rg_relative(struct swm_screen *, struct binding *, union arg *); void send_to_ws(struct swm_screen *, struct binding *, union arg *); static void set_attention(struct ws_win *); void set_focus(struct swm_screen *, struct ws_win *); void set_focus_prev(struct ws_win *); void set_focus_redirect(struct ws_win *); void set_input_focus(xcb_window_t, bool); void set_region(struct swm_region *); void set_win_state(struct ws_win *, uint32_t); int setautorun(const char *, const char *, int, char **); void setbinding(uint16_t, enum binding_type, uint32_t, enum actionid, uint32_t, const char *); int setconfbinding(const char *, const char *, int, char **); int setconfcancelkey(const char *, const char *, int, char **); int setconfcolor(const char *, const char *, int, char **); int setconfcolorlist(const char *, const char *, int, char **); int setconfmodkey(const char *, const char *, int, char **); int setconfquirk(const char *, const char *, int, char **); int setconfregion(const char *, const char *, int, char **); int setconfspawn(const char *, const char *, int, char **); int setconfvalue(const char *, const char *, int, char **); int setkeymapping(const char *, const char *, int, char **); int setlayout(const char *, const char *, int, char **); void setquirk(const char *, const char *, const char *, uint32_t, int); void setscreencolor(struct swm_screen *, const char *, int); void setspawn(const char *, const char *, int); void setup_btnbindings(void); void setup_ewmh(void); void setup_extensions(void); void setup_focus(void); void setup_fonts(void); void setup_globals(void); void setup_keybindings(void); void setup_marks(void); void setup_quirks(void); void setup_screens(void); void setup_spawn(void); #if defined(SWM_XCB_HAS_XINPUT) && defined(XCB_INPUT_RAW_BUTTON_PRESS) void setup_xinput2(struct swm_screen *); #endif void shutdown_cleanup(void); void sighdlr(int); void socket_setnonblock(int); void spawn(int, union arg *, bool); void spawn_custom(struct swm_region *, union arg *, const char *); int spawn_expand(struct swm_region *, union arg *, const char *, char ***); struct spawn_prog *spawn_find(const char *); void spawn_insert(const char *, const char *, int); void spawn_remove(struct spawn_prog *); void spawn_replace(struct spawn_prog *, const char *, const char *, int); void spawn_select(struct swm_region *, union arg *, const char *, int *); static xcb_window_t st_window_id(struct swm_stackable *); void stack_config(struct swm_screen *, struct binding *, union arg *); void stack_master(struct workspace *, struct swm_geometry *, int, bool); static void update_layout(struct swm_screen *); void store_float_geom(struct ws_win *); char *strdupsafe(const char *); static int32_t strtoint32(const char *, int32_t, int32_t, int *); void swapwin(struct swm_screen *, struct binding *, union arg *); void switch_workspace(struct swm_region *, struct workspace *, bool); void switchlayout(struct swm_screen *, struct binding *, union arg *); void switchws(struct swm_screen *, struct binding *, union arg *); void teardown_ewmh(void); void transfer_win(struct ws_win *, struct workspace *); void update_mapping(struct swm_screen *); void update_region_mapping(struct swm_region *); void update_stacking(struct swm_screen *); void unescape_selector(char *); char *unescape_value(const char *); void unfocus_win(struct ws_win *); void uniconify(struct swm_screen *, struct binding *, union arg *); void unmanage_window(struct ws_win *); void unmap_window(struct ws_win *); void unmap_workspace(struct workspace *); void unmapnotify(xcb_unmap_notify_event_t *); void unparent_window(struct ws_win *); static void unsnap_win(struct ws_win *, bool); static void update_bars(struct swm_screen *); void update_debug(struct swm_screen *); void update_floater(struct ws_win *); void update_focus(struct swm_screen *); static void update_gravity(struct ws_win *); void update_keycodes(void); void update_modkey(uint16_t); void update_stackable(struct swm_stackable *, struct swm_stackable *); void update_win_layer(struct ws_win *); void update_win_layer_related(struct ws_win *); void update_window(struct ws_win *); void update_wm_state(struct ws_win *win); void updatenumlockmask(void); static void usage(void); int validate_rg(struct swm_region *); void validate_spawns(void); int validate_win(struct ws_win *); int validate_ws(struct workspace *); void version(struct swm_screen *, struct binding *, union arg *); static bool win_below(struct ws_win *); static uint16_t win_border(struct ws_win *); static bool win_floating(struct ws_win *); static bool win_focused(struct ws_win *); static bool win_free(struct ws_win *); static uint8_t win_gravity(struct ws_win *); static bool win_main(struct ws_win *); static bool win_raised(struct ws_win *); static bool win_related(struct ws_win *, struct ws_win *); static bool win_reparented(struct ws_win *); void win_to_ws(struct ws_win *, struct workspace *, uint32_t); static bool win_transient(struct ws_win *); static bool win_urgent(struct ws_win *); pid_t window_get_pid(xcb_window_t); void wkill(struct swm_screen *, struct binding *, union arg *); int workspace_cmp(struct workspace *, struct workspace *); struct workspace *workspace_insert(struct swm_screen *, int); struct workspace *workspace_lookup(struct swm_screen *, int); void workspace_remove(struct workspace *); static bool ws_floating(struct workspace *); static bool ws_focused(struct workspace *); static bool ws_maponfocus(struct workspace *); static bool ws_maxstack(struct workspace *); static bool ws_maxstack_prior(struct workspace *); static bool ws_root(struct workspace *); int xft_init(struct swm_screen *); void _add_startup_exception(const char *, va_list); void add_startup_exception(const char *, ...); RB_PROTOTYPE(binding_tree, binding, entry, binding_cmp); RB_PROTOTYPE(atom_name_tree, atom_name, entry, atom_name_cmp); RB_PROTOTYPE(workspace_tree, workspace, entry, workspace_cmp); RB_GENERATE(binding_tree, binding, entry, binding_cmp); RB_GENERATE(atom_name_tree, atom_name, entry, atom_name_cmp); RB_GENERATE(workspace_tree, workspace, entry, workspace_cmp); static bool win_free(struct ws_win *win) { return (win && win->ws == win->s->r->ws); } static bool win_floating(struct ws_win *win) { return (win_transient(win) || win->ewmh_flags & EWMH_F_UNTILED || ws_floating(win->ws) || WINDOCK(win) || win_below(win)); } static bool win_focused(struct ws_win *win) { return (win->s->focus == win); } static bool win_raised(struct ws_win *win) { return (win->ws->focus_raise == win || (win->ws->always_raise && win->ws->focus == win)); } static bool win_below(struct ws_win *win) { return (BELOW(win) || (((win_free(win) && !win_focused(win)) || !win_related(get_ws_focus(win->ws), win) || (ws_focused(win->ws) && win_free(win->s->focus))) && ((FULLSCREEN(win) && fullscreen_unfocus == SWM_UNFOCUS_QUICK_BELOW) || (MAXIMIZED(win) && maximized_unfocus == SWM_UNFOCUS_QUICK_BELOW)))); } static bool win_reparented(struct ws_win *win) { return (win->frame != XCB_WINDOW_NONE); } static bool win_transient(struct ws_win *win) { return (win->transient_for != XCB_WINDOW_NONE); } static bool win_main(struct ws_win *win) { return (win->main == win); } static bool win_related(struct ws_win *w1, struct ws_win *w2) { return (w1 && w2 && w1->main == w2->main); } static uint16_t win_border(struct ws_win *win) { return (win->bordered ? border_width : 0); } static bool win_prioritized(struct ws_win *win) { return (TAILQ_FIRST(&win->s->priority) == win); } static bool ws_focused(struct workspace *ws) { return (ws->r && ws->s->r_focus == ws->r); } static bool ws_maponfocus(struct workspace *ws) { return (ws->cur_layout->flags & SWM_L_MAPONFOCUS); } static bool ws_maxstack(struct workspace *ws) { return (ws->cur_layout == &layouts[SWM_MAX_STACK]); } static bool ws_maxstack_prior(struct workspace *ws) { return (ws->prev_layout == &layouts[SWM_MAX_STACK]); } static bool ws_floating(struct workspace *ws) { return (ws && ws->cur_layout->flags & SWM_L_NOTILE); } static bool rg_root(struct swm_region *r) { return (r && r->s->r == r); } static bool ws_root(struct workspace *ws) { return (ws && ws->s->r->ws == ws); } static xcb_window_t st_window_id(struct swm_stackable *st) { xcb_window_t wid; switch (st->type) { case STACKABLE_WIN: wid = (win_reparented(st->win) ? st->win->frame : st->win->id); break; case STACKABLE_BAR: wid = st->bar->id; break; case STACKABLE_REGION: wid = st->region->id; break; default: wid = XCB_WINDOW_NONE; break; } return (wid); } int workspace_cmp(struct workspace *ws1, struct workspace *ws2) { if (ws1->idx < ws2->idx) return (-1); if (ws1->idx > ws2->idx) return (1); return (0); } struct workspace * workspace_lookup(struct swm_screen *s, int id) { struct workspace ws; ws.idx = id; return (RB_FIND(workspace_tree, &s->workspaces, &ws)); } /* Get/create workspace for given screen and id. */ struct workspace * get_workspace(struct swm_screen *s, int id) { struct workspace *ws; /* Hard limit. */ if (id >= SWM_WS_MAX || id < -1) return (NULL); if ((ws = workspace_lookup(s, id)) == NULL) ws = workspace_insert(s, id); return (ws); } struct workspace * workspace_insert(struct swm_screen *s, int id) { struct workspace *ws; int i; if ((ws = calloc(1, sizeof(struct workspace))) == NULL) err(1, "workspace_insert: calloc"); ws->s = s; ws->idx = id; ws->name = NULL; ws->bar_enabled = true; ws->prev_layout = NULL; ws->focus = NULL; ws->focus_raise = NULL; ws->r = NULL; ws->old_r = NULL; ws->rotation = ROTATION_DEFAULT; TAILQ_INIT(&ws->winlist); ws->stacker_len = stack_mark_maxlen; ws->stacker = calloc(ws->stacker_len, sizeof(char)); if (ws->stacker == NULL) err(1, "setup_marks: calloc"); for (i = 0; layouts[i].l_stack != NULL; i++) if (layouts[i].l_config != NULL) layouts[i].l_config(ws, SWM_ARG_ID_STACKINIT); ws->cur_layout = &layouts[0]; if (RB_INSERT(workspace_tree, &s->workspaces, ws)) /* An entry already exists. */ errx(1, "workspace_insert: RB_INSERT"); return (ws); } void workspace_remove(struct workspace *ws) { RB_REMOVE(workspace_tree, &ws->s->workspaces, ws); free(ws->name); free(ws->stacker); free(ws); } void cursors_load(void) { xcb_font_t cf = XCB_NONE; int i; for (i = 0; i < LENGTH(cursors); ++i) { /* try to load Xcursor first. */ cursors[i].cid = XcursorLibraryLoadCursor(display, cursors[i].name); /* fallback to cursorfont. */ if (cursors[i].cid == XCB_CURSOR_NONE) { if (cf == XCB_NONE) { cf = xcb_generate_id(conn); xcb_open_font(conn, cf, strlen("cursor"), "cursor"); } cursors[i].cid = xcb_generate_id(conn); xcb_create_glyph_cursor(conn, cursors[i].cid, cf, cf, cursors[i].cf_char, cursors[i].cf_char + 1, 0, 0, 0, 0xffff, 0xffff, 0xffff); } } if (cf != XCB_NONE) xcb_close_font(conn, cf); } void cursors_cleanup(void) { int i; for (i = 0; i < LENGTH(cursors); ++i) xcb_free_cursor(conn, cursors[i].cid); } char * expand_tilde(const char *s) { struct passwd *ppwd; int i; long max; char *user; const char *sc = s; char *result; if (s == NULL) errx(1, "expand_tilde: NULL string."); if (s[0] != '~') { result = strdup(sc); goto out; } ++s; if ((max = sysconf(_SC_LOGIN_NAME_MAX)) == -1) errx(1, "expand_tilde: sysconf"); if ((user = calloc(1, max + 1)) == NULL) errx(1, "expand_tilde: calloc"); for (i = 0; s[i] != '/' && s[i] != '\0'; ++i) user[i] = s[i]; user[i] = '\0'; s = &s[i]; ppwd = strlen(user) == 0 ? getpwuid(getuid()) : getpwnam(user); free(user); if (ppwd == NULL) result = strdup(sc); else if (asprintf(&result, "%s%s", ppwd->pw_dir, s) == -1) result = NULL; out: if (result == NULL) errx(1, "expand_tilde: failed to allocate memory."); return (result); } int parse_rgba(const char *rgba, uint16_t *rr, uint16_t *gg, uint16_t *bb, uint16_t *aa) { unsigned int tmpr, tmpg, tmpb, tmpa; if (sscanf(rgba, "rgba:%x/%x/%x/%x", &tmpr, &tmpg, &tmpb, &tmpa) != 4) return (-1); *rr = tmpr; *gg = tmpg; *bb = tmpb; *aa = tmpa; return (0); } int parse_rgb(const char *rgb, uint16_t *rr, uint16_t *gg, uint16_t *bb) { unsigned int tmpr, tmpg, tmpb; if (sscanf(rgb, "rgb:%x/%x/%x", &tmpr, &tmpg, &tmpb) != 3) return (-1); *rr = tmpr; *gg = tmpg; *bb = tmpb; return (0); } const struct xcb_setup_t * get_setup(void) { int errcode = xcb_connection_has_error(conn); #ifdef XCB_CONN_ERROR /* libxcb >= 1.8 */ char *s; switch (errcode) { case XCB_CONN_ERROR: s = "Socket, pipe or other stream error."; break; case XCB_CONN_CLOSED_EXT_NOTSUPPORTED: s = "Extension not supported."; break; case XCB_CONN_CLOSED_MEM_INSUFFICIENT: s = "Insufficient memory."; break; case XCB_CONN_CLOSED_REQ_LEN_EXCEED: s = "Request length exceeded."; break; case XCB_CONN_CLOSED_PARSE_ERR: s = "Error parsing display string."; break; #ifdef XCB_CONN_CLOSED_INVALID_SCREEN /* libxcb >= 1.9 */ case XCB_CONN_CLOSED_INVALID_SCREEN: s = "Invalid screen."; break; #ifdef XCB_CONN_CLOSED_FDPASSING_FAILED /* libxcb >= 1.9.2 */ case XCB_CONN_CLOSED_FDPASSING_FAILED: s = "Failed to pass file descriptor."; break; #endif #endif default: s = "Unknown error."; } if (errcode) errx(errcode, "X CONNECTION ERROR: %s", s); #else if (errcode) errx(errcode, "X CONNECTION ERROR"); #endif return (xcb_get_setup(conn)); } xcb_screen_t * get_screen(int screen) { const xcb_setup_t *r; xcb_screen_iterator_t iter; r = get_setup(); iter = xcb_setup_roots_iterator(r); for (; iter.rem; --screen, xcb_screen_next(&iter)) if (screen == 0) return (iter.data); return (NULL); } int get_screen_count(void) { return (xcb_setup_roots_length(get_setup())); } struct swm_region * get_region(struct swm_screen *s, int index) { struct swm_region *r; int i; DNPRINTF(SWM_D_FOCUS, "id: %d\n", index); if (index == 0) return (s->r); r = TAILQ_FIRST(&s->rl); for (i = 0; r && i < index - 1; ++i) r = TAILQ_NEXT(r, entry); return (r); } int get_region_index(struct swm_region *r) { struct swm_region *rr; int ridx; if (r == NULL) return (-1); if (rg_root(r)) return (0); /* Dynamic regions begin at 1. */ ridx = 1; TAILQ_FOREACH(rr, &r->s->rl, entry) { if (rr == r) break; ridx++; } if (rr == NULL) return (-1); return (ridx); } void flush(void) { xcb_generic_event_t *e; static bool flushing = false; /* Ensure all pending requests have been processed. */ xcb_aux_sync(conn); /* If called recursively via below loop, only sync. */ if (flushing) return; flushing = true; while ((e = get_next_event(false))) { switch (XCB_EVENT_RESPONSE_TYPE(e)) { case XCB_ENTER_NOTIFY: event_time = ((xcb_enter_notify_event_t *)e)->time; pointer_window = ((xcb_enter_notify_event_t *)e)->event; DNPRINTF(SWM_D_EVENT, "pointer_window: %#x\n", pointer_window); break; case XCB_MOTION_NOTIFY: event_time = ((xcb_motion_notify_event_t *)e)->time; break; default: event_handle(e); } free(e); } flushing = false; } xcb_atom_t get_intern_atom(const char *str) { xcb_intern_atom_cookie_t c; xcb_intern_atom_reply_t *r; xcb_atom_t atom; c = xcb_intern_atom(conn, 0, strlen(str), str); r = xcb_intern_atom_reply(conn, c, NULL); if (r) { atom = r->atom; free(r); return (atom); } return (XCB_ATOM_NONE); } char * get_atom_name(xcb_atom_t atom) { xcb_get_atom_name_reply_t *r; size_t len; char *name = NULL; if (!(swm_debug & SWM_D_ATOM)) return (NULL); r = xcb_get_atom_name_reply(conn, xcb_get_atom_name(conn, atom), NULL); if (r) { len = xcb_get_atom_name_name_length(r); if (len > 0) { name = malloc(len + 1); if (name) { memcpy(name, xcb_get_atom_name_name(r), len); name[len] = '\0'; } } free(r); } return (name); } int atom_name_cmp(struct atom_name *ap1, struct atom_name *ap2) { if (ap1->atom < ap2->atom) return (-1); if (ap1->atom > ap2->atom) return (1); return (0); } void atom_name_insert(xcb_atom_t atom, char *name) { struct atom_name *ap; if ((ap = malloc(sizeof *ap)) == NULL) err(1, "atom_name_insert: malloc"); ap->atom = atom; ap->name = name; if (RB_INSERT(atom_name_tree, &atom_names, ap)) err(1, "atom_name_insert: RB_INSERT"); } void atom_name_remove(struct atom_name *ap) { RB_REMOVE(atom_name_tree, &atom_names, ap); free(ap->name); free(ap); } void clear_atom_names(void) { struct atom_name *ap; #ifndef __clang_analyzer__ /* Suppress false warnings. */ while((ap = RB_ROOT(&atom_names))) atom_name_remove(ap); #endif } struct atom_name * atom_name_lookup(xcb_atom_t atom) { struct atom_name ap; ap.atom = atom; return (RB_FIND(atom_name_tree, &atom_names, &ap)); } xcb_atom_t get_atom_from_string(const char *str) { xcb_atom_t atom; char *name; if (str == NULL) return (XCB_ATOM_NONE); atom = get_intern_atom(str); if (atom != XCB_ATOM_NONE && atom_name_lookup(atom) == NULL) { if ((name = strdup(str)) == NULL) err(1, "get_atom_from_string: strdup"); atom_name_insert(atom, name); } return (atom); } const char * get_atom_label(xcb_atom_t atom) { struct atom_name *ap; char *name; ap = atom_name_lookup(atom); if (ap) name = ap->name; else if (swm_debug & SWM_D_ATOM) { name = get_atom_name(atom); atom_name_insert(atom, name); } else name = ""; return (name); } void get_wm_protocols(struct ws_win *win) { int i; xcb_icccm_get_wm_protocols_reply_t wpr; if (xcb_icccm_get_wm_protocols_reply(conn, xcb_icccm_get_wm_protocols(conn, win->id, a_prot), &wpr, NULL)) { for (i = 0; i < (int)wpr.atoms_len; i++) { if (wpr.atoms[i] == a_takefocus) win->take_focus = true; if (wpr.atoms[i] == a_delete) win->can_delete = true; } xcb_icccm_get_wm_protocols_reply_wipe(&wpr); } } void setup_ewmh(void) { xcb_window_t root, swmwin; int i, j, num_screens; for (i = 0; i < LENGTH(ewmh); i++) ewmh[i].atom = get_atom_from_string(ewmh[i].name); num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) { root = screens[i].root; swmwin = screens[i].swmwin; /* Set up _NET_SUPPORTING_WM_CHECK. */ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, swmwin, ewmh[_NET_WM_NAME].atom, a_utf8_string, 8, strlen("spectrwm"), "spectrwm"); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, a_net_wm_check, XCB_ATOM_WINDOW, 32, 1, &swmwin); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, swmwin, a_net_wm_check, XCB_ATOM_WINDOW, 32, 1, &swmwin); /* Report supported atoms */ xcb_delete_property(conn, root, a_net_supported); for (j = 0; j < LENGTH(ewmh); j++) xcb_change_property(conn, XCB_PROP_MODE_APPEND, root, a_net_supported, XCB_ATOM_ATOM, 32, 1, &ewmh[j].atom); ewmh_update_desktops(&screens[i]); ewmh_get_desktop_names(&screens[i]); } } void teardown_ewmh(void) { int i, num_screens; num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) { xcb_delete_property(conn, screens[i].swmwin, a_net_wm_check); xcb_delete_property(conn, screens[i].root, a_net_wm_check); xcb_delete_property(conn, screens[i].root, a_net_supported); } } void ewmh_get_window_type(struct ws_win *win) { xcb_get_property_reply_t *r; xcb_get_property_cookie_t c; xcb_atom_t *type; int i, n; c = xcb_get_property(conn, 0, win->id, ewmh[_NET_WM_WINDOW_TYPE].atom, XCB_ATOM_ATOM, 0, UINT32_MAX); r = xcb_get_property_reply(conn, c, NULL); if (r == NULL) return; type = xcb_get_property_value(r); n = xcb_get_property_value_length(r) / sizeof(xcb_atom_t); win->type = 0; for (i = 0; i < n; i++) { if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_DESKTOP].atom) win->type |= EWMH_WINDOW_TYPE_DESKTOP; else if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_DOCK].atom) win->type |= EWMH_WINDOW_TYPE_DOCK; else if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_TOOLBAR].atom) win->type |= EWMH_WINDOW_TYPE_TOOLBAR; else if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_MENU].atom) win->type |= EWMH_WINDOW_TYPE_MENU; else if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_UTILITY].atom) win->type |= EWMH_WINDOW_TYPE_UTILITY; else if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_SPLASH].atom) win->type |= EWMH_WINDOW_TYPE_SPLASH; else if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_DIALOG].atom) win->type |= EWMH_WINDOW_TYPE_DIALOG; else if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_DROPDOWN_MENU].atom) win->type |= EWMH_WINDOW_TYPE_DROPDOWN_MENU; else if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_POPUP_MENU].atom) win->type |= EWMH_WINDOW_TYPE_POPUP_MENU; else if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_TOOLTIP].atom) win->type |= EWMH_WINDOW_TYPE_TOOLTIP; else if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_NOTIFICATION].atom) win->type |= EWMH_WINDOW_TYPE_NOTIFICATION; else if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_COMBO].atom) win->type |= EWMH_WINDOW_TYPE_COMBO; else if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_DND].atom) win->type |= EWMH_WINDOW_TYPE_DND; else if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_NORMAL].atom) win->type |= EWMH_WINDOW_TYPE_NORMAL; } free(r); } void ewmh_print_window_type(uint32_t type) { if (type == 0) { DPRINTF("None "); return; } if (type & EWMH_WINDOW_TYPE_DESKTOP) DPRINTF("DESKTOP "); if (type & EWMH_WINDOW_TYPE_DOCK) DPRINTF("DOCK "); if (type & EWMH_WINDOW_TYPE_TOOLBAR) DPRINTF("TOOLBAR "); if (type & EWMH_WINDOW_TYPE_MENU) DPRINTF("MENU "); if (type & EWMH_WINDOW_TYPE_UTILITY) DPRINTF("UTILITY "); if (type & EWMH_WINDOW_TYPE_SPLASH) DPRINTF("SPLASH "); if (type & EWMH_WINDOW_TYPE_DIALOG) DPRINTF("DIALOG "); if (type & EWMH_WINDOW_TYPE_DROPDOWN_MENU) DPRINTF("DROPDOWN_MENU "); if (type & EWMH_WINDOW_TYPE_POPUP_MENU) DPRINTF("POPUP_MENU "); if (type & EWMH_WINDOW_TYPE_TOOLTIP) DPRINTF("TOOLTIP "); if (type & EWMH_WINDOW_TYPE_NOTIFICATION) DPRINTF("NOTIFICATION "); if (type & EWMH_WINDOW_TYPE_COMBO) DPRINTF("COMBO "); if (type & EWMH_WINDOW_TYPE_DND) DPRINTF("DND "); if (type & EWMH_WINDOW_TYPE_NORMAL) DPRINTF("NORMAL "); } void ewmh_update_actions(struct ws_win *win) { xcb_atom_t action[SWM_EWMH_ACTION_COUNT_MAX]; int n = 0; if (win == NULL) return; action[n++] = ewmh[_NET_WM_ACTION_CLOSE].atom; if (ABOVE(win)) { action[n++] = ewmh[_NET_WM_ACTION_MOVE].atom; action[n++] = ewmh[_NET_WM_ACTION_RESIZE].atom; action[n++] = ewmh[_NET_WM_ACTION_ABOVE].atom; } xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win->id, ewmh[_NET_WM_ALLOWED_ACTIONS].atom, XCB_ATOM_ATOM, 32, 1, action); } #define _NET_WM_STATE_REMOVE 0 /* remove/unset property */ #define _NET_WM_STATE_ADD 1 /* add/set property */ #define _NET_WM_STATE_TOGGLE 2 /* toggle property */ void ewmh_change_wm_state(struct ws_win *win, xcb_atom_t state, long action) { uint32_t flag = 0; uint32_t new_flags; DNPRINTF(SWM_D_PROP, "win %#x, state: %s(%u), " "action: %ld\n", WINID(win), get_atom_label(state), state, action); if (win == NULL) goto out; if (state == ewmh[_NET_WM_STATE_MAXIMIZED_VERT].atom || state == ewmh[_NET_WM_STATE_MAXIMIZED_HORZ].atom) flag = EWMH_F_MAXIMIZED; else if (state == ewmh[_NET_WM_STATE_SKIP_TASKBAR].atom) flag = EWMH_F_SKIP_TASKBAR; else if (state == ewmh[_NET_WM_STATE_SKIP_PAGER].atom) flag = EWMH_F_SKIP_PAGER; else if (state == ewmh[_NET_WM_STATE_HIDDEN].atom) flag = EWMH_F_HIDDEN; else if (state == ewmh[_NET_WM_STATE_FULLSCREEN].atom) flag = EWMH_F_FULLSCREEN; else if (state == ewmh[_NET_WM_STATE_ABOVE].atom) flag = EWMH_F_ABOVE; else if (state == ewmh[_NET_WM_STATE_BELOW].atom) flag = EWMH_F_BELOW; else if (state == ewmh[_NET_WM_STATE_DEMANDS_ATTENTION].atom) flag = EWMH_F_DEMANDS_ATTENTION; else if (state == ewmh[_SWM_WM_STATE_MANUAL].atom) flag = SWM_F_MANUAL; /* Disallow unfloating transients. */ if (win_transient(win) && flag == EWMH_F_ABOVE) goto out; new_flags = win->ewmh_flags; switch (action) { case _NET_WM_STATE_REMOVE: new_flags &= ~flag; break; case _NET_WM_STATE_ADD: new_flags |= flag; break; case _NET_WM_STATE_TOGGLE: new_flags ^= flag; break; } ewmh_apply_flags(win, new_flags); out: DNPRINTF(SWM_D_PROP, "done\n"); } uint32_t ewmh_apply_flags(struct ws_win *win, uint32_t pending) { uint32_t changed; changed = win->ewmh_flags ^ pending; if (changed == 0) return (changed); DNPRINTF(SWM_D_PROP, "pending: %u\n", pending); win->ewmh_flags = pending; if (changed & EWMH_F_HIDDEN) { if (HIDDEN(win)) { unmap_window(win); } else { /* Reload floating geometry in case region changed. */ if (win_floating(win)) load_float_geom(win); } } if (changed & EWMH_F_ABOVE || changed & EWMH_F_BELOW) { if (ABOVE(win) || BELOW(win)) load_float_geom(win); else if (!MAXIMIZED(win)) store_float_geom(win); } if (changed & EWMH_F_MAXIMIZED) { /* VERT and/or HORZ changed. */ if (ABOVE(win) || BELOW(win)) { if (!MAXIMIZED(win)) load_float_geom(win); else store_float_geom(win); } draw_frame(win); } if (changed & EWMH_F_FULLSCREEN) { if (!FULLSCREEN(win)) load_float_geom(win); win->ewmh_flags &= ~EWMH_F_MAXIMIZED; } DNPRINTF(SWM_D_PROP, "changed: %#x\n", changed); return (changed); } void ewmh_update_wm_state(struct ws_win *win) { xcb_atom_t vals[SWM_EWMH_ACTION_COUNT_MAX]; int n = 0; if (MAXIMIZED_VERT(win)) vals[n++] = ewmh[_NET_WM_STATE_MAXIMIZED_VERT].atom; if (MAXIMIZED_HORZ(win)) vals[n++] = ewmh[_NET_WM_STATE_MAXIMIZED_HORZ].atom; if (SKIP_TASKBAR(win)) vals[n++] = ewmh[_NET_WM_STATE_SKIP_TASKBAR].atom; if (SKIP_PAGER(win)) vals[n++] = ewmh[_NET_WM_STATE_SKIP_PAGER].atom; if (HIDDEN(win)) vals[n++] = ewmh[_NET_WM_STATE_HIDDEN].atom; if (FULLSCREEN(win)) vals[n++] = ewmh[_NET_WM_STATE_FULLSCREEN].atom; if (ABOVE(win)) vals[n++] = ewmh[_NET_WM_STATE_ABOVE].atom; if (BELOW(win)) vals[n++] = ewmh[_NET_WM_STATE_BELOW].atom; if (DEMANDS_ATTENTION(win)) vals[n++] = ewmh[_NET_WM_STATE_DEMANDS_ATTENTION].atom; if (MANUAL(win)) vals[n++] = ewmh[_SWM_WM_STATE_MANUAL].atom; if (n > 0) xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win->id, ewmh[_NET_WM_STATE].atom, XCB_ATOM_ATOM, 32, n, vals); else xcb_delete_property(conn, win->id, ewmh[_NET_WM_STATE].atom); } void print_strut(struct swm_strut *st) { if (st == NULL) return; DNPRINTF(SWM_D_MISC, "win %#x left:%u right:%u top:%u bottom:%u " "left_start_y:%u left_end_y:%u right_start_y:%u right_end_y:%u " "top_start_x:%u top_end_x:%u bottom_start_x:%u bottom_end_x:%u\n", st->win->id, st->left, st->right, st->top, st->bottom, st->left_start_y, st->left_end_y, st->right_start_y, st->right_end_y, st->top_start_x, st->top_end_x, st->bottom_start_x, st->bottom_end_x); } void ewmh_get_strut(struct ws_win *win) { xcb_get_property_cookie_t c; xcb_get_property_reply_t *r; struct swm_strut *srt = NULL; uint32_t *pv; if (win == NULL) return; if (win->strut) { SLIST_REMOVE(&win->s->struts, win->strut, swm_strut, entry); free(win->strut); win->strut = NULL; } /* _NET_WM_STRUT_PARTIAL: CARDINAL[12]/32 */ c = xcb_get_property(conn, 0, win->id, ewmh[_NET_WM_STRUT_PARTIAL].atom, XCB_ATOM_CARDINAL, 0, 12); r = xcb_get_property_reply(conn, c, NULL); if (r && r->format == 32 && r->length == 12) { if ((srt = calloc(1, sizeof(struct swm_strut))) == NULL) err(1, "ewmh_get_strut: calloc"); pv = xcb_get_property_value(r); srt->left= pv[0]; srt->right= pv[1]; srt->top= pv[2]; srt->bottom= pv[3]; srt->left_start_y= pv[4]; srt->left_end_y= pv[5]; srt->right_start_y= pv[6]; srt->right_end_y= pv[7]; srt->top_start_x= pv[8]; srt->top_end_x= pv[9]; srt->bottom_start_x= pv[10]; srt->bottom_end_x= pv[11]; srt->win = win; if (swm_debug) print_strut(srt); } else { free(r); /* _NET_WM_STRUT: CARDINAL[4]/32 */ c = xcb_get_property(conn, 0, win->id, ewmh[_NET_WM_STRUT].atom, XCB_ATOM_CARDINAL, 0, 4); r = xcb_get_property_reply(conn, c, NULL); if (r && r->format == 32 && r->length == 4) { if ((srt = calloc(1, sizeof(struct swm_strut))) == NULL) err(1, "ewmh_get_strut: calloc"); pv = xcb_get_property_value(r); srt->left= pv[0]; srt->right= pv[1]; srt->top= pv[2]; srt->bottom= pv[3]; srt->win = win; if (swm_debug) print_strut(srt); } } if (srt) { win->strut = srt; SLIST_INSERT_HEAD(&win->s->struts, srt, entry); } free(r); } int refresh_strut(struct swm_screen *s) { int changed = 0; struct swm_region *r; struct swm_strut *srt; struct swm_geometry g; uint32_t wc[4]; TAILQ_FOREACH(r, &s->rl, entry) { g = r->g; /* Reduce available area for struts. */ SLIST_FOREACH(srt, &s->struts, entry) { if (HIDDEN(srt->win) || srt->win->ws->r == NULL) continue; if (srt->top && (srt->top > (uint32_t)g.y && srt->top <= (uint32_t)g.y + g.h) && ((srt->top_start_x == 0 && srt->top_end_x == 0) || (srt->top_end_x > (uint32_t)g.x && srt->top_start_x < (uint32_t)g.x + g.w))) { g.h -= srt->top - (uint32_t)g.y; g.y = srt->top; } if (srt->bottom && (HEIGHT(s->r) - srt->bottom < (uint32_t)g.y + g.h) && ((srt->bottom_start_x == 0 && srt->bottom_end_x == 0) || (srt->bottom_end_x > (uint32_t)g.x && srt->bottom_start_x < (uint32_t)g.x + g.w))) { g.h = HEIGHT(s->r) - srt->bottom - g.y; } if (srt->left && (srt->left > (uint32_t)g.x && srt->left <= (uint32_t)g.x + g.w) && ((srt->left_start_y == 0 && srt->left_end_y == 0) || (srt->left_end_y > (uint32_t)g.y && srt->left_start_y < (uint32_t)g.y + g.h))) { g.w -= srt->left - (uint32_t)g.x; g.x = srt->left; } if (srt->right && (WIDTH(s->r) - srt->right < (uint32_t)g.x + g.w) && ((srt->right_start_y == 0 && srt->right_end_y == 0) || (srt->right_end_y > (uint32_t)g.y && srt->right_start_y < (uint32_t)g.y + g.h))) { g.w = WIDTH(s->r) - srt->right - g.x; } } if ((g.x == r->g_usable.x && g.y == r->g_usable.y && g.w == r->g_usable.w && g.h == r->g_usable.h) || g.w < 1 || g.h < 1) continue; DNPRINTF(SWM_D_MISC, "r%d usable:%dx%d+%d+%d\n", get_region_index(r), g.w, g.h, g.x, g.y); r->g_usable = g; changed++; if (r->bar) { if (bar_at_bottom) g.y += g.h - bar_height; g.h = bar_height; wc[0] = g.x; wc[1] = g.y; wc[2] = g.w; wc[3] = g.h; g.x += bar_border_width; g.y += bar_border_width; g.w -= 2 * bar_border_width; g.h -= 2 * bar_border_width; r->bar->g = g; xcb_configure_window(conn, r->bar->id, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, wc); } } ewmh_update_workarea(s); return (changed); } void ewmh_get_wm_state(struct ws_win *win) { xcb_atom_t *states; xcb_get_property_cookie_t c; xcb_get_property_reply_t *r; int i, n; if (win == NULL) return; win->ewmh_flags = 0; c = xcb_get_property(conn, 0, win->id, ewmh[_NET_WM_STATE].atom, XCB_ATOM_ATOM, 0, UINT32_MAX); r = xcb_get_property_reply(conn, c, NULL); if (r == NULL) return; states = xcb_get_property_value(r); n = xcb_get_property_value_length(r) / sizeof(xcb_atom_t); for (i = 0; i < n; i++) ewmh_change_wm_state(win, states[i], _NET_WM_STATE_ADD); free(r); } static void ewmh_update_active_window(struct swm_screen *s) { xcb_window_t awid; if (s->focus) awid = s->focus->id; else awid = XCB_WINDOW_NONE; if (awid != s->active_window) { DNPRINTF(SWM_D_FOCUS, "root: %#x win: %#x\n", s->root, s->active_window); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, s->root, ewmh[_NET_ACTIVE_WINDOW].atom, XCB_ATOM_WINDOW, 32, 1, &awid); s->active_window = awid; } } void dumpwins(struct swm_screen *s, struct binding *bp, union arg *args) { struct workspace *ws; struct ws_win *w; uint32_t state; xcb_get_window_attributes_cookie_t c; xcb_get_window_attributes_reply_t *wa; int i; /* Suppress warning. */ (void)bp; (void)args; if (swm_debug == 0) return; DPRINTF("=== Screen %d Focus Information ===\n", s->idx); DPRINTF("r_focus:%d focus:%#x\n", get_region_index(s->r_focus), WINID(s->focus)); RB_FOREACH(ws, workspace_tree, &s->workspaces) DPRINTF("ws:%2d f:%#9x pf:%#9x ar:%d fr:%#x\n", ws->idx, WINID(ws->focus), WINID(get_ws_focus_prev(ws)), ws->always_raise, WINID(ws->focus_raise)); DPRINTF("=== Screen %d Managed Windows ===\n", s->idx); TAILQ_FOREACH(w, &s->managed, manage_entry) { state = get_win_state(w->id); c = xcb_get_window_attributes(conn, w->id); wa = xcb_get_window_attributes_reply(conn, c, NULL); if (wa) { DPRINTF("win %#9x (f:%#x) ws:%02d map_state:%d" " state:%.3s transient_for:%#x main:%#9x" " parent:%#x\n", w->id, w->frame, w->ws->idx, wa->map_state, get_wm_state_label(state), w->transient_for, WINID(w->main), WINID(w->parent)); free(wa); } else DPRINTF("win %#x GetWindowAttributes failed.\n", w->id); } DPRINTF("=== Screen %d Window Priority (high to low) ===\n", s->idx); i = 0; TAILQ_FOREACH(w, &s->priority, priority_entry) DPRINTF("%2d) win %#9x (f:%#x) st:%u\n", i++, w->id, w->frame, w->st->layer); print_stacking(s); } void print_stackable(struct swm_stackable *st) { struct ws_win *w; struct swm_bar *b; struct swm_region *r; switch (st->type) { case STACKABLE_WIN: w = st->win; DPRINTF("l:%d win %#9x (f:%#x) mp:%d ws:%2i fs:%d mx:%d " "ab:%d bl:%d ic:%d ra:%d\n", st->layer, w->id, w->frame, w->mapped, w->ws->idx, (FULLSCREEN(w) != 0), (MAXIMIZED(w) != 0), (ABOVE(w) != 0), (BELOW(w) != 0), (HIDDEN(w) != 0), win_raised(w)); break; case STACKABLE_BAR: b = st->bar; DPRINTF("l:%d bar %#9x region:%d enabled:%d\n", b->st->layer, b->id, get_region_index(b->r), b->r->ws->bar_enabled); break; case STACKABLE_REGION: r = st->region; DPRINTF("l:%d rgn %#9x region:%d\n", st->layer, r->id, get_region_index(r)); break; default: DPRINTF("invalid type:%d\n", st->type); break; } } void print_stacking(struct swm_screen *s) { struct swm_stackable *st; DPRINTF("=== stacking order (bottom up) ===\n"); SLIST_FOREACH(st, &s->stack, entry) print_stackable(st); DPRINTF("=================================\n"); } void debug_toggle(struct swm_screen *s, struct binding *bp, union arg *args) { int num_screens, i; /* Suppress warnings. */ (void)s; (void)bp; (void)args; if (swm_debug == 0) return; debug_enabled = !debug_enabled; DNPRINTF(SWM_D_MISC, "debug_enabled: %s\n", YESNO(debug_enabled)); num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) update_debug(&screens[i]); xcb_flush(conn); } #define DEBUG_MAXROWS (3) void debug_refresh(struct ws_win *win) { struct swm_stackable *st; struct ws_win *w; XftDraw *draw; XGlyphInfo info; GC l_draw; XGCValues l_gcv; XRectangle l_ibox, l_lbox = {0, 0, 0, 0}; xcb_rectangle_t rect; uint32_t wc[4], mask, width, height, gcv[1]; int widx, sidx, fidx, pidx, i, rows; size_t len[DEBUG_MAXROWS]; char *s[DEBUG_MAXROWS]; char *buf, *sp, *b; if (debug_enabled) { /* Create debug window if it doesn't exist. */ if (win->debug == XCB_WINDOW_NONE) { win->debug = xcb_generate_id(conn); wc[0] = win->s->c[SWM_S_COLOR_BAR].pixel; wc[1] = win->s->c[SWM_S_COLOR_BAR_BORDER].pixel; wc[2] = win->s->colormap; xcb_create_window(conn, win->s->depth, win->debug, win->frame, 0, 0, 10, 10, 1, XCB_WINDOW_CLASS_INPUT_OUTPUT, win->s->visual, XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_COLORMAP, wc); if (win->mapped) xcb_map_window(conn, win->debug); } if (!win->mapped) return; /* Determine workspace window list index. */ widx = 0; TAILQ_FOREACH(w, &win->ws->winlist, entry) { ++widx; if (w == win) break; } /* Determine stacking index (bottom up). */ sidx = 0; SLIST_FOREACH(st, &win->s->stack, entry) { ++sidx; if (st->type == STACKABLE_WIN && st->win == win) break; } /* Determine recent focus index (most recent first). */ fidx = 0; TAILQ_FOREACH(w, &win->s->fl, focus_entry) { ++fidx; if (w == win) break; } /* Determine priority index (highest first). */ pidx = 0; TAILQ_FOREACH(w, &win->s->priority, priority_entry) { ++pidx; if (w == win) break; } if (asprintf(&buf, "%#x f:%#x m:%#x p:%#x\n" "l:%d wl:%d st:%d fl:%d pr:%d\n" "vi:%#x cm:%#x im:%s", win->id, win->frame, win->main->id, WINID(win->parent), st->layer, widx, sidx, fidx, pidx, win->s->visual, win->s->colormap, get_win_input_model_label(win)) == -1) return; /* Determine rows and window dimensions. */ sp = buf; width = 1; rows = 0; while ((b = strsep(&sp, "\n"))) { if (*b == '\0') continue; s[rows] = b; len[rows] = strlen(b); if (bar_font_legacy) { TEXTEXTENTS(bar_fs, s[rows], len[rows], &l_ibox, &l_lbox); if (l_lbox.width > (int)width) width = l_lbox.width; } else { XftTextExtentsUtf8(display, win->s->bar_xftfonts[0], (FcChar8 *)s[rows], len[rows], &info); if (info.xOff > (int)width) width = info.xOff; } rows++; if (rows == DEBUG_MAXROWS) break; } if (bar_font_legacy) height = bar_fs_extents-> max_logical_extent.height; else height = win->s->bar_xftfonts[0]->height; /* Add 1px pad. */ width += 2; height += 2; mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; wc[0] = wc[1] = win_border(win); /* Add 1px pad for window. */ wc[2] = width + 2; wc[3] = height * rows + 2; xcb_configure_window(conn, win->debug, mask, wc); /* Draw a filled rectangle to 'clear' window. */ rect.x = 0; rect.y = 0; rect.width = wc[2]; rect.height = wc[3]; gcv[0] = win->s->c[SWM_S_COLOR_BAR].pixel; xcb_change_gc(conn, win->s->gc, XCB_GC_FOREGROUND, gcv); xcb_poly_fill_rectangle(conn, win->debug, win->s->gc, 1, &rect); /* Draw text. */ if (bar_font_legacy) { l_gcv.graphics_exposures = 0; l_draw = XCreateGC(display, win->debug, 0, &l_gcv); XSetForeground(display, l_draw, win->s->c[SWM_S_COLOR_BAR_FONT].pixel); for (i = 0; i < rows; i++) DRAWSTRING(display, win->debug, bar_fs, l_draw, 2, (height - l_lbox.height) / 2 - l_lbox.y + height * i + 1, s[i], len[i]); XFreeGC(display, l_draw); } else { draw = XftDrawCreate(display, win->debug, win->s->xvisual, win->s->colormap); for (i = 0; i < rows; i++) XftDrawStringUtf8(draw, &bar_fg_colors[0], win->s->bar_xftfonts[0], 2, (height + win->s->bar_xftfonts[0]->height) / 2 - win->s->bar_xftfonts[0]->descent + height * i + 1, (FcChar8 *)s[i], len[i]); XftDrawDestroy(draw); } free(buf); } else if (win->debug != XCB_WINDOW_NONE) { xcb_destroy_window(conn, win->debug); win->debug = XCB_WINDOW_NONE; } } void update_debug(struct swm_screen *s) { struct ws_win *w; if (swm_debug == 0) return; TAILQ_FOREACH(w, &s->managed, manage_entry) debug_refresh(w); } void sighdlr(int sig) { int saved_errno, status; pid_t pid; saved_errno = errno; switch (sig) { case SIGCHLD: while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) { if (pid == -1) { if (errno == EINTR) continue; if (errno != ECHILD) warn("sighdlr: waitpid"); break; } if (pid == searchpid) search_resp = 1; if (WIFEXITED(status)) { if (WEXITSTATUS(status) != 0) warnx("sighdlr: child exit status: %d", WEXITSTATUS(status)); } else warnx("sighdlr: child is terminated " "abnormally"); } break; case SIGHUP: restart_wm = 1; break; case SIGINT: case SIGTERM: case SIGQUIT: running = 0; break; } errno = saved_errno; } struct pid_e * find_pid(pid_t pid) { struct pid_e *p = NULL; DNPRINTF(SWM_D_MISC, "pid: %d\n", pid); if (pid == 0) return (NULL); TAILQ_FOREACH(p, &pidlist, entry) { if (p->pid == pid) return (p); } return (NULL); } static char * color_to_rgb(struct swm_color *color) { char *name; if (asprintf(&name, "rgb:%04x/%04x/%04x", color->r_orig, color->g_orig, color->b_orig) == -1) err(1, "asprintf"); return (name); } int parse_color(struct swm_screen *s, const char *name, struct swm_color *color) { char cname[32] = "#"; xcb_lookup_color_reply_t *lcr; uint16_t rr, gg, bb, aa; bool valid = false; if (s == NULL || name == NULL || color == NULL) return (1); /* * rgba color is in format rgba://rr/gg/bb/aa * rgb color is in format rgb://rr/gg/bb */ if (strncmp(name, "rgba:", 5) == 0) { if (parse_rgba(name, &rr, &gg, &bb, &aa) != -1) valid = true; else warnx("could not parse rgba %s", name); } else if (strncmp(name, "rgb:", 4) == 0) { if (parse_rgb(name, &rr, &gg, &bb) != -1) { aa = 0xff; valid = true; } else warnx("could not parse rgb %s", name); } else { lcr = xcb_lookup_color_reply(conn, xcb_lookup_color(conn, s->colormap, strlen(name), name), NULL); if (lcr == NULL) { strlcat(cname, name + 2, sizeof cname - 1); lcr = xcb_lookup_color_reply(conn, xcb_lookup_color(conn, s->colormap, strlen(cname), cname), NULL); } if (lcr) { rr = lcr->visual_red; gg = lcr->visual_green; bb = lcr->visual_blue; aa = 0xff; valid = true; } else warnx("color '%s' not found", name); } if (!valid) return (1); color->r = color->r_orig = RGB_8_TO_16(rr); color->g = color->g_orig = RGB_8_TO_16(gg); color->b = color->b_orig = RGB_8_TO_16(bb); color->a = RGB_8_TO_16(aa); return (0); } void setscreencolor(struct swm_screen *s, const char *val, int c) { xcb_screen_t *scr; xcb_visualtype_t *vis; xcb_alloc_color_reply_t *cr; uint32_t mask; int rgbdepth = 0; if (s == NULL || val == NULL || c < 0 || c >= SWM_S_COLOR_MAX) return; if (parse_color(s, val, &s->c[c])) { DNPRINTF(SWM_D_CONF, "failed to parse color: %s\n", val); return; } if ((scr = get_screen(s->idx)) == NULL) { DNPRINTF(SWM_D_CONF, "failed to get screen %d\n", s->idx); return; } vis = xcb_aux_find_visual_by_id(scr, s->visual); DNPRINTF(SWM_D_CONF, "vis %#x, class:%u, red_mask: %#x, " "green_mask %#x, blue_mask %#x\n", vis->visual_id, vis->_class, vis->red_mask, vis->green_mask, vis->blue_mask); /* Count RGB bits. */ mask = vis->red_mask | vis->blue_mask | vis->green_mask; while (mask) { if (mask & 0x1) rgbdepth++; mask >>= 1; } if (s->depth <= rgbdepth) /* No extra bits for alpha. */ s->c[c].a = 0xffff; if (vis->_class == XCB_VISUAL_CLASS_TRUE_COLOR) { /* Roll our own pixel. */ /* Premultiply alpha. */ s->c[c].r = (uint32_t)(s->c[c].r * (double)s->c[c].a / 0xffff); s->c[c].g = (uint32_t)(s->c[c].g * (double)s->c[c].a / 0xffff); s->c[c].b = (uint32_t)(s->c[c].b * (double)s->c[c].a / 0xffff); /* Fit color values into pixel masks. */ #define FITMASK(c, m) ((uint32_t)((double)(c)/0xffff * (m)) & (m)) s->c[c].pixel = FITMASK(s->c[c].r, vis->red_mask) | FITMASK(s->c[c].g, vis->green_mask) | FITMASK(s->c[c].b, vis->blue_mask); if (s->depth > rgbdepth) { /* Assume extra bits are for alpha. */ mask = ~(vis->red_mask | vis->blue_mask | vis->green_mask); s->c[c].pixel |= FITMASK(s->c[c].a, mask); } #undef FITMASK } else { /* Get pixel from server. */ cr = xcb_alloc_color_reply(conn, xcb_alloc_color(conn, s->colormap, s->c[c].r, s->c[c].g, s->c[c].b), NULL); if (cr) { s->c[c].pixel = cr->pixel; free(cr); } else { warnx("color '%s' not found", val); return; } } DNPRINTF(SWM_D_CONF, "set c[%d] r:%#x g:%#x b:%#x a:%#x pixel:%#x\n", c, s->c[c].r, s->c[c].g, s->c[c].b, s->c[c].a, s->c[c].pixel); } void fancy_stacker(struct workspace *ws) { if (ws->cur_layout->l_stack == vertical_stack) snprintf(ws->stacker, ws->stacker_len, ws->l_state.vertical_flip ? "[%d>%d]" : "[%d|%d]", ws->l_state.vertical_mwin, ws->l_state.vertical_stacks); else if (ws->cur_layout->l_stack == horizontal_stack) snprintf(ws->stacker, ws->stacker_len, ws->l_state.horizontal_flip ? "[%dv%d]" : "[%d-%d]", ws->l_state.horizontal_mwin, ws->l_state.horizontal_stacks); else if (ws->cur_layout->l_stack == floating_stack) strlcpy(ws->stacker, "[ ~ ]", ws->stacker_len); else strlcpy(ws->stacker, "[ ]", ws->stacker_len); } void plain_stacker(struct workspace *ws) { if (ws->cur_layout->l_stack == vertical_stack) strlcpy(ws->stacker, (ws->l_state.vertical_flip ? stack_mark_vertical_flip : stack_mark_vertical), ws->stacker_len); else if (ws->cur_layout->l_stack == horizontal_stack) strlcpy(ws->stacker, (ws->l_state.horizontal_flip ? stack_mark_horizontal_flip : stack_mark_horizontal), ws->stacker_len); else if (ws->cur_layout->l_stack == floating_stack) strlcpy(ws->stacker, stack_mark_floating, ws->stacker_len); else strlcpy(ws->stacker, stack_mark_max, ws->stacker_len); } void socket_setnonblock(int fd) { int flags; if ((flags = fcntl(fd, F_GETFL, 0)) == -1) err(1, "fcntl F_GETFL"); flags |= O_NONBLOCK; if (fcntl(fd, F_SETFL, flags) == -1) err(1, "fcntl F_SETFL"); } void bar_print_legacy(struct swm_region *r, const char *s) { xcb_rectangle_t rect; uint32_t gcv[1]; XGCValues gcvd; int x = 0, fg_type, bg_type; size_t len; XRectangle ibox, lbox; GC draw; len = strlen(s); TEXTEXTENTS(bar_fs, s, len, &ibox, &lbox); switch (bar_justify) { case SWM_BAR_JUSTIFY_LEFT: x = SWM_BAR_OFFSET; break; case SWM_BAR_JUSTIFY_CENTER: x = (WIDTH(r) - lbox.width) / 2; break; case SWM_BAR_JUSTIFY_RIGHT: x = WIDTH(r) - lbox.width - SWM_BAR_OFFSET; break; } if (x < SWM_BAR_OFFSET) x = SWM_BAR_OFFSET; /* Setup default fg/bg index type */ if (win_free(r->s->focus) && r->s->r_focus == r) { fg_type = SWM_S_COLOR_BAR_FONT_FREE; bg_type = SWM_S_COLOR_BAR_FREE; } else if (ws_focused(r->ws)) { fg_type = SWM_S_COLOR_BAR_FONT; bg_type = SWM_S_COLOR_BAR; } else { fg_type = SWM_S_COLOR_BAR_FONT_UNFOCUS; bg_type = SWM_S_COLOR_BAR_UNFOCUS; } /* clear back buffer */ rect.x = 0; rect.y = 0; rect.width = WIDTH(r->bar); rect.height = HEIGHT(r->bar); gcv[0] = r->s->c[bg_type].pixel; xcb_change_gc(conn, r->s->gc, XCB_GC_FOREGROUND, gcv); xcb_poly_fill_rectangle(conn, r->bar->buffer, r->s->gc, 1, &rect); /* draw back buffer */ gcvd.graphics_exposures = 0; draw = XCreateGC(display, r->bar->buffer, GCGraphicsExposures, &gcvd); XSetForeground(display, draw, r->s->c[fg_type].pixel); DRAWSTRING(display, r->bar->buffer, bar_fs, draw, x, (bar_fs_extents->max_logical_extent.height - lbox.height) / 2 - lbox.y, s, len); XFreeGC(display, draw); /* blt */ xcb_copy_area(conn, r->bar->buffer, r->bar->id, r->s->gc, 0, 0, 0, 0, WIDTH(r->bar), HEIGHT(r->bar)); } void bar_print(struct swm_region *r, const char *s) { size_t len; xcb_rectangle_t rect; uint32_t gcv[1]; int32_t x = 0; XGlyphInfo info; XftDraw *draw; XftFont *xf; len = strlen(s); xf = r->s->bar_xftfonts[0]; XftTextExtentsUtf8(display, xf, (FcChar8 *)s, len, &info); switch (bar_justify) { case SWM_BAR_JUSTIFY_LEFT: x = SWM_BAR_OFFSET; break; case SWM_BAR_JUSTIFY_CENTER: x = (WIDTH(r) - info.xOff) / 2; break; case SWM_BAR_JUSTIFY_RIGHT: x = WIDTH(r) - info.xOff - SWM_BAR_OFFSET; break; } if (x < SWM_BAR_OFFSET) x = SWM_BAR_OFFSET; /* clear back buffer */ rect.x = 0; rect.y = 0; rect.width = WIDTH(r->bar) + 2 * bar_border_width; rect.height = HEIGHT(r->bar) + 2 * bar_border_width; gcv[0] = r->s->c[SWM_S_COLOR_BAR].pixel; xcb_change_gc(conn, r->s->gc, XCB_GC_FOREGROUND, gcv); xcb_poly_fill_rectangle(conn, r->bar->buffer, r->s->gc, 1, &rect); /* draw back buffer */ draw = XftDrawCreate(display, r->bar->buffer, r->s->xvisual, r->s->colormap); XftDrawStringUtf8(draw, &bar_fg_colors[0], xf, x, (HEIGHT(r->bar) + xf->height) / 2 - xf->descent, (FcChar8 *)s, len); XftDrawDestroy(draw); /* blt */ xcb_copy_area(conn, r->bar->buffer, r->bar->id, r->s->gc, 0, 0, 0, 0, WIDTH(r->bar) + 2 * bar_border_width, HEIGHT(r->bar) + 2 * bar_border_width); } void bar_print_layout(struct swm_region *r) { struct text_fragment *frag; xcb_rectangle_t rect; xcb_point_t points[5]; XftDraw *xft_draw = NULL; XRectangle x_rect; XftFont *xf; GC draw = 0; XGCValues gcvd; uint32_t gcv[2]; int xpos, i, j; int bd_type, bg, bg_type, fg, fg_type, fn; int space, remain, weight; XftColor *fg_colors; space = WIDTH(r) - 2 * bar_border_width; weight = 0; /* Parse markup sequences in each section */ /* For the legacy font, just setup one text fragment */ for (i = 0; i < numsect; i++) { bar_parse_markup(r->s, bsect + i); if (bsect[i].fit_to_text) { bsect[i].width = bsect[i].text_width + 2 * SWM_BAR_OFFSET; space -= bsect[i].width; } else weight += bsect[i].weight; } /* Calculate width for each text justified section */ remain = space; j = -1; for (i = 0; i < numsect; i++) if (!bsect[i].fit_to_text && weight > 0) { bsect[i].width = bsect[i].weight * space / weight; remain -= bsect[i].width; j = i; } /* Add any space that was rounded off to the last section. */ if (j != -1) bsect[j].width += remain; /* Calculate starting position of each section and text */ xpos = 0; for (i = 0; i < numsect; i++) { bsect[i].start = xpos; if (bsect[i].fit_to_text) bsect[i].text_start = bsect[i].start + SWM_BAR_OFFSET; else { if (bsect[i].justify == SWM_BAR_JUSTIFY_LEFT) bsect[i].text_start = bsect[i].start + SWM_BAR_OFFSET; else if (bsect[i].justify == SWM_BAR_JUSTIFY_RIGHT) bsect[i].text_start = bsect[i].start + bsect[i].width - bsect[i].text_width - SWM_BAR_OFFSET; else bsect[i].text_start = bsect[i].start + (bsect[i].width - bsect[i].text_width) / 2; } /* Don't overflow text to the left */ if (bsect[i].text_start < (bsect[i].start + SWM_BAR_OFFSET)) bsect[i].text_start = bsect[i].start + SWM_BAR_OFFSET; xpos += bsect[i].width; } /* Create drawing context */ if (bar_font_legacy) { gcvd.graphics_exposures = 0; draw = XCreateGC(display, r->bar->buffer, GCGraphicsExposures, &gcvd); } else xft_draw = XftDrawCreate(display, r->bar->buffer, r->s->xvisual, r->s->colormap); /* Setup default fg/bg index type */ if (win_free(r->s->focus) && r->s->r_focus == r) { fg_type = SWM_S_COLOR_BAR_FONT_FREE; bg_type = SWM_S_COLOR_BAR_FREE; bd_type = SWM_S_COLOR_BAR_BORDER_FREE; fg_colors = bar_fg_colors_free; } else if (ws_focused(r->ws)) { fg_type = SWM_S_COLOR_BAR_FONT; bg_type = SWM_S_COLOR_BAR; bd_type = SWM_S_COLOR_BAR_BORDER; fg_colors = bar_fg_colors; } else { fg_type = SWM_S_COLOR_BAR_FONT_UNFOCUS; bg_type = SWM_S_COLOR_BAR_UNFOCUS; bd_type = SWM_S_COLOR_BAR_BORDER_UNFOCUS; fg_colors = bar_fg_colors_unfocus; } /* Paint entire bar with default background color */ rect.x = bar_border_width; rect.y = bar_border_width; rect.width = WIDTH(r->bar); rect.height = HEIGHT(r->bar); gcv[0] = r->s->c[bg_type].pixel; xcb_change_gc(conn, r->s->gc, XCB_GC_FOREGROUND, gcv); xcb_poly_fill_rectangle(conn, r->bar->buffer, r->s->gc, 1, &rect); /* Draw border. */ if (bar_border_width > 0) { points[0].x = points[0].y = bar_border_width / 2; points[1].x = bar_border_width + WIDTH(r->bar) + points[0].x; points[1].y = points[0].y; points[2].x = points[1].x; points[2].y = bar_border_width + HEIGHT(r->bar) + points[0].y; points[3].x = points[0].x; points[3].y = points[2].y; points[4] = points[0]; gcv[0] = r->s->c[bd_type].pixel; gcv[1] = bar_border_width; xcb_change_gc(conn, r->s->gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, gcv); xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, r->bar->buffer, r->s->gc, 5, points); } /* Display the text for each section */ for (i = 0; i < numsect; i++) { rect.x = bar_border_width + bsect[i].start; rect.y = bar_border_width; rect.width = bsect[i].width; rect.height = HEIGHT(r->bar); /* No space to draw that section */ if (rect.width < 1) continue; /* No space to draw anything else */ if (rect.width < SWM_BAR_OFFSET) continue; /* Set the clip rectangle to avoid text overflow */ x_rect.x = rect.x; x_rect.y = rect.y; x_rect.width = rect.width; x_rect.height = rect.height; if (bar_font_legacy) XSetClipRectangles(display, draw, 0, 0, &x_rect, 1, YXBanded); else XftDrawSetClipRectangles(xft_draw, 0, 0, &x_rect, 1); /* Draw the text fragments in the current section */ xpos = bar_border_width + bsect[i].text_start; for (j = 0; j < bsect[i].nfrags; j++) { frag = bsect[i].frag + j; fn = frag->font; fg = frag->fg; bg = frag->bg; xf = r->s->bar_xftfonts[fn]; /* Paint background color of the text fragment */ if (bg != 0) { rect.x = xpos; rect.width = frag->width; gcv[0] = r->s->c[bg_type+bg].pixel; xcb_change_gc(conn, r->s->gc, XCB_GC_FOREGROUND, gcv); xcb_poly_fill_rectangle(conn, r->bar->buffer, r->s->gc, 1, &rect); } /* Draw text */ if (bar_font_legacy) { XSetForeground(display, draw, r->s->c[fg_type+fg].pixel); DRAWSTRING(display, r->bar->buffer, bar_fs, draw, xpos, bar_border_width + (bar_fs_extents->max_logical_extent.height - bsect[i].height) / 2 - bsect[i].ypos, frag->text, frag->length); } else { XftDrawStringUtf8(xft_draw, &fg_colors[fg], xf, xpos, bar_border_width + (HEIGHT(r->bar) + xf->height) / 2 - xf->descent, (FcChar8 *)frag->text, frag->length); } xpos += frag->width; } } if (bar_font_legacy) XFreeGC(display, draw); else XftDrawDestroy(xft_draw); /* blt */ xcb_copy_area(conn, r->bar->buffer, r->bar->id, r->s->gc, 0, 0, 0, 0, WIDTH(r->bar) + 2 * bar_border_width, HEIGHT(r->bar) + 2 * bar_border_width); } void bar_extra_stop(void) { if (bar_pid) { kill(bar_pid, SIGTERM); bar_pid = 0; } strlcpy(bar_ext, "", sizeof bar_ext); bar_extra = false; } void bar_window_class(char *s, size_t sz, struct ws_win *win) { if (win && win->ch.class_name) bar_strlcat_esc(s, win->ch.class_name, sz); } void bar_window_instance(char *s, size_t sz, struct ws_win *win) { if (win && win->ch.instance_name) bar_strlcat_esc(s, win->ch.instance_name, sz); } void bar_window_class_instance(char *s, size_t sz, struct ws_win *win) { if (win == NULL) return; bar_window_class(s, sz, win); strlcat(s, ":", sz); bar_window_instance(s, sz, win); } void bar_window_state(char *s, size_t sz, struct ws_win *win) { if (win) { if (MAXIMIZED(win)) strlcpy(s, focus_mark_maximized, sz); else if (win_free(win)) strlcpy(s, focus_mark_free, sz); else if (win_floating(win) && !FULLSCREEN(win)) strlcpy(s, focus_mark_floating, sz); else strlcpy(s, focus_mark_normal, sz); } else strlcpy(s, focus_mark_none, sz); } void bar_window_name(char *s, size_t sz, struct ws_win *win) { char *title; if (win == NULL) return; if (win->mapped) { title = get_win_name(win->id); bar_strlcat_esc(s, title, sz); free(title); } } static void get_wm_normal_hints(struct ws_win *win) { xcb_icccm_get_wm_normal_hints_reply(conn, xcb_icccm_get_wm_normal_hints(conn, win->id), &win->sh, NULL); } /* Get/refresh current WM_HINTS on a window. */ static void get_wm_hints(struct ws_win *win) { xcb_icccm_get_wm_hints_reply(conn, xcb_icccm_get_wm_hints(conn, win->id), &win->hints, NULL); } /* Get/refresh WM_TRANSIENT_FOR on a window. */ static bool get_wm_transient_for(struct ws_win *win) { xcb_window_t trans; DNPRINTF(SWM_D_MISC, "win %#x\n", WINID(win)); if (xcb_icccm_get_wm_transient_for_reply(conn, xcb_icccm_get_wm_transient_for(conn, win->id), &trans, NULL)) { if (win->transient_for != trans) { win->transient_for = trans; win->parent = find_window(win->transient_for); if (win->parent == win) win->parent = NULL; DNPRINTF(SWM_D_PROP, "transient_for: %#x, " "parent: %#x\n", trans, WINID(win->parent)); return (true); } } return (false); } static void clear_attention(struct ws_win *win) { if (!DEMANDS_ATTENTION(win)) return; win->ewmh_flags &= ~EWMH_F_DEMANDS_ATTENTION; ewmh_update_wm_state(win); } static void set_attention(struct ws_win *win) { if (DEMANDS_ATTENTION(win)) return; win->ewmh_flags |= EWMH_F_DEMANDS_ATTENTION; ewmh_update_wm_state(win); } static bool win_urgent(struct ws_win *win) { return (xcb_icccm_wm_hints_get_urgency(&win->hints) != 0 || DEMANDS_ATTENTION(win)); } void bar_urgent(struct swm_screen *s, char *str, size_t sz) { struct workspace *ws; struct ws_win *win; bool urgent; char b[13]; RB_FOREACH(ws, workspace_tree, &s->workspaces) { if (ws_root(ws)) continue; urgent = false; TAILQ_FOREACH(win, &ws->winlist, entry) if (win_urgent(win)) { urgent = true; break; } if (urgent) { snprintf(b, sizeof b, "%d ", ws->idx + 1); strlcat(str, b, sz); } else if (!urgent_collapse) strlcat(str, "- ", sz); } if (urgent_collapse && str[0]) str[strlen(str) - 1] = 0; } void bar_workspace_indicator(char *s, size_t sz, struct swm_region *r) { struct ws_win *w; struct workspace *ws; int count = 0; char tmp[SWM_BAR_MAX], *mark, *suffix; bool current, active, named, urgent, collapse; if (r == NULL) return; RB_FOREACH(ws, workspace_tree, &r->s->workspaces) { if (ws_root(ws)) continue; current = (ws == r->ws); named = (ws->name != NULL); urgent = false; active = (TAILQ_FIRST(&ws->winlist) != NULL); /* Get urgency status if needed. */ if (workspace_indicator & SWM_WSI_LISTURGENT || workspace_indicator & SWM_WSI_MARKURGENT) TAILQ_FOREACH(w, &ws->winlist, entry) if ((urgent = win_urgent(w))) break; collapse = !(workspace_indicator & SWM_WSI_MARKCURRENT || workspace_indicator & SWM_WSI_MARKURGENT); if (!(current && workspace_indicator & SWM_WSI_HIDECURRENT) && ((current && workspace_indicator & SWM_WSI_LISTCURRENT) || (active && workspace_indicator & SWM_WSI_LISTACTIVE) || (!active && workspace_indicator & SWM_WSI_LISTEMPTY) || (urgent && workspace_indicator & SWM_WSI_LISTURGENT) || (named && workspace_indicator & SWM_WSI_LISTNAMED))) { if (count > 0) strlcat(s, " ", sz); if (current && workspace_indicator & SWM_WSI_MARKCURRENT) { mark = workspace_mark_current; suffix = workspace_mark_current_suffix; } else if (urgent && workspace_indicator & SWM_WSI_MARKURGENT) { mark = workspace_mark_urgent; suffix = workspace_mark_urgent_suffix; } else if (active && workspace_indicator & SWM_WSI_MARKACTIVE) { mark = workspace_mark_active; suffix = workspace_mark_active_suffix; } else if (!active && workspace_indicator & SWM_WSI_MARKEMPTY) { mark = workspace_mark_empty; suffix = workspace_mark_empty_suffix; } else if (!collapse) { mark = " "; suffix = NULL; } else { mark = NULL; suffix = NULL; } if (mark) strlcat(s, mark, sz); *tmp = '\0'; if (named && workspace_indicator & SWM_WSI_PRINTNAMES) { if (workspace_indicator & SWM_WSI_NOINDEXES) snprintf(tmp, sizeof tmp, "%s", ws->name); else snprintf(tmp, sizeof tmp, "%d:%s", ws->idx + 1, ws->name); } else if (workspace_indicator & SWM_WSI_NOINDEXES) snprintf(tmp, sizeof tmp, "%s", " "); else snprintf(tmp, sizeof tmp, "%d", ws->idx + 1); strlcat(s, tmp, sz); if (suffix) strlcat(s, suffix, sz); count++; } } } void bar_workspace_name(char *s, size_t sz, struct workspace *ws) { if (ws == NULL) return; if (ws->name) bar_strlcat_esc(s, ws->name, sz); } /* build the default bar format according to the defined enabled options */ void bar_fmt(const char *fmtexp, char *fmtnew, struct swm_region *r, size_t sz) { struct ws_win *w; /* if format provided, just copy the buffers */ if (bar_format != NULL) { strlcpy(fmtnew, fmtexp, sz); return; } /* reset the output buffer */ *fmtnew = '\0'; strlcat(fmtnew, "+N:+I ", sz); if (stack_enabled) strlcat(fmtnew, "+S", sz); strlcat(fmtnew, " ", sz); /* only show the workspace name if there's actually one */ if (r != NULL && r->ws != NULL && r->ws->name != NULL) strlcat(fmtnew, "<+D>", sz); /* If enabled, only show the iconic count if there are iconic wins. */ if (iconic_enabled && r != NULL && r->ws != NULL) TAILQ_FOREACH(w, &r->ws->winlist, entry) if (HIDDEN(w)) { strlcat(fmtnew, "{+M}", sz); break; } strlcat(fmtnew, "+3<", sz); if (clock_enabled) { strlcat(fmtnew, fmtexp, sz); strlcat(fmtnew, "+4<", sz); } /* bar_urgent already adds the space before the last asterisk */ if (urgent_enabled) strlcat(fmtnew, (urgent_collapse ? "*+U*+4<" : "* +U*+4<"), sz); if (window_class_enabled) { strlcat(fmtnew, "+C", sz); if (!window_instance_enabled) strlcat(fmtnew, "+4<", sz); } /* checks needed by the colon and floating strlcat(3) calls below */ if (r != NULL && r->ws != NULL && r->ws->focus != NULL) { if (window_instance_enabled) { if (window_class_enabled) strlcat(fmtnew, ":", sz); strlcat(fmtnew, "+T+4<", sz); } if (window_name_enabled) { if (ABOVE(r->ws->focus) || MAXIMIZED(r->ws->focus)) strlcat(fmtnew, "+F ", sz); strlcat(fmtnew, "+64W ", sz); } } /* finally add the action script output and the version */ strlcat(fmtnew, "+4<+A+4<+V", sz); } void bar_replace_pad(char *tmp, int *limit, size_t sz) { /* special case; no limit given, pad one space, instead */ if (*limit == (int)sz - 1) *limit = 1; snprintf(tmp, sz, "%*s", *limit, " "); } /* replaces the bar format character sequences (like in tmux(1)) */ char * bar_replace_seq(char *fmt, char *fmtrep, struct swm_region *r, size_t *offrep, size_t sz) { struct ws_win *w, *cfw; char *ptr, *cur = fmt; char tmp[SWM_BAR_MAX]; int limit, size, count; size_t len; int pre_padding = 0; int post_padding = 0; int padding_len = 0; /* Reset replace buffer. */ bzero(tmp, sizeof tmp); cur++; /* determine if pre-padding is requested */ if (*cur == '_') { pre_padding = 1; cur++; } /* get number, if any */ size = 0; if (sscanf(cur, "%d%n", &limit, &size) != 1) limit = sizeof tmp - 1; if (limit <= 0 || limit >= (int)sizeof tmp) limit = sizeof tmp - 1; cur += size; /* determine if post padding is requested */ if (*cur == '_') { post_padding = 1; cur++; } if (r->s->focus && win_free(r->s->focus) && r->s->r_focus == r) cfw = r->s->focus; else cfw = r->ws->focus; /* character sequence */ switch (*cur) { case '+': strlcpy(tmp, "+", sizeof tmp); break; case '<': bar_replace_pad(tmp, &limit, sizeof tmp); break; case 'A': if (bar_action_expand) snprintf(tmp, sizeof tmp, "%s", bar_ext); else bar_strlcat_esc(tmp, bar_ext, sizeof tmp); break; case 'C': bar_window_class(tmp, sizeof tmp, cfw); break; case 'D': bar_workspace_name(tmp, sizeof tmp, r->ws); break; case 'F': bar_window_state(tmp, sizeof tmp, cfw); break; case 'I': snprintf(tmp, sizeof tmp, "%d", r->ws->idx + 1); break; case 'L': bar_workspace_indicator(tmp, sizeof tmp, r); break; case 'M': count = 0; TAILQ_FOREACH(w, &r->ws->winlist, entry) if (HIDDEN(w)) ++count; snprintf(tmp, sizeof tmp, "%d", count); break; case 'N': snprintf(tmp, sizeof tmp, "%d", r->s->idx + 1); break; case 'P': bar_window_class_instance(tmp, sizeof tmp, cfw); break; case 'R': snprintf(tmp, sizeof tmp, "%d", get_region_index(r)); break; case 'S': snprintf(tmp, sizeof tmp, "%s", r->ws->stacker); break; case 'T': bar_window_instance(tmp, sizeof tmp, cfw); break; case 'U': bar_urgent(r->s, tmp, sizeof tmp); break; case 'V': snprintf(tmp, sizeof tmp, "%s", bar_vertext); break; case 'w': count = 0; TAILQ_FOREACH(w, &r->ws->winlist, entry) ++count; snprintf(tmp, sizeof tmp, "%d", count); break; case 'W': bar_window_name(tmp, sizeof tmp, cfw); break; default: /* Unknown character sequence or EOL; copy as-is. */ strlcpy(tmp, fmt, cur - fmt + 2); break; } len = strlen(tmp); /* calculate the padding lengths */ padding_len = limit - (int)len; if (padding_len > 0) { limit = len; if (pre_padding) pre_padding = padding_len / (pre_padding + post_padding); if (post_padding) post_padding = padding_len - pre_padding; } else { pre_padding = 0; post_padding = 0; } /* do pre padding */ while (pre_padding-- > 0) { if (*offrep >= sz - 1) break; fmtrep[(*offrep)++] = ' '; } ptr = tmp; while (limit-- > 0) { if (*offrep >= sz - 1) break; fmtrep[(*offrep)++] = *ptr++; } /* do post padding */ while (post_padding-- > 0) { if (*offrep >= sz - 1) break; fmtrep[(*offrep)++] = ' '; } if (*cur != '\0') cur++; return (cur); } void bar_replace_action(char *fmt, char *fmtact, struct swm_region *r, size_t sz) { size_t off; char *s; off = 0; while (*fmt != '\0') { if (*fmt != '+') { /* skip ordinary characters */ if (off >= sz - 1) break; fmtact[off++] = *fmt++; continue; } /* Find the first character after the padding */ s = fmt + 1; while ((*s == '_') || ((*s >= '0') && (*s <= '9'))) s++; if (*s == 'A') { /* Replace the action script character sequence */ fmt = bar_replace_seq(fmt, fmtact, r, &off, sz); if (off >= sz - 1) break; } else { /* Copy '+' and the next character */ fmtact[off++] = *fmt++; if (*fmt != '\0') fmtact[off++] = *fmt++; } } fmtact[off] = '\0'; } void bar_strlcat_esc(char *dst, char *src, size_t sz) { /* Find end of destination string */ while (*dst != '\0' && sz != 0) { dst++; sz--; } /* Concat string and escape every '+' */ while (*src != '\0' && sz > 1) { if ((*src == '+') && (sz > 2)) { *dst++ = '+'; sz--; } *dst++ = *src++; sz--; } *dst = '\0'; } void bar_replace(char *fmt, char *fmtrep, struct swm_region *r, size_t sz) { size_t off; char *s; off = 0; while (*fmt != '\0') { if (*fmt != '+') { /* skip ordinary characters */ if (off >= sz - 1) break; fmtrep[off++] = *fmt++; continue; } /* Find the first character after the padding */ s = fmt + 1; while ((*s == '_') || ((*s >= '0') && (*s <= '9'))) s++; if ((bar_action_expand) && (*s == 'A')) { /* skip this character sequence */ fmt = s + 1; continue; } /* character sequence found; replace it */ fmt = bar_replace_seq(fmt, fmtrep, r, &off, sz); if (off >= sz - 1) break; } fmtrep[off] = '\0'; } void bar_split_format(char *format) { char *src, *dst; int i = 0; /* Count the number of sections in format */ numsect = 1; src = format; while (*src != '\0') { if ((*src == '+') && (*(src+1) == '+')) src++; else if ((*src == '+') && (*(src+1) == '|')) { if (src != format) numsect++; src++; } src++; } /* Allocate the data structures for the bar sections */ if (numsect > maxsect) { free(bsect); if ((bsect = calloc(numsect, sizeof(struct bar_section) )) == NULL) err(1, "bar_split_format: failed to calloc memory."); maxsect = numsect; } /* Defaults for first section */ bsect[0].weight = 1; bsect[0].justify = bar_justify; /* split format into sections */ src = format; dst = bsect[0].fmtsplit; while (*src != '\0') { if ((*src == '+') && (*(src+1) == '+')) { *dst++ = *src++; *dst++ = *src++; } else if ((*src == '+') && (*(src+1) == '|')) { if (src != format) i++; if (i == numsect) break; *dst = '\0'; dst = bsect[i].fmtsplit; src += 2; /* Set weight and justification */ bsect[i].weight = atoi(src); if (bsect[i].weight == 0) bsect[i].weight = 1; while ((*src >= '0') && (*src <= '9')) src++; bsect[i].fit_to_text = false; if (*src == 'T') { bsect[i].fit_to_text = true; src++; } else if (*src == 'L') { bsect[i].justify = SWM_BAR_JUSTIFY_LEFT; src++; } else if (*src == 'C') { bsect[i].justify = SWM_BAR_JUSTIFY_CENTER; src++; } else if (*src == 'R') { bsect[i].justify = SWM_BAR_JUSTIFY_RIGHT; src++; } else bsect[i].justify = bar_justify; } else *dst++ = *src++; } while (*src != '\0') *dst++ = *src++; *dst = '\0'; } bool is_valid_markup(char *f, size_t *size) { char *s = f; int n; *size = 0; if (*s != '+') return false; s++; if (*s != '@') return false; s++; if ((*s == 'b') && (*(s+1) == 'g') && (*(s+2) == '=') && (*(s+3) >= '0') && (*(s+3) <= '9') && (*(s+4) == ';')) { *size = 7; n = (*(s+3) - '0'); if (n < num_bg_colors) return true; else return false; } if ((*s == 'f') && (*(s+1) == 'g') && (*(s+2) == '=') && (*(s+3) >= '0') && (*(s+3) <= '9') && (*(s+4) == ';')) { *size = 7; n = (*(s+3) - '0'); if (n < num_fg_colors) return true; else return false; } if ((*s == 'f') && (*(s+1) == 'n') && (*(s+2) == '=') && (*(s+3) >= '0') && (*(s+3) <= '9') && (*(s+4) == ';')) { if (bar_font_legacy) return false; *size = 7; n = (*(s+3) - '0'); if (n < num_xftfonts) return true; else return false; } if ((*s == 's') && (*(s+1) == 't') && (*(s+2) == 'p') && (*(s+3) == ';')) { *size = 6; return true; } return false; } int get_character_font(struct swm_screen *s, FcChar32 c, int pref) { int i; /* Try special font for PUA codepoints. */ if (font_pua_index && s->bar_xftfonts[font_pua_index] && ((0xe000 <= c && c <= 0xf8ff) || (0xf0000 <= c && c <= 0xffffd) || (0x100000 <= c && c <= 0x10fffd)) && XftCharExists(display, s->bar_xftfonts[font_pua_index], c)) return (font_pua_index); if (pref >= num_xftfonts) pref = -1; /* Try specified font. */ if (pref >= 0 && s->bar_xftfonts[pref] && XftCharExists(display, s->bar_xftfonts[pref], c)) return (pref); /* Search the rest, from the top. */ for (i = 0; i < num_xftfonts; i++) if (i != pref && s->bar_xftfonts[i] && XftCharExists(display, s->bar_xftfonts[i], c)) return (i); /* Fallback to the specified font, if valid. */ if (pref >= 0) return (pref); return (0); } void bar_parse_markup(struct swm_screen *s, struct bar_section *sect) { XRectangle ibox, lbox; XGlyphInfo info; struct text_fragment *frag; int i = 0, len = 0, stop = 0, fmtlen, termfrag = 0; int idx, fn, fg, bg; FcChar32 c; char *fmt; size_t size; sect->text_width = 0; sect->nfrags = 0; frag = sect->frag; frag[0].text = sect->fmtrep; frag[0].length = 0; frag[0].font = fn = 0; frag[0].width = 0; frag[0].bg = bg = 0; frag[0].fg = fg = 0; fmt = sect->fmtrep; fmtlen = strlen(fmt); if (bar_font_legacy) { TEXTEXTENTS(bar_fs, fmt, fmtlen, &ibox, &lbox); sect->height = lbox.height; sect->ypos = lbox.y; } while (*fmt != '\0') { /* Handle markup sequences. */ if (*fmt == '+' && !stop) { if (*(fmt+1) == '@' && (is_valid_markup(fmt, &size))) { idx = *(fmt+5) - '0'; if ((*(fmt+2) == 'f') && (*(fmt+3) == 'n')) fn = idx; else if ((*(fmt+2) == 'f') && (*(fmt+3) == 'g')) fg = idx; else if ((*(fmt+2) == 'b') && (*(fmt+3) == 'g')) bg = idx; else if ((*(fmt+2) == 's') && (*(fmt+3) == 't') && (*(fmt+4) == 'p')) stop = 1; /* Eat markup. */ fmt += size; fmtlen -= size; termfrag = 1; continue; } else if (*(fmt+1) == '+') { /* Eat escape. */ fmt++; fmtlen--; } } /* Decode current character. */ len = FcUtf8ToUcs4((FcChar8 *)fmt, &c, fmtlen); if (len <= 0) break; idx = get_character_font(s, c, fn); if (idx != frag[i].font || termfrag) { /* Terminate current fragment. */ if (frag[i].length > 0) { if (bar_font_legacy) { TEXTEXTENTS(bar_fs, frag[i].text, frag[i].length, &ibox, &lbox); frag[i].width = lbox.width; } else { XftTextExtentsUtf8(display, s->bar_xftfonts[frag[i].font], (FcChar8 *)frag[i].text, frag[i].length, &info); frag[i].width = info.xOff; } sect->text_width += frag[i].width; i++; if (i == SWM_TEXTFRAGS_MAX) break; } /* Begin new fragment. */ frag[i].text = fmt; frag[i].length = 0; frag[i].font = idx; frag[i].fg = fg; frag[i].bg = bg; termfrag = 0; } frag[i].length += len; fmt += len; fmtlen -= len; } if ((frag[i].length > 0) && (i < SWM_TEXTFRAGS_MAX)) { /* Process last text fragment */ if (bar_font_legacy) { TEXTEXTENTS(bar_fs, frag[i].text, frag[i].length, &ibox, &lbox); frag[i].width = lbox.width; } else { XftTextExtentsUtf8(display, s->bar_xftfonts[frag[i].font], (FcChar8 *)frag[i].text, frag[i].length, &info); frag[i].width = info.xOff; } sect->text_width += frag[i].width; i++; } sect->nfrags = i; } void bar_fmt_expand(char *fmtexp, size_t sz) { char *fmt = NULL; size_t len; struct tm tm; time_t tmt; /* start by grabbing the current time and date */ time(&tmt); localtime_r(&tmt, &tm); /* figure out what to expand */ if (bar_format != NULL) fmt = bar_format; else if (bar_format == NULL && clock_enabled) fmt = clock_format; /* if nothing to expand bail out */ if (fmt == NULL) { *fmtexp = '\0'; return; } /* copy as-is, just in case the format shouldn't be expanded below */ strlcpy(fmtexp, fmt, sz); /* finally pass the string through strftime(3) */ #ifndef SWM_DENY_CLOCK_FORMAT if ((len = strftime(fmtexp, sz, fmt, &tm)) == 0) warnx("format too long"); fmtexp[len] = '\0'; #endif } static void update_bars(struct swm_screen *s) { struct swm_region *r; TAILQ_FOREACH(r, &s->rl, entry) bar_draw(r->bar); } void bar_draw(struct swm_bar *bar) { struct swm_region *r; char fmtexp[SWM_BAR_MAX], fmtnew[SWM_BAR_MAX]; char fmtact[SWM_BAR_MAX * 2]; int i; /* expand the format by first passing it through strftime(3) */ bar_fmt_expand(fmtexp, sizeof fmtexp); if (bar == NULL) return; r = bar->r; if (!bar_enabled || !r->ws->bar_enabled || r->bar->disabled) { xcb_unmap_window(conn, bar->id); return; } xcb_map_window(conn, bar->id); if (startup_exception) { snprintf(fmtexp, sizeof fmtexp, "total exceptions: %u, first exception: %s", nr_exceptions, startup_exception); if (bar_font_legacy) bar_print_legacy(r, fmtexp); else bar_print(r, fmtexp); return; } bar_fmt(fmtexp, fmtnew, r, sizeof fmtnew); if (bar_action_expand) { bar_replace_action(fmtnew, fmtact, r, sizeof fmtact); bar_split_format(fmtact); } else bar_split_format(fmtnew); for (i = 0; i < numsect; i++) bar_replace(bsect[i].fmtsplit, bsect[i].fmtrep, r, sizeof bsect[i].fmtrep); bar_print_layout(r); } /* * Reads external script output; call when stdin is readable. * Returns 1 if bar_ext was updated; otherwise 0. */ int bar_extra_update(void) { size_t len; char b[SWM_BAR_MAX]; bool changed = false; if (!bar_extra) return (changed); while (fgets(b, sizeof(b), stdin) != NULL) { if (bar_enabled) { len = strlen(b); if (len > 0 && b[len - 1] == '\n') { /* Remove newline. */ b[--len] = '\0'; /* "Clear" bar_ext. */ bar_ext[0] = '\0'; /* Flush buffered output. */ strlcpy(bar_ext, bar_ext_buf, sizeof(bar_ext)); bar_ext_buf[0] = '\0'; /* Append new output to bar. */ strlcat(bar_ext, b, sizeof(bar_ext)); changed = true; } else { /* Buffer output. */ strlcat(bar_ext_buf, b, sizeof(bar_ext_buf)); } } } if (errno != EAGAIN) { warn("bar_action failed"); bar_extra_stop(); changed = true; } return (changed); } void bar_toggle(struct swm_screen *s, struct binding *bp, union arg *args) { struct swm_region *r; int i, num_screens; /* Suppress warning. */ (void)bp; (void)args; switch (args->id) { case SWM_ARG_ID_BAR_TOGGLE_WS: if ((r = get_current_region(s))) { /* Only change if master switch is enabled. */ if (bar_enabled) r->ws->bar_enabled = !r->ws->bar_enabled; else bar_enabled = r->ws->bar_enabled = true; DNPRINTF(SWM_D_BAR, "ws%d->bar_enabled: %s\n", r->ws->idx, YESNO(bar_enabled)); } break; case SWM_ARG_ID_BAR_TOGGLE: bar_enabled = !bar_enabled; break; } DNPRINTF(SWM_D_BAR, "bar_enabled: %s\n", YESNO(bar_enabled)); /* update bars as necessary */ num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) TAILQ_FOREACH(r, &screens[i].rl, entry) { if (r->bar) { if (bar_enabled && r->ws->bar_enabled) { xcb_map_window(conn, r->bar->id); bar_draw(r->bar); } else xcb_unmap_window(conn, r->bar->id); } stack(r); update_mapping(&screens[i]); } flush(); } void bar_extra_setup(void) { int fd, bar_pipe[2]; /* do this here because the conf file is in memory */ if (!bar_extra && bar_argv[0]) { /* launch external status app */ bar_extra = true; if (pipe(bar_pipe) == -1) err(1, "pipe error"); /* Read must not block or spectrwm will hang. */ socket_setnonblock(bar_pipe[0]); if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) err(1, "could not disable SIGPIPE"); switch (bar_pid = fork()) { case -1: err(1, "cannot fork"); break; case 0: /* child */ close(bar_pipe[0]); /* Isolate stdin. */ if ((fd = open(_PATH_DEVNULL, O_RDONLY, 0)) == -1) { warn("open /dev/null"); _exit(1); } if (dup2(fd, STDIN_FILENO) == -1) { warn("dup2 stdin"); _exit(1); } if (fd > STDERR_FILENO) close(fd); /* Reassign stdout to write end of pipe. */ if (dup2(bar_pipe[1], STDOUT_FILENO) == -1) { warn("dup2 stdout"); _exit(1); } if (bar_pipe[1] > STDERR_FILENO) close(bar_pipe[1]); execvp(bar_argv[0], bar_argv); warn("%s external app failed", bar_argv[0]); _exit(1); break; default: /* parent */ close(bar_pipe[1]); /* Reassign stdin to read end of pipe. */ if (dup2(bar_pipe[0], STDIN_FILENO) == -1) err(1, "dup2"); if (bar_pipe[0] > STDERR_FILENO) close(bar_pipe[0]); break; } atexit(kill_bar_extra_atexit); } } void kill_bar_extra_atexit(void) { if (bar_pid) kill(bar_pid, SIGTERM); } bool isxlfd(char *s) { int count = 0; while ((s = index(s, '-'))) { ++count; ++s; } return (count == 14); } int fontset_init(void) { char *default_string; char **missing_charsets; int num_missing_charsets = 0; int i; if (bar_fs) { XFreeFontSet(display, bar_fs); bar_fs = NULL; } DNPRINTF(SWM_D_INIT, "loading bar_fonts: %s\n", bar_fonts); bar_fs = XCreateFontSet(display, bar_fonts, &missing_charsets, &num_missing_charsets, &default_string); if (num_missing_charsets > 0) { warnx("Unable to load charset(s):"); for (i = 0; i < num_missing_charsets; ++i) warnx("%s", missing_charsets[i]); XFreeStringList(missing_charsets); if(bar_fs && default_string) { if (strcmp(default_string, "")) warnx("Glyphs from those sets will be replaced " "by '%s'.", default_string); else warnx("Glyphs from those sets won't be drawn."); } } if (bar_fs == NULL) { warnx("Error creating font set structure."); return (1); } bar_fs_extents = XExtentsOfFontSet(bar_fs); bar_height = bar_fs_extents->max_logical_extent.height + 2 * bar_border_width; if (bar_height < 1) bar_height = 1; return (0); } int xft_init(struct swm_screen *s) { XRenderColor color; int i; DNPRINTF(SWM_D_INIT, "loading bar_fonts: %s\n", bar_fonts); for (i = 0; i < num_xftfonts; i++) { s->bar_xftfonts[i] = XftFontOpenName(display, s->idx, bar_fontnames[i]); if (s->bar_xftfonts[i] == NULL) warnx("unable to load font %s", bar_fontnames[i]); } font_pua_index = 0; if (bar_fontname_pua) { s->bar_xftfonts[num_xftfonts] = XftFontOpenName(display, s->idx, bar_fontname_pua); if (s->bar_xftfonts[num_xftfonts] == NULL) warnx("unable to load font %s", bar_fontname_pua); else font_pua_index = num_xftfonts; } for (i = 0; i < num_fg_colors; i++) { /* Focused */ SWM_COLOR_TO_XRENDERCOLOR(s->c[SWM_S_COLOR_BAR_FONT+i], color); if (!XftColorAllocValue(display, s->xvisual, s->colormap, &color, &bar_fg_colors[i])) warn("Xft error: unable to allocate color."); /* 'Free' */ SWM_COLOR_TO_XRENDERCOLOR(s->c[SWM_S_COLOR_BAR_FONT_FREE+i], color); if (!XftColorAllocValue(display, s->xvisual, s->colormap, &color, &bar_fg_colors_free[i])) warn("Xft error: unable to allocate color."); /* Unfocused */ SWM_COLOR_TO_XRENDERCOLOR(s->c[SWM_S_COLOR_BAR_FONT_UNFOCUS+i], color); if (!XftColorAllocValue(display, s->xvisual, s->colormap, &color, &bar_fg_colors_unfocus[i])) warn("Xft error: unable to allocate color."); } SWM_COLOR_TO_XRENDERCOLOR(s->c[SWM_S_COLOR_BAR], color); if (!XftColorAllocValue(display, s->xvisual, s->colormap, &color, &search_font_color)) warn("Xft error: unable to allocate color."); if (s->bar_xftfonts[0] == NULL) return (1); bar_height = s->bar_xftfonts[0]->height + 2 * bar_border_width; if (bar_height < 1) bar_height = 1; return (0); } void setup_fonts(void) { int i, num_screens; int fail = 0; bool custom = false; size_t len; char *buf, *b, *sp; /* Process bar_fonts. */ if (bar_fonts && bar_fonts[0] != '\0') custom = true; else { /* Use defaults. */ free(bar_fonts); if ((bar_fonts = strdup(SWM_BAR_FONTS)) == NULL) err(1, "setup_fonts: strdup"); } DNPRINTF(SWM_D_CONF, "custom: %s\n", YESNO(custom)); len = strlen(bar_fonts) + 1; if ((buf = malloc(len)) == NULL) err(1, "setup_fonts: calloc"); /* If all entries are XLFD, use legacy mode. */ memcpy(buf, bar_fonts, len); bar_font_legacy = true; sp = buf; while ((b = strsep(&sp, SWM_CONF_DELIMLIST)) != NULL) { if (*b == '\0') continue; if (!isxlfd(b)) { bar_font_legacy = false; break; } } /* Otherwise, use Xft mode and parse list into array. */ if (!bar_font_legacy) { memcpy(buf, bar_fonts, len); sp = buf; while ((b = strsep(&sp, SWM_CONF_DELIMLIST)) != NULL) { if (*b == '\0') continue; if ((bar_fontnames[num_xftfonts] = strdup(b)) == NULL) err(1, "setup_fonts: strdup"); num_xftfonts++; if (num_xftfonts == SWM_BAR_MAX_FONTS) break; } } free(buf); DNPRINTF(SWM_D_CONF, "legacy: %s, bar_fonts: %s\n", YESNO(bar_font_legacy), bar_fonts); if (bar_font_legacy) fail = fontset_init(); else { num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) if ((fail = xft_init(&screens[i]))) break; } if (fail) { warnx("Failed to load bar_font. Switching to fallback."); if (custom) add_startup_exception("Error loading bar_font: %s", bar_fonts); free(bar_fonts); if ((bar_fonts = strdup(SWM_BAR_FONTS_FALLBACK)) == NULL) err(1, "setup_fonts: strdup"); bar_font_legacy = true; if (fontset_init()) errx(1, "Failed to load a font."); } } void bar_setup(struct swm_region *r) { struct swm_screen *s; struct swm_region *rfirst; uint32_t wa[4]; size_t len; char *name; if (r == NULL || r->bar) return; s = r->s; DNPRINTF(SWM_D_BAR, "screen %d, region %d\n", s->idx, get_region_index(r)); if ((r->bar = calloc(1, sizeof(struct swm_bar))) == NULL) err(1, "bar_setup: calloc"); if ((r->bar->st = calloc(1, sizeof(struct swm_stackable))) == NULL) err(1, "bar_setup: calloc"); r->bar->st->type = STACKABLE_BAR; r->bar->st->bar = r->bar; r->bar->st->layer = SWM_LAYER_BAR; r->bar->st->s = s; r->bar->r = r; X(r->bar) = X(r) + bar_border_width; Y(r->bar) = bar_at_bottom ? (Y(r) + HEIGHT(r) - bar_height + bar_border_width) : Y(r) + bar_border_width; WIDTH(r->bar) = WIDTH(r) - 2 * bar_border_width; HEIGHT(r->bar) = bar_height - 2 * bar_border_width; r->bar->disabled = false; /* Assume region is unfocused when we create the bar. */ r->bar->id = xcb_generate_id(conn); wa[0] = s->c[SWM_S_COLOR_BAR_UNFOCUS].pixel; wa[1] = s->c[SWM_S_COLOR_BAR_BORDER_UNFOCUS].pixel; wa[2] = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_POINTER_MOTION_HINT | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_FOCUS_CHANGE; wa[3] = s->colormap; xcb_create_window(conn, s->depth, r->bar->id, s->root, X(r->bar) - bar_border_width, Y(r->bar) - bar_border_width, WIDTH(r->bar) + 2 * bar_border_width, HEIGHT(r->bar) + 2 * bar_border_width, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, s->visual, XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP, wa); /* Set class and title to make the bar window identifiable. */ xcb_icccm_set_wm_class(conn, r->bar->id, strlen(SWM_WM_CLASS_BAR) + strlen(SWM_WM_CLASS_INSTANCE) + 2, SWM_WM_CLASS_BAR "\0" SWM_WM_CLASS_INSTANCE "\0"); if (asprintf(&name, "Status Bar - Region %d - " SWM_WM_CLASS_INSTANCE, get_region_index(r)) == -1) err(1, "asprintf"); len = strlen(name); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, r->bar->id, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, len, name); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, r->bar->id, ewmh[_NET_WM_NAME].atom, a_utf8_string, 8, len, name); free(name); rfirst = TAILQ_FIRST(&s->rl); if (rfirst == r) { wa[0] = r->id; wa[1] = XCB_STACK_MODE_ABOVE; } else { wa[0] = rfirst->bar->id; wa[1] = XCB_STACK_MODE_BELOW; } xcb_configure_window(conn, r->bar->id, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, wa); r->bar->buffer = xcb_generate_id(conn); xcb_create_pixmap(conn, s->depth, r->bar->buffer, r->bar->id, WIDTH(r->bar) + 2 * bar_border_width, HEIGHT(r->bar) + 2 * bar_border_width); if (randr_support) xcb_randr_select_input(conn, r->bar->id, XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE); if (bar_enabled) xcb_map_window(conn, r->bar->id); DNPRINTF(SWM_D_BAR, "win %#x, (x,y) w x h: (%d,%d) %d x %d\n", WINID(r->bar), X(r->bar), Y(r->bar), WIDTH(r->bar), HEIGHT(r->bar)); bar_extra_setup(); } void bar_cleanup(struct swm_region *r) { if (r->bar == NULL) return; xcb_destroy_window(conn, r->bar->id); xcb_free_pixmap(conn, r->bar->buffer); free(r->bar); r->bar = NULL; } void setup_marks(void) { struct workspace *ws; int i, num_screens; size_t mlen, len; /* Allocate stacking indicator buffers for longest mark. */ if (verbose_layout) mlen = SWM_FANCY_MAXLEN; else { mlen = strlen(stack_mark_max); if ((len = strlen(stack_mark_vertical)) > mlen) mlen = len; if ((len = strlen(stack_mark_vertical_flip)) > mlen) mlen = len; if ((len = strlen(stack_mark_horizontal)) > mlen) mlen = len; if ((len = strlen(stack_mark_horizontal_flip)) > mlen) mlen = len; if ((len = strlen(stack_mark_floating)) > mlen) mlen = len; mlen++; /* null byte. */ } stack_mark_maxlen = mlen; num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) RB_FOREACH(ws, workspace_tree, &screens[i].workspaces) { if (ws_root(ws)) continue; free(ws->stacker); if ((ws->stacker = calloc(mlen, sizeof(char))) == NULL) err(1, "setup_marks: calloc"); ws->stacker_len = mlen; } } void set_win_state(struct ws_win *win, uint32_t state) { uint32_t data[2] = { state, XCB_WINDOW_NONE }; DNPRINTF(SWM_D_EVENT, "win %#x, state: %s(%u)\n", WINID(win), get_wm_state_label(state), state); if (win == NULL) return; win->state = state; xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win->id, a_state, a_state, 32, 2, data); } uint32_t get_win_state(xcb_window_t w) { xcb_get_property_reply_t *r; xcb_get_property_cookie_t c; uint32_t result = 0; c = xcb_get_property(conn, 0, w, a_state, a_state, 0L, 2L); r = xcb_get_property_reply(conn, c, NULL); if (r) { if (r->type == a_state && r->format == 32 && r->length == 2) result = *((uint32_t *)xcb_get_property_value(r)); free(r); } return (result); } void version(struct swm_screen *s, struct binding *bp, union arg *args) { int i, num_screens; /* Suppress warning. */ (void)s; (void)bp; (void)args; bar_version = !bar_version; if (bar_version) snprintf(bar_vertext, sizeof bar_vertext, "Version: %s Build: %s", SPECTRWM_VERSION, buildstr); else strlcpy(bar_vertext, "", sizeof bar_vertext); num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) update_bars(&screens[i]); xcb_flush(conn); } void client_msg(struct ws_win *win, xcb_atom_t a, xcb_timestamp_t t) { xcb_client_message_event_t ev; if (win == NULL) return; DNPRINTF(SWM_D_EVENT, "win %#x, atom: %s(%u), time: %#x\n", win->id, get_atom_label(a), a, t); bzero(&ev, sizeof ev); ev.response_type = XCB_CLIENT_MESSAGE; ev.window = win->id; ev.type = a_prot; ev.format = 32; ev.data.data32[0] = a; ev.data.data32[1] = t; xcb_send_event(conn, 0, win->id, XCB_EVENT_MASK_NO_EVENT, (const char *)&ev); } /* synthetic response to a ConfigureRequest when not making a change */ void config_win(struct ws_win *win, xcb_configure_request_event_t *ev) { xcb_configure_notify_event_t ce; if (win == NULL) return; /* send notification of unchanged state. */ bzero(&ce, sizeof(ce)); ce.response_type = XCB_CONFIGURE_NOTIFY; ce.x = X(win); ce.y = Y(win); ce.width = WIDTH(win); ce.height = HEIGHT(win); ce.border_width = 0; ce.override_redirect = 0; if (ev == NULL) { /* EWMH */ ce.event = win->id; ce.window = win->id; ce.above_sibling = XCB_WINDOW_NONE; } else { /* normal */ ce.event = ev->window; ce.window = ev->window; /* make response appear more WM_SIZE_HINTS-compliant */ if (win->sh.flags) { DNPRINTF(SWM_D_MISC, "hints: win %#x, sh.flags: %u, " "min: %d x %d, max: %d x %d, inc: %d x %d\n", win->id, win->sh.flags, SH_MIN_W(win), SH_MIN_H(win), SH_MAX_W(win), SH_MAX_H(win), SH_INC_W(win), SH_INC_H(win)); } /* min size */ if (SH_MIN(win)) { /* the hint may be set... to 0! */ if (SH_MIN_W(win) > 0 && ce.width < SH_MIN_W(win)) ce.width = SH_MIN_W(win); if (SH_MIN_H(win) > 0 && ce.height < SH_MIN_H(win)) ce.height = SH_MIN_H(win); } /* max size */ if (SH_MAX(win)) { /* may also be advertized as 0 */ if (SH_MAX_W(win) > 0 && ce.width > SH_MAX_W(win)) ce.width = SH_MAX_W(win); if (SH_MAX_H(win) > 0 && ce.height > SH_MAX_H(win)) ce.height = SH_MAX_H(win); } /* resize increment. */ if (SH_INC(win)) { if (SH_INC_W(win) > 1 && ce.width > SH_INC_W(win)) ce.width -= (ce.width - SH_MIN_W(win)) % SH_INC_W(win); if (SH_INC_H(win) > 1 && ce.height > SH_INC_H(win)) ce.height -= (ce.height - SH_MIN_H(win)) % SH_INC_H(win); } /* adjust x and y for requested border_width. */ ce.x += ev->border_width; ce.y += ev->border_width; ce.above_sibling = ev->sibling; } DNPRINTF(SWM_D_MISC, "ewmh: %s, win %#x, (x,y) w x h: (%d,%d) %d x %d, " "border: %d\n", YESNO(ev == NULL), win->id, ce.x, ce.y, ce.width, ce.height, ce.border_width); xcb_send_event(conn, 0, win->id, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char *)&ce); } int count_win(struct workspace *ws, uint32_t flags) { struct ws_win *win; int count = 0; TAILQ_FOREACH(win, &ws->winlist, entry) { if (!(flags & SWM_COUNT_ICONIC) && HIDDEN(win)) continue; if (!(flags & SWM_COUNT_DESKTOP) && WINDESKTOP(win)) continue; if (!(flags & SWM_COUNT_TILED) && !win_floating(win)) continue; if (!(flags & SWM_COUNT_FLOATING) && win_floating(win)) continue; count++; } return (count); } void quit(struct swm_screen *s, struct binding *bp, union arg *args) { /* Suppress warning. */ (void)s; (void)bp; (void)args; DNPRINTF(SWM_D_MISC, "shutting down...\n"); running = 0; } void prioritize_window(struct ws_win *win) { DNPRINTF(SWM_D_STACK, "win %#x\n", win->id); TAILQ_REMOVE(&win->s->priority, win, priority_entry); TAILQ_INSERT_HEAD(&win->s->priority, win, priority_entry); } /* Updates current stacking order with all rules/priorities/etc. */ void refresh_stack(struct swm_screen *s) { struct swm_region *r; struct ws_win *w; enum swm_layer layer; DNPRINTF(SWM_D_STACK, "rebuilding stack on screen %d\n", s->idx); /* Rebuild from scratch for now. */ while (!SLIST_EMPTY(&s->stack)) SLIST_REMOVE_HEAD(&s->stack, entry); /* Start from the top. */ for (layer = SWM_LAYER_INVALID; layer; layer--) { switch (layer - 1) { case SWM_LAYER_REGION: /* Region input windows. */ TAILQ_FOREACH(r, &s->rl, entry) SLIST_INSERT_HEAD(&s->stack, r->st, entry); break; case SWM_LAYER_BAR: /* Status bar windows. */ TAILQ_FOREACH(r, &s->rl, entry) SLIST_INSERT_HEAD(&s->stack, r->bar->st, entry); break; case SWM_LAYER_TILED: /* Windows by recent focus. */ TAILQ_FOREACH(w, &s->fl, focus_entry) if (w->st->layer == layer - 1) SLIST_INSERT_HEAD(&s->stack, w->st, entry); break; default: /* Windows by priority. */ TAILQ_FOREACH(w, &s->priority, priority_entry) if (w->st->layer == layer - 1) SLIST_INSERT_HEAD(&s->stack, w->st, entry); break; } } } void update_win_layer_related(struct ws_win *win) { struct ws_win *w; /* Update main window first. */ update_win_layer(win->main); TAILQ_FOREACH(w, &win->ws->winlist, entry) if (win_related(w, win)) update_win_layer(w); } /* Caution: update main windows first before descendents. */ void update_win_layer(struct ws_win *win) { enum swm_layer layer; if (win_raised(win)) layer = SWM_LAYER_RAISED; else if (WINDESKTOP(win)) layer = SWM_LAYER_DESKTOP; else if (win_below(win)) layer = SWM_LAYER_BELOW; else if (FULLSCREEN(win)) layer = SWM_LAYER_FULLSCREEN; else if (MAXIMIZED(win)) layer = SWM_LAYER_MAXIMIZED; else if (WINDOCK(win)) layer = SWM_LAYER_DOCK; else if (win_floating(win)) layer = SWM_LAYER_ABOVE; else layer = SWM_LAYER_TILED; /* Keep descendents above main. */ if (!win_main(win) && layer < win->main->st->layer) layer = win->main->st->layer; DNPRINTF(SWM_D_MISC, "win 0x%08x, layer %d->%d\n", win->id, win->st->layer, layer); win->st->layer = layer; } void update_stackable(struct swm_stackable *st, struct swm_stackable *st_sib) { uint32_t val[2]; if (st == NULL) return; val[0] = st_sib ? st_window_id(st_sib) : st->s->swmwin; val[1] = XCB_STACK_MODE_ABOVE; DNPRINTF(SWM_D_STACK, "win:%#x sibling:%#x\n", st_window_id(st), val[0]); xcb_configure_window(conn, st_window_id(st), XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, val); } void map_window(struct ws_win *win) { if (win == NULL) return; DNPRINTF(SWM_D_EVENT, "win %#x, mapped: %s\n", win->id, YESNO(win->mapped)); if (!win_reparented(win)) { DNPRINTF(SWM_D_EVENT, "skip win %#x; not reparented\n", win->id); return; } if (win->mapped) return; xcb_map_window(conn, win->frame); xcb_map_window(conn, win->id); if (win->debug != XCB_WINDOW_NONE) xcb_map_window(conn, win->debug); win->mapping += 2; win->mapped = true; set_win_state(win, XCB_ICCCM_WM_STATE_NORMAL); } void unmap_window(struct ws_win *win) { if (win == NULL) return; DNPRINTF(SWM_D_EVENT, "win %#x, mapped: %s\n", win->id, YESNO(win->mapped)); if (!win_reparented(win)) { DNPRINTF(SWM_D_EVENT, "skip win %#x; not reparented\n", win->id); return; } if (!win->mapped) return; if (win->debug != XCB_WINDOW_NONE) xcb_unmap_window(conn, win->debug); xcb_unmap_window(conn, win->id); xcb_unmap_window(conn, win->frame); win->unmapping += 2; win->mapped = false; set_win_state(win, XCB_ICCCM_WM_STATE_ICONIC); } void fake_keypress(struct ws_win *win, xcb_keysym_t keysym, uint16_t modifiers) { xcb_key_press_event_t event; xcb_keycode_t *keycode; if (win == NULL) return; keycode = xcb_key_symbols_get_keycode(syms, keysym); DNPRINTF(SWM_D_MISC, "win %#x, keycode %u\n", win->id, *keycode); bzero(&event, sizeof(event)); event.event = win->id; event.root = win->s->root; event.child = XCB_WINDOW_NONE; event.time = XCB_CURRENT_TIME; event.event_x = X(win); event.event_y = Y(win); event.root_x = 1; event.root_y = 1; event.same_screen = 1; event.detail = *keycode; event.state = modifiers; event.response_type = XCB_KEY_PRESS; xcb_send_event(conn, 1, win->id, XCB_EVENT_MASK_KEY_PRESS, (const char *)&event); event.response_type = XCB_KEY_RELEASE; xcb_send_event(conn, 1, win->id, XCB_EVENT_MASK_KEY_RELEASE, (const char *)&event); free(keycode); } void restart(struct swm_screen *s, struct binding *bp, union arg *args) { /* Suppress warning. */ (void)s; (void)bp; DNPRINTF(SWM_D_MISC, "%s\n", start_argv[0]); shutdown_cleanup(); if (args && args->id == SWM_ARG_ID_RESTARTOFDAY) unsetenv("SWM_STARTED"); execvp(start_argv[0], start_argv); warn("execvp failed"); quit(NULL, NULL, NULL); } static bool follow_pointer(struct swm_screen *s) { bool result; result = (focus_mode == SWM_FOCUS_FOLLOW && s->r_focus && !ws_maponfocus(s->r_focus->ws) && s->r_focus == get_pointer_region(s)); DNPRINTF(SWM_D_FOCUS, "%s\n", YESNO(result)); return (result); } struct swm_region * get_pointer_region(struct swm_screen *s) { struct swm_region *r = NULL; struct ws_win *w; xcb_query_pointer_reply_t *qpr; if (s == NULL) return (NULL); qpr = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, s->root), NULL); if (qpr) { w = find_window(qpr->child); if (w && !win_free(w) && w->ws->r) { pointer_window = qpr->child; r = w->ws->r; } else { DNPRINTF(SWM_D_MISC, "pointer: (%d,%d)\n", qpr->root_x, qpr->root_y); r = region_under(s, qpr->root_x, qpr->root_y); } free(qpr); } return (r); } struct ws_win * get_pointer_win(struct swm_screen *s) { struct ws_win *win = NULL; xcb_query_pointer_reply_t *qpr; qpr = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, s->root), NULL); if (qpr) { win = find_window(qpr->child); if (win) pointer_window = qpr->child; free(qpr); } return (win); } void center_pointer(struct swm_region *r) { struct ws_win *win; xcb_window_t dwinid; int dx, dy; #ifdef SWM_XCB_HAS_XINPUT xcb_input_xi_get_client_pointer_reply_t *gcpr; #endif if (!warp_pointer || r == NULL) return; win = r->ws->focus; DNPRINTF(SWM_D_EVENT, "win %#x\n", WINID(win)); if (win && win->mapped) { dwinid = win->frame; dx = WIDTH(win) / 2; dy = HEIGHT(win) / 2; } else { dwinid = r->id; dx = WIDTH(r) / 2; dy = HEIGHT(r) / 2; } #ifdef SWM_XCB_HAS_XINPUT if (xinput2_support) { gcpr = xcb_input_xi_get_client_pointer_reply(conn, xcb_input_xi_get_client_pointer(conn, XCB_NONE), NULL); if (gcpr) /* XIWarpPointer takes FP1616. */ xcb_input_xi_warp_pointer(conn, XCB_NONE, dwinid, 0, 0, 0, 0, dx << 16, dy << 16, gcpr->deviceid); } else { #endif xcb_warp_pointer(conn, XCB_NONE, dwinid, 0, 0, 0, 0, dx, dy); #ifdef SWM_XCB_HAS_XINPUT } #endif } xcb_window_t get_input_focus(void) { xcb_window_t id = XCB_WINDOW_NONE; xcb_get_input_focus_reply_t *gifr; gifr = xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL); if (gifr) { if (gifr->focus != XCB_INPUT_FOCUS_POINTER_ROOT) id = gifr->focus; free(gifr); } return (id); } struct swm_region * get_current_region(struct swm_screen *s) { struct swm_region *r; struct ws_win *w; if (s == NULL) errx(1, "missing screen."); /* 1. Focused region */ r = s->r_focus; if (r == NULL) { /* 2. Region of window with input focus. */ w = find_window(get_input_focus()); if (w && w->ws) r = w->ws->r; if (r == NULL) { /* 3. Region with pointer. */ r = get_pointer_region(s); if (r == NULL) /* 4. Default. */ r = TAILQ_FIRST(&s->rl); } } DNPRINTF(SWM_D_MISC, "idx: %d\n", get_region_index(r)); return (r); } struct swm_region * find_region(xcb_window_t id) { struct swm_region *r; int i, num_screens; if (id == XCB_WINDOW_NONE) return (NULL); num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) TAILQ_FOREACH(r, &screens[i].rl, entry) if (r->id == id) return (r); return (NULL); } struct swm_screen * find_screen(xcb_window_t id) { int i, num_screens; if (id == XCB_WINDOW_NONE) return (NULL); num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) if (screens[i].root == id) return (&screens[i]); return (NULL); } struct swm_bar * find_bar(xcb_window_t id) { struct swm_region *r; int i, num_screens; if (id == XCB_WINDOW_NONE) return (NULL); num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) TAILQ_FOREACH(r, &screens[i].rl, entry) if (r->bar && r->bar->id == id) return (r->bar); return (NULL); } struct ws_win * find_window(xcb_window_t id) { struct ws_win *win = NULL; int i, num_screens; xcb_query_tree_reply_t *qtr; DNPRINTF(SWM_D_MISC, "id: %#x\n", id); if (id == XCB_WINDOW_NONE) return (NULL); num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) TAILQ_FOREACH(win, &screens[i].managed, manage_entry) if (id == win->id || id == win->frame) return (win); /* If window isn't top-level, try to find managed ancestor. */ qtr = xcb_query_tree_reply(conn, xcb_query_tree(conn, id), NULL); if (qtr) { if (qtr->parent != XCB_WINDOW_NONE && qtr->parent != qtr->root) win = find_window(qtr->parent); if (win) DNPRINTF(SWM_D_MISC, "%#x is decendent of %#x.\n", id, qtr->parent); free(qtr); } if (win == NULL) DNPRINTF(SWM_D_MISC, "unmanaged.\n"); return (win); } void spawn(int ws_idx, union arg *args, bool close_fd) { int fd; char *ret = NULL; if (args == NULL || args->argv[0] == NULL) return; DNPRINTF(SWM_D_MISC, "%s\n", args->argv[0]); close(xcb_get_file_descriptor(conn)); if ((ret = getenv("LD_PRELOAD"))) { if (asprintf(&ret, "%s:%s", SWM_LIB, ret) == -1) { warn("spawn: asprintf LD_PRELOAD"); _exit(1); } setenv("LD_PRELOAD", ret, 1); free(ret); } else { setenv("LD_PRELOAD", SWM_LIB, 1); } if (asprintf(&ret, "%d", ws_idx) == -1) { warn("spawn: asprintf SWM_WS"); _exit(1); } setenv("_SWM_WS", ret, 1); free(ret); ret = NULL; if (asprintf(&ret, "%d", getpid()) == -1) { warn("spawn: asprintf _SWM_PID"); _exit(1); } setenv("_SWM_PID", ret, 1); free(ret); ret = NULL; if (setsid() == -1) { warn("spawn: setsid"); _exit(1); } if (close_fd) { /* * close stdin and stdout to prevent interaction between apps * and the baraction script * leave stderr open to record errors */ if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) == -1) { warn("spawn: open"); _exit(1); } dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); if (fd > 2) close(fd); } if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) err(1, "could not reset SIGPIPE"); execvp(args->argv[0], args->argv); warn("spawn: execvp"); _exit(1); } /* Cleanup all traces of a (possibly invalid) window pointer. */ void kill_refs(struct ws_win *win) { struct workspace *ws; struct ws_win *w; int i, num_screens; if (win == NULL) return; num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) { if (screens[i].focus == win) screens[i].focus = NULL; RB_FOREACH(ws, workspace_tree, &screens[i].workspaces) { if (win == ws->focus) ws->focus = NULL; if (win == ws->focus_raise) ws->focus_raise = NULL; } TAILQ_FOREACH(w, &screens[i].managed, manage_entry) { if (win == w->focus_redirect) w->focus_redirect = NULL; if (win == w->parent) { w->parent = NULL; w->main = w; } if (win == w->main) w->main = find_main_window(w); } } } /* Check if window pointer is still valid. */ int validate_win(struct ws_win *testwin) { struct ws_win *win; int i, num_screens; if (testwin == NULL) return (0); num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) TAILQ_FOREACH(win, &screens[i].managed, manage_entry) if (win == testwin) return (0); return (1); } /* Check if workspace pointer is still valid. */ int validate_ws(struct workspace *testws) { struct workspace *ws; int i, num_screens; num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) RB_FOREACH(ws, workspace_tree, &screens[i].workspaces) if (ws == testws) return (0); return (1); } void unfocus_win(struct ws_win *win) { bool raise = false; DNPRINTF(SWM_D_FOCUS, "win %#x\n", WINID(win)); if (win == NULL) return; if (!win->mapped) { DNPRINTF(SWM_D_FOCUS, "unmapped\n"); return; } if (validate_win(win)) { DNPRINTF(SWM_D_FOCUS, "invalid win\n"); kill_refs(win); return; } if (win->s->focus == win) win->s->focus = NULL; if (win->ws) { if (validate_ws(win->ws)) { DNPRINTF(SWM_D_FOCUS, "invalid ws\n"); return; } raise = win->ws->always_raise; if (win->ws->focus == win) { win->ws->focus = NULL; if (win->ws->focus_raise == win) { win->ws->focus_raise = NULL; raise = true; } } if (validate_win(win->ws->focus)) { kill_refs(win->ws->focus); win->ws->focus = NULL; } if (raise) { update_win_layer(win); refresh_stack(win->s); update_stacking(win->s); } } /* Update border width */ if (win->bordered && (win->quirks & SWM_Q_MINIMALBORDER) && win_floating(win)) { win->bordered = false; update_gravity(win); X(win) += border_width; Y(win) += border_width; update_window(win); } draw_frame(win); DNPRINTF(SWM_D_FOCUS, "done\n"); } static bool accepts_focus(struct ws_win *win) { return (!(win->hints.flags & XCB_ICCCM_WM_HINT_INPUT) || win->hints.input); } static bool win_noinput(struct ws_win *win) { return (!accepts_focus(win) && !win->take_focus); } void focus_win_input(struct ws_win *win, bool force_input) { /* Set input focus if no input hint, or indicated by hint. */ if (accepts_focus(win)) set_input_focus(win->id, force_input); else set_input_focus(win->frame, false); /* Tell app it can adjust focus to a specific window. */ if (win->take_focus) client_msg(win, a_takefocus, event_time); } void set_input_focus(xcb_window_t winid, bool force) { if (force) { DNPRINTF(SWM_D_FOCUS, "SetInputFocus: %#x, revert-to: " "PointerRoot, time: CurrentTime\n", winid); xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, winid, XCB_CURRENT_TIME); } else { DNPRINTF(SWM_D_FOCUS, "SetInputFocus: %#x, revert-to: " "PointerRoot, time: %#x\n", winid, event_time); xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, winid, event_time); } } /* Focus a window all in one go. */ void focus_win(struct swm_screen *s, struct ws_win *win) { struct ws_win *w; DNPRINTF(SWM_D_FOCUS, "win %#x\n", WINID(win)); if (validate_win(win)) { kill_refs(win); win = NULL; } set_focus(s, win); if (win) update_win_layer_related(win); refresh_stack(s); update_stacking(s); update_mapping(s); update_focus(s); /* Redraw all frames for now. */ TAILQ_FOREACH(w, &s->managed, manage_entry) if (w->mapped) draw_frame(w); DNPRINTF(SWM_D_FOCUS, "done\n"); } /* Apply follow mode focus policy. */ void focus_follow(struct swm_screen *s, struct swm_region *r, struct ws_win *win) { struct swm_region *rr; /* Try to focus pointer, otherwise window, otherwise region. */ if (focus_mode != SWM_FOCUS_FOLLOW) return; if (win && (validate_win(win) || win->ws->r != r)) win = NULL; if (r && !ws_maponfocus(r->ws) && (rr = get_pointer_region(s)) && (rr == r || rr == s->r)) { if (pointer_window != XCB_WINDOW_NONE) focus_window(pointer_window); else if (r->ws->focus == NULL) focus_win(s, win ? get_focus_magic(win) : get_focus_magic(get_ws_focus(r->ws))); } else if (win) { focus_win(s, get_focus_magic(win)); } else if (r) focus_win(s, get_focus_magic(get_ws_focus(r->ws))); xcb_flush(conn); } void grab_buttons_win(xcb_window_t win) { struct binding *bp; int i; uint16_t modifiers[4]; if (win == XCB_WINDOW_NONE) return; modifiers[0] = 0; modifiers[1] = numlockmask; modifiers[2] = XCB_MOD_MASK_LOCK; modifiers[3] = numlockmask | XCB_MOD_MASK_LOCK; xcb_ungrab_button(conn, XCB_BUTTON_INDEX_ANY, win, XCB_MOD_MASK_ANY); RB_FOREACH(bp, binding_tree, &bindings) { if (bp->type != BTNBIND) continue; if (xinput2_raw && bp->flags & BINDING_F_REPLAY) continue; /* When binding ws actions, skip unused workspaces. */ if ((int)bp->action > FN_WS_1 + workspace_limit - 1 && bp->action <= FN_WS_22) continue; if ((int)bp->action > FN_MVWS_1 + workspace_limit - 1 && bp->action <= FN_MVWS_22) continue; if (bp->mod == XCB_MOD_MASK_ANY) { /* Grab ANYMOD case. */ DNPRINTF(SWM_D_MOUSE, "grab btn: %u, modmask: %d, " "win: %#x\n", bp->value, bp->mod, win); xcb_grab_button(conn, 0, win, BUTTONMASK, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, XCB_WINDOW_NONE, XCB_CURSOR_NONE, bp->value, bp->mod); } else { /* Need to grab each modifier permutation. */ for (i = 0; i < LENGTH(modifiers); ++i) { DNPRINTF(SWM_D_MOUSE, "grab btn: %u, " "modmask: %u\n", bp->value, bp->mod | modifiers[i]); xcb_grab_button(conn, 0, win, BUTTONMASK, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, XCB_WINDOW_NONE, XCB_CURSOR_NONE, bp->value, bp->mod | modifiers[i]); } } } } /* If a transient window should have focus instead, return it. */ struct ws_win * get_focus_magic(struct ws_win *win) { struct ws_win *winfocus = NULL; int i, wincount; DNPRINTF(SWM_D_FOCUS, "win %#x\n", WINID(win)); if (win == NULL) return (win); winfocus = win->main; if (winfocus->focus_redirect == NULL) return (winfocus); /* Put limit just in case a redirect loop exists. */ wincount = count_win(win->ws, SWM_COUNT_NORMAL); for (i = 0; i < wincount; ++i) { if (winfocus->focus_redirect == NULL) break; if (HIDDEN(winfocus->focus_redirect)) break; if (validate_win(winfocus->focus_redirect)) break; winfocus = winfocus->focus_redirect; } return (winfocus); } void set_focus(struct swm_screen *s, struct ws_win *win) { struct ws_win *w; DNPRINTF(SWM_D_FOCUS, "screen %d, win: %#x\n", s->idx, WINID(win)); if (win == NULL) { s->focus = NULL; return; } if (win->s != s) return; TAILQ_REMOVE(&s->fl, win, focus_entry); TAILQ_INSERT_HEAD(&s->fl, win, focus_entry); if ((w = win->ws->focus) != win) { win->ws->focus = win; win->ws->focus_raise = NULL; if (w) update_win_layer(w); if (win->ws->always_raise) update_win_layer(win); } s->focus = win; set_focus_redirect(win); } void set_focus_prev(struct ws_win *win) { struct ws_win *w; DNPRINTF(SWM_D_FOCUS, "win: %#x\n", WINID(win)); if (win == NULL || win->ws == NULL) return; if (win->ws->focus) { w = TAILQ_FIRST(&win->s->fl); if (win != w) { TAILQ_REMOVE(&win->s->fl, win, focus_entry); TAILQ_INSERT_AFTER(&win->s->fl, w, win, focus_entry); } } else { TAILQ_REMOVE(&win->s->fl, win, focus_entry); TAILQ_INSERT_HEAD(&win->s->fl, win, focus_entry); } } void update_focus(struct swm_screen *s) { struct ws_win *win, *cfw = NULL; xcb_get_window_attributes_reply_t *war = NULL; xcb_window_t cfid; if (s == NULL) return; /* 1. Check if a managed window has focus right now. */ cfid = get_input_focus(); cfw = find_window(cfid); /* 2. Get currently set focus, if any. */ if (s->focus) win = s->focus; else if (s->r_focus) win = get_focus_magic(get_ws_focus(s->r_focus->ws)); else win = NULL; DNPRINTF(SWM_D_FOCUS, "win: %#x, cfw: %#x, sfocus: %#x\n", WINID(win), WINID(cfw), WINID(s->focus)); /* Skip reenter from override-redirect. */ if (cfw == NULL && cfid != XCB_WINDOW_NONE) { war = xcb_get_window_attributes_reply(conn, xcb_get_window_attributes(conn, cfid), NULL); if (war && war->override_redirect && (win == NULL || win->id == s->active_window)) { DNPRINTF(SWM_D_FOCUS, "skip refocus from " "override_redirect.\n"); free(war); return; } free(war); } /* 3. Handle current focus. */ if (cfw && cfw != win) { if (cfw->mapped && win_reparented(cfw) && (win == NULL || (cfw->ws != win->ws && !ws_root(cfw->ws) && !ws_root(win->ws)))) draw_frame(cfw); else unfocus_win(cfw); } /* 4. Set current focus and unfocus existing focus. */ if (s->focus != win) { if (s->focus && s->focus != cfw) unfocus_win(s->focus); set_focus(s, win); } if (win && DEMANDS_ATTENTION(win)) clear_attention(win); /* 5. Set input focus. */ if (win && cfw == win) return; /* Already has input focus. */ if (win && win->mapped) { focus_win_input(((win_noinput(win) && win_transient(win)) ? win->main : win), false); set_region(win->ws->r); } else set_input_focus((s->r_focus ? s->r_focus->id : s->swmwin), true); draw_frame(win); update_bars(s); ewmh_update_active_window(s); } void set_region(struct swm_region *r) { struct swm_region *rf; int vals[2]; if (r == NULL) return; if (rg_root(r)) return; rf = r->s->r_focus; if (rf && rf == r) return; if (rf != NULL && rf != r && (X(rf) != X(r) || Y(rf) != Y(r) || WIDTH(rf) != WIDTH(r) || HEIGHT(rf) != HEIGHT(r))) { /* Set _NET_DESKTOP_GEOMETRY. */ vals[0] = WIDTH(r); vals[1] = HEIGHT(r); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, r->s->root, ewmh[_NET_DESKTOP_GEOMETRY].atom, XCB_ATOM_CARDINAL, 32, 2, &vals); } r->s->r_focus = r; /* Update the focus window frame on the now unfocused region. */ if (rf && rf->ws->focus) draw_frame(rf->ws->focus); ewmh_update_current_desktop(r->s); } void focus_region(struct swm_region *r) { struct ws_win *nfw; struct swm_region *old_r; if (r == NULL) return; nfw = get_focus_magic(get_ws_focus(r->ws)); if (nfw) { focus_win(nfw->s, nfw); } else { old_r = r->s->r_focus; set_region(r); /* New region is empty; need to manually unfocus win. */ if (old_r && old_r != r) { unfocus_win(old_r->ws->focus); /* Clear bar since empty. */ bar_draw(old_r->bar); } set_input_focus(r->id, true); ewmh_update_active_window(r->s); } if (apply_unfocus(r->s->r->ws, NULL)) { refresh_stack(r->s); update_stacking(r->s); stack(r->s->r); update_mapping(r->s); } } #define SWM_MODE_VFLIP (1 << 0) #define SWM_MODE_HFLIP (1 << 1) #define SWM_MODE_NORMAL (0) #define SWM_MODE_LEFT (SWM_MODE_VFLIP) #define SWM_MODE_INVERTED (SWM_MODE_VFLIP | SWM_MODE_HFLIP) #define SWM_MODE_RIGHT (SWM_MODE_HFLIP) struct rotation_mode { uint16_t r; uint8_t mode; } rotation_map[] = { { XCB_RANDR_ROTATION_ROTATE_0, SWM_MODE_NORMAL }, { XCB_RANDR_ROTATION_ROTATE_90, SWM_MODE_LEFT }, { XCB_RANDR_ROTATION_ROTATE_180, SWM_MODE_INVERTED }, { XCB_RANDR_ROTATION_ROTATE_270, SWM_MODE_RIGHT }, }; void rotatews(struct workspace *ws, uint16_t rot) { int i, j, d; uint8_t mode = 0; if (ws->cur_layout != &layouts[SWM_V_STACK] && ws->cur_layout != &layouts[SWM_H_STACK]) return; /* Get current mode. */ mode = (ws->l_state.vertical_flip ? SWM_MODE_VFLIP : 0) | (ws->l_state.horizontal_flip ? SWM_MODE_HFLIP : 0); /* Get offset to new rotation. */ d = 0; for (i = 0; i < LENGTH(rotation_map); i++) if (rotation_map[i].r == ws->rotation) { for (j = 0; j < LENGTH(rotation_map); j++) { if (rotation_map[(i + j) % LENGTH(rotation_map)].r == rot) { d = j; break; } } break; } /* Apply offset to current flip. */ for (i = 0; i < LENGTH(rotation_map); i++) if (rotation_map[i].mode == mode) { mode = rotation_map[(i + d) % LENGTH(rotation_map)].mode; break; } /* Swap layout if rotation axis changed. */ if ((ws->rotation & ROTATION_VERT) != (rot & ROTATION_VERT)) ws->cur_layout = (ws->cur_layout == &layouts[SWM_V_STACK] ? &layouts[SWM_H_STACK] : &layouts[SWM_V_STACK]); /* Set new mode. */ ws->l_state.vertical_flip = (mode & SWM_MODE_VFLIP); ws->l_state.horizontal_flip = (mode & SWM_MODE_HFLIP); ws->rotation = rot; } void switch_workspace(struct swm_region *r, struct workspace *ws, bool noclamp) { struct swm_screen *s; struct swm_region *other_r; struct workspace *old_ws; struct ws_win *nfw; if (r == NULL || ws == NULL) return; if (rg_root(r) || ws_root(ws)) return; s = r->s; old_ws = r->ws; if (old_ws == ws) { if (win_free(s->focus)) { set_focus(s, get_focus_magic(get_ws_focus(ws))); apply_unfocus(s->r->ws, NULL); refresh_stack(s); update_stacking(s); update_mapping(s); update_focus(s); center_pointer(r); flush(); } return; } DNPRINTF(SWM_D_WS, "screen[%d]:%dx%d+%d+%d: %d -> %d\n", s->idx, WIDTH(r), HEIGHT(r), X(r), Y(r), (old_ws ? old_ws->idx : -2), ws->idx); other_r = ws->r; if (other_r && workspace_clamp && !noclamp) { DNPRINTF(SWM_D_WS, "ws clamped.\n"); if (warp_focus) { DNPRINTF(SWM_D_WS, "warping focus to region " "with ws %d\n", ws->idx); focus_region(other_r); center_pointer(other_r); flush(); } return; } if (other_r) { /* The other ws is visible in another region, exchange them. */ other_r->ws_prior = ws; other_r->ws = old_ws; old_ws->r = other_r; if (workspace_autorotate) rotatews(old_ws, ROTATION(other_r)); } else /* The other workspace is hidden, hide this one. */ old_ws->r = NULL; if (workspace_autorotate) rotatews(ws, ROTATION(r)); /* Attach new ws. */ r->ws_prior = old_ws; r->ws = ws; ws->r = r; /* Prepare focus. */ nfw = get_focus_magic(get_ws_focus(ws)); set_focus(s, nfw); if (apply_unfocus(s->r->ws, NULL)) stack(s->r); apply_unfocus(ws, nfw); refresh_stack(s); update_stacking(s); if (refresh_strut(s)) update_layout(s); else { stack(other_r); stack(r); } update_mapping(s); /* Unmap old windows. */ if (old_ws->r == NULL) unmap_workspace(old_ws); bar_draw(r->bar); if (other_r) bar_draw(other_r->bar); ewmh_update_current_desktop(s); ewmh_update_desktops(s); if (focus_mode != SWM_FOCUS_FOLLOW) update_focus(s); center_pointer(r); flush(); focus_follow(s, r, NULL); DNPRINTF(SWM_D_WS, "done\n"); } void switchws(struct swm_screen *s, struct binding *bp, union arg *args) { struct swm_region *r; struct workspace *ws; /* Suppress warning. */ (void)bp; ws = get_workspace(s, args->id); if (ws == NULL) return; r = get_current_region(s); switch_workspace(r, ws, false); DNPRINTF(SWM_D_WS, "done\n"); } void cyclews(struct swm_screen *s, struct binding *bp, union arg *args) { struct swm_region *r, *rr; struct workspace *ws, *nws = NULL; struct ws_win *winfocus; int i; bool allowempty = false, mv = false; /* Suppress warning. */ (void)bp; if ((r = get_current_region(s)) == NULL) return; ws = r->ws; winfocus = ws->focus; DNPRINTF(SWM_D_WS, "id: %d, screen[%d]:%dx%d+%d+%d, ws: %d\n", args->id, s->idx, WIDTH(r), HEIGHT(r), X(r), Y(r), ws->idx); for (i = 0; i < workspace_limit; ++i) { switch (args->id) { case SWM_ARG_ID_CYCLEWS_MOVE_UP: mv = true; /* FALLTHROUGH */ case SWM_ARG_ID_CYCLEWS_UP_ALL: allowempty = true; /* FALLTHROUGH */ case SWM_ARG_ID_CYCLEWS_UP: nws = get_workspace(s, (ws->idx + i + 1) % workspace_limit); break; case SWM_ARG_ID_CYCLEWS_MOVE_DOWN: mv = true; /* FALLTHROUGH */ case SWM_ARG_ID_CYCLEWS_DOWN_ALL: allowempty = true; /* FALLTHROUGH */ case SWM_ARG_ID_CYCLEWS_DOWN: nws = get_workspace(s, (workspace_limit + ws->idx - i - 1) % workspace_limit); break; default: return; }; if (nws == NULL) return; /* Shouldn't happen. */ DNPRINTF(SWM_D_WS, "curws: %d, nws: %d, allowempty: %d, mv: %d," " cycle_visible: %d, cycle_empty: %d\n", ws->idx, nws->idx, allowempty, mv, cycle_visible, cycle_empty); if (!allowempty && !cycle_empty && TAILQ_EMPTY(&nws->winlist)) continue; if (!cycle_visible && nws->r) continue; /* New workspace found. */ break; } if (nws && nws != ws) { if (mv && winfocus) { /* Move window to new ws without unmapping. */ win_to_ws(winfocus, nws, SWM_WIN_NOUNMAP); rr = nws->r; if (rr) { if (!workspace_clamp) { /* Swap workspaces */ r->ws_prior = ws; r->ws = nws; nws->r = r; rr->ws_prior = nws; rr->ws = ws; ws->r = rr; } /* else leave ws on other region. */ } else { nws->r = r; r->ws = nws; r->ws_prior = ws; ws->r = NULL; } /* Hot set new focus. */ prioritize_window(winfocus); draw_frame(get_ws_focus_prev(nws)); if (nws->r != r) { set_region(nws->r); draw_frame(ws->focus); } apply_unfocus(nws, NULL); refresh_stack(r->s); update_stacking(s); if (rr) stack(rr); else unmap_workspace(ws); stack(r); update_mapping(s); ewmh_update_current_desktop(r->s); center_pointer(nws->r); flush(); } else { switch_workspace(r, nws, false); } } DNPRINTF(SWM_D_FOCUS, "done\n"); } void unmap_workspace(struct workspace *ws) { struct ws_win *w; if (ws == NULL) return; TAILQ_FOREACH(w, &ws->winlist, entry) unmap_window(w); } void emptyws(struct swm_screen *s, struct binding *bp, union arg *args) { struct swm_region *r; struct workspace *ws = NULL; struct ws_win *win; int i; /* Suppress warning. */ (void)bp; if ((r = get_current_region(s)) == NULL) return; DNPRINTF(SWM_D_WS, "id: %d, screen[%d]:%dx%d+%d+%d, ws: %d\n", args->id, s->idx, WIDTH(r), HEIGHT(r), X(r), Y(r), r->ws->idx); /* Find first empty ws. */ for (i = 0; i < workspace_limit; ++i) { ws = get_workspace(s, i); if (TAILQ_EMPTY(&ws->winlist)) break; } if (ws == NULL) { DNPRINTF(SWM_D_FOCUS, "no empty ws.\n"); return; } switch (args->id) { case SWM_ARG_ID_WS_EMPTY_MOVE: win = s->focus; if (win == NULL || win_free(win)) return; transfer_win(win, ws); /* FALLTHROUGH */ case SWM_ARG_ID_WS_EMPTY: switch_workspace(r, ws, false); break; default: DNPRINTF(SWM_D_FOCUS, "invalid id: %d\n", args->id); } DNPRINTF(SWM_D_FOCUS, "done\n"); } void priorws(struct swm_screen *s, struct binding *bp, union arg *args) { struct swm_region *r; /* Suppress warning. */ (void)bp; (void)args; if ((r = get_current_region(s)) == NULL) return; DNPRINTF(SWM_D_WS, "id: %d, screen[%d]:%dx%d+%d+%d, ws: %d\n", args->id, s->idx, WIDTH(r), HEIGHT(r), X(r), Y(r), r->ws->idx); if (r->ws_prior == NULL) return; switch_workspace(r, r->ws_prior, false); DNPRINTF(SWM_D_FOCUS, "done\n"); } void focusrg(struct swm_screen *s, struct binding *bp, union arg *args) { struct swm_region *r; /* Suppress warning. */ (void)bp; r = get_region(s, args->id); if (r == NULL || s->r_focus == r) return; focus_region(r); center_pointer(r); flush(); DNPRINTF(SWM_D_FOCUS, "done\n"); } void cyclerg(struct swm_screen *s, struct binding *bp, union arg *args) { struct swm_region *r, *rr = NULL; int num_screens; /* Suppress warning. */ (void)bp; if ((r = get_current_region(s)) == NULL) return; num_screens = get_screen_count(); /* do nothing if we don't have more than one screen */ if (!(num_screens > 1 || outputs > 1)) return; DNPRINTF(SWM_D_FOCUS, "id: %d, region: %d\n", args->id, get_region_index(r)); switch (args->id) { case SWM_ARG_ID_CYCLERG_UP: case SWM_ARG_ID_CYCLERG_MOVE_UP: rr = TAILQ_NEXT(r, entry); if (rr == NULL) rr = TAILQ_FIRST(&s->rl); break; case SWM_ARG_ID_CYCLERG_DOWN: case SWM_ARG_ID_CYCLERG_MOVE_DOWN: rr = TAILQ_PREV(r, swm_region_list, entry); if (rr == NULL) rr = TAILQ_LAST(&s->rl, swm_region_list); break; default: return; }; if (rr == NULL) return; switch (args->id) { case SWM_ARG_ID_CYCLERG_UP: case SWM_ARG_ID_CYCLERG_DOWN: focus_region(rr); center_pointer(rr); flush(); break; case SWM_ARG_ID_CYCLERG_MOVE_UP: case SWM_ARG_ID_CYCLERG_MOVE_DOWN: switch_workspace(r, rr->ws, true); break; default: return; }; DNPRINTF(SWM_D_FOCUS, "done\n"); } void swapwin(struct swm_screen *s, struct binding *bp, union arg *args) { struct swm_region *r; struct ws_win *w, *sw, *pw; struct ws_win_list *wl; /* Suppress warning. */ (void)bp; if (s->focus && win_free(s->focus)) return; if ((r = get_current_region(s)) == NULL) return; DNPRINTF(SWM_D_WS, "id: %d, screen[%d]:%dx%d+%d+%d, ws: %d\n", args->id, s->idx, WIDTH(r), HEIGHT(r), X(r), Y(r), r->ws->idx); sw = r->ws->focus; if (sw == NULL || FULLSCREEN(sw)) return; if (ws_maxstack(r->ws)) return; if (apply_unfocus(r->ws, NULL) > 0) refresh_stack(r->s); wl = &sw->ws->winlist; switch (args->id) { case SWM_ARG_ID_SWAPPREV: w = sw = sw->main; /* Find prev 'main' */ while ((w = TAILQ_PREV(w, ws_win_list, entry))) if (!HIDDEN(w) && win_main(w)) break; TAILQ_REMOVE(wl, sw, entry); if (w == NULL) TAILQ_INSERT_TAIL(wl, sw, entry); else TAILQ_INSERT_BEFORE(w, sw, entry); break; case SWM_ARG_ID_SWAPNEXT: w = sw = sw->main; /* Find next 'main' */ while ((w = TAILQ_NEXT(w, entry))) if (!HIDDEN(w) && win_main(w)) break; TAILQ_REMOVE(wl, sw, entry); if (w == NULL) TAILQ_INSERT_HEAD(wl, sw, entry); else TAILQ_INSERT_AFTER(wl, w, sw, entry); break; case SWM_ARG_ID_SWAPMAIN: sw = sw->main; w = get_main_window(r->ws); if (w) { w = w->main; if (w == sw) { sw = get_ws_focus_prev(r->ws); if (sw) sw = sw->main; } } if (w == NULL || sw == NULL || w->ws != sw->ws) return; pw = TAILQ_PREV(w, ws_win_list, entry); set_focus_prev(w); TAILQ_REMOVE(wl, w, entry); TAILQ_INSERT_BEFORE(sw, w, entry); TAILQ_REMOVE(wl, sw, entry); if (pw) TAILQ_INSERT_AFTER(wl, pw, sw, entry); else TAILQ_INSERT_HEAD(wl, sw, entry); break; default: DNPRINTF(SWM_D_MOVE, "invalid id: %d\n", args->id); return; } ewmh_update_client_list(s); update_stacking(s); stack(r); center_pointer(r); flush(); DNPRINTF(SWM_D_MOVE, "done\n"); } /* Determine focus other than specified window, but on the same workspace. */ struct ws_win * get_focus_other(struct ws_win *win) { struct ws_win *w, *winfocus = NULL; struct ws_win_list *wl = NULL; struct workspace *ws = NULL; if (!(win && win->ws)) goto done; ws = win->ws; wl = &ws->winlist; DNPRINTF(SWM_D_FOCUS, "win %#x, focus: %#x, focus_prev: %#x, " "focus_close: %d, focus_close_wrap: %d, focus_default: %d\n", WINID(win), WINID(ws->focus), WINID(get_ws_focus_prev(ws)), focus_close, focus_close_wrap, focus_default); if (ws->focus == NULL) { /* Fallback to default. */ if (focus_default == SWM_STACK_TOP) { TAILQ_FOREACH_REVERSE(winfocus, wl, ws_win_list, entry) if (!HIDDEN(winfocus) && winfocus != win) break; } else { TAILQ_FOREACH(winfocus, wl, entry) if (!HIDDEN(winfocus) && winfocus != win) break; } goto done; } if (ws->focus != win && !HIDDEN(ws->focus)) { winfocus = ws->focus; goto done; } /* FOCUSPREV quirk: try previously focused window. */ if (win->quirks & SWM_Q_FOCUSPREV) { winfocus = get_ws_focus_prev(ws); if (winfocus) goto done; } if (ws->focus == win && win_transient(win)) { w = win->parent; if (w && w != win && w->ws == ws && !HIDDEN(w)) { winfocus = w; goto done; } } if (ws->cur_layout->flags & SWM_L_FOCUSPREV) { winfocus = get_ws_focus_prev(ws); if (winfocus) goto done; } switch (focus_close) { case SWM_STACK_BOTTOM: TAILQ_FOREACH(winfocus, wl, entry) if (!HIDDEN(winfocus) && winfocus != win) break; break; case SWM_STACK_TOP: TAILQ_FOREACH_REVERSE(winfocus, wl, ws_win_list, entry) if (!HIDDEN(winfocus) && winfocus != win) break; break; case SWM_STACK_ABOVE: winfocus = TAILQ_NEXT(win, entry); while (winfocus && HIDDEN(winfocus)) winfocus = TAILQ_NEXT(winfocus, entry); if (winfocus == NULL) { if (focus_close_wrap) { TAILQ_FOREACH(winfocus, wl, entry) if (!HIDDEN(winfocus) && winfocus != win) break; } else { TAILQ_FOREACH_REVERSE(winfocus, wl, ws_win_list, entry) if (!HIDDEN(winfocus) && winfocus != win) break; } } break; case SWM_STACK_PRIOR: winfocus = get_ws_focus_prev(ws); break; case SWM_STACK_BELOW: winfocus = TAILQ_PREV(win, ws_win_list, entry); while (winfocus && HIDDEN(winfocus)) winfocus = TAILQ_PREV(winfocus, ws_win_list, entry); if (winfocus == NULL) { if (focus_close_wrap) { TAILQ_FOREACH_REVERSE(winfocus, wl, ws_win_list, entry) if (!HIDDEN(winfocus) && winfocus != win) break; } else { TAILQ_FOREACH(winfocus, wl, entry) if (!HIDDEN(winfocus) && winfocus != win) break; } } break; } done: DNPRINTF(SWM_D_FOCUS, "winfocus: %#x\n", WINID(winfocus)); return (winfocus); } struct ws_win * get_ws_focus_prev(struct workspace *ws) { struct ws_win *w; /* Find last available focus that is unrelated to the current focus. */ TAILQ_FOREACH(w, &ws->s->fl, focus_entry) if (w->ws == ws && !HIDDEN(w) && w != ws->focus && !win_related(w, ws->focus)) break; return (w); } struct ws_win * get_ws_focus(struct workspace *ws) { struct ws_win *winfocus = NULL; if (ws == NULL) return (NULL); if (ws->focus && !HIDDEN(ws->focus)) winfocus = ws->focus; else winfocus = get_ws_focus_prev(ws); return (winfocus); } /* Return the 'main' window in specified workspace. */ struct ws_win * get_main_window(struct workspace *ws) { struct ws_win *mwin = NULL; if (ws == NULL) return (NULL); /* Use the first tiled window, unless layout is max. */ if (!ws_maxstack(ws)) { TAILQ_FOREACH(mwin, &ws->winlist, entry) { if (HIDDEN(mwin)) continue; if (!win_floating(mwin)) break; } } /* Fallback to the first 'main' window. */ if (mwin == NULL) TAILQ_FOREACH(mwin, &ws->winlist, entry) if (!HIDDEN(mwin) && win_main(mwin)) break; return (mwin); } void focus(struct swm_screen *s, struct binding *bp, union arg *args) { struct swm_region *r; struct ws_win *head, *cur_focus = NULL, *winfocus = NULL; struct ws_win_list *wl = NULL; struct workspace *ws, *cws, *wws; int i, d, wincount; /* Suppress warning. */ (void)bp; if ((r = get_current_region(s)) == NULL) return; ws = r->ws; wl = &ws->winlist; cur_focus = s->focus; if (cur_focus == NULL) cur_focus = get_focus_magic(get_ws_focus(ws)); DNPRINTF(SWM_D_FOCUS, "id: %d, cur_focus: %#x\n", args->id, WINID(cur_focus)); /* Make sure an uniconified window has focus, if one exists. */ if (cur_focus == NULL) { cur_focus = TAILQ_FIRST(wl); while (cur_focus != NULL && HIDDEN(cur_focus)) cur_focus = TAILQ_NEXT(cur_focus, entry); DNPRINTF(SWM_D_FOCUS, "new cur_focus: %#x\n", WINID(cur_focus)); } cws = cur_focus ? cur_focus->ws : NULL; switch (args->id) { case SWM_ARG_ID_FOCUSPREV: if (cur_focus == NULL) goto out; wincount = count_win(ws, SWM_COUNT_ALL) + count_win(s->r->ws, SWM_COUNT_ALL); winfocus = cur_focus->main; for (i = 0; i < wincount; ++i) { winfocus = TAILQ_PREV(winfocus, ws_win_list, entry); if (winfocus == NULL) { winfocus = TAILQ_LAST(wl, ws_win_list); if (winfocus == NULL) break; } if (HIDDEN(winfocus) || !win_main(winfocus)) continue; if (winfocus->quirks & SWM_Q_NOFOCUSCYCLE) continue; break; } break; case SWM_ARG_ID_FOCUSNEXT: if (cur_focus == NULL) goto out; wincount = count_win(ws, SWM_COUNT_ALL) + count_win(s->r->ws, SWM_COUNT_ALL); winfocus = cur_focus->main; for (i = 0; i < wincount; ++i) { winfocus = TAILQ_NEXT(winfocus, entry); if (winfocus == NULL) { winfocus = TAILQ_FIRST(wl); if (winfocus == NULL) break; } if (HIDDEN(winfocus) || !win_main(winfocus)) continue; if (winfocus->quirks & SWM_Q_NOFOCUSCYCLE) continue; break; } break; case SWM_ARG_ID_FOCUSMAIN: if (cur_focus == NULL) goto out; winfocus = get_main_window(ws); if (winfocus == cur_focus) winfocus = get_ws_focus_prev(ws); break; case SWM_ARG_ID_FOCUSPRIOR: if (cur_focus == NULL) goto out; winfocus = get_ws_focus_prev(ws); break; case SWM_ARG_ID_FOCUSURGENT: /* Search forward for the next urgent window. */ winfocus = NULL; head = cur_focus; d = cws ? cws->idx : ws->idx; for (i = 0; i <= workspace_limit; ++i) { if (head == NULL) { wws = workspace_lookup(s, (d + i + 1) % (workspace_limit + 1) - 1); if (wws) head = TAILQ_FIRST(&wws->winlist); } while (head) { if (head == cur_focus) { if (i > 0) { winfocus = NULL; break; } } else if (win_urgent(head)) { winfocus = head; break; } head = TAILQ_NEXT(head, entry); } if (winfocus) break; } /* Switch ws if new focus is on a different ws. */ if (winfocus && winfocus->ws != ws && !ws_root(winfocus->ws)) switch_workspace(r, winfocus->ws, false); break; case SWM_ARG_ID_FOCUSFREE: if (s->focus && win_free(s->focus)) { winfocus = get_focus_magic(get_ws_focus(ws)); } else { winfocus = get_focus_magic(get_ws_focus(s->r->ws)); if (winfocus == NULL) winfocus = cur_focus; } break; default: goto out; } set_focus(s, get_focus_magic(winfocus)); if (winfocus && winfocus->ws != cws) apply_unfocus(winfocus->ws, NULL); else if (winfocus == NULL) { apply_unfocus(s->r->ws, NULL); } apply_unfocus(cws, NULL); if (winfocus) update_win_layer_related(winfocus); refresh_stack(s); update_stacking(s); stack(r); stack(s->r); update_mapping(s); update_focus(s); center_pointer(r); flush(); out: DNPRINTF(SWM_D_FOCUS, "done\n"); } void focus_pointer(struct swm_screen *s, struct binding *bp, union arg *args) { struct ws_win *win; /* Suppress warning. */ (void)args; /* Not needed for buttons since this is already done in buttonpress. */ if (bp->type == KEYBIND) { win = get_pointer_win(s); if (click_to_raise && !win_prioritized(win)) prioritize_window(win); focus_win(s, get_pointer_win(s)); flush(); } } void switchlayout(struct swm_screen *s, struct binding *bp, union arg *args) { struct layout *new_layout = NULL; struct swm_region *r; struct workspace *ws; struct ws_win *w; uint32_t changed = 0; /* Suppress warning. */ (void)bp; if ((r = get_current_region(s)) == NULL) return; ws = r->ws; DNPRINTF(SWM_D_EVENT, "workspace: %d\n", ws->idx); switch (args->id) { case SWM_ARG_ID_CYCLE_LAYOUT: new_layout = ws->cur_layout + 1; if (new_layout->l_stack == NULL) new_layout = &layouts[0]; break; case SWM_ARG_ID_LAYOUT_VERTICAL: new_layout = &layouts[SWM_V_STACK]; break; case SWM_ARG_ID_LAYOUT_HORIZONTAL: new_layout = &layouts[SWM_H_STACK]; break; case SWM_ARG_ID_LAYOUT_MAX: new_layout = &layouts[SWM_MAX_STACK]; break; case SWM_ARG_ID_LAYOUT_FLOATING: new_layout = &layouts[SWM_FLOATING_STACK]; break; case SWM_ARG_ID_PRIOR_LAYOUT: if (ws->prev_layout) new_layout = ws->prev_layout; break; default: goto out; } if (new_layout == NULL || new_layout == ws->cur_layout) goto out; ws->prev_layout = ws->cur_layout; ws->cur_layout = new_layout; if (max_layout_maximize) { if (!ws_maxstack_prior(ws) && ws_maxstack(ws)) { /* Enter max layout. */ TAILQ_FOREACH(w, &ws->winlist, entry) { if (win_transient(w) || WINDOCK(w) || WINDESKTOP(w)) continue; w->normalmax = MAXIMIZED(w); if (!MAXIMIZED(w) && w->maxstackmax) { changed |= ewmh_apply_flags(w, w->ewmh_flags | EWMH_F_MAXIMIZED); ewmh_update_wm_state(w); } } } else if (ws_maxstack_prior(ws) && !ws_maxstack(ws)) { /* Leave max layout. */ TAILQ_FOREACH(w, &ws->winlist, entry) { if (win_transient(w) || WINDOCK(w) || WINDESKTOP(w)) continue; w->maxstackmax = MAXIMIZED(w); if (MAXIMIZED(w) && !w->normalmax) { changed |= ewmh_apply_flags(w, w->ewmh_flags & ~EWMH_F_MAXIMIZED); ewmh_update_wm_state(w); } } } } changed |= apply_unfocus(ws, NULL); if (changed || ws->prev_layout->flags & SWM_L_NOTILE || ws->cur_layout->flags & SWM_L_NOTILE) { TAILQ_FOREACH(w, &ws->winlist, entry) if (win_main(w)) update_win_layer_related(w); refresh_stack(s); update_stacking(s); } stack(r); update_mapping(s); bar_draw(r->bar); focus_win(s, get_focus_magic(get_ws_focus(ws))); center_pointer(r); flush(); focus_follow(s, r, NULL); out: DNPRINTF(SWM_D_FOCUS, "done\n"); } void stack_config(struct swm_screen *s, struct binding *bp, union arg *args) { struct swm_region *r; struct workspace *ws; /* Suppress warning. */ (void)bp; if ((r = get_current_region(s)) == NULL) return; ws = r->ws; DNPRINTF(SWM_D_STACK, "id: %d workspace: %d\n", args->id, ws->idx); if (apply_unfocus(ws, NULL) > 0) { refresh_stack(s); update_stacking(s); stack(r); } if (ws->cur_layout->l_config != NULL) ws->cur_layout->l_config(ws, args->id); if (args->id != SWM_ARG_ID_STACKINIT) stack(r); update_mapping(s); bar_draw(r->bar); center_pointer(r); flush(); } void stack(struct swm_region *r) { struct swm_geometry g; if (r == NULL) return; DNPRINTF(SWM_D_STACK, "begin\n"); /* Adjust stack area for region bar and padding. */ g = r->g_usable; g.x += region_padding; g.y += region_padding; g.w -= 2 * border_width + 2 * region_padding; g.h -= 2 * border_width + 2 * region_padding; if (bar_enabled && r->ws->bar_enabled) { if (!bar_at_bottom) g.y += bar_height; g.h -= bar_height; } DNPRINTF(SWM_D_STACK, "workspace: %d (screen: %d, region: %d), (x,y) " "WxH: (%d,%d) %d x %d\n", r->ws->idx, r->s->idx, get_region_index(r), g.x, g.y, g.w, g.h); r->ws->cur_layout->l_stack(r->ws, &g); r->ws->cur_layout->l_string(r->ws); /* save r so we can track region changes */ r->ws->old_r = r; if (font_adjusted) font_adjusted--; DNPRINTF(SWM_D_STACK, "end\n"); } void store_float_geom(struct ws_win *win) { struct swm_region *r; if (win == NULL) return; /* Exclude fullscreen/maximized/tiled. */ if (FULLSCREEN(win) || MAXIMIZED(win) || (!ABOVE(win) && !win_transient(win) && !ws_floating(win->ws))) return; /* Retain window geometry and update reference coordinates. */ win->g_float = win->g; win->g_float.x -= win->g_grav.x; win->g_float.y -= win->g_grav.y; r = (win_free(win) && win->s->r_focus) ? win->s->r_focus : win->ws->r; if (r) { win->g_floatref = r->g; win->g_floatref_root = false; } DNPRINTF(SWM_D_MISC, "win %#x, g_float: (%d,%d) %d x %d, " "g_floatref: (%d,%d) %d x %d\n", win->id, win->g_float.x, win->g_float.y, win->g_float.w, win->g_float.h, win->g_floatref.x, win->g_floatref.y, win->g_floatref.w, win->g_floatref.h); } void load_float_geom(struct ws_win *win) { if (win == NULL) return; win->g = win->g_float; win->g.x += win->g_grav.x; win->g.y += win->g_grav.y; if (!win_free(win) && win->ws->r && !win->g_floatref_root) { /* Adjust position to current region. */ X(win) += X(win->ws->r) - win->g_floatref.x; Y(win) += Y(win->ws->r) - win->g_floatref.y; } DNPRINTF(SWM_D_MISC, "win %#x, g: (%d,%d) %d x %d, ref_root:%d\n", win->id, X(win), Y(win), WIDTH(win), HEIGHT(win), win->g_floatref_root); } void update_floater(struct ws_win *win) { struct workspace *ws; struct swm_region *r, *rf; bool bordered, fs; DNPRINTF(SWM_D_MISC, "win %#x\n", WINID(win)); if (win == NULL) return; ws = win->ws; if ((r = ws->r) == NULL) return; if (win_free(win)) { rf = region_under(win->s, X(win) + WIDTH(win) / 2, Y(win) + HEIGHT(win) / 2); if (rf == NULL) rf = r->s->r_focus ? r->s->r_focus : r->s->r; } else rf = r; bordered = !WINDOCK(win) && !WINDESKTOP(win); if (FULLSCREEN(win)) { /* _NET_WM_FULLSCREEN: fullscreen without border. */ win->g = rf->g; if (win->bordered) { win->bordered = false; update_gravity(win); } } else if (MAXIMIZED(win)) { /* Maximize: like a single stacked window. */ win->g = rf->g_usable; if (bar_enabled && ws->bar_enabled && !maximize_hide_bar) { if (!bar_at_bottom) Y(win) += bar_height; HEIGHT(win) -= bar_height; } else if (disable_border) { bordered = false; } if (disable_border_always) bordered = false; if (bordered) { /* Window geometry excludes frame. */ X(win) += border_width; Y(win) += border_width; HEIGHT(win) -= 2 * border_width; WIDTH(win) -= 2 * border_width; } if (win->bordered != bordered) { win->bordered = bordered; update_gravity(win); } } else { /* Normal floating window. */ if (rf != ws->old_r || ws_floating(ws) || win->quirks & SWM_Q_ANYWHERE) load_float_geom(win); fs = ((win->quirks & SWM_Q_FULLSCREEN) && WIDTH(win) >= WIDTH(rf) && HEIGHT(win) >= HEIGHT(rf)); if (fs || ((!ws_focused(win->ws) || win->ws->focus != win) && (win->quirks & SWM_Q_MINIMALBORDER))) bordered = false; if (win->bordered != bordered) { win->bordered = bordered; update_gravity(win); load_float_geom(win); } /* Invalidate client position if out of region. */ if (win->g_float_xy_valid && !bounds_intersect(&win->g, &r->g)) win->g_float_xy_valid = false; if (!fs && !MANUAL(win) && !win->g_float_xy_valid) { if (win_transient(win) && (win->quirks & SWM_Q_TRANSSZ)) { /* Adjust size on TRANSSZ quirk. */ WIDTH(win) = (double)WIDTH(rf) * dialog_ratio; HEIGHT(win) = (double)HEIGHT(rf) * dialog_ratio; } if (!(win->quirks & SWM_Q_ANYWHERE) && !WINDOCK(win) && !WINDESKTOP(win)) { /* * Floaters and transients are auto-centred * unless manually moved, resized or ANYWHERE * quirk is set. */ X(win) = X(rf) + (WIDTH(rf) - WIDTH(win)) / 2; Y(win) = Y(rf) + (HEIGHT(rf) - HEIGHT(win)) / 2; store_float_geom(win); } } } /* Ensure at least 1 pixel of the window is in the region. */ contain_window(win, r->g, boundary_width, SWM_CW_ALLSIDES); update_window(win); } /* * Send keystrokes to terminal to decrease/increase the font size as the * window size changes. */ void adjust_font(struct ws_win *win) { if (!(win->quirks & SWM_Q_XTERM_FONTADJ) || ABOVE(win) || win_transient(win)) return; if (win->sh.width_inc && win->last_inc != win->sh.width_inc && WIDTH(win) / win->sh.width_inc < term_width && win->font_steps < SWM_MAX_FONT_STEPS) { win->font_size_boundary[win->font_steps] = (win->sh.width_inc * term_width) + win->sh.base_width; win->font_steps++; font_adjusted++; win->last_inc = win->sh.width_inc; fake_keypress(win, XK_KP_Subtract, XCB_MOD_MASK_SHIFT); } else if (win->font_steps && win->last_inc != win->sh.width_inc && WIDTH(win) > win->font_size_boundary[win->font_steps - 1]) { win->font_steps--; font_adjusted++; win->last_inc = win->sh.width_inc; fake_keypress(win, XK_KP_Add, XCB_MOD_MASK_SHIFT); } } #define SWAPXY(g) do { \ int tmp; \ tmp = (g)->y; (g)->y = (g)->x; (g)->x = tmp; \ tmp = (g)->h; (g)->h = (g)->w; (g)->w = tmp; \ } while (0) void stack_master(struct workspace *ws, struct swm_geometry *g, int rot, bool flip) { struct swm_geometry cell, r_g = *g; struct ws_win *win; int i = 0, j = 0, s = 0, stacks = 0; int w_inc = 1, h_inc, w_base = 1, h_base; int hrh = 0, extra = 0, h_slice = 0, last_h = 0; int split = 0, colno = 0; int winno, mwin = 0, msize = 0; int remain, missing, v_slice, mscale; bool bordered = true, reconfigure = false; /* * cell: geometry for window, including frame. * mwin: # of windows in master area. * mscale: size increment of master area. * stacks: # of stack columns */ DNPRINTF(SWM_D_STACK, "workspace: %d, rot: %s, flip: %s\n", ws->idx, YESNO(rot), YESNO(flip)); memset(&cell, 0, sizeof(cell)); /* Prepare tiling variables, if needed. */ if ((winno = count_win(ws, SWM_COUNT_TILED)) > 0) { /* Find first tiled window. */ TAILQ_FOREACH(win, &ws->winlist, entry) if (!win_floating(win) && !HIDDEN(win)) break; /* Take into account size hints of first tiled window. */ if (rot) { w_inc = win->sh.width_inc; w_base = win->sh.base_width; mwin = ws->l_state.horizontal_mwin; mscale = ws->l_state.horizontal_msize; stacks = ws->l_state.horizontal_stacks; SWAPXY(&r_g); } else { w_inc = win->sh.height_inc; w_base = win->sh.base_height; mwin = ws->l_state.vertical_mwin; mscale = ws->l_state.vertical_msize; stacks = ws->l_state.vertical_stacks; } cell = r_g; cell.x += border_width; cell.y += border_width; if (stacks > winno - mwin) stacks = winno - mwin; if (stacks < 1) stacks = 1; h_slice = r_g.h / SWM_H_SLICE; if (mwin && winno > mwin) { v_slice = r_g.w / SWM_V_SLICE; split = mwin; colno = split; cell.w = v_slice * mscale; if (w_inc > 1 && w_inc < v_slice) { /* Adjust for requested size increment. */ remain = (cell.w - w_base) % w_inc; cell.w -= remain; } msize = cell.w; if (flip) cell.x += r_g.w - msize; s = stacks; } else { msize = - 2 * border_width; colno = split = winno / stacks; cell.w = ((r_g.w - (stacks * 2 * border_width) + 2 * border_width) / stacks); s = stacks - 1; } hrh = r_g.h / colno; extra = r_g.h - (colno * hrh); cell.h = hrh - 2 * border_width; i = j = 0; } /* Update window geometry. */ TAILQ_FOREACH(win, &ws->winlist, entry) { if (HIDDEN(win)) continue; if (win_floating(win) || WINDESKTOP(win)) { update_floater(win); continue; } /* Tiled. */ if (split && i == split) { colno = (winno - mwin) / stacks; if (s <= (winno - mwin) % stacks) colno++; split += colno; if (colno > 0) hrh = r_g.h / colno; extra = r_g.h - (colno * hrh); if (!flip) cell.x += cell.w + 2 * border_width + tile_gap; cell.w = (r_g.w - msize - (stacks * (2 * border_width + tile_gap))) / stacks; if (s == 1) cell.w += (r_g.w - msize - (stacks * (2 * border_width + tile_gap))) % stacks; if (flip) cell.x -= cell.w + 2 * border_width + tile_gap; s--; j = 0; } cell.h = hrh - 2 * border_width - tile_gap; if (rot) { h_inc = win->sh.width_inc; h_base = win->sh.base_width; } else { h_inc = win->sh.height_inc; h_base = win->sh.base_height; } if (j == colno - 1) { cell.h = hrh + extra; } else if (h_inc > 1 && h_inc < h_slice) { /* adjust for window's requested size increment */ remain = (cell.h - h_base) % h_inc; missing = h_inc - remain; if (missing <= extra || j == 0) { extra -= missing; cell.h += missing; } else { cell.h -= remain; extra += remain; } } if (j == 0) cell.y = r_g.y + border_width; else cell.y += last_h + 2 * border_width + tile_gap; /* Window coordinates exclude frame. */ bordered = (winno > 1 || !disable_border || (bar_enabled && ws->bar_enabled && !disable_border_always)); if (rot) { if (X(win) != cell.y || Y(win) != cell.x || WIDTH(win) != cell.h || HEIGHT(win) != cell.w) { reconfigure = true; X(win) = cell.y; Y(win) = cell.x; WIDTH(win) = cell.h; HEIGHT(win) = cell.w; } } else { if (X(win) != cell.x || Y(win) != cell.y || WIDTH(win) != cell.w || HEIGHT(win) != cell.h) { reconfigure = true; X(win) = cell.x; Y(win) = cell.y; WIDTH(win) = cell.w; HEIGHT(win) = cell.h; } } if (!bordered) { reconfigure = true; X(win) -= border_width; Y(win) -= border_width; WIDTH(win) += 2 * border_width; HEIGHT(win) += 2 * border_width; } if (bordered != win->bordered) { reconfigure = true; win->bordered = bordered; update_gravity(win); } if (reconfigure) { adjust_font(win); update_window(win); } last_h = cell.h; i++; j++; } DNPRINTF(SWM_D_STACK, "done\n"); } void vertical_config(struct workspace *ws, int id) { DNPRINTF(SWM_D_STACK, "id: %d, workspace: %d\n", id, ws->idx); switch (id) { case SWM_ARG_ID_STACKRESET: case SWM_ARG_ID_STACKINIT: ws->l_state.vertical_msize = SWM_V_SLICE / 2; ws->l_state.vertical_mwin = 1; ws->l_state.vertical_stacks = 1; break; case SWM_ARG_ID_MASTERSHRINK: if (ws->l_state.vertical_msize > 1) ws->l_state.vertical_msize--; break; case SWM_ARG_ID_MASTERGROW: if (ws->l_state.vertical_msize < SWM_V_SLICE - 1) ws->l_state.vertical_msize++; break; case SWM_ARG_ID_MASTERADD: ws->l_state.vertical_mwin++; break; case SWM_ARG_ID_MASTERDEL: if (ws->l_state.vertical_mwin > 0) ws->l_state.vertical_mwin--; break; case SWM_ARG_ID_STACKBALANCE: ws->l_state.vertical_msize = SWM_V_SLICE / (ws->l_state.vertical_stacks + 1); break; case SWM_ARG_ID_STACKINC: ws->l_state.vertical_stacks++; break; case SWM_ARG_ID_STACKDEC: if (ws->l_state.vertical_stacks > 1) ws->l_state.vertical_stacks--; break; case SWM_ARG_ID_FLIPLAYOUT: ws->l_state.vertical_flip = !ws->l_state.vertical_flip; break; default: return; } } void vertical_stack(struct workspace *ws, struct swm_geometry *g) { DNPRINTF(SWM_D_STACK, "workspace: %d\n", ws->idx); stack_master(ws, g, 0, ws->l_state.vertical_flip); } void horizontal_config(struct workspace *ws, int id) { DNPRINTF(SWM_D_STACK, "workspace: %d\n", ws->idx); switch (id) { case SWM_ARG_ID_STACKRESET: case SWM_ARG_ID_STACKINIT: ws->l_state.horizontal_mwin = 1; ws->l_state.horizontal_msize = SWM_H_SLICE / 2; ws->l_state.horizontal_stacks = 1; break; case SWM_ARG_ID_MASTERSHRINK: if (ws->l_state.horizontal_msize > 1) ws->l_state.horizontal_msize--; break; case SWM_ARG_ID_MASTERGROW: if (ws->l_state.horizontal_msize < SWM_H_SLICE - 1) ws->l_state.horizontal_msize++; break; case SWM_ARG_ID_MASTERADD: ws->l_state.horizontal_mwin++; break; case SWM_ARG_ID_MASTERDEL: if (ws->l_state.horizontal_mwin > 0) ws->l_state.horizontal_mwin--; break; case SWM_ARG_ID_STACKBALANCE: ws->l_state.horizontal_msize = SWM_H_SLICE / (ws->l_state.horizontal_stacks + 1); break; case SWM_ARG_ID_STACKINC: ws->l_state.horizontal_stacks++; break; case SWM_ARG_ID_STACKDEC: if (ws->l_state.horizontal_stacks > 1) ws->l_state.horizontal_stacks--; break; case SWM_ARG_ID_FLIPLAYOUT: ws->l_state.horizontal_flip = !ws->l_state.horizontal_flip; break; default: return; } } void horizontal_stack(struct workspace *ws, struct swm_geometry *g) { DNPRINTF(SWM_D_STACK, "workspace: %d\n", ws->idx); stack_master(ws, g, 1, ws->l_state.horizontal_flip); } void floating_stack(struct workspace *ws, struct swm_geometry *g) { struct ws_win *w; /* Suppress warning. */ (void)g; /* Update window geometry. */ TAILQ_FOREACH(w, &ws->winlist, entry) { if (HIDDEN(w)) continue; update_floater(w); } } void max_config(struct workspace *ws, int id) { struct swm_screen *s = NULL; struct ws_win *w; uint32_t changed = 0; DNPRINTF(SWM_D_STACK, "workspace: %d\n", ws->idx); if (id == SWM_ARG_ID_STACKRESET) { TAILQ_FOREACH(w, &ws->winlist, entry) { w->maxstackmax = max_layout_maximize; if (max_layout_maximize) changed |= ewmh_apply_flags(w, w->ewmh_flags | EWMH_F_MAXIMIZED); else changed |= ewmh_apply_flags(w, w->ewmh_flags & ~EWMH_F_MAXIMIZED); if (changed) { changed = 0; s = w->s; ewmh_update_wm_state(w); } } if (s) update_stacking(s); } } /* Single-tiled layout. */ void max_stack(struct workspace *ws, struct swm_geometry *g) { struct ws_win *w; bool bordered; DNPRINTF(SWM_D_STACK, "workspace: %d\n", ws->idx); /* Update window geometry. */ TAILQ_FOREACH(w, &ws->winlist, entry) { if (HIDDEN(w)) continue; if (win_floating(w)) { update_floater(w); continue; } /* Single tile. */ bordered = (!disable_border || (bar_enabled && ws->bar_enabled && !disable_border_always)); if (bordered != w->bordered || X(w) != g->x || Y(w) != g->y || WIDTH(w) != g->w || HEIGHT(w) != g->h) { if (w->bordered != bordered) { w->bordered = bordered; update_gravity(w); } w->g = *g; if (bordered) { X(w) += border_width; Y(w) += border_width; } else { WIDTH(w) += 2 * border_width; HEIGHT(w) += 2 * border_width; } adjust_font(w); update_window(w); } } } void update_layout(struct swm_screen *s) { struct swm_region *r; /* Update root and dynamic regions. */ stack(s->r); TAILQ_FOREACH(r, &s->rl, entry) stack(r); } void update_stacking(struct swm_screen *s) { struct swm_stackable *st, *st_prev; /* Stack windows from bottom up. */ st_prev = NULL; SLIST_FOREACH(st, &s->stack, entry) { update_stackable(st, st_prev); st_prev = st; } update_debug(s); } void update_region_mapping(struct swm_region *r) { struct ws_win *w, *wf; bool mof; if (r == NULL || r->ws == NULL) return; wf = get_focus_magic(get_ws_focus(r->ws)); if (r->bar) r->bar->disabled = (wf && ((MAXIMIZED(wf) && maximize_hide_bar && maximize_hide_other) || (FULLSCREEN(wf) && fullscreen_hide_other)) && !win_below(wf)); mof = (ws_maponfocus(r->ws) || (wf && ((maximize_hide_other && MAXIMIZED(wf)) || (fullscreen_hide_other && FULLSCREEN(wf))) && !win_below(wf))); DNPRINTF(SWM_D_MISC, "r:%d ws:%d wf:%#x mof:%#x\n", get_region_index(r), r->ws->idx, WINID(wf), mof); /* Map first, then unmap. */ TAILQ_FOREACH(w, &r->ws->winlist, entry) if (!HIDDEN(w) && (!mof || win_related(w, wf) || WINDOCK(w))) map_window(w); TAILQ_FOREACH(w, &r->ws->winlist, entry) if (HIDDEN(w) || (mof && !win_related(w, wf) && !WINDOCK(w))) unmap_window(w); } void update_mapping(struct swm_screen *s) { struct swm_region *r; /* Update root and dynamic regions. */ update_region_mapping(s->r); TAILQ_FOREACH(r, &s->rl, entry) update_region_mapping(r); } void transfer_win(struct ws_win *win, struct workspace *ws) { struct swm_screen *s; struct workspace *ows; bool follow; s = win->s; ows = win->ws; DNPRINTF(SWM_D_MOVE, "win %#x, id: %d\n", win->id, ws->idx); follow = follow_pointer(s); if (win_free(win)) win->ewmh_flags |= EWMH_F_ABOVE; win_to_ws(win, ws, SWM_WIN_UNFOCUS); apply_unfocus(ws, NULL); /* Set new focus on target ws. */ if (!follow) { ws->focus = win; set_focus(s, ows->focus); draw_frame(get_ws_focus_prev(ws)); } DNPRINTF(SWM_D_STACK, "focus: %#x, focus_prev: %#x, first: %#x, " "win: %#x\n", WINID(ows->focus), WINID(get_ws_focus_prev(ows)), WINID(TAILQ_FIRST(&ows->winlist)), win->id); update_win_layer_related(win); refresh_stack(s); update_stacking(s); stack(ows->r); if (ws->r) { if (win_floating(win)) load_float_geom(win); stack(ws->r); } else store_float_geom(win); update_mapping(s); if (!follow) { update_focus(s); center_pointer(ows->r); } flush(); if (follow) { if (pointer_window != XCB_WINDOW_NONE) focus_window(pointer_window); else if (ows->focus == NULL) focus_win(s, get_focus_magic(get_ws_focus(ows))); xcb_flush(conn); } } void send_to_rg(struct swm_screen *s, struct binding *bp, union arg *args) { struct swm_region *r; struct ws_win *win; /* Suppress warning. */ (void)bp; DNPRINTF(SWM_D_FOCUS, "id: %d\n", args->id); win = s->focus; if (win == NULL) return; r = get_region(s, args->id); if (r == NULL) return; transfer_win(win, r->ws); } struct swm_region * region_under(struct swm_screen *s, int x, int y) { struct swm_region *r; if (s == NULL) return (NULL); TAILQ_FOREACH(r, &s->rl, entry) if (X(r) <= x && x < MAX_X(r) && Y(r) <= y && y < MAX_Y(r)) break; return (r); } /* Transfer focused window to target workspace and focus. */ void send_to_ws(struct swm_screen *s, struct binding *bp, union arg *args) { struct workspace *ws; struct ws_win *win; /* Suppress warning. */ (void)bp; win = s->focus; if (win == NULL) return; ws = get_workspace(s, args->id); if (ws == NULL || win->ws == ws) return; transfer_win(win, ws); DNPRINTF(SWM_D_MISC, "done\n"); } /* Transfer focused window to region-relative workspace and focus. */ void send_to_rg_relative(struct swm_screen *s, struct binding *bp, union arg *args) { struct ws_win *win; struct swm_region *r, *r_other; /* Suppress warning. */ (void)bp; win = s->focus; if (win == NULL) return; if ((r = get_current_region(s)) == NULL) return; win = r->ws->focus; if (win == NULL) return; if (args->id == 1) { r_other = TAILQ_NEXT(r, entry); if (r_other == NULL) r_other = TAILQ_FIRST(&s->rl); } else { r_other = TAILQ_PREV(r, swm_region_list, entry); if (r_other == NULL) r_other = TAILQ_LAST(&s->rl, swm_region_list); } if (r_other == r) return; transfer_win(win, r_other->ws); } static void update_win_refs(struct ws_win *win) { struct workspace *ws; struct ws_win *w; RB_FOREACH(ws, workspace_tree, &win->s->workspaces) TAILQ_FOREACH(w, &ws->winlist, entry) if (w->transient_for == win->id) w->parent = win; RB_FOREACH(ws, workspace_tree, &win->s->workspaces) TAILQ_FOREACH(w, &ws->winlist, entry) w->main = find_main_window(w); } /* Determine a window to consider 'main' for specified window. */ struct ws_win * find_main_window(struct ws_win *win) { struct ws_win *w; int i; if (win == NULL || win->parent == NULL) return (win); /* Resolve TRANSIENT_FOR as far as possible. */ w = win; for (i = 0; w && w->parent && i < win->s->managed_count; i++) { w = w->parent; if (w == win) /* Transient loop shouldn't occur. */ break; } if (w == NULL) w = win; DNPRINTF(SWM_D_MISC, "win %#x, count: %d, main: %#x\n", WINID(win), win->s->managed_count, WINID(w)); return (w); } void set_focus_redirect(struct ws_win *win) { struct ws_win *w; int i; if (win == NULL || win->parent == NULL || win_noinput(win)) return; /* Set focus_redirect along transient chain. */ w = win; for (i = 0; w && w->parent && i < win->s->managed_count; i++) { /* Transient loop shouldn't occur. */ if (w->parent == win) break; w->parent->focus_redirect = w; w = w->parent; } win->focus_redirect = NULL; /* Clear any redirect from this window. */ } void win_to_ws(struct ws_win *win, struct workspace *nws, uint32_t flags) { struct ws_win *w, *tmpw; struct workspace *ws; uint32_t wsid; bool focused; if (win == NULL || nws == NULL) return; ws = win->ws; if (ws == nws) return; DNPRINTF(SWM_D_MOVE, "win %#x, ws %d -> %d, focus: %#x, main: %#x\n", win->id, ws->idx, nws->idx, WINID(ws->focus), WINID(win->main)); wsid = (nws->idx >= 0 ? (uint32_t)(nws->idx) : EWMH_ALL_DESKTOPS); /* Transfer main window and any related transients. */ TAILQ_FOREACH_SAFE(w, &ws->winlist, entry, tmpw) { if (win_related(w, win)) { focused = (ws->focus == w); if (focused) { ws->focus = get_focus_other(w); if (flags & SWM_WIN_UNFOCUS) unfocus_win(w); } /* Unmap if new ws is hidden. */ if (!(flags & SWM_WIN_NOUNMAP) && nws->r == NULL) unmap_window(w); /* Transfer */ TAILQ_REMOVE(&ws->winlist, w, entry); TAILQ_INSERT_TAIL(&nws->winlist, w, entry); w->ws = nws; if (focused) nws->focus = w; /* Cleanup references. */ if (ws->focus == w) ws->focus = NULL; if (ws->focus_raise == w) ws->focus_raise = NULL; DNPRINTF(SWM_D_PROP, "win %#x, set property: " "_NET_WM_DESKTOP: %d\n", w->id, wsid); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, w->id, ewmh[_NET_WM_DESKTOP].atom, XCB_ATOM_CARDINAL, 32, 1, &wsid); } } ewmh_update_client_list(win->s); DNPRINTF(SWM_D_MOVE, "done\n"); } void pressbutton(struct swm_screen *s, struct binding *bp, union arg *args) { /* Suppress warning. */ (void)s; (void)bp; xcb_test_fake_input(conn, XCB_BUTTON_PRESS, args->id, XCB_CURRENT_TIME, XCB_WINDOW_NONE, 0, 0, 0); xcb_test_fake_input(conn, XCB_BUTTON_RELEASE, args->id, XCB_CURRENT_TIME, XCB_WINDOW_NONE, 0, 0, 0); } void raise_focus(struct swm_screen *s, struct binding *bp, union arg *args) { struct ws_win *win; /* Suppress warning. */ (void)bp; (void)args; win = s->focus; if (win == NULL || win_raised(win)) return; win->ws->focus_raise = win; update_win_layer(win); prioritize_window(win); refresh_stack(s); update_stacking(s); flush(); } void raise_toggle(struct swm_screen *s, struct binding *bp, union arg *args) { struct swm_region *r; /* Suppress warning. */ (void)bp; (void)args; if ((r = get_current_region(s)) == NULL) return; if (r->ws->focus && MAXIMIZED(r->ws->focus)) return; r->ws->always_raise = !r->ws->always_raise; /* Update focused win stacking order based on new always_raise value. */ if (r->ws->focus) { update_win_layer(r->ws->focus); refresh_stack(s); update_stacking(s); } flush(); } void iconify(struct swm_screen *s, struct binding *bp, union arg *args) { struct swm_region *r; struct ws_win *win, *nfw; /* Suppress warning. */ (void)bp; (void)args; win = s->focus; if (win == NULL) return; nfw = get_focus_other(win); unfocus_win(win); ewmh_apply_flags(win, win->ewmh_flags | EWMH_F_HIDDEN); ewmh_update_wm_state(win); if (nfw == NULL && win_free(win)) { if ((r = get_current_region(s)) == NULL) return; nfw = get_focus_magic(get_ws_focus(r->ws)); } if (focus_mode != SWM_FOCUS_FOLLOW && nfw) set_focus(s, get_focus_magic(nfw)); refresh_strut(s); stack(win->ws->r); update_mapping(s); if (focus_mode != SWM_FOCUS_FOLLOW) { update_focus(win->s); center_pointer(win->ws->r); } flush(); /* win can be freed. */ focus_follow(s, s->r_focus, nfw); } char * get_win_name(xcb_window_t win) { char *name = NULL; xcb_get_property_cookie_t c; xcb_get_property_reply_t *r; /* First try _NET_WM_NAME for UTF-8. */ c = xcb_get_property(conn, 0, win, ewmh[_NET_WM_NAME].atom, XCB_GET_PROPERTY_TYPE_ANY, 0, UINT_MAX); r = xcb_get_property_reply(conn, c, NULL); if (r && r->type == XCB_NONE) { free(r); /* Use WM_NAME instead; no UTF-8. */ c = xcb_get_property(conn, 0, win, XCB_ATOM_WM_NAME, XCB_GET_PROPERTY_TYPE_ANY, 0, UINT_MAX); r = xcb_get_property_reply(conn, c, NULL); } if (r && r->type != XCB_NONE && r->length > 0) name = strndup(xcb_get_property_value(r), xcb_get_property_value_length(r)); else name = strdup(""); if (name == NULL) err(1, "get_win_name: strdup"); free(r); return (name); } void uniconify(struct swm_screen *s, struct binding *bp, union arg *args) { struct swm_region *r; struct ws_win *win; FILE *lfile; char *name; int count = 0; /* Suppress warnings. */ (void)bp; DNPRINTF(SWM_D_MISC, "begin\n"); if ((r = get_current_region(s)) == NULL) return; /* make sure we have anything to uniconify */ TAILQ_FOREACH(win, &r->ws->winlist, entry) { if (win->ws == NULL) continue; /* should never happen */ if (!HIDDEN(win)) continue; count++; } /* Tack on 'free' wins. */ TAILQ_FOREACH(win, &s->r->ws->winlist, entry) { if (win->ws == NULL) continue; /* should never happen */ if (!HIDDEN(win)) continue; count++; } DNPRINTF(SWM_D_MISC, "count: %d\n", count); if (count == 0) return; search_r = r; search_resp_action = SWM_SEARCH_UNICONIFY; spawn_select(r, args, "search", &searchpid); if ((lfile = fdopen(select_list_pipe[1], "w")) == NULL) return; TAILQ_FOREACH(win, &r->ws->winlist, entry) { if (win->ws == NULL) continue; /* should never happen */ if (!HIDDEN(win)) continue; name = get_win_name(win->id); fprintf(lfile, "%s.%u\n", name, win->id); free(name); } /* Tack on 'free' wins. */ TAILQ_FOREACH(win, &s->r->ws->winlist, entry) { if (!HIDDEN(win)) continue; name = get_win_name(win->id); fprintf(lfile, "%s.%u\n", name, win->id); free(name); } fclose(lfile); } void name_workspace(struct swm_screen *s, struct binding *bp, union arg *args) { struct swm_region *r; FILE *lfile; /* Suppress warning. */ (void)bp; DNPRINTF(SWM_D_MISC, "begin\n"); if ((r = get_current_region(s)) == NULL) return; search_r = r; search_resp_action = SWM_SEARCH_NAME_WORKSPACE; spawn_select(r, args, "name_workspace", &searchpid); if ((lfile = fdopen(select_list_pipe[1], "w")) == NULL) return; fprintf(lfile, "%s", ""); fclose(lfile); } void search_workspace(struct swm_screen *s, struct binding *bp, union arg *args) { struct swm_region *r; struct workspace *ws; int i; FILE *lfile; /* Suppress warning. */ (void)bp; DNPRINTF(SWM_D_MISC, "begin\n"); if ((r = get_current_region(s)) == NULL) return; search_r = r; search_resp_action = SWM_SEARCH_SEARCH_WORKSPACE; spawn_select(r, args, "search", &searchpid); if ((lfile = fdopen(select_list_pipe[1], "w")) == NULL) return; for (i = 0; i < workspace_limit; ++i) { ws = workspace_lookup(s, i); fprintf(lfile, "%d%s%s\n", i + 1, ((ws && ws->name) ? ":" : ""), ((ws && ws->name) ? ws->name : "")); } fclose(lfile); } void search_win_cleanup(void) { struct search_window *sw = NULL; #ifndef __clang_analyzer__ /* Suppress false warnings. */ while ((sw = TAILQ_FIRST(&search_wl)) != NULL) { xcb_destroy_window(conn, sw->indicator); TAILQ_REMOVE(&search_wl, sw, entry); free(sw); } #endif } int create_search_win(struct ws_win *win, int index) { struct search_window *sw = NULL; xcb_window_t w; uint32_t wa[3]; uint32_t offset; int width, height; char str[11]; size_t len; XftDraw *draw; XGlyphInfo info; GC l_draw; XGCValues l_gcv; XRectangle l_ibox, l_lbox = {0, 0, 0, 0}; if ((sw = calloc(1, sizeof(struct search_window))) == NULL) { warn("search_win: calloc"); return (1); } sw->idx = index; sw->win = win; snprintf(str, sizeof str, "%d", index); len = strlen(str); w = xcb_generate_id(conn); wa[0] = win->s->c[SWM_S_COLOR_FOCUS].pixel; wa[1] = win->s->c[SWM_S_COLOR_UNFOCUS].pixel; wa[2] = win->s->colormap; if (bar_font_legacy) { TEXTEXTENTS(bar_fs, str, len, &l_ibox, &l_lbox); width = l_lbox.width + 4; height = bar_fs_extents->max_logical_extent.height + 4; } else { XftTextExtentsUtf8(display, win->s->bar_xftfonts[0], (FcChar8 *)str, len, &info); width = info.xOff + 4; height = win->s->bar_xftfonts[0]->height + 4; } offset = win_border(win); xcb_create_window(conn, win->s->depth, w, win->frame, offset, offset, width, height, 1, XCB_WINDOW_CLASS_INPUT_OUTPUT, win->s->visual, XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_COLORMAP, wa); xcb_map_window(conn, w); sw->indicator = w; TAILQ_INSERT_TAIL(&search_wl, sw, entry); if (bar_font_legacy) { l_gcv.graphics_exposures = 0; l_draw = XCreateGC(display, w, 0, &l_gcv); XSetForeground(display, l_draw, win->s->c[SWM_S_COLOR_BAR].pixel); DRAWSTRING(display, w, bar_fs, l_draw, 2, (bar_fs_extents->max_logical_extent.height - l_lbox.height) / 2 - l_lbox.y + 2, str, len); XFreeGC(display, l_draw); } else { draw = XftDrawCreate(display, w, win->s->xvisual, win->s->colormap); XftDrawStringUtf8(draw, &search_font_color, win->s->bar_xftfonts[0], 2, height - 2 - win->s->bar_xftfonts[0]->descent, (FcChar8 *)str, len); XftDrawDestroy(draw); } DNPRINTF(SWM_D_MISC, "mapped win %#x\n", w); return (0); } void search_win(struct swm_screen *s, struct binding *bp, union arg *args) { struct swm_region *r; struct ws_win *win = NULL; int i; FILE *lfile; char *title; /* Suppress warning. */ (void)bp; DNPRINTF(SWM_D_MISC, "begin\n"); if ((r = get_current_region(s)) == NULL) return; search_r = r; search_resp_action = SWM_SEARCH_SEARCH_WINDOW; spawn_select(r, args, "search", &searchpid); if ((lfile = fdopen(select_list_pipe[1], "w")) == NULL) return; i = 1; TAILQ_FOREACH(win, &r->ws->winlist, entry) { if (HIDDEN(win)) continue; if (create_search_win(win, i)) { fclose(lfile); search_win_cleanup(); return; } title = get_win_name(win->id); fprintf(lfile, "%d%s%s\n", i, (title ? ":" : ""), (title ? title : "")); free(title); i++; } /* Tack on 'free' wins. */ TAILQ_FOREACH(win, &s->r->ws->winlist, entry) { if (HIDDEN(win)) continue; if (create_search_win(win, i)) { fclose(lfile); search_win_cleanup(); return; } title = get_win_name(win->id); fprintf(lfile, "%d%s%s\n", i, (title ? ":" : ""), (title ? title : "")); free(title); i++; } fclose(lfile); xcb_flush(conn); } void search_resp_uniconify(const char *resp, size_t len) { char *name; struct ws_win *win; struct swm_screen *s; char *str; DNPRINTF(SWM_D_MISC, "resp: %s\n", resp); if (search_r == NULL) return; s = search_r->s; TAILQ_FOREACH(win, &search_r->ws->winlist, entry) { if (!HIDDEN(win)) continue; name = get_win_name(win->id); if (asprintf(&str, "%s.%u", name, win->id) == -1) { free(name); continue; } free(name); if (strncmp(str, resp, len) == 0) { free(str); break; } free(str); } /* Try root ws. */ if (win == NULL) TAILQ_FOREACH(win, &s->r->ws->winlist, entry) { if (!HIDDEN(win)) continue; name = get_win_name(win->id); if (asprintf(&str, "%s.%u", name, win->id) == -1) { free(name); continue; } free(name); if (strncmp(str, resp, len) == 0) { free(str); break; } free(str); } if (win) { /* XXX this should be a callback to generalize */ ewmh_apply_flags(win, win->ewmh_flags & ~EWMH_F_HIDDEN); ewmh_update_wm_state(win); set_focus_redirect(win); if (focus_mode != SWM_FOCUS_FOLLOW) set_focus(s, get_focus_magic(win)); apply_unfocus(win->ws, win); refresh_stack(s); update_stacking(s); refresh_strut(s); stack(win->ws->r); update_mapping(s); if (focus_mode != SWM_FOCUS_FOLLOW) { update_focus(s); center_pointer(win->ws->r); } flush(); /* win can be freed. */ focus_follow(s, s->r_focus, win); if (validate_win(win) == 0) { draw_frame(win); debug_refresh(win); } } } void search_resp_name_workspace(const char *resp, size_t len) { struct workspace *ws; DNPRINTF(SWM_D_MISC, "resp: %s\n", resp); if (search_r->ws == NULL) return; ws = search_r->ws; if (ws->name) { free(ws->name); ws->name = NULL; } if (len) { ws->name = strdup(resp); if (ws->name == NULL) { DNPRINTF(SWM_D_MISC, "strdup: %s", strerror(errno)); return; } } ewmh_update_desktop_names(search_r->s); ewmh_get_desktop_names(search_r->s); update_bars(search_r->s); } void ewmh_update_desktop_names(struct swm_screen *s) { struct workspace *ws; char *name_list = NULL, *p; int i; size_t len = 0, tot = 0; for (i = 0; i < workspace_limit; ++i) { if ((ws = workspace_lookup(s, i))) if (ws->name) len += strlen(ws->name); ++len; } if ((name_list = calloc(len, sizeof(char))) == NULL) err(1, "update_desktop_names: calloc"); p = name_list; for (i = 0; i < workspace_limit; ++i) { if ((ws = workspace_lookup(s, i))) { if (ws->name) { len = strlen(ws->name); memcpy(p, ws->name, len); } else len = 0; } p += len + 1; tot += len + 1; } xcb_change_property(conn, XCB_PROP_MODE_REPLACE, s->root, ewmh[_NET_DESKTOP_NAMES].atom, a_utf8_string, 8, tot, name_list); free(name_list); } void ewmh_get_desktop_names(struct swm_screen *s) { struct workspace *ws; xcb_get_property_reply_t *gpr; xcb_get_property_cookie_t gpc; int i, n, k; char *names = NULL; for (i = 0; i < workspace_limit; ++i) { if ((ws = workspace_lookup(s, i))) { free(ws->name); ws->name = NULL; } } gpc = xcb_get_property(conn, 0, s->root, ewmh[_NET_DESKTOP_NAMES].atom, a_utf8_string, 0, UINT32_MAX); gpr = xcb_get_property_reply(conn, gpc, NULL); if (gpr == NULL) return; names = xcb_get_property_value(gpr); n = xcb_get_property_value_length(gpr); for (i = 0, k = 0; i < n; ++i) { if (*(names + i) != '\0') { if ((ws = get_workspace(s, k))) { ws->name = strdup(names + i); i += strlen(names + i); } } ++k; } free(gpr); } void ewmh_update_client_list(struct swm_screen *s) { struct ws_win *w; struct workspace *ws; xcb_window_t *wins; int i; DNPRINTF(SWM_D_PROP, "win count: %d\n", s->managed_count); if (s->managed_count == 0) return; wins = calloc(s->managed_count, sizeof(xcb_window_t)); if (wins == NULL) err(1, "ewmh_update_client_list: calloc: failed to " "allocate memory."); /* Save workspace window order. */ i = 0; RB_FOREACH(ws, workspace_tree, &s->workspaces) TAILQ_FOREACH(w, &ws->winlist, entry) wins[i++] = w->id; xcb_change_property(conn, XCB_PROP_MODE_REPLACE, s->root, ewmh[_NET_CLIENT_LIST].atom, XCB_ATOM_WINDOW, 32, s->managed_count, wins); free(wins); } void ewmh_update_current_desktop(struct swm_screen *s) { struct swm_region *r; uint32_t val; if ((r = get_current_region(s)) == NULL) return; val = r->ws->idx; xcb_change_property(conn, XCB_PROP_MODE_REPLACE, s->root, ewmh[_NET_CURRENT_DESKTOP].atom, XCB_ATOM_CARDINAL, 32, 1, &val); } void ewmh_update_desktops(struct swm_screen *s) { struct workspace *ws; int i; uint32_t *vals; vals = calloc(workspace_limit * 2, sizeof(uint32_t)); if (vals == NULL) err(1, "ewmh_update_desktops: calloc"); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, s->root, ewmh[_NET_NUMBER_OF_DESKTOPS].atom, XCB_ATOM_CARDINAL, 32, 1, &workspace_limit); for (i = 0; i < workspace_limit; ++i) { ws = workspace_lookup(s, i); if (ws && ws->r) { vals[i * 2] = X(ws->r); vals[i * 2 + 1] = Y(ws->r); } else if (ws && ws->old_r) { vals[i * 2] = X(ws->old_r); vals[i * 2 + 1] = Y(ws->old_r); } else { vals[i * 2] = vals[i * 2 + 1] = 0; } } xcb_change_property(conn, XCB_PROP_MODE_REPLACE, s->root, ewmh[_NET_DESKTOP_VIEWPORT].atom, XCB_ATOM_CARDINAL, 32, workspace_limit * 2, vals); free(vals); } void ewmh_update_workarea(struct swm_screen *s) { struct swm_region *r = NULL; struct workspace *ws; int i; uint32_t *vals; if ((vals = calloc(workspace_limit * 4, sizeof(uint32_t))) == NULL) err(1, "ewmh_update_workarea: calloc"); for (i = 0; i < workspace_limit; ++i) { if ((ws = workspace_lookup(s, i))) r = ws->r ? ws->r : ws->old_r; if (r == NULL) r = get_current_region(s); if (r == NULL) r = s->r; vals[i * 4] = r->g_usable.x; vals[i * 4 + 1] = r->g_usable.y; vals[i * 4 + 2] = r->g_usable.w; vals[i * 4 + 3] = r->g_usable.h; } xcb_change_property(conn, XCB_PROP_MODE_REPLACE, s->root, ewmh[_NET_WORKAREA].atom, XCB_ATOM_CARDINAL, 32, workspace_limit * 4, vals); free(vals); } void search_resp_search_workspace(const char *resp) { struct workspace *ws; char *p, *q; int ws_idx, fail; DNPRINTF(SWM_D_MISC, "resp: %s\n", resp); q = strdup(resp); if (q == NULL) { DNPRINTF(SWM_D_MISC, "strdup: %s", strerror(errno)); return; } p = strchr(q, ':'); if (p != NULL) *p = '\0'; ws_idx = strtoint32(q, 1, workspace_limit, &fail) - 1; if (fail) { DNPRINTF(SWM_D_MISC, "integer conversion failed for %s\n", q); free(q); return; } free(q); ws = get_workspace(search_r->s, ws_idx); if (ws) switch_workspace(search_r, ws, false); } void search_resp_search_window(const char *resp) { char *s, *p; int idx, fail; struct search_window *sw; DNPRINTF(SWM_D_MISC, "resp: %s\n", resp); s = strdup(resp); if (s == NULL) { DNPRINTF(SWM_D_MISC, "strdup: %s", strerror(errno)); return; } p = strchr(s, ':'); if (p != NULL) *p = '\0'; idx = strtoint32(s, 1, INT_MAX, &fail); if (fail) { DNPRINTF(SWM_D_MISC, "integer conversion failed for %s\n", s); free(s); return; } free(s); TAILQ_FOREACH(sw, &search_wl, entry) if (idx == sw->idx) { focus_win(sw->win->s, sw->win); break; } } #define MAX_RESP_LEN 1024 void search_do_resp(void) { ssize_t rbytes; char *resp; size_t len; DNPRINTF(SWM_D_MISC, "begin\n"); search_resp = 0; searchpid = 0; if ((resp = calloc(1, MAX_RESP_LEN + 1)) == NULL) { warn("search: calloc"); goto done; } rbytes = read(select_resp_pipe[0], resp, MAX_RESP_LEN); if (rbytes <= 0) { warn("search: read error"); goto done; } resp[rbytes] = '\0'; /* XXX: * Older versions of dmenu (Atleast pre 4.4.1) do not send a * newline, so work around that by sanitizing the resp now. */ resp[strcspn(resp, "\n")] = '\0'; len = strlen(resp); switch (search_resp_action) { case SWM_SEARCH_UNICONIFY: search_resp_uniconify(resp, len); break; case SWM_SEARCH_NAME_WORKSPACE: search_resp_name_workspace(resp, len); break; case SWM_SEARCH_SEARCH_WORKSPACE: search_resp_search_workspace(resp); break; case SWM_SEARCH_SEARCH_WINDOW: search_resp_search_window(resp); break; } done: if (search_resp_action == SWM_SEARCH_SEARCH_WINDOW) search_win_cleanup(); search_resp_action = SWM_SEARCH_NONE; close(select_resp_pipe[0]); free(resp); xcb_flush(conn); DNPRINTF(SWM_D_MISC, "done\n"); } void wkill(struct swm_screen *s, struct binding *bp, union arg *args) { (void)bp; DNPRINTF(SWM_D_MISC, "win %#x, id: %d\n", WINID(s->focus), args->id); if (s->focus == NULL) return; if (args->id == SWM_ARG_ID_KILLWINDOW) xcb_kill_client(conn, s->focus->id); else if (s->focus->can_delete) client_msg(s->focus, a_delete, 0); xcb_flush(conn); } /* Apply unfocus conditions on windows in workspace unrelated to win. */ int apply_unfocus(struct workspace *ws, struct ws_win *win) { struct ws_win *w; int count = 0; uint32_t changed = 0; if (ws == NULL) goto out; DNPRINTF(SWM_D_MISC, "ws: %d\n", ws->idx); if (ws_maxstack(ws)) goto out; if (maximized_unfocus == SWM_UNFOCUS_NONE && fullscreen_unfocus == SWM_UNFOCUS_NONE) goto out; TAILQ_FOREACH(w, &ws->winlist, entry) { if (win_related(w, win)) continue; if (MAXIMIZED(w)) switch (maximized_unfocus) { case SWM_UNFOCUS_RESTORE: changed |= ewmh_apply_flags(w, w->ewmh_flags & ~EWMH_F_MAXIMIZED); break; case SWM_UNFOCUS_ICONIFY: changed |= ewmh_apply_flags(w, w->ewmh_flags | EWMH_F_HIDDEN); break; case SWM_UNFOCUS_FLOAT: changed |= ewmh_apply_flags(w, (w->ewmh_flags | EWMH_F_ABOVE) & ~EWMH_F_MAXIMIZED); break; case SWM_UNFOCUS_BELOW: changed |= ewmh_apply_flags(w, (w->ewmh_flags | EWMH_F_BELOW)); break; case SWM_UNFOCUS_QUICK_BELOW: changed = 1; break; default: break; } if (FULLSCREEN(w)) switch (fullscreen_unfocus) { case SWM_UNFOCUS_RESTORE: changed |= ewmh_apply_flags(w, w->ewmh_flags & ~EWMH_F_FULLSCREEN); break; case SWM_UNFOCUS_ICONIFY: changed |= ewmh_apply_flags(w, w->ewmh_flags | EWMH_F_HIDDEN); break; case SWM_UNFOCUS_FLOAT: changed |= ewmh_apply_flags(w, (w->ewmh_flags | EWMH_F_ABOVE) & ~EWMH_F_FULLSCREEN); break; case SWM_UNFOCUS_BELOW: changed |= ewmh_apply_flags(w, (w->ewmh_flags | EWMH_F_BELOW)); break; case SWM_UNFOCUS_QUICK_BELOW: changed = 1; break; default: break; } if (changed) { update_win_layer_related(w); ewmh_update_wm_state(w); changed = 0; ++count; } } out: return (count); } void free_toggle(struct swm_screen *s, struct binding *bp, union arg *args) { struct swm_region *r; struct ws_win *win; struct workspace *nws; (void)bp; (void)args; win = s->focus; if (win == NULL) return; r = get_current_region(s); nws = (win_free(win) ? r->ws : s->r->ws); apply_unfocus(win->ws, win); if (!win_free(win)) { if (win_floating(win) && !FULLSCREEN(win) && !MAXIMIZED(win)) { win->g_float = win->g; update_gravity(win); /* Maintain original position. */ win->g_float.x -= win->g_grav.x; win->g_float.y -= win->g_grav.y; win->g_floatref = nws->r->g; win->g_floatref_root = false; } } win_to_ws(win, nws, SWM_WIN_NOUNMAP); update_win_layer_related(win); refresh_stack(s); update_stacking(s); stack(r); if (nws->r != r) stack(nws->r); if (win_free(win)) store_float_geom(win); update_mapping(s); draw_frame(win); bar_draw(r->bar); flush(); } void maximize_toggle(struct swm_screen *s, struct binding *bp, union arg *args) { struct swm_region *r; struct ws_win *win; bool follow; /* Suppress warnings. */ (void)bp; (void)args; win = s->focus; DNPRINTF(SWM_D_MISC, "win %#x\n", WINID(win)); if (win == NULL) return; if (FULLSCREEN(win) || WINDOCK(win) || WINDESKTOP(win)) return; r = win->ws->r; ewmh_apply_flags(win, win->ewmh_flags ^ EWMH_F_MAXIMIZED); ewmh_update_wm_state(win); if (ws_maxstack(win->ws)) win->maxstackmax = MAXIMIZED(win); apply_unfocus(win->ws, win); update_win_layer_related(win); refresh_stack(s); update_stacking(s); stack(win->ws->r); update_mapping(s); follow = follow_pointer(s); if (!follow && win_focused(win)) { focus_win(s, win); center_pointer(win->ws->r); } flush(); focus_follow(s, r, NULL); DNPRINTF(SWM_D_MISC, "done\n"); } void floating_toggle(struct swm_screen *s, struct binding *bp, union arg *args) { struct swm_region *r; struct ws_win *win; uint32_t newf; /* Suppress warnings. */ (void)bp; (void)args; win = s->focus; DNPRINTF(SWM_D_MISC, "win %#x\n", WINID(win)); if (win == NULL) return; r = win->ws->r; if ((ws_floating(win->ws) && !ws_root(win->ws) && !BELOW(win)) || FULLSCREEN(win) || win_transient(win)) return; if (MAXIMIZED(win) || BELOW(win)) newf = (win->ewmh_flags & ~EWMH_F_ABOVE); else newf = (win->ewmh_flags ^ EWMH_F_ABOVE); newf &= ~(EWMH_F_MAXIMIZED | EWMH_F_BELOW); if (ws_maxstack(win->ws)) win->maxstackmax = MAXIMIZED(win); else if (ws_root(win->ws)) { if ((r = get_current_region(s))) { if (!ws_floating(r->ws)) newf &= ~EWMH_F_ABOVE; win_to_ws(win, r->ws, SWM_WIN_NOUNMAP); } } ewmh_apply_flags(win, newf); ewmh_update_wm_state(win); update_win_layer_related(win); refresh_stack(s); update_stacking(s); stack(r); update_mapping(s); if (focus_mode != SWM_FOCUS_FOLLOW && win_focused(win)) focus_win(s, win); center_pointer(r); flush(); focus_follow(s, r, NULL); DNPRINTF(SWM_D_MISC, "done\n"); } void fullscreen_toggle(struct swm_screen *s, struct binding *bp, union arg *args) { struct ws_win *win; /* Suppress warnings. */ (void)bp; (void)args; win = s->focus; DNPRINTF(SWM_D_MISC, "win %#x\n", WINID(win)); if (win == NULL) return; if (WINDOCK(win) || WINDESKTOP(win)) return; ewmh_apply_flags(win, win->ewmh_flags ^ EWMH_F_FULLSCREEN); ewmh_update_wm_state(win); update_win_layer_related(win); refresh_stack(s); update_stacking(s); stack(win->ws->r); update_mapping(s); if (win == win->ws->focus) focus_win(s, win); center_pointer(win->ws->r); flush(); DNPRINTF(SWM_D_MISC, "done\n"); } void below_toggle(struct swm_screen *s, struct binding *bp, union arg *args) { struct ws_win *win; /* Suppress warning. */ (void)bp; (void)args; win = s->focus; if (win == NULL) return; ewmh_apply_flags(win, win->ewmh_flags ^ EWMH_F_BELOW); ewmh_update_wm_state(win); update_win_layer_related(win); refresh_stack(s); update_stacking(s); stack(win->ws->r); update_mapping(s); if (win->ws->focus == win) focus_win(s, win); center_pointer(win->ws->r); flush(); DNPRINTF(SWM_D_MISC, "done\n"); } static bool bounds_intersect(struct swm_geometry *b1, struct swm_geometry *b2) { return (!(b1->x + b1->w < b2->x || b1->x > b2->x + b2->w || b1->y + b1->h < b2->y || b1->y > b2->y + b2->h)); } struct swm_geometry get_boundary(struct ws_win *win) { if (win->ws->r) { DNPRINTF(SWM_D_MISC, "r:%d\n", get_region_index(win->ws->r)); return (win->ws->r->g); } if (win->ws->old_r) { DNPRINTF(SWM_D_MISC, "old r:%d\n", get_region_index(win->ws->old_r)); return (win->ws->old_r->g); } /* Workspace not mapped, use screen geometry. */ DNPRINTF(SWM_D_MISC, "root r:%d\n", get_region_index(win->s->r)); return (win->s->r->g); } /* Try to keep window within a boundary. Return true if window was contained, */ static bool contain_window(struct ws_win *win, struct swm_geometry g, int bw, uint32_t opts) { int rt, lt, tp, bm; bool contained = true; if (win == NULL) return (contained); if (!(opts & SWM_CW_SOFTBOUNDARY)) bw = 0; /* * Perpendicular distance of each side of the window to the respective * side of the region boundary. Positive values indicate the side of * the window has passed beyond the region boundary. */ rt = (opts & SWM_CW_RIGHT) ? MAX_X(win) - (g.x + g.w) : bw; lt = (opts & SWM_CW_LEFT) ? g.x - X(win) : bw; bm = (opts & SWM_CW_BOTTOM) ? MAX_Y(win) - (g.y + g.h) : bw; tp = (opts & SWM_CW_TOP) ? g.y - Y(win) : bw; DNPRINTF(SWM_D_MISC, "win %#x, rt: %d, lt: %d, bm: %d, tp: %d, " "SOFTBOUNDARY: %s, HARDBOUNDARY: %s\n", win->id, rt, lt, bm, tp, YESNO(opts & SWM_CW_SOFTBOUNDARY), YESNO(opts & SWM_CW_HARDBOUNDARY)); /* * Disable containment if any of the flagged sides went beyond the * containment boundary, or if containment is disabled. */ if (!(opts & SWM_CW_HARDBOUNDARY || opts & SWM_CW_SOFTBOUNDARY) || (bw != 0 && ((rt > bw) || (lt > bw) || (bm > bw) || (tp > bw)))) { /* Make sure window has at least 1 pixel in the region */ g.x += 1 - WIDTH(win); g.y += 1 - HEIGHT(win); g.w += 2 * WIDTH(win) - 2; g.h += 2 * HEIGHT(win) - 2; contained = false; } constrain_window(win, &g, &opts); return (contained); } /* Move or resize a window so that flagged side(s) fit into the supplied box. */ static void constrain_window(struct ws_win *win, struct swm_geometry *b, uint32_t *opts) { DNPRINTF(SWM_D_MISC, "win %#x, (x,y) w x h: (%d,%d) %d x %d, " "box: (x,y) w x h: (%d,%d) %d x %d, rt: %s, lt: %s, bt: %s, " "tp: %s, allow resize: %s\n", win->id, X(win), Y(win), WIDTH(win), HEIGHT(win), b->x, b->y, b->w, b->h, YESNO(*opts & SWM_CW_RIGHT), YESNO(*opts & SWM_CW_LEFT), YESNO(*opts & SWM_CW_BOTTOM), YESNO(*opts & SWM_CW_TOP), YESNO(*opts & SWM_CW_RESIZABLE)); if ((*opts & SWM_CW_RIGHT) && MAX_X(win) > b->x + b->w) { if (*opts & SWM_CW_RESIZABLE) WIDTH(win) = b->x + b->w - X(win); else X(win) = b->x + b->w - WIDTH(win); } if ((*opts & SWM_CW_LEFT) && X(win) < b->x) { if (*opts & SWM_CW_RESIZABLE) WIDTH(win) -= b->x - X(win); X(win) = b->x; } if ((*opts & SWM_CW_BOTTOM) && MAX_Y(win) > b->y + b->h) { if (*opts & SWM_CW_RESIZABLE) HEIGHT(win) = b->y + b->h - Y(win); else Y(win) = b->y + b->h - HEIGHT(win); } if ((*opts & SWM_CW_TOP) && Y(win) < b->y) { if (*opts & SWM_CW_RESIZABLE) HEIGHT(win) -= b->y - Y(win); Y(win) = b->y; } if (*opts & SWM_CW_RESIZABLE) { if (WIDTH(win) < 1) WIDTH(win) = 1; if (HEIGHT(win) < 1) HEIGHT(win) = 1; } } void draw_frame(struct ws_win *win) { xcb_point_t points[5]; uint32_t gcv[2]; if (win == NULL || border_width == 0) return; if (!win_reparented(win)) { DNPRINTF(SWM_D_EVENT, "win %#x not reparented\n", win->id); return; } if (!win->bordered) { DNPRINTF(SWM_D_EVENT, "win %#x frame disabled\n", win->id); } if (win_focused(win)) { if (win_free(win)) gcv[0] = MAXIMIZED(win) ? win->s->c[SWM_S_COLOR_FOCUS_MAXIMIZED_FREE].pixel : win->s->c[SWM_S_COLOR_FOCUS_FREE].pixel; else gcv[0] = MAXIMIZED(win) ? win->s->c[SWM_S_COLOR_FOCUS_MAXIMIZED].pixel : win->s->c[SWM_S_COLOR_FOCUS].pixel; } else { if (win_free(win)) gcv[0] = MAXIMIZED(win) ? win->s->c[SWM_S_COLOR_UNFOCUS_MAXIMIZED_FREE].pixel : win->s->c[SWM_S_COLOR_UNFOCUS_FREE].pixel; else gcv[0] = MAXIMIZED(win) ? win->s->c[SWM_S_COLOR_UNFOCUS_MAXIMIZED].pixel : win->s->c[SWM_S_COLOR_UNFOCUS].pixel; } points[0].x = points[0].y = border_width / 2; points[1].x = border_width + WIDTH(win) + points[0].x; points[1].y = points[0].y; points[2].x = points[1].x; points[2].y = border_width + HEIGHT(win) + points[0].y; points[3].x = points[0].x; points[3].y = points[2].y; points[4] = points[0]; gcv[1] = border_width; xcb_change_gc(conn, win->s->gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, gcv); xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, win->frame, win->s->gc, 5, points); } void update_window(struct ws_win *win) { uint16_t mask; uint32_t wc[5]; if (!win_reparented(win)) { DNPRINTF(SWM_D_EVENT, "skip win %#x; not reparented\n", win->id); return; } mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_BORDER_WIDTH; /* Reconfigure frame. */ if (win->bordered) { wc[0] = X(win) - border_width; wc[1] = Y(win) - border_width; wc[2] = WIDTH(win) + 2 * border_width; wc[3] = HEIGHT(win) + 2 * border_width; } else { wc[0] = X(win); wc[1] = Y(win); wc[2] = WIDTH(win); wc[3] = HEIGHT(win); } wc[4] = 0; DNPRINTF(SWM_D_EVENT, "win %#x (f:%#x), (x,y) w x h: (%d,%d) %d x %d," " bordered: %s\n", win->id, win->frame, wc[0], wc[1], wc[2], wc[3], YESNO(win->bordered)); xcb_configure_window(conn, win->frame, mask, wc); /* Reconfigure client window. */ wc[0] = wc[1] = win_border(win); wc[2] = WIDTH(win); wc[3] = HEIGHT(win); DNPRINTF(SWM_D_EVENT, "win %#x, (x,y) w x h: (%d,%d) %d x %d, " "bordered: %s\n", win->id, wc[0], wc[1], wc[2], wc[3], YESNO(win->bordered)); xcb_configure_window(conn, win->id, mask, wc); /* * ICCCM 4.2.3 send a synthetic ConfigureNotify to the window with its * geometry in root coordinates. It's redundant when a window is * resized, but Java has special needs... */ config_win(win, NULL); } struct event { STAILQ_ENTRY(event) entry; xcb_generic_event_t *ev; }; STAILQ_HEAD(event_queue, event) events = STAILQ_HEAD_INITIALIZER(events); xcb_generic_event_t * get_next_event(bool dowait) { struct event *ep; xcb_generic_event_t *evt; /* Try queue first. */ if ((ep = STAILQ_FIRST(&events))) { evt = ep->ev; STAILQ_REMOVE_HEAD(&events, entry); free(ep); } else if (dowait) evt = xcb_wait_for_event(conn); else evt = xcb_poll_for_event(conn); return (evt); } void put_back_event(xcb_generic_event_t *evt) { struct event *ep; if ((ep = malloc(sizeof (struct event))) == NULL) err(1, "put_back_event: malloc"); ep->ev = evt; STAILQ_INSERT_HEAD(&events, ep, entry); } /* Peeks at next event to detect auto-repeat. */ bool keyrepeating(xcb_key_release_event_t *kre) { xcb_generic_event_t *evt; /* Ensure repeating keypress is finished processing. */ xcb_aux_sync(conn); if ((evt = get_next_event(false))) { put_back_event(evt); if (XCB_EVENT_RESPONSE_TYPE(evt) == XCB_KEY_PRESS && kre->sequence == evt->sequence && kre->detail == ((xcb_key_press_event_t *)evt)->detail) return (true); } return (false); } bool keybindreleased(struct binding *bp, xcb_key_release_event_t *kre) { if (bp->type == KEYBIND && !keyrepeating(kre) && bp->value == xcb_key_press_lookup_keysym(syms, kre, 0)) return (true); return (false); } #define SWM_RESIZE_STEPS (50) void resize_win(struct ws_win *win, struct binding *bp, int opt) { struct swm_geometry b; xcb_query_pointer_reply_t *xpr = NULL; uint32_t dir; bool inplace = false, step = false; if (win == NULL) return; if (FULLSCREEN(win) || WINDESKTOP(win) || WINDOCK(win)) return; b = get_boundary(win); DNPRINTF(SWM_D_EVENT, "win %#x, floating: %s, transient: %#x\n", win->id, YESNO(ABOVE(win)), win->transient_for); /* Override floating geometry when resizing maximized windows. */ if (MAXIMIZED(win) || ws_floating(win->ws)) { inplace = true; } else if (!(win_transient(win) || ABOVE(win))) return; switch (opt) { case SWM_ARG_ID_WIDTHSHRINK: WIDTH(win) -= SWM_RESIZE_STEPS; step = true; break; case SWM_ARG_ID_WIDTHGROW: WIDTH(win) += SWM_RESIZE_STEPS; step = true; break; case SWM_ARG_ID_HEIGHTSHRINK: HEIGHT(win) -= SWM_RESIZE_STEPS; step = true; break; case SWM_ARG_ID_HEIGHTGROW: HEIGHT(win) += SWM_RESIZE_STEPS; step = true; break; default: break; } if (step) { unsnap_win(win, inplace); flush(); /* It's possible for win to have been freed during flush(). */ if (validate_win(win)) { DNPRINTF(SWM_D_EVENT, "invalid win\n"); return; } contain_window(win, b, boundary_width, SWM_CW_ALLSIDES | SWM_CW_RESIZABLE | SWM_CW_HARDBOUNDARY); update_window(win); store_float_geom(win); return; } contain_window(win, b, boundary_width, SWM_CW_ALLSIDES | SWM_CW_RESIZABLE | SWM_CW_SOFTBOUNDARY); update_window(win); /* get cursor offset from window root */ xpr = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, win->id), NULL); if (xpr == NULL) return; dir = SWM_SIZE_HORZ | SWM_SIZE_VERT; if (xpr->win_x < WIDTH(win) / 2) dir |= SWM_SIZE_HFLIP; if (xpr->win_y < HEIGHT(win) / 2) dir |= SWM_SIZE_VFLIP; resize_win_pointer(win, bp, xpr->root_x, xpr->root_y, dir, (opt == SWM_ARG_ID_CENTER)); free(xpr); DNPRINTF(SWM_D_EVENT, "done\n"); } void resize_win_pointer(struct ws_win *win, struct binding *bp, uint32_t x_root, uint32_t y_root, uint32_t dir, bool center) { struct swm_geometry g, b; xcb_cursor_t cursor; xcb_generic_event_t *evt; xcb_motion_notify_event_t *mne; xcb_button_press_event_t *bpe; xcb_key_press_event_t *kpe; xcb_client_message_event_t *cme; xcb_timestamp_t timestamp = 0, mintime; int dx, dy; bool resizing; bool inplace = false; if (MAXIMIZED(win) || ws_floating(win->ws)) inplace = true; else if (!(win_transient(win) || ABOVE(win))) return; if (center) cursor = cursors[XC_SIZING].cid; else switch (dir) { case SWM_SIZE_TOPLEFT: cursor = cursors[XC_TOP_LEFT_CORNER].cid; break; case SWM_SIZE_TOP: cursor = cursors[XC_TOP_SIDE].cid; break; case SWM_SIZE_TOPRIGHT: cursor = cursors[XC_TOP_RIGHT_CORNER].cid; break; case SWM_SIZE_RIGHT: cursor = cursors[XC_RIGHT_SIDE].cid; break; case SWM_SIZE_BOTTOMRIGHT: cursor = cursors[XC_BOTTOM_RIGHT_CORNER].cid; break; case SWM_SIZE_BOTTOM: cursor = cursors[XC_BOTTOM_SIDE].cid; break; case SWM_SIZE_BOTTOMLEFT: cursor = cursors[XC_BOTTOM_LEFT_CORNER].cid; break; case SWM_SIZE_LEFT: cursor = cursors[XC_LEFT_SIDE].cid; break; default: cursor = cursors[XC_SIZING].cid; break; } xcb_grab_pointer(conn, 0, win->id, MOUSEMASK, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_WINDOW_NONE, cursor, XCB_CURRENT_TIME); /* Release keyboard freeze if called via keybind. */ if (bp->type == KEYBIND) xcb_allow_events(conn, XCB_ALLOW_ASYNC_KEYBOARD, XCB_CURRENT_TIME); /* Offer another means of termination, as recommended by the spec. */ xcb_grab_key(conn, 0, win->id, XCB_MOD_MASK_ANY, cancel_keycode, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_SYNC); unsnap_win(win, inplace); xcb_flush(conn); mintime = 1000 / win->s->rate; g = win->g; resizing = true; while (resizing && (evt = get_next_event(true))) { switch (XCB_EVENT_RESPONSE_TYPE(evt)) { case XCB_BUTTON_RELEASE: bpe = (xcb_button_press_event_t *)evt; event_time = bpe->time; if (bp->type == BTNBIND && (bp->value == bpe->detail || bp->value == XCB_BUTTON_INDEX_ANY)) resizing = false; break; case XCB_KEY_RELEASE: kpe = (xcb_key_press_event_t *)evt; event_time = kpe->time; if (keybindreleased(bp, kpe)) resizing = false; break; case XCB_MOTION_NOTIFY: mne = (xcb_motion_notify_event_t *)evt; event_time = mne->time; DNPRINTF(SWM_D_EVENT, "MOTION_NOTIFY: root: %#x\n", mne->root); /* cursor offset/delta from start of the operation */ dx = mne->root_x - x_root; dy = mne->root_y - y_root; if (dir & SWM_SIZE_VERT) { if (dir & SWM_SIZE_VFLIP) dy = -dy; if (center) { if (g.h / 2 + dy < 1) dy = 1 - g.h / 2; Y(win) = g.y - dy; HEIGHT(win) = g.h + 2 * dy; } else { if (g.h + dy < 1) dy = 1 - g.h; if (dir & SWM_SIZE_VFLIP) Y(win) = g.y - dy; HEIGHT(win) = g.h + dy; } } if (dir & SWM_SIZE_HORZ) { if (dir & SWM_SIZE_HFLIP) dx = -dx; if (center) { if (g.w / 2 + dx < 1) dx = 1 - g.w / 2; X(win) = g.x - dx; WIDTH(win) = g.w + 2 * dx; } else { if (g.w + dx < 1) dx = 1 - g.w; if (dir & SWM_SIZE_HFLIP) X(win) = g.x - dx; WIDTH(win) = g.w + dx; } } update_gravity(win); /* Don't sync faster than the current rate limit. */ if ((mne->time - timestamp) > mintime) { store_float_geom(win); timestamp = mne->time; regionize(win, mne->root_x, mne->root_y); b = get_boundary(win); contain_window(win, b, boundary_width, SWM_CW_ALLSIDES | SWM_CW_RESIZABLE | SWM_CW_HARDBOUNDARY | SWM_CW_SOFTBOUNDARY); update_window(win); xcb_flush(conn); } break; case XCB_BUTTON_PRESS: bpe = (xcb_button_press_event_t *)evt; event_time = bpe->time; /* Ignore. */ DNPRINTF(SWM_D_EVENT, "BUTTON_PRESS ignored\n"); xcb_allow_events(conn, XCB_ALLOW_ASYNC_POINTER, bpe->time); xcb_flush(conn); break; case XCB_KEY_PRESS: kpe = (xcb_key_press_event_t *)evt; event_time = kpe->time; /* Handle cancel_key. */ if ((xcb_key_press_lookup_keysym(syms, kpe, 0)) == cancel_key) resizing = false; /* Ignore. */ DNPRINTF(SWM_D_EVENT, "KEY_PRESS ignored\n"); xcb_allow_events(conn, XCB_ALLOW_ASYNC_KEYBOARD, kpe->time); xcb_flush(conn); break; case XCB_CLIENT_MESSAGE: cme = (xcb_client_message_event_t *)evt; if (cme->type == ewmh[_NET_WM_MOVERESIZE].atom) { DNPRINTF(SWM_D_EVENT, "_NET_WM_MOVERESIZE\n"); if (cme->data.data32[2] == EWMH_WM_MOVERESIZE_CANCEL) resizing = false; } else clientmessage(cme); break; default: /* Window can be freed or lose focus here. */ event_handle(evt); if (validate_win(win)) { DNPRINTF(SWM_D_EVENT, "invalid win\n"); goto out; } if (!win_focused(win)) { DNPRINTF(SWM_D_EVENT, "win lost focus\n"); goto out; } break; } free(evt); } if (timestamp) { contain_window(win, b, boundary_width, SWM_CW_ALLSIDES | SWM_CW_RESIZABLE | SWM_CW_HARDBOUNDARY | SWM_CW_SOFTBOUNDARY); update_window(win); } store_float_geom(win); out: xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); xcb_flush(conn); DNPRINTF(SWM_D_EVENT, "done\n"); } void resize(struct swm_screen *s, struct binding *bp, union arg *args) { struct ws_win *win = NULL; if (args->id != SWM_ARG_ID_DONTCENTER && args->id != SWM_ARG_ID_CENTER) /* keyboard resize uses the focus window. */ win = s->focus; else /* mouse resize uses pointer window. */ win = get_pointer_win(s); if (win == NULL) return; resize_win(win, bp, args->id); if (args->id && bp->type == KEYBIND) center_pointer(win->ws->r); flush(); } /* Try to set window region based on supplied coordinates or window center. */ void regionize(struct ws_win *win, int x, int y) { struct swm_region *r, *r_orig; if (win == NULL) return; r = region_under(win->s, x, y); if (r == NULL) { r = region_under(win->s, X(win) + WIDTH(win) / 2, Y(win) + HEIGHT(win) / 2); if (r == NULL) return; } /* Only change focused region with ws-free windows. */ if (win_free(win)) { set_region(r); update_bars(r->s); return; } if (r != win->ws->r) { apply_unfocus(r->ws, NULL); win->g_float = win->g; update_gravity(win); /* Maintain original position. */ win->g_float.x -= win->g_grav.x; win->g_float.y -= win->g_grav.y; win->g_floatref = r->g; win->g_floatref_root = false; if (!ws_floating(r->ws)) win->ewmh_flags |= EWMH_F_ABOVE; r_orig = win->ws->r; win_to_ws(win, r->ws, 0); set_region(r); update_win_layer_related(win); refresh_stack(win->s); update_stacking(win->s); /* Need to restack both regions. */ stack(r_orig); stack(r); update_mapping(win->s); update_bars(r->s); } } static void unsnap_win(struct ws_win *win, bool inplace) { uint32_t newf; DNPRINTF(SWM_D_MISC, "win %#x inplace: %s\n", WINID(win), YESNO(inplace)); if (inplace) { win->g_float = win->g; update_gravity(win); /* Maintain original position. */ win->g_float.x -= win->g_grav.x; win->g_float.y -= win->g_grav.y; win->g_floatref = get_boundary(win); win->g_floatref_root = false; } newf = (win->ewmh_flags | SWM_F_MANUAL) & ~EWMH_F_MAXIMIZED; if (!ws_floating(win->ws) || win_free(win)) newf |= EWMH_F_ABOVE; ewmh_apply_flags(win, newf); ewmh_update_wm_state(win); update_win_layer_related(win); refresh_stack(win->s); update_stacking(win->s); if (inplace) { stack(win->ws->r); update_mapping(win->s); } } #define SWM_MOVE_STEPS (50) void move_win(struct ws_win *win, struct binding *bp, int opt) { xcb_query_pointer_reply_t *qpr = NULL; bool step = false, inplace; if (win == NULL) return; if (FULLSCREEN(win) || WINDESKTOP(win) || WINDOCK(win)) return; DNPRINTF(SWM_D_EVENT, "win %#x, floating: %s, transient: %#x\n", win->id, YESNO(ABOVE(win)), win->transient_for); switch (opt) { case SWM_ARG_ID_MOVELEFT: X(win) -= (SWM_MOVE_STEPS - border_width); step = true; break; case SWM_ARG_ID_MOVERIGHT: X(win) += (SWM_MOVE_STEPS - border_width); step = true; break; case SWM_ARG_ID_MOVEUP: Y(win) -= (SWM_MOVE_STEPS - border_width); step = true; break; case SWM_ARG_ID_MOVEDOWN: Y(win) += (SWM_MOVE_STEPS - border_width); step = true; break; default: break; } if (step) { inplace = (!win_floating(win) || MAXIMIZED(win)); unsnap_win(win, inplace); flush(); /* It's possible for win to have been freed during flush(). */ if (validate_win(win)) { DNPRINTF(SWM_D_EVENT, "invalid win.\n"); goto out; } regionize(win, -1, -1); contain_window(win, get_boundary(win), boundary_width, SWM_CW_ALLSIDES); update_window(win); store_float_geom(win); return; } /* get cursor offset from window root */ qpr = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, win->id), NULL); if (qpr == NULL) return; move_win_pointer(win, bp, qpr->root_x, qpr->root_y); free(qpr); out: DNPRINTF(SWM_D_EVENT, "done\n"); } void move_win_pointer(struct ws_win *win, struct binding *bp, uint32_t x_root, uint32_t y_root) { struct swm_geometry g_orig, b; xcb_generic_event_t *evt; xcb_motion_notify_event_t *mne; xcb_button_press_event_t *bpe; xcb_key_press_event_t *kpe; xcb_client_message_event_t *cme; xcb_timestamp_t timestamp = 0, mintime; int dx, dy; bool moving, snapped, inplace; xcb_grab_pointer(conn, 0, win->id, MOUSEMASK, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_WINDOW_NONE, cursors[XC_FLEUR].cid, XCB_CURRENT_TIME); /* Release keyboard freeze if called via keybind. */ if (bp->type == KEYBIND) xcb_allow_events(conn, XCB_ALLOW_ASYNC_KEYBOARD, XCB_CURRENT_TIME); /* Offer another means of termination, as recommended by the spec. */ xcb_grab_key(conn, 0, win->id, XCB_MOD_MASK_ANY, cancel_keycode, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_SYNC); g_orig = win->g; b = get_boundary(win); inplace = (!win_floating(win) || MAXIMIZED(win)); snapped = (snap_range && inplace); if (!snapped) { unsnap_win(win, inplace); regionize(win, x_root, y_root); contain_window(win, b, boundary_width, SWM_CW_ALLSIDES | SWM_CW_SOFTBOUNDARY); update_window(win); } xcb_flush(conn); dx = x_root - X(win); dy = y_root - Y(win); mintime = 1000 / win->s->rate; moving = true; while (moving && (evt = get_next_event(true))) { switch (XCB_EVENT_RESPONSE_TYPE(evt)) { case XCB_BUTTON_RELEASE: bpe = (xcb_button_press_event_t *)evt; event_time = bpe->time; if (bp->type == BTNBIND && (bp->value == bpe->detail || bp->value == XCB_BUTTON_INDEX_ANY)) moving = false; xcb_allow_events(conn, XCB_ALLOW_ASYNC_POINTER, bpe->time); xcb_flush(conn); break; case XCB_KEY_RELEASE: kpe = (xcb_key_press_event_t *)evt; event_time = kpe->time; if (keybindreleased(bp, kpe)) moving = false; xcb_allow_events(conn, XCB_ALLOW_ASYNC_KEYBOARD, kpe->time); xcb_flush(conn); break; case XCB_MOTION_NOTIFY: mne = (xcb_motion_notify_event_t *)evt; event_time = mne->time; DNPRINTF(SWM_D_EVENT, "MOTION_NOTIFY: root: %#x time: " "%#x, root_x: %d, root_y: %d\n", mne->root, mne->time, mne->root_x, mne->root_y); X(win) = mne->root_x - dx; Y(win) = mne->root_y - dy; /* Don't sync faster than the current rate limit. */ if ((mne->time - timestamp) > mintime) { timestamp = mne->time; if (snapped && !contain_window(win, g_orig, snap_range, SWM_CW_ALLSIDES | SWM_CW_SOFTBOUNDARY)) { unsnap_win(win, inplace); snapped = false; } else { regionize(win, mne->root_x, mne->root_y); b = get_boundary(win); contain_window(win, b, boundary_width, SWM_CW_ALLSIDES | SWM_CW_SOFTBOUNDARY); } update_window(win); xcb_flush(conn); } break; case XCB_BUTTON_PRESS: bpe = (xcb_button_press_event_t *)evt; event_time = bpe->time; /* Thaw and ignore. */ xcb_allow_events(conn, XCB_ALLOW_ASYNC_POINTER, bpe->time); xcb_flush(conn); break; case XCB_KEY_PRESS: kpe = (xcb_key_press_event_t *)evt; event_time = kpe->time; /* Handle cancel_key. */ if ((xcb_key_press_lookup_keysym(syms, kpe, 0)) == cancel_key) moving = false; /* Ignore. */ xcb_allow_events(conn, XCB_ALLOW_ASYNC_KEYBOARD, kpe->time); xcb_flush(conn); break; case XCB_CLIENT_MESSAGE: cme = (xcb_client_message_event_t *)evt; if (cme->type == ewmh[_NET_WM_MOVERESIZE].atom) { DNPRINTF(SWM_D_EVENT, "_NET_WM_MOVERESIZE\n"); if (cme->data.data32[2] == EWMH_WM_MOVERESIZE_CANCEL) moving = false; } else clientmessage(cme); break; default: /* Window can be freed or lose focus here. */ event_handle(evt); if (validate_win(win)) { DNPRINTF(SWM_D_EVENT, "invalid win\n"); goto out; } if (!win_focused(win)) { DNPRINTF(SWM_D_EVENT, "win lost focus\n"); goto out; } break; } free(evt); } if (snapped) { contain_window(win, g_orig, snap_range, SWM_CW_ALLSIDES | SWM_CW_SOFTBOUNDARY); update_window(win); } else if (timestamp) { b = get_boundary(win); contain_window(win, b, boundary_width, SWM_CW_ALLSIDES | SWM_CW_SOFTBOUNDARY); update_window(win); } store_float_geom(win); out: xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); xcb_flush(conn); DNPRINTF(SWM_D_EVENT, "done\n"); } void move(struct swm_screen *s, struct binding *bp, union arg *args) { struct ws_win *win = NULL; if (args->id) { /* Keyboard move uses the focus window. */ win = s->focus; /* Disallow move_ on tiled. */ if (win && !win_transient(win) && !ABOVE(win) && !ws_floating(win->ws)) return; } else /* Mouse move uses the pointer window. */ win = get_pointer_win(s); if (win == NULL) return; move_win(win, bp, args->id); if (args->id && bp->type == KEYBIND) center_pointer(win->ws->r); flush(); } /* action definitions */ struct action { char name[SWM_FUNCNAME_LEN]; void (*func)(struct swm_screen *, struct binding *, union arg *); uint32_t flags; union arg args; } actions[FN_INVALID + 2] = { /* name function argument */ { "focus_free", focus, 0, {.id = SWM_ARG_ID_FOCUSFREE} }, { "free_toggle", free_toggle, 0, {0} }, { "bar_toggle", bar_toggle, 0, {.id = SWM_ARG_ID_BAR_TOGGLE} }, { "bar_toggle_ws", bar_toggle, 0, {.id = SWM_ARG_ID_BAR_TOGGLE_WS} }, { "button2", pressbutton, 0, {.id = 2} }, { "cycle_layout", switchlayout, 0, {.id = SWM_ARG_ID_CYCLE_LAYOUT} }, { "flip_layout", stack_config, 0, {.id = SWM_ARG_ID_FLIPLAYOUT} }, { "float_toggle", floating_toggle,0, {0} }, { "below_toggle", below_toggle, 0, {0} }, { "focus", focus_pointer, 0, {0} }, { "focus_main", focus, 0, {.id = SWM_ARG_ID_FOCUSMAIN} }, { "focus_next", focus, 0, {.id = SWM_ARG_ID_FOCUSNEXT} }, { "focus_prev", focus, 0, {.id = SWM_ARG_ID_FOCUSPREV} }, { "focus_prior", focus, 0, {.id = SWM_ARG_ID_FOCUSPRIOR} }, { "focus_urgent", focus, 0, {.id = SWM_ARG_ID_FOCUSURGENT} }, { "fullscreen_toggle", fullscreen_toggle, 0, {0} }, { "maximize_toggle", maximize_toggle,0, {0} }, { "height_grow", resize, 0, {.id = SWM_ARG_ID_HEIGHTGROW} }, { "height_shrink", resize, 0, {.id = SWM_ARG_ID_HEIGHTSHRINK} }, { "iconify", iconify, 0, {0} }, { "layout_vertical", switchlayout, 0, {.id = SWM_ARG_ID_LAYOUT_VERTICAL} }, { "layout_horizontal", switchlayout, 0, {.id = SWM_ARG_ID_LAYOUT_HORIZONTAL} }, { "layout_max", switchlayout, 0, {.id = SWM_ARG_ID_LAYOUT_MAX} }, { "layout_floating", switchlayout, 0, {.id = SWM_ARG_ID_LAYOUT_FLOATING} }, { "master_shrink", stack_config, 0, {.id = SWM_ARG_ID_MASTERSHRINK} }, { "master_grow", stack_config, 0, {.id = SWM_ARG_ID_MASTERGROW} }, { "master_add", stack_config, 0, {.id = SWM_ARG_ID_MASTERADD} }, { "master_del", stack_config, 0, {.id = SWM_ARG_ID_MASTERDEL} }, { "move", move, FN_F_NOREPLAY, {0} }, { "move_down", move, 0, {.id = SWM_ARG_ID_MOVEDOWN} }, { "move_left", move, 0, {.id = SWM_ARG_ID_MOVELEFT} }, { "move_right", move, 0, {.id = SWM_ARG_ID_MOVERIGHT} }, { "move_up", move, 0, {.id = SWM_ARG_ID_MOVEUP} }, { "mvrg_1", send_to_rg, 0, {.id = 1} }, { "mvrg_2", send_to_rg, 0, {.id = 2} }, { "mvrg_3", send_to_rg, 0, {.id = 3} }, { "mvrg_4", send_to_rg, 0, {.id = 4} }, { "mvrg_5", send_to_rg, 0, {.id = 5} }, { "mvrg_6", send_to_rg, 0, {.id = 6} }, { "mvrg_7", send_to_rg, 0, {.id = 7} }, { "mvrg_8", send_to_rg, 0, {.id = 8} }, { "mvrg_9", send_to_rg, 0, {.id = 9} }, { "mvrg_next", send_to_rg_relative, 0, {.id = 1} }, { "mvrg_prev", send_to_rg_relative, 0, {.id = -1} }, { "mvws_1", send_to_ws, 0, {.id = 0} }, { "mvws_2", send_to_ws, 0, {.id = 1} }, { "mvws_3", send_to_ws, 0, {.id = 2} }, { "mvws_4", send_to_ws, 0, {.id = 3} }, { "mvws_5", send_to_ws, 0, {.id = 4} }, { "mvws_6", send_to_ws, 0, {.id = 5} }, { "mvws_7", send_to_ws, 0, {.id = 6} }, { "mvws_8", send_to_ws, 0, {.id = 7} }, { "mvws_9", send_to_ws, 0, {.id = 8} }, { "mvws_10", send_to_ws, 0, {.id = 9} }, { "mvws_11", send_to_ws, 0, {.id = 10} }, { "mvws_12", send_to_ws, 0, {.id = 11} }, { "mvws_13", send_to_ws, 0, {.id = 12} }, { "mvws_14", send_to_ws, 0, {.id = 13} }, { "mvws_15", send_to_ws, 0, {.id = 14} }, { "mvws_16", send_to_ws, 0, {.id = 15} }, { "mvws_17", send_to_ws, 0, {.id = 16} }, { "mvws_18", send_to_ws, 0, {.id = 17} }, { "mvws_19", send_to_ws, 0, {.id = 18} }, { "mvws_20", send_to_ws, 0, {.id = 19} }, { "mvws_21", send_to_ws, 0, {.id = 20} }, { "mvws_22", send_to_ws, 0, {.id = 21} }, { "name_workspace", name_workspace, 0, {0} }, { "prior_layout", switchlayout, 0, {.id = SWM_ARG_ID_PRIOR_LAYOUT} }, { "quit", quit, 0, {0} }, { "raise", raise_focus, 0, {0} }, { "raise_toggle", raise_toggle, 0, {0} }, { "resize", resize, FN_F_NOREPLAY, {.id = SWM_ARG_ID_DONTCENTER} }, { "resize_centered", resize, FN_F_NOREPLAY, {.id = SWM_ARG_ID_CENTER} }, { "restart", restart, 0, {0} }, { "restart_of_day", restart, 0, {SWM_ARG_ID_RESTARTOFDAY} }, { "rg_1", focusrg, 0, {.id = 1} }, { "rg_2", focusrg, 0, {.id = 2} }, { "rg_3", focusrg, 0, {.id = 3} }, { "rg_4", focusrg, 0, {.id = 4} }, { "rg_5", focusrg, 0, {.id = 5} }, { "rg_6", focusrg, 0, {.id = 6} }, { "rg_7", focusrg, 0, {.id = 7} }, { "rg_8", focusrg, 0, {.id = 8} }, { "rg_9", focusrg, 0, {.id = 9} }, { "rg_move_next", cyclerg, 0, {.id = SWM_ARG_ID_CYCLERG_MOVE_UP} }, { "rg_move_prev", cyclerg, 0, {.id = SWM_ARG_ID_CYCLERG_MOVE_DOWN} }, { "rg_next", cyclerg, 0, {.id = SWM_ARG_ID_CYCLERG_UP} }, { "rg_prev", cyclerg, 0, {.id = SWM_ARG_ID_CYCLERG_DOWN} }, { "screen_next", cyclerg, 0, {.id = SWM_ARG_ID_CYCLERG_UP} }, { "screen_prev", cyclerg, 0, {.id = SWM_ARG_ID_CYCLERG_DOWN} }, { "search_win", search_win, 0, {0} }, { "search_workspace", search_workspace, 0, {0} }, { "spawn_custom", NULL, 0, {0} }, { "stack_balance", stack_config, 0, {.id = SWM_ARG_ID_STACKBALANCE} }, { "stack_inc", stack_config, 0, {.id = SWM_ARG_ID_STACKINC} }, { "stack_dec", stack_config, 0, {.id = SWM_ARG_ID_STACKDEC} }, { "stack_reset", stack_config, 0, {.id = SWM_ARG_ID_STACKRESET} }, { "swap_main", swapwin, 0, {.id = SWM_ARG_ID_SWAPMAIN} }, { "swap_next", swapwin, 0, {.id = SWM_ARG_ID_SWAPNEXT} }, { "swap_prev", swapwin, 0, {.id = SWM_ARG_ID_SWAPPREV} }, { "uniconify", uniconify, 0, {0} }, { "version", version, 0, {0} }, { "width_grow", resize, 0, {.id = SWM_ARG_ID_WIDTHGROW} }, { "width_shrink", resize, 0, {.id = SWM_ARG_ID_WIDTHSHRINK} }, { "wind_del", wkill, 0, {.id = SWM_ARG_ID_DELETEWINDOW} }, { "wind_kill", wkill, 0, {.id = SWM_ARG_ID_KILLWINDOW} }, { "ws_1", switchws, 0, {.id = 0} }, { "ws_2", switchws, 0, {.id = 1} }, { "ws_3", switchws, 0, {.id = 2} }, { "ws_4", switchws, 0, {.id = 3} }, { "ws_5", switchws, 0, {.id = 4} }, { "ws_6", switchws, 0, {.id = 5} }, { "ws_7", switchws, 0, {.id = 6} }, { "ws_8", switchws, 0, {.id = 7} }, { "ws_9", switchws, 0, {.id = 8} }, { "ws_10", switchws, 0, {.id = 9} }, { "ws_11", switchws, 0, {.id = 10} }, { "ws_12", switchws, 0, {.id = 11} }, { "ws_13", switchws, 0, {.id = 12} }, { "ws_14", switchws, 0, {.id = 13} }, { "ws_15", switchws, 0, {.id = 14} }, { "ws_16", switchws, 0, {.id = 15} }, { "ws_17", switchws, 0, {.id = 16} }, { "ws_18", switchws, 0, {.id = 17} }, { "ws_19", switchws, 0, {.id = 18} }, { "ws_20", switchws, 0, {.id = 19} }, { "ws_21", switchws, 0, {.id = 20} }, { "ws_22", switchws, 0, {.id = 21} }, { "ws_empty", emptyws, 0, {.id = SWM_ARG_ID_WS_EMPTY} }, { "ws_empty_move", emptyws, 0, {.id = SWM_ARG_ID_WS_EMPTY_MOVE} }, { "ws_next", cyclews, 0, {.id = SWM_ARG_ID_CYCLEWS_UP} }, { "ws_next_all", cyclews, 0, {.id = SWM_ARG_ID_CYCLEWS_UP_ALL} }, { "ws_next_move", cyclews, 0, {.id = SWM_ARG_ID_CYCLEWS_MOVE_UP} }, { "ws_prev", cyclews, 0, {.id = SWM_ARG_ID_CYCLEWS_DOWN} }, { "ws_prev_all", cyclews, 0, {.id = SWM_ARG_ID_CYCLEWS_DOWN_ALL} }, { "ws_prev_move", cyclews, 0, {.id = SWM_ARG_ID_CYCLEWS_MOVE_DOWN} }, { "ws_prior", priorws, 0, {0} }, { "debug_toggle", debug_toggle, 0, {0} }, { "dumpwins", dumpwins, 0, {0} }, /* ALWAYS last: */ { "invalid action", NULL, 0, {0} }, }; void update_modkey(uint16_t mod) { struct binding *bp; /* Replace all instances of the old mod key. */ RB_FOREACH(bp, binding_tree, &bindings) if (bp->mod & mod_key) bp->mod = (bp->mod & ~mod_key) | mod; mod_key = mod; } void update_keycodes(void) { if ((cancel_keycode = get_keysym_keycode(cancel_key)) == XCB_NO_SYMBOL) cancel_keycode = get_keysym_keycode(CANCELKEY); } int spawn_expand(struct swm_region *r, union arg *args, const char *spawn_name, char ***ret_args) { struct spawn_prog *prog = NULL; int i, c; char *ap, **real_args; /* Suppress warning. */ (void)args; DNPRINTF(SWM_D_SPAWN, "%s\n", spawn_name); /* find program */ TAILQ_FOREACH(prog, &spawns, entry) { if (strcasecmp(spawn_name, prog->name) == 0) break; } if (prog == NULL) { warnx("spawn_custom: program %s not found", spawn_name); return (-1); } /* make room for expanded args */ if ((real_args = calloc(prog->argc + 1, sizeof(char *))) == NULL) err(1, "spawn_custom: calloc real_args"); /* expand spawn_args into real_args */ for (i = c = 0; i < prog->argc; i++) { ap = prog->argv[i]; DNPRINTF(SWM_D_SPAWN, "raw arg: %s\n", ap); if (strcasecmp(ap, "$bar_border") == 0) { real_args[c] = color_to_rgb(&r->s->c[SWM_S_COLOR_BAR_BORDER]); } else if (strcasecmp(ap, "$bar_color") == 0) { real_args[c] = color_to_rgb(&r->s->c[SWM_S_COLOR_BAR]); } else if (strcasecmp(ap, "$bar_color_selected") == 0) { real_args[c] = color_to_rgb(&r->s->c[SWM_S_COLOR_BAR_SELECTED]); } else if (strcasecmp(ap, "$bar_font") == 0) { if ((real_args[c] = strdup(bar_fonts)) == NULL) err(1, "spawn_custom: strdup"); } else if (strcasecmp(ap, "$bar_font_color") == 0) { real_args[c] = color_to_rgb(&r->s->c[SWM_S_COLOR_BAR_FONT]); } else if (strcasecmp(ap, "$bar_font_color_selected") == 0) { real_args[c] = color_to_rgb( &r->s->c[SWM_S_COLOR_BAR_FONT_SELECTED]); } else if (strcasecmp(ap, "$color_focus_free") == 0) { real_args[c] = color_to_rgb(&r->s->c[SWM_S_COLOR_FOCUS_FREE]); } else if (strcasecmp(ap, "$color_focus_maximized_free") == 0) { real_args[c] = color_to_rgb( &r->s->c[SWM_S_COLOR_FOCUS_MAXIMIZED_FREE]); } else if (strcasecmp(ap, "$color_unfocus_free") == 0) { real_args[c] = color_to_rgb(&r->s->c[SWM_S_COLOR_UNFOCUS_FREE]); } else if (strcasecmp(ap, "$color_unfocus_maximized_free") == 0) { real_args[c] = color_to_rgb( &r->s->c[SWM_S_COLOR_UNFOCUS_MAXIMIZED_FREE]); } else if (strcasecmp(ap, "$color_focus") == 0) { real_args[c] = color_to_rgb(&r->s->c[SWM_S_COLOR_FOCUS]); } else if (strcasecmp(ap, "$color_focus_maximized") == 0) { real_args[c] = color_to_rgb( &r->s->c[SWM_S_COLOR_FOCUS_MAXIMIZED]); } else if (strcasecmp(ap, "$color_unfocus") == 0) { real_args[c] = color_to_rgb(&r->s->c[SWM_S_COLOR_UNFOCUS]); } else if (strcasecmp(ap, "$color_unfocus_maximized") == 0) { real_args[c] = color_to_rgb( &r->s->c[SWM_S_COLOR_UNFOCUS_MAXIMIZED]); } else if (strcasecmp(ap, "$region_index") == 0) { if (asprintf(&real_args[c], "%d", get_region_index(r)) < 1) err(1, "spawn_custom region index"); } else if (strcasecmp(ap, "$workspace_index") == 0) { if (asprintf(&real_args[c], "%d", r->ws->idx + 1) < 1) err(1, "spawn_custom workspace index"); } else if (strcasecmp(ap, "$dmenu_bottom") == 0) { if (!bar_at_bottom) continue; if ((real_args[c] = strdup("-b")) == NULL) err(1, "spawn_custom workspace index"); } else { /* no match --> copy as is */ if ((real_args[c] = strdup(ap)) == NULL) err(1, "spawn_custom strdup(ap)"); } DNPRINTF(SWM_D_SPAWN, "cooked arg: %s\n", real_args[c]); ++c; } if (swm_debug & SWM_D_SPAWN) { DPRINTF("result: "); for (i = 0; i < c; ++i) DPRINTF("\"%s\" ", real_args[i]); DPRINTF("\n"); } *ret_args = real_args; return (c); } void spawn_custom(struct swm_region *r, union arg *args, const char *spawn_name) { union arg a; char **real_args; int spawn_argc, i; if (r == NULL) return; if ((spawn_argc = spawn_expand(r, args, spawn_name, &real_args)) < 0) return; a.argv = real_args; if (fork() == 0) spawn(r->ws->idx, &a, true); for (i = 0; i < spawn_argc; i++) free(real_args[i]); free(real_args); } void spawn_select(struct swm_region *r, union arg *args, const char *spawn_name, int *pid) { union arg a; char **real_args; int i, spawn_argc; if ((spawn_argc = spawn_expand(r, args, spawn_name, &real_args)) < 0) return; a.argv = real_args; if (pipe(select_list_pipe) == -1) err(1, "pipe error"); if (pipe(select_resp_pipe) == -1) err(1, "pipe error"); if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) err(1, "could not disable SIGPIPE"); switch (*pid = fork()) { case -1: err(1, "cannot fork"); break; case 0: /* child */ if (dup2(select_list_pipe[0], STDIN_FILENO) == -1) { warn("spawn_select: dup2"); _exit(1); } if (dup2(select_resp_pipe[1], STDOUT_FILENO) == -1) { warn("spawn_select: dup2"); _exit(1); } close(select_list_pipe[1]); close(select_resp_pipe[0]); spawn(r->ws->idx, &a, false); break; default: /* parent */ close(select_list_pipe[0]); close(select_resp_pipe[1]); break; } for (i = 0; i < spawn_argc; i++) free(real_args[i]); free(real_args); } /* Argument tokenizer. */ char * argsep(char **sp) { char *arg, *cp, *next; bool single_quoted = false, double_quoted = false; if (*sp == NULL) return (NULL); /* Eat and move characters until end of argument is found. */ for (arg = next = cp = *sp; *cp != '\0'; ++cp) { if (!double_quoted && *cp == '\'') { /* Eat single-quote. */ single_quoted = !single_quoted; } else if (!single_quoted && *cp == '"') { /* Eat double-quote. */ double_quoted = !double_quoted; } else if (!single_quoted && *cp == '\\' && *(cp + 1) == '"') { /* Eat backslash; copy escaped character to arg. */ *next++ = *(++cp); } else if (!single_quoted && !double_quoted && *cp == '\\' && (*(cp + 1) == '\'' || *(cp + 1) == ' ')) { /* Eat backslash; move escaped character. */ *next++ = *(++cp); } else if (!single_quoted && !double_quoted && (*cp == ' ' || *cp == '\t')) { /* Terminate argument. */ *next++ = '\0'; /* Point sp to beginning of next argument. */ *sp = ++cp; break; } else { /* Move regular character. */ *next++ = *cp; } } /* Terminate argument if end of string. */ if (*cp == '\0') { *next = '\0'; *sp = NULL; } return (arg); } /* Process escape chars in string and return allocated result. */ char * unescape_value(const char *value) { const char *vp; char *result, *rp; bool single_quoted = false, double_quoted = false; if (value == NULL) return (NULL); result = malloc(strlen(value) + 1); if (result == NULL) err(1, "unescape_value: malloc"); for (rp = result, vp = value; *vp != '\0'; ++vp) { if (*vp == '\'' && !double_quoted) single_quoted = !single_quoted; else if (*vp == '\"' && !single_quoted) double_quoted = !double_quoted; else if (*vp == '\\' && ((single_quoted && *(vp + 1) == '\'') || (double_quoted && *(vp + 1) == '\"') || (!single_quoted && !double_quoted))) *rp++ = *(++vp); else *rp++ = *vp; } /* Ensure result is terminated. */ *rp = '\0'; return (result); } void spawn_insert(const char *name, const char *args, int flags) { struct spawn_prog *sp; char *arg, *cp, *ptr; DNPRINTF(SWM_D_SPAWN, "%s[%s]\n", name, args); if (args == NULL || *args == '\0') return; if ((sp = calloc(1, sizeof *sp)) == NULL) err(1, "spawn_insert: calloc"); if ((sp->name = strdup(name)) == NULL) err(1, "spawn_insert: strdup"); /* Convert the arguments to an argument list. */ if ((ptr = cp = strdup(args)) == NULL) err(1, "spawn_insert: strdup"); while ((arg = argsep(&ptr)) != NULL) { /* Null argument; skip it. */ if (*arg == '\0') continue; sp->argc++; if ((sp->argv = realloc(sp->argv, sp->argc * sizeof *sp->argv)) == NULL) err(1, "spawn_insert: realloc"); if ((sp->argv[sp->argc - 1] = strdup(arg)) == NULL) err(1, "spawn_insert: strdup"); } free(cp); sp->flags = flags; if (sp->argv != NULL) { DNPRINTF(SWM_D_SPAWN, "arg %d: [%s]\n", sp->argc, sp->argv[sp->argc-1]); } TAILQ_INSERT_TAIL(&spawns, sp, entry); DNPRINTF(SWM_D_SPAWN, "leave\n"); } void spawn_remove(struct spawn_prog *sp) { int i; DNPRINTF(SWM_D_SPAWN, "name: %s\n", sp->name); TAILQ_REMOVE(&spawns, sp, entry); for (i = 0; i < sp->argc; i++) free(sp->argv[i]); free(sp->argv); free(sp->name); free(sp); DNPRINTF(SWM_D_SPAWN, "leave\n"); } void clear_spawns(void) { struct spawn_prog *sp; #ifndef __clang_analyzer__ /* Suppress false warnings. */ while ((sp = TAILQ_FIRST(&spawns)) != NULL) { spawn_remove(sp); } #endif } struct spawn_prog* spawn_find(const char *name) { struct spawn_prog *sp; TAILQ_FOREACH(sp, &spawns, entry) if (strcasecmp(sp->name, name) == 0) return (sp); return (NULL); } void setspawn(const char *name, const char *args, int flags) { struct spawn_prog *sp; DNPRINTF(SWM_D_SPAWN, "name: %s\n", name); if (name == NULL) return; #ifndef __clang_analyzer__ /* Suppress false warnings. */ /* Remove any old spawn under the same name. */ if ((sp = spawn_find(name)) != NULL) spawn_remove(sp); #endif if (*args != '\0') spawn_insert(name, args, flags); else warnx("error: setspawn: cannot find program: %s", name); DNPRINTF(SWM_D_SPAWN, "leave\n"); } int setconfspawn(const char *selector, const char *value, int flags, char **emsg) { char *args; if (selector == NULL || strlen(selector) == 0) { ALLOCSTR(emsg, "missing selector"); return (1); } args = expand_tilde(value); DNPRINTF(SWM_D_SPAWN, "[%s] [%s]\n", selector, args); setspawn(selector, args, flags); free(args); DNPRINTF(SWM_D_SPAWN, "done\n"); return (0); } void validate_spawns(void) { struct binding *bp; struct spawn_prog *sp; char which[PATH_MAX]; size_t i; RB_FOREACH(bp, binding_tree, &bindings) { if (bp->action != FN_SPAWN_CUSTOM) continue; /* find program */ sp = spawn_find(bp->spawn_name); if (sp == NULL || sp->flags & SWM_SPAWN_OPTIONAL) continue; /* verify we have the goods */ snprintf(which, sizeof which, "which %s", sp->argv[0]); DNPRINTF(SWM_D_CONF, "which %s\n", sp->argv[0]); for (i = strlen("which "); i < strlen(which); i++) if (which[i] == ' ') { which[i] = '\0'; break; } if (system(which) != 0) add_startup_exception("could not find %s", &which[strlen("which ")]); } } void setup_spawn(void) { setconfspawn("lock", "xlock", 0, NULL); setconfspawn("term", "xterm", 0, NULL); setconfspawn("spawn_term", "xterm", 0, NULL); setconfspawn("menu", "dmenu_run $dmenu_bottom -fn $bar_font " "-nb $bar_color -nf $bar_font_color -sb $bar_color_selected " "-sf $bar_font_color_selected", 0, NULL); setconfspawn("search", "dmenu $dmenu_bottom -i -fn $bar_font " "-nb $bar_color -nf $bar_font_color -sb $bar_color_selected " "-sf $bar_font_color_selected", 0, NULL); setconfspawn("name_workspace", "dmenu $dmenu_bottom -p Workspace " "-fn $bar_font -nb $bar_color -nf $bar_font_color " "-sb $bar_color_selected -sf $bar_font_color_selected", 0, NULL); /* These are not verified for existence, even with a binding set. */ setconfspawn("screenshot_all", "screenshot.sh full", SWM_SPAWN_OPTIONAL, NULL); setconfspawn("screenshot_wind", "screenshot.sh window", SWM_SPAWN_OPTIONAL, NULL); setconfspawn("initscr", "initscreen.sh", SWM_SPAWN_OPTIONAL, NULL); } /* bindings */ #define SWM_MODNAME_SIZE 32 #define SWM_KEY_WS "\n+ \t" int parsebinding(const char *bindstr, uint16_t *mod, enum binding_type *type, uint32_t *val, uint32_t *flags, char **emsg) { char *str, *cp, *name; xcb_keysym_t ks; DNPRINTF(SWM_D_KEY, "enter [%s]\n", bindstr); if (mod == NULL || val == NULL) { DNPRINTF(SWM_D_KEY, "no mod or key vars\n"); return (1); } if (bindstr == NULL || strlen(bindstr) == 0) { DNPRINTF(SWM_D_KEY, "no bindstr\n"); return (1); } if ((cp = str = strdup(bindstr)) == NULL) err(1, "parsebinding: strdup"); *val = XCB_NO_SYMBOL; *mod = 0; *flags = 0; *type = KEYBIND; while ((name = strsep(&cp, SWM_KEY_WS)) != NULL) { DNPRINTF(SWM_D_KEY, "entry [%s]\n", name); if (cp) cp += (long)strspn(cp, SWM_KEY_WS); if (strncasecmp(name, "MOD", SWM_MODNAME_SIZE) == 0) *mod |= mod_key; else if (strncasecmp(name, "Mod1", SWM_MODNAME_SIZE) == 0) *mod |= XCB_MOD_MASK_1; else if (strncasecmp(name, "Mod2", SWM_MODNAME_SIZE) == 0) *mod |= XCB_MOD_MASK_2; else if (strncmp(name, "Mod3", SWM_MODNAME_SIZE) == 0) *mod |= XCB_MOD_MASK_3; else if (strncmp(name, "Mod4", SWM_MODNAME_SIZE) == 0) *mod |= XCB_MOD_MASK_4; else if (strncmp(name, "Mod5", SWM_MODNAME_SIZE) == 0) *mod |= XCB_MOD_MASK_5; else if (strncasecmp(name, "SHIFT", SWM_MODNAME_SIZE) == 0) *mod |= XCB_MOD_MASK_SHIFT; else if (strncasecmp(name, "CONTROL", SWM_MODNAME_SIZE) == 0) *mod |= XCB_MOD_MASK_CONTROL; else if (strncasecmp(name, "ANYMOD", SWM_MODNAME_SIZE) == 0) *mod |= XCB_MOD_MASK_ANY; else if (strncasecmp(name, "REPLAY", SWM_MODNAME_SIZE) == 0) *flags |= BINDING_F_REPLAY; else if (sscanf(name, "Button%u", val) == 1) { DNPRINTF(SWM_D_KEY, "button %u\n", *val); *type = BTNBIND; if (*val > 255 || *val == 0) { DNPRINTF(SWM_D_KEY, "invalid btn %u\n", *val); ALLOCSTR(emsg, "invalid button: %s", name); free(str); return (1); } } else { if ((ks = get_string_keysym(name)) == XCB_NO_SYMBOL) { ALLOCSTR(emsg, "invalid key: %s", name); free(str); return (1); } *val = ks; } } /* If ANYMOD was specified, ignore the rest. */ if (*mod & XCB_MOD_MASK_ANY) *mod = XCB_MOD_MASK_ANY; free(str); DNPRINTF(SWM_D_KEY, "leave\n"); return (0); } char * strdupsafe(const char *str) { if (str == NULL) return (NULL); else return (strdup(str)); } int binding_cmp(struct binding *bp1, struct binding *bp2) { if (bp1->type < bp2->type) return (-1); if (bp1->type > bp2->type) return (1); if (bp1->value < bp2->value) return (-1); if (bp1->value > bp2->value) return (1); if (bp1->mod < bp2->mod) return (-1); if (bp1->mod > bp2->mod) return (1); return (0); } void binding_insert(uint16_t mod, enum binding_type type, uint32_t val, enum actionid aid, uint32_t flags, const char *spawn_name) { struct binding *bp; DNPRINTF(SWM_D_KEY, "mod: %u, type: %d, val: %u, action: %s(%d), " "spawn_name: %s\n", mod, type, val, actions[aid].name, aid, spawn_name); if ((bp = malloc(sizeof *bp)) == NULL) err(1, "binding_insert: malloc"); bp->mod = mod; bp->type = type; bp->value = val; bp->action = aid; bp->flags = flags; bp->spawn_name = strdupsafe(spawn_name); if (RB_INSERT(binding_tree, &bindings, bp)) err(1, "binding_insert: RB_INSERT"); DNPRINTF(SWM_D_KEY, "leave\n"); } struct binding * binding_lookup(uint16_t mod, enum binding_type type, uint32_t val) { struct binding bp; bp.mod = mod; bp.type = type; bp.value = val; return (RB_FIND(binding_tree, &bindings, &bp)); } void binding_remove(struct binding *bp) { DNPRINTF(SWM_D_KEY, "mod: %u, type: %d, val: %u, action: %s(%d), " "spawn_name: %s\n", bp->mod, bp->type, bp->value, actions[bp->action].name, bp->action, bp->spawn_name); RB_REMOVE(binding_tree, &bindings, bp); free(bp->spawn_name); free(bp); DNPRINTF(SWM_D_KEY, "leave\n"); } void setbinding(uint16_t mod, enum binding_type type, uint32_t val, enum actionid aid, uint32_t flags, const char *spawn_name) { struct binding *bp; if (spawn_name != NULL) { DNPRINTF(SWM_D_KEY, "enter %s [%s]\n", actions[aid].name, spawn_name); } /* Unbind any existing. Loop is to handle MOD_MASK_ANY. */ while ((bp = binding_lookup(mod, type, val))) binding_remove(bp); if (aid != FN_INVALID) binding_insert(mod, type, val, aid, flags, spawn_name); DNPRINTF(SWM_D_KEY, "leave\n"); } int setconfbinding(const char *selector, const char *value, int flags, char **emsg) { struct spawn_prog *sp; uint32_t keybtn, opts; uint16_t mod; enum actionid aid; enum binding_type type; /* Suppress warning. */ (void)flags; DNPRINTF(SWM_D_KEY, "selector: [%s], value: [%s]\n", selector, value); if (selector == NULL || strlen(selector) == 0) { DNPRINTF(SWM_D_KEY, "unbind %s\n", value); if (parsebinding(value, &mod, &type, &keybtn, &opts, emsg) == 0) { setbinding(mod, type, keybtn, FN_INVALID, opts, NULL); return (0); } else return (1); } /* search by key function name */ for (aid = 0; aid < FN_INVALID; aid++) { if (strncasecmp(selector, actions[aid].name, SWM_FUNCNAME_LEN) == 0) { DNPRINTF(SWM_D_KEY, "%s: match action\n", selector); if (parsebinding(value, &mod, &type, &keybtn, &opts, emsg) == 0) { setbinding(mod, type, keybtn, aid, opts, NULL); return (0); } else return (1); } } /* search by custom spawn name */ if ((sp = spawn_find(selector)) != NULL) { DNPRINTF(SWM_D_KEY, "%s: match spawn\n", selector); if (parsebinding(value, &mod, &type, &keybtn, &opts, emsg) == 0) { setbinding(mod, type, keybtn, FN_SPAWN_CUSTOM, opts, sp->name); return (0); } else return (1); } DNPRINTF(SWM_D_KEY, "no match\n"); ALLOCSTR(emsg, "invalid action: %s", selector); return (1); } #define MOD mod_key #define MODSHIFT mod_key | XCB_MOD_MASK_SHIFT void setup_keybindings(void) { #define BINDKEY(m, k, a) setbinding(m, KEYBIND, k, a, 0, NULL) #define BINDKEYSPAWN(m, k, s) setbinding(m, KEYBIND, k, FN_SPAWN_CUSTOM, 0, s) BINDKEY(MOD, XK_grave, FN_FOCUS_FREE); BINDKEY(MODSHIFT, XK_grave, FN_FREE_TOGGLE); BINDKEY(MOD, XK_b, FN_BAR_TOGGLE); BINDKEY(MODSHIFT, XK_b, FN_BAR_TOGGLE_WS); BINDKEY(MOD, XK_v, FN_BUTTON2); BINDKEY(MOD, XK_space, FN_CYCLE_LAYOUT); BINDKEY(MODSHIFT, XK_backslash, FN_FLIP_LAYOUT); BINDKEY(MOD, XK_t, FN_FLOAT_TOGGLE); BINDKEY(MODSHIFT, XK_t, FN_BELOW_TOGGLE); BINDKEY(MOD, XK_m, FN_FOCUS_MAIN); BINDKEY(MOD, XK_j, FN_FOCUS_NEXT); BINDKEY(MOD, XK_Tab, FN_FOCUS_NEXT); BINDKEY(MOD, XK_k, FN_FOCUS_PREV); BINDKEY(MODSHIFT, XK_Tab, FN_FOCUS_PREV); BINDKEY(MODSHIFT, XK_a, FN_FOCUS_PRIOR); BINDKEY(MOD, XK_u, FN_FOCUS_URGENT); BINDKEY(MODSHIFT, XK_e, FN_FULLSCREEN_TOGGLE); BINDKEY(MOD, XK_e, FN_MAXIMIZE_TOGGLE); BINDKEY(MODSHIFT, XK_equal, FN_HEIGHT_GROW); BINDKEY(MODSHIFT, XK_minus, FN_HEIGHT_SHRINK); BINDKEY(MOD, XK_w, FN_ICONIFY); BINDKEY(MOD, XK_h, FN_MASTER_SHRINK); BINDKEY(MOD, XK_l, FN_MASTER_GROW); BINDKEY(MOD, XK_comma, FN_MASTER_ADD); BINDKEY(MOD, XK_period, FN_MASTER_DEL); BINDKEY(MODSHIFT, XK_bracketright, FN_MOVE_DOWN); BINDKEY(MOD, XK_bracketleft, FN_MOVE_LEFT); BINDKEY(MOD, XK_bracketright, FN_MOVE_RIGHT); BINDKEY(MODSHIFT, XK_bracketleft, FN_MOVE_UP); BINDKEY(MODSHIFT, XK_KP_End, FN_MVRG_1); BINDKEY(MODSHIFT, XK_KP_Down, FN_MVRG_2); BINDKEY(MODSHIFT, XK_KP_Next, FN_MVRG_3); BINDKEY(MODSHIFT, XK_KP_Left, FN_MVRG_4); BINDKEY(MODSHIFT, XK_KP_Begin, FN_MVRG_5); BINDKEY(MODSHIFT, XK_KP_Right, FN_MVRG_6); BINDKEY(MODSHIFT, XK_KP_Home, FN_MVRG_7); BINDKEY(MODSHIFT, XK_KP_Up, FN_MVRG_8); BINDKEY(MODSHIFT, XK_KP_Prior, FN_MVRG_9); BINDKEY(MODSHIFT, XK_1, FN_MVWS_1); BINDKEY(MODSHIFT, XK_2, FN_MVWS_2); BINDKEY(MODSHIFT, XK_3, FN_MVWS_3); BINDKEY(MODSHIFT, XK_4, FN_MVWS_4); BINDKEY(MODSHIFT, XK_5, FN_MVWS_5); BINDKEY(MODSHIFT, XK_6, FN_MVWS_6); BINDKEY(MODSHIFT, XK_7, FN_MVWS_7); BINDKEY(MODSHIFT, XK_8, FN_MVWS_8); BINDKEY(MODSHIFT, XK_9, FN_MVWS_9); BINDKEY(MODSHIFT, XK_0, FN_MVWS_10); BINDKEY(MODSHIFT, XK_F1, FN_MVWS_11); BINDKEY(MODSHIFT, XK_F2, FN_MVWS_12); BINDKEY(MODSHIFT, XK_F3, FN_MVWS_13); BINDKEY(MODSHIFT, XK_F4, FN_MVWS_14); BINDKEY(MODSHIFT, XK_F5, FN_MVWS_15); BINDKEY(MODSHIFT, XK_F6, FN_MVWS_16); BINDKEY(MODSHIFT, XK_F7, FN_MVWS_17); BINDKEY(MODSHIFT, XK_F8, FN_MVWS_18); BINDKEY(MODSHIFT, XK_F9, FN_MVWS_19); BINDKEY(MODSHIFT, XK_F10, FN_MVWS_20); BINDKEY(MODSHIFT, XK_F11, FN_MVWS_21); BINDKEY(MODSHIFT, XK_F12, FN_MVWS_22); BINDKEY(MODSHIFT, XK_slash, FN_NAME_WORKSPACE); BINDKEY(MODSHIFT, XK_q, FN_QUIT); BINDKEY(MOD, XK_r, FN_RAISE); BINDKEY(MODSHIFT, XK_r, FN_RAISE_TOGGLE); BINDKEY(MOD, XK_q, FN_RESTART); BINDKEY(MOD, XK_KP_End, FN_RG_1); BINDKEY(MOD, XK_KP_Down, FN_RG_2); BINDKEY(MOD, XK_KP_Next, FN_RG_3); BINDKEY(MOD, XK_KP_Left, FN_RG_4); BINDKEY(MOD, XK_KP_Begin, FN_RG_5); BINDKEY(MOD, XK_KP_Right, FN_RG_6); BINDKEY(MOD, XK_KP_Home, FN_RG_7); BINDKEY(MOD, XK_KP_Up, FN_RG_8); BINDKEY(MOD, XK_KP_Prior, FN_RG_9); BINDKEY(MODSHIFT, XK_Right, FN_RG_NEXT); BINDKEY(MODSHIFT, XK_Left, FN_RG_PREV); BINDKEY(MOD, XK_f, FN_SEARCH_WIN); BINDKEY(MOD, XK_slash, FN_SEARCH_WORKSPACE); BINDKEYSPAWN(MODSHIFT, XK_i, "initscr"); BINDKEYSPAWN(MODSHIFT, XK_Delete, "lock"); BINDKEYSPAWN(MOD, XK_p, "menu"); BINDKEYSPAWN(MOD, XK_s, "screenshot_all"); BINDKEYSPAWN(MODSHIFT, XK_s, "screenshot_wind"); BINDKEYSPAWN(MODSHIFT, XK_Return, "term"); BINDKEY(MODSHIFT, XK_comma, FN_STACK_INC); BINDKEY(MODSHIFT, XK_period, FN_STACK_DEC); BINDKEY(MODSHIFT, XK_space, FN_STACK_RESET); BINDKEY(MOD, XK_Return, FN_SWAP_MAIN); BINDKEY(MODSHIFT, XK_j, FN_SWAP_NEXT); BINDKEY(MODSHIFT, XK_k, FN_SWAP_PREV); BINDKEY(MODSHIFT, XK_w, FN_UNICONIFY); BINDKEY(MODSHIFT, XK_v, FN_VERSION); BINDKEY(MOD, XK_equal, FN_WIDTH_GROW); BINDKEY(MOD, XK_minus, FN_WIDTH_SHRINK); BINDKEY(MOD, XK_x, FN_WIND_DEL); BINDKEY(MODSHIFT, XK_x, FN_WIND_KILL); BINDKEY(MOD, XK_1, FN_WS_1); BINDKEY(MOD, XK_2, FN_WS_2); BINDKEY(MOD, XK_3, FN_WS_3); BINDKEY(MOD, XK_4, FN_WS_4); BINDKEY(MOD, XK_5, FN_WS_5); BINDKEY(MOD, XK_6, FN_WS_6); BINDKEY(MOD, XK_7, FN_WS_7); BINDKEY(MOD, XK_8, FN_WS_8); BINDKEY(MOD, XK_9, FN_WS_9); BINDKEY(MOD, XK_0, FN_WS_10); BINDKEY(MOD, XK_F1, FN_WS_11); BINDKEY(MOD, XK_F2, FN_WS_12); BINDKEY(MOD, XK_F3, FN_WS_13); BINDKEY(MOD, XK_F4, FN_WS_14); BINDKEY(MOD, XK_F5, FN_WS_15); BINDKEY(MOD, XK_F6, FN_WS_16); BINDKEY(MOD, XK_F7, FN_WS_17); BINDKEY(MOD, XK_F8, FN_WS_18); BINDKEY(MOD, XK_F9, FN_WS_19); BINDKEY(MOD, XK_F10, FN_WS_20); BINDKEY(MOD, XK_F11, FN_WS_21); BINDKEY(MOD, XK_F12, FN_WS_22); BINDKEY(MOD, XK_Right, FN_WS_NEXT); BINDKEY(MOD, XK_Left, FN_WS_PREV); BINDKEY(MOD, XK_Up, FN_WS_NEXT_ALL); BINDKEY(MOD, XK_Down, FN_WS_PREV_ALL); BINDKEY(MODSHIFT, XK_Up, FN_WS_NEXT_MOVE); BINDKEY(MODSHIFT, XK_Down, FN_WS_PREV_MOVE); BINDKEY(MOD, XK_a, FN_WS_PRIOR); if (swm_debug) { BINDKEY(MOD, XK_d, FN_DEBUG_TOGGLE); BINDKEY(MODSHIFT, XK_d, FN_DUMPWINS); } #undef BINDKEY #undef BINDKEYSPAWN } void setup_btnbindings(void) { setbinding(ANYMOD, BTNBIND, XCB_BUTTON_INDEX_1, FN_FOCUS, BINDING_F_REPLAY, NULL); setbinding(MOD, BTNBIND, XCB_BUTTON_INDEX_3, FN_RESIZE, 0, NULL); setbinding(MODSHIFT, BTNBIND, XCB_BUTTON_INDEX_3, FN_RESIZE_CENTERED, 0, NULL); setbinding(MOD, BTNBIND, XCB_BUTTON_INDEX_1, FN_MOVE, 0, NULL); } #undef MODSHIFT #undef MOD void clear_bindings(void) { struct binding *bp; while ((bp = RB_ROOT(&bindings))) binding_remove(bp); } void clear_keybindings(void) { struct binding *bp, *bptmp; RB_FOREACH_SAFE(bp, binding_tree, &bindings, bptmp) { if (bp->type != KEYBIND) continue; binding_remove(bp); } } bool button_has_binding(uint32_t button) { struct binding b, *bp; b.type = BTNBIND; b.value = button; b.mod = 0; bp = RB_NFIND(binding_tree, &bindings, &b); return (bp && bp->type == BTNBIND && bp->value == button); } int setkeymapping(const char *selector, const char *value, int flags, char **emsg) { char *keymapping_file; /* suppress unused warnings since vars are needed */ (void)selector; (void)flags; DNPRINTF(SWM_D_KEY, "enter\n"); keymapping_file = expand_tilde(value); clear_keybindings(); /* load new key bindings; if it fails, revert to default bindings */ if (conf_load(keymapping_file, SWM_CONF_KEYMAPPING)) { ALLOCSTR(emsg, "failed to load '%s'", keymapping_file); free(keymapping_file); clear_keybindings(); setup_keybindings(); return (1); } free(keymapping_file); DNPRINTF(SWM_D_KEY, "leave\n"); return (0); } void updatenumlockmask(void) { unsigned int i, j; xcb_get_modifier_mapping_reply_t *modmap_r; xcb_keycode_t *modmap, kc, *keycode; numlockmask = 0; modmap_r = xcb_get_modifier_mapping_reply(conn, xcb_get_modifier_mapping(conn), NULL); if (modmap_r) { modmap = xcb_get_modifier_mapping_keycodes(modmap_r); for (i = 0; i < 8; i++) { for (j = 0; j < modmap_r->keycodes_per_modifier; j++) { kc = modmap[i * modmap_r->keycodes_per_modifier + j]; keycode = xcb_key_symbols_get_keycode(syms, XK_Num_Lock); if (keycode) { if (kc == *keycode) numlockmask = (1 << i); free(keycode); } } } free(modmap_r); } DNPRINTF(SWM_D_MISC, "numlockmask: %#x\n", numlockmask); } xcb_keysym_t get_string_keysym(const char *name) { KeySym ks, lks, uks; /* TODO: do this without Xlib. */ ks = XStringToKeysym(name); if (ks == NoSymbol) { DNPRINTF(SWM_D_KEY, "invalid key %s\n", name); return (XCB_NO_SYMBOL); } XConvertCase(ks, &lks, &uks); return ((xcb_keysym_t)lks); } xcb_keycode_t get_keysym_keycode(xcb_keysym_t ks) { const xcb_setup_t *s; xcb_get_keyboard_mapping_reply_t *kmr; int col; xcb_keycode_t kc, min, max; s = get_setup(); min = s->min_keycode; max = s->max_keycode; kmr = xcb_get_keyboard_mapping_reply(conn, xcb_get_keyboard_mapping(conn, min, max - min + 1), NULL); if (kmr == NULL) return (XCB_NO_SYMBOL); /* Search for keycode by keysym column. */ for (col = 0; col < kmr->keysyms_per_keycode; col++) { /* Keycodes are unsigned, bail if kc++ is reduced to 0. */ for (kc = min; kc > 0 && kc <= max; kc++) { if (xcb_key_symbols_get_keysym(syms, kc, col) == ks) { free(kmr); return (kc); } } } free(kmr); return (XCB_NO_SYMBOL); } void grabkeys(void) { struct binding *bp; int num_screens, i, j; uint16_t modifiers[4]; xcb_keycode_t keycode; DNPRINTF(SWM_D_MISC, "begin\n"); updatenumlockmask(); update_keycodes(); modifiers[0] = 0; modifiers[1] = numlockmask; modifiers[2] = XCB_MOD_MASK_LOCK; modifiers[3] = numlockmask | XCB_MOD_MASK_LOCK; num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) { if (TAILQ_EMPTY(&screens[i].rl)) continue; xcb_ungrab_key(conn, XCB_GRAB_ANY, screens[i].root, XCB_MOD_MASK_ANY); RB_FOREACH(bp, binding_tree, &bindings) { if (bp->type != KEYBIND) continue; /* If there is a catch-all, only bind that. */ if ((binding_lookup(ANYMOD, KEYBIND, bp->value)) && bp->mod != ANYMOD) continue; /* Skip unused ws binds. */ if ((int)bp->action > FN_WS_1 + workspace_limit - 1 && bp->action <= FN_WS_22) continue; /* Skip unused mvws binds. */ if ((int)bp->action > FN_MVWS_1 + workspace_limit - 1 && bp->action <= FN_MVWS_22) continue; /* Try to get keycode for the grab. */ keycode = get_keysym_keycode(bp->value); if (keycode == XCB_NO_SYMBOL) continue; if (bp->mod == XCB_MOD_MASK_ANY) { /* All modifiers are grabbed in one pass. */ DNPRINTF(SWM_D_KEY, "key: %u, modmask: %d\n", bp->value, bp->mod); xcb_grab_key(conn, 1, screens[i].root, bp->mod, keycode, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_SYNC); } else { /* Need to grab each modifier permutation. */ for (j = 0; j < LENGTH(modifiers); j++) { DNPRINTF(SWM_D_KEY, "key: %u, " "modmask: %d\n", bp->value, bp->mod | modifiers[j]); xcb_grab_key(conn, 1, screens[i].root, bp->mod | modifiers[j], keycode, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_SYNC); } } } } DNPRINTF(SWM_D_MISC, "done\n"); } #ifdef SWM_XCB_HAS_XINPUT const char * get_input_event_label(xcb_ge_generic_event_t *ev) { char *label; switch (ev->event_type) { case XCB_INPUT_DEVICE_CHANGED: label = "DeviceChanged"; break; case XCB_INPUT_KEY_PRESS: label = "KeyPress"; break; case XCB_INPUT_KEY_RELEASE: label = "KeyRelease"; break; case XCB_INPUT_BUTTON_PRESS: label = "ButtonPress"; break; case XCB_INPUT_BUTTON_RELEASE: label = "ButtonRelease"; break; case XCB_INPUT_MOTION: label = "Motion"; break; case XCB_INPUT_ENTER: label = "Enter"; break; case XCB_INPUT_LEAVE: label = "Leave"; break; case XCB_INPUT_FOCUS_IN: label = "FocusIn"; break; case XCB_INPUT_FOCUS_OUT: label = "FocusOut"; break; case XCB_INPUT_HIERARCHY: label = "Hierarchy"; break; case XCB_INPUT_PROPERTY: label = "Property"; break; #ifdef XCB_INPUT_RAW_KEY_PRESS /* XI >= 2.1 */ case XCB_INPUT_RAW_KEY_PRESS: label = "RawKeyPress"; break; case XCB_INPUT_RAW_KEY_RELEASE: label = "RawKeyRelease"; break; case XCB_INPUT_RAW_BUTTON_PRESS: label = "RawButtonPress"; break; case XCB_INPUT_RAW_BUTTON_RELEASE: label = "RawButtonRelease"; break; case XCB_INPUT_RAW_MOTION: label = "RawMotion"; break; #ifdef XCB_INPUT_TOUCH_BEGIN /* XI >= 2.2 */ case XCB_INPUT_TOUCH_BEGIN: label = "TouchBegin"; break; case XCB_INPUT_TOUCH_UPDATE: label = "TouchUpdate"; break; case XCB_INPUT_TOUCH_END: label = "TouchEnd"; break; case XCB_INPUT_TOUCH_OWNERSHIP: label = "TouchOwnership"; break; case XCB_INPUT_RAW_TOUCH_BEGIN: label = "RawTouchBegin"; break; case XCB_INPUT_RAW_TOUCH_UPDATE: label = "RawTouchUpdate"; break; case XCB_INPUT_RAW_TOUCH_END: label = "RawTouchEnd"; break; #ifdef XCB_INPUT_BARRIER_HIT /* XI >= 2.3 */ case XCB_INPUT_BARRIER_HIT: label = "BarrierHit"; break; case XCB_INPUT_BARRIER_LEAVE: label = "BarrierLeave"; break; #ifdef XCB_INPUT_GESTURE_PINCH_BEGIN /* XI >= 2.4 */ case XCB_INPUT_GESTURE_PINCH_BEGIN: label = "GesturePinchBegin"; break; case XCB_INPUT_GESTURE_PINCH_UPDATE: label = "GesturePinchUpdate"; break; case XCB_INPUT_GESTURE_PINCH_END: label = "GesturePinchEnd"; break; case XCB_INPUT_GESTURE_SWIPE_BEGIN: label = "GestureSwipeBegin"; break; case XCB_INPUT_GESTURE_SWIPE_UPDATE: label = "GestureSwipeUpdate"; break; case XCB_INPUT_GESTURE_SWIPE_END: label = "GestureSwipeEnd"; break; #endif /* XCB_INPUT_GESTURE_PINCH_BEGIN */ #endif /* XCB_INPUT_BARRIER_HIT */ #endif /* XCB_INPUT_TOUCH_BEGIN */ #endif /* XCB_INPUT_RAW_KEY_PRESS */ default: label = "Unknown"; } return (label); } #endif /* SWM_XCB_HAS_XINPUT */ #if defined(SWM_XCB_HAS_XINPUT) && defined(XCB_INPUT_RAW_BUTTON_PRESS) void setup_xinput2(struct swm_screen *s) { xcb_void_cookie_t ck; xcb_generic_error_t *error; if (!xinput2_support || !xinput2_raw) return; struct { xcb_input_event_mask_t head; uint32_t val; } masks; masks.head.deviceid = XCB_INPUT_DEVICE_ALL_MASTER; masks.head.mask_len = 1; masks.val = XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS; ck = xcb_input_xi_select_events_checked(conn, s->root, 1, &masks.head); if ((error = xcb_request_check(conn, ck))) { DNPRINTF(SWM_D_INIT, "xi2 error_code: %u\n", error->error_code); free(error); } } void rawbuttonpress(xcb_input_raw_button_press_event_t *e) { struct swm_screen *s; struct binding *bp; struct action *ap; xcb_query_pointer_reply_t *qpr; DPRINTF("length: %u, deviceid: %u, time: %#x, detail: %#x, " "sourceid: %u, valuators_len: %u, flags: %#x\n", e->length, e->deviceid, e->time, e->detail, e->sourceid, e->valuators_len, e->flags); if (!button_has_binding(e->detail)) goto done; /* Try to find binding with the current modifier state. */ qpr = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, screens[0].root), NULL); if (qpr == NULL) { DNPRINTF(SWM_D_MISC, "failed to query pointer.\n"); goto done; } bp = binding_lookup(CLEANMASK(qpr->mask), BTNBIND, e->detail); if (bp == NULL) { bp = binding_lookup(ANYMOD, BTNBIND, e->detail); if (bp == NULL) goto out; } DNPRINTF(SWM_D_MISC, "mask: %u, bound: %s\n", qpr->mask, YESNO(bp)); if (!(bp->flags & BINDING_F_REPLAY)) { DNPRINTF(SWM_D_FOCUS, "skip; binding grabbed.\n"); goto out; } /* Handle ungrabbed binding. */ /* Click to focus. */ s = find_screen(qpr->root); click_focus(s, qpr->child, qpr->root_x, qpr->root_y); if ((ap = &actions[bp->action])) { if (bp->action == FN_SPAWN_CUSTOM) spawn_custom(get_current_region(s), &ap->args, bp->spawn_name); else if (ap->func) ap->func(s, bp, &ap->args); } flush(); out: free(qpr); done: DNPRINTF(SWM_D_FOCUS, "done\n"); } #endif void grabbuttons(void) { struct ws_win *w; int num_screens, i; DNPRINTF(SWM_D_MOUSE, "begin\n"); updatenumlockmask(); num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) { if (xinput2_raw) grab_buttons_win(screens[i].root); else TAILQ_FOREACH(w, &screens[i].managed, manage_entry) grab_buttons_win(w->id); } DNPRINTF(SWM_D_MOUSE, "done\n"); } struct wsi_flag { char *name; uint32_t mask; } wsiflags[] = { {"listcurrent", SWM_WSI_LISTCURRENT}, {"listactive", SWM_WSI_LISTACTIVE}, {"listempty", SWM_WSI_LISTEMPTY}, {"listnamed", SWM_WSI_LISTNAMED}, {"listurgent", SWM_WSI_LISTURGENT}, {"listall", SWM_WSI_LISTALL}, {"hidecurrent", SWM_WSI_HIDECURRENT}, {"markcurrent", SWM_WSI_MARKCURRENT}, {"markurgent", SWM_WSI_MARKURGENT}, {"markactive", SWM_WSI_MARKACTIVE}, {"markempty", SWM_WSI_MARKEMPTY}, {"printnames", SWM_WSI_PRINTNAMES}, {"noindexes", SWM_WSI_NOINDEXES}, }; int parse_workspace_indicator(const char *str, uint32_t *mode, char **emsg) { char *tmp, *cp, *name; size_t len; int i; if (str == NULL || mode == NULL) return (1); if ((cp = tmp = strdup(str)) == NULL) err(1, "parse_workspace_indicator: strdup"); *mode = 0; while ((name = strsep(&cp, SWM_CONF_DELIMLIST)) != NULL) { if (cp) cp += (long)strspn(cp, SWM_CONF_WHITESPACE); name += strspn(name, SWM_CONF_WHITESPACE); len = strcspn(name, SWM_CONF_WHITESPACE); for (i = 0; i < LENGTH(wsiflags); i++) { if (strncasecmp(name, wsiflags[i].name, len) == 0) { DNPRINTF(SWM_D_CONF, "flag: [%s]\n", name); *mode |= wsiflags[i].mask; break; } } if (i >= LENGTH(wsiflags)) { ALLOCSTR(emsg, "invalid flag: %s", name); DNPRINTF(SWM_D_CONF, "invalid flag: [%s]\n", name); free(tmp); return (1); } } free(tmp); return (0); } const char *quirkname[] = { "NONE", /* config string for "no value" */ "FLOAT", "TRANSSZ", "ANYWHERE", "XTERM_FONTADJ", "FULLSCREEN", "FOCUSPREV", "NOFOCUSONMAP", "FOCUSONMAP_SINGLE", "OBEYAPPFOCUSREQ", "IGNOREPID", "IGNORESPAWNWS", "NOFOCUSCYCLE", "MINIMALBORDER", }; /* SWM_Q_DELIM: retain '|' for back compat for now (2009-08-11) */ #define SWM_Q_DELIM "\n|+ \t" int parsequirks(const char *qstr, uint32_t *quirk, int *ws, char **emsg) { char *str, *cp, *name; int i; if (quirk == NULL || qstr == NULL) return (1); if ((cp = str = strdup(qstr)) == NULL) err(1, "parsequirks: strdup"); *quirk = 0; while ((name = strsep(&cp, SWM_Q_DELIM)) != NULL) { if (cp) cp += (long)strspn(cp, SWM_Q_DELIM); if (sscanf(name, "WS[%d]", ws) == 1) { if (*ws > 0) *ws -= 1; continue; } for (i = 0; i < LENGTH(quirkname); i++) { if (strncasecmp(name, quirkname[i], SWM_QUIRK_LEN) == 0) { DNPRINTF(SWM_D_QUIRK, "%s\n", name); if (i == 0) { *quirk = 0; free(str); return (0); } *quirk |= 1 << (i-1); break; } } if (i >= LENGTH(quirkname)) { DNPRINTF(SWM_D_QUIRK, "invalid quirk [%s]\n", name); ALLOCSTR(emsg, "invalid quirk: %s", name); free(str); return (1); } } free(str); return (0); } void quirk_insert(const char *class, const char *instance, const char *name, uint32_t quirk, int ws) { struct quirk *qp; char *str; bool failed = false; DNPRINTF(SWM_D_QUIRK, "class: %s, instance: %s, name: %s, value: %u, " "ws: %d\n", class, instance, name, quirk, ws); if ((qp = malloc(sizeof *qp)) == NULL) err(1, "quirk_insert: malloc"); if ((qp->class = strdup(class)) == NULL) err(1, "quirk_insert: strdup"); if ((qp->instance = strdup(instance)) == NULL) err(1, "quirk_insert: strdup"); if ((qp->name = strdup(name)) == NULL) err(1, "quirk_insert: strdup"); if (asprintf(&str, "^%s$", class) == -1) err(1, "quirk_insert: asprintf"); if (regcomp(&qp->regex_class, str, REG_EXTENDED | REG_NOSUB)) { add_startup_exception("regex failed to compile quirk 'class' " "field: %s", class); failed = true; } DNPRINTF(SWM_D_QUIRK, "compiled: %s\n", str); free(str); if (asprintf(&str, "^%s$", instance) == -1) err(1, "quirk_insert: asprintf"); if (regcomp(&qp->regex_instance, str, REG_EXTENDED | REG_NOSUB)) { add_startup_exception("regex failed to compile quirk 'instance'" " field: %s", instance); failed = true; } DNPRINTF(SWM_D_QUIRK, "compiled: %s\n", str); free(str); if (asprintf(&str, "^%s$", name) == -1) err(1, "quirk_insert: asprintf"); if (regcomp(&qp->regex_name, str, REG_EXTENDED | REG_NOSUB)) { add_startup_exception("regex failed to compile quirk 'name' " "field: %s", name); failed = true; } DNPRINTF(SWM_D_QUIRK, "compiled: %s\n", str); free(str); if (failed) { DNPRINTF(SWM_D_QUIRK, "regex error\n"); quirk_free(qp); } else { qp->quirk = quirk; qp->ws = ws; TAILQ_INSERT_TAIL(&quirks, qp, entry); } DNPRINTF(SWM_D_QUIRK, "leave\n"); } void quirk_remove(struct quirk *qp) { DNPRINTF(SWM_D_QUIRK, "%s:%s [%u]\n", qp->class, qp->name, qp->quirk); TAILQ_REMOVE(&quirks, qp, entry); quirk_free(qp); DNPRINTF(SWM_D_QUIRK, "leave\n"); } void quirk_free(struct quirk *qp) { regfree(&qp->regex_class); regfree(&qp->regex_instance); regfree(&qp->regex_name); free(qp->class); free(qp->instance); free(qp->name); free(qp); } void clear_quirks(void) { struct quirk *qp; #ifndef __clang_analyzer__ /* Suppress false warnings. */ while ((qp = TAILQ_FIRST(&quirks)) != NULL) { quirk_remove(qp); } #endif } void quirk_replace(struct quirk *qp, const char *class, const char *instance, const char *name, uint32_t quirk, int ws) { DNPRINTF(SWM_D_QUIRK, "%s:%s:%s [%u], ws: %d\n", qp->class, qp->instance, qp->name, qp->quirk, qp->ws); quirk_remove(qp); quirk_insert(class, instance, name, quirk, ws); DNPRINTF(SWM_D_QUIRK, "leave\n"); } void setquirk(const char *class, const char *instance, const char *name, uint32_t quirk, int ws) { struct quirk *qp; DNPRINTF(SWM_D_QUIRK, "enter %s:%s:%s [%u], ws: %d\n", class, instance, name, quirk, ws); #ifndef __clang_analyzer__ /* Suppress false warnings. */ /* Remove/replace existing quirk. */ TAILQ_FOREACH(qp, &quirks, entry) { if (strcmp(qp->class, class) == 0 && strcmp(qp->instance, instance) == 0 && strcmp(qp->name, name) == 0) { if (quirk == 0 && ws == -2) quirk_remove(qp); else quirk_replace(qp, class, instance, name, quirk, ws); goto out; } } #endif /* Only insert if quirk is not NONE or forced ws is set. */ if (quirk || ws >= -1) quirk_insert(class, instance, name, quirk, ws); out: DNPRINTF(SWM_D_QUIRK, "leave\n"); } /* Eat '\' in str used to escape square brackets and colon. */ void unescape_selector(char *str) { char *cp; for (cp = str; *str != '\0'; ++str, ++cp) { if (*str == '\\' && (*(str + 1) == ':' || *(str + 1) == ']' || *(str + 1) == '[')) ++str; *cp = *str; } *cp = '\0'; } int setconfquirk(const char *selector, const char *value, int flags, char **emsg) { char *str, *cp, *class; char *instance = NULL, *name = NULL; int retval, count = 0, ws = -2; uint32_t qrks; /* Suppress warning. */ (void)flags; if (selector == NULL || strlen(selector) == 0) return (0); if ((str = strdup(selector)) == NULL) err(1, "setconfquirk: strdup"); /* Find non-escaped colon. */ class = cp = str; if (*cp == ':') { *cp = '\0'; ++count; } for (++cp; *cp != '\0'; ++cp) { if (*cp == ':' && *(cp - 1) != '\\') { *cp = '\0'; ++count; } } unescape_selector(class); if (count) { instance = class + strlen(class) + 1; unescape_selector(instance); } else { instance = ".*"; } if (count > 1) { name = instance + strlen(instance) + 1; unescape_selector(name); } else { name = ".*"; } DNPRINTF(SWM_D_CONF, "class: %s, instance: %s, name: %s\n", class, instance, name); if ((retval = parsequirks(value, &qrks, &ws, emsg)) == 0) setquirk(class, instance, name, qrks, ws); free(str); return (retval); } void setup_quirks(void) { setquirk("MPlayer", "xv", ".*", SWM_Q_FLOAT | SWM_Q_FULLSCREEN | SWM_Q_FOCUSPREV, -2); setquirk("OpenOffice.org 3.2", "VCLSalFrame", ".*", SWM_Q_FLOAT, -2); setquirk("Firefox-bin", "firefox-bin", ".*", SWM_Q_TRANSSZ, -2); setquirk("Firefox", "Dialog", ".*", SWM_Q_FLOAT, -2); setquirk("Gimp", "gimp", ".*", SWM_Q_FLOAT | SWM_Q_ANYWHERE, -2); setquirk("XTerm", "xterm", ".*", SWM_Q_XTERM_FONTADJ, -2); setquirk("xine", "Xine Window", ".*", SWM_Q_FLOAT | SWM_Q_ANYWHERE, -2); setquirk("Xitk", "Xitk Combo", ".*", SWM_Q_FLOAT | SWM_Q_ANYWHERE, -2); setquirk("xine", "xine Panel", ".*", SWM_Q_FLOAT | SWM_Q_ANYWHERE, -2); setquirk("Xitk", "Xine Window", ".*", SWM_Q_FLOAT | SWM_Q_ANYWHERE, -2); setquirk("xine", "xine Video Fullscreen Window", ".*", SWM_Q_FULLSCREEN | SWM_Q_FLOAT, -2); setquirk("pcb", "pcb", ".*", SWM_Q_FLOAT, -2); setquirk("SDL_App", "SDL_App", ".*", SWM_Q_FLOAT | SWM_Q_FULLSCREEN, -2); } /* conf file stuff */ #define SWM_CONF_FILE "spectrwm.conf" #define SWM_CONF_FILE_OLD "scrotwm.conf" enum { SWM_S_BAR_ACTION, SWM_S_BAR_ACTION_EXPAND, SWM_S_BAR_AT_BOTTOM, SWM_S_BAR_BORDER_WIDTH, SWM_S_BAR_ENABLED, SWM_S_BAR_ENABLED_WS, SWM_S_BAR_FONT, SWM_S_BAR_FONT_PUA, SWM_S_BAR_FORMAT, SWM_S_BAR_JUSTIFY, SWM_S_BORDER_WIDTH, SWM_S_BOUNDARY_WIDTH, SWM_S_CLICK_TO_RAISE, SWM_S_CLOCK_ENABLED, SWM_S_CLOCK_FORMAT, SWM_S_CYCLE_EMPTY, SWM_S_CYCLE_VISIBLE, SWM_S_DIALOG_RATIO, SWM_S_DISABLE_BORDER, SWM_S_FOCUS_CLOSE, SWM_S_FOCUS_CLOSE_WRAP, SWM_S_FOCUS_DEFAULT, SWM_S_FOCUS_MODE, SWM_S_FULLSCREEN_HIDE_OTHER, SWM_S_FULLSCREEN_UNFOCUS, SWM_S_ICONIC_ENABLED, SWM_S_MAX_LAYOUT_MAXIMIZE, SWM_S_MAXIMIZE_HIDE_BAR, SWM_S_MAXIMIZE_HIDE_OTHER, SWM_S_MAXIMIZED_UNFOCUS, SWM_S_REGION_PADDING, SWM_S_SNAP_RANGE, SWM_S_SPAWN_ORDER, SWM_S_SPAWN_TERM, SWM_S_STACK_ENABLED, SWM_S_TERM_WIDTH, SWM_S_TILE_GAP, SWM_S_URGENT_COLLAPSE, SWM_S_URGENT_ENABLED, SWM_S_VERBOSE_LAYOUT, SWM_S_WARP_FOCUS, SWM_S_WARP_POINTER, SWM_S_WINDOW_CLASS_ENABLED, SWM_S_WINDOW_INSTANCE_ENABLED, SWM_S_WINDOW_NAME_ENABLED, SWM_S_WORKSPACE_AUTOROTATE, SWM_S_WORKSPACE_CLAMP, SWM_S_WORKSPACE_LIMIT, SWM_S_WORKSPACE_INDICATOR, SWM_S_WORKSPACE_NAME, SWM_S_FOCUS_MARK_NONE, SWM_S_FOCUS_MARK_NORMAL, SWM_S_FOCUS_MARK_FLOATING, SWM_S_FOCUS_MARK_FREE, SWM_S_FOCUS_MARK_MAXIMIZED, SWM_S_WORKSPACE_MARK_CURRENT, SWM_S_WORKSPACE_MARK_CURRENT_SUFFIX, SWM_S_WORKSPACE_MARK_URGENT, SWM_S_WORKSPACE_MARK_URGENT_SUFFIX, SWM_S_WORKSPACE_MARK_ACTIVE, SWM_S_WORKSPACE_MARK_ACTIVE_SUFFIX, SWM_S_WORKSPACE_MARK_EMPTY, SWM_S_WORKSPACE_MARK_EMPTY_SUFFIX, SWM_S_STACK_MARK_FLOATING, SWM_S_STACK_MARK_MAX, SWM_S_STACK_MARK_VERTICAL, SWM_S_STACK_MARK_VERTICAL_FLIP, SWM_S_STACK_MARK_HORIZONTAL, SWM_S_STACK_MARK_HORIZONTAL_FLIP, }; int setconfvalue(const char *selector, const char *value, int flags, char **emsg) { struct swm_region *r; struct workspace *ws; int i, ws_id, num_screens, n; switch (flags) { case SWM_S_BAR_ACTION: free(bar_argv[0]); if ((bar_argv[0] = expand_tilde(value)) == NULL) err(1, "setconfvalue: bar_action"); break; case SWM_S_BAR_ACTION_EXPAND: bar_action_expand = (atoi(value) != 0); break; case SWM_S_BAR_AT_BOTTOM: bar_at_bottom = (atoi(value) != 0); break; case SWM_S_BAR_BORDER_WIDTH: bar_border_width = atoi(value); if (bar_border_width < 0) bar_border_width = 0; break; case SWM_S_BAR_ENABLED: bar_enabled = (atoi(value) != 0); break; case SWM_S_BAR_ENABLED_WS: ws_id = atoi(selector) - 1; if (ws_id < 0 || ws_id >= workspace_limit) { ALLOCSTR(emsg, "invalid workspace: %d", ws_id + 1); return (1); } num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) { if ((ws = get_workspace(&screens[i], ws_id))) ws->bar_enabled = (atoi(value) != 0); } break; case SWM_S_BAR_FONT: free(bar_fonts); if ((bar_fonts = strdup(value)) == NULL) err(1, "setconfvalue: strdup"); break; case SWM_S_BAR_FONT_PUA: free(bar_fontname_pua); if ((bar_fontname_pua = strdup(value)) == NULL) err(1, "setconfvalue: bar_font_pua"); break; case SWM_S_BAR_FORMAT: free(bar_format); if ((bar_format = strdup(value)) == NULL) err(1, "setconfvalue: bar_format"); break; case SWM_S_BAR_JUSTIFY: if (strcmp(value, "left") == 0) bar_justify = SWM_BAR_JUSTIFY_LEFT; else if (strcmp(value, "center") == 0) bar_justify = SWM_BAR_JUSTIFY_CENTER; else if (strcmp(value, "right") == 0) bar_justify = SWM_BAR_JUSTIFY_RIGHT; else { ALLOCSTR(emsg, "invalid value: %s", value); return (1); } break; case SWM_S_BORDER_WIDTH: border_width = atoi(value); if (border_width < 0) border_width = 0; break; case SWM_S_BOUNDARY_WIDTH: boundary_width = atoi(value); if (boundary_width < 0) boundary_width = 0; break; case SWM_S_CLICK_TO_RAISE: click_to_raise = (atoi(value) != 0); break; case SWM_S_CLOCK_ENABLED: clock_enabled = (atoi(value) != 0); break; case SWM_S_CLOCK_FORMAT: #ifndef SWM_DENY_CLOCK_FORMAT free(clock_format); if ((clock_format = strdup(value)) == NULL) err(1, "setconfvalue: clock_format"); #endif break; case SWM_S_CYCLE_EMPTY: cycle_empty = (atoi(value) != 0); break; case SWM_S_CYCLE_VISIBLE: cycle_visible = (atoi(value) != 0); break; case SWM_S_DIALOG_RATIO: dialog_ratio = atof(value); if (dialog_ratio > 1.0 || dialog_ratio <= .3) dialog_ratio = .6; break; case SWM_S_DISABLE_BORDER: disable_border_always = (strcmp(value, "always") == 0); disable_border = (atoi(value) != 0) || disable_border_always; break; case SWM_S_FOCUS_CLOSE: if (strcmp(value, "first") == 0) focus_close = SWM_STACK_BOTTOM; else if (strcmp(value, "last") == 0) focus_close = SWM_STACK_TOP; else if (strcmp(value, "next") == 0) focus_close = SWM_STACK_ABOVE; else if (strcmp(value, "previous") == 0) focus_close = SWM_STACK_BELOW; else if (!strcmp(value, "prior")) focus_close = SWM_STACK_PRIOR; else { ALLOCSTR(emsg, "invalid value: %s", value); return (1); } break; case SWM_S_FOCUS_CLOSE_WRAP: focus_close_wrap = (atoi(value) != 0); break; case SWM_S_FOCUS_DEFAULT: if (strcmp(value, "last") == 0) focus_default = SWM_STACK_TOP; else if (strcmp(value, "first") == 0) focus_default = SWM_STACK_BOTTOM; else { ALLOCSTR(emsg, "invalid value: %s", value); return (1); } break; case SWM_S_FOCUS_MODE: if (strcmp(value, "default") == 0) focus_mode = SWM_FOCUS_DEFAULT; else if (strcmp(value, "follow") == 0 || strcmp(value, "follow_cursor") == 0) focus_mode = SWM_FOCUS_FOLLOW; else if (strcmp(value, "manual") == 0) focus_mode = SWM_FOCUS_MANUAL; else { ALLOCSTR(emsg, "invalid value: %s", value); return (1); } break; case SWM_S_FULLSCREEN_HIDE_OTHER: fullscreen_hide_other = atoi(value); break; case SWM_S_FULLSCREEN_UNFOCUS: if (strcmp(value, "none") == 0 || strcmp(value, "default") == 0) fullscreen_unfocus = SWM_UNFOCUS_NONE; else if (strcmp(value, "restore") == 0) fullscreen_unfocus = SWM_UNFOCUS_RESTORE; else if (strcmp(value, "iconify") == 0) fullscreen_unfocus = SWM_UNFOCUS_ICONIFY; else if (strcmp(value, "float") == 0) fullscreen_unfocus = SWM_UNFOCUS_FLOAT; else if (strcmp(value, "below") == 0) fullscreen_unfocus = SWM_UNFOCUS_BELOW; else if (strcmp(value, "quick_below") == 0) fullscreen_unfocus = SWM_UNFOCUS_QUICK_BELOW; else { ALLOCSTR(emsg, "invalid value: %s", value); return (1); } break; case SWM_S_ICONIC_ENABLED: iconic_enabled = (atoi(value) != 0); break; case SWM_S_MAX_LAYOUT_MAXIMIZE: max_layout_maximize = atoi(value); break; case SWM_S_MAXIMIZE_HIDE_BAR: maximize_hide_bar = atoi(value); break; case SWM_S_MAXIMIZE_HIDE_OTHER: maximize_hide_other = atoi(value); break; case SWM_S_MAXIMIZED_UNFOCUS: if (strcmp(value, "none") == 0) maximized_unfocus = SWM_UNFOCUS_NONE; else if (strcmp(value, "restore") == 0 || strcmp(value, "default") == 0) maximized_unfocus = SWM_UNFOCUS_RESTORE; else if (strcmp(value, "iconify") == 0) maximized_unfocus = SWM_UNFOCUS_ICONIFY; else if (strcmp(value, "float") == 0) maximized_unfocus = SWM_UNFOCUS_FLOAT; else if (strcmp(value, "below") == 0) maximized_unfocus = SWM_UNFOCUS_BELOW; else if (strcmp(value, "quick_below") == 0) maximized_unfocus = SWM_UNFOCUS_QUICK_BELOW; else { ALLOCSTR(emsg, "invalid value: %s", value); return (1); } break; case SWM_S_REGION_PADDING: region_padding = atoi(value); if (region_padding < 0) region_padding = 0; break; case SWM_S_SNAP_RANGE: snap_range = atoi(value); if (snap_range < 0) snap_range = 0; break; case SWM_S_SPAWN_ORDER: if (strcmp(value, "first") == 0) spawn_position = SWM_STACK_BOTTOM; else if (strcmp(value, "last") == 0) spawn_position = SWM_STACK_TOP; else if (strcmp(value, "next") == 0) spawn_position = SWM_STACK_ABOVE; else if (strcmp(value, "previous") == 0) spawn_position = SWM_STACK_BELOW; else { ALLOCSTR(emsg, "invalid value: %s", value); return (1); } break; case SWM_S_SPAWN_TERM: setconfspawn("term", value, 0, emsg); setconfspawn("spawn_term", value, 0, emsg); break; case SWM_S_STACK_ENABLED: stack_enabled = (atoi(value) != 0); break; case SWM_S_TERM_WIDTH: term_width = atoi(value); if (term_width < 0) term_width = 0; break; case SWM_S_TILE_GAP: tile_gap = atoi(value); break; case SWM_S_URGENT_COLLAPSE: urgent_collapse = (atoi(value) != 0); break; case SWM_S_URGENT_ENABLED: urgent_enabled = (atoi(value) != 0); break; case SWM_S_VERBOSE_LAYOUT: verbose_layout = (atoi(value) != 0); for (i = 0; layouts[i].l_stack != NULL; i++) { if (verbose_layout) layouts[i].l_string = fancy_stacker; else layouts[i].l_string = plain_stacker; } break; case SWM_S_WARP_FOCUS: warp_focus = (atoi(value) != 0); break; case SWM_S_WARP_POINTER: warp_pointer = (atoi(value) != 0); break; case SWM_S_WINDOW_CLASS_ENABLED: window_class_enabled = (atoi(value) != 0); break; case SWM_S_WINDOW_INSTANCE_ENABLED: window_instance_enabled = (atoi(value) != 0); break; case SWM_S_WINDOW_NAME_ENABLED: window_name_enabled = (atoi(value) != 0); break; case SWM_S_WORKSPACE_AUTOROTATE: workspace_autorotate = (atoi(value) != 0); /* Update existing region(s). */ num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) TAILQ_FOREACH(r, &screens[i].rl, entry) rotatews(r->ws, (workspace_autorotate ? ROTATION(r) : ROTATION_DEFAULT)); break; case SWM_S_WORKSPACE_CLAMP: workspace_clamp = (atoi(value) != 0); break; case SWM_S_WORKSPACE_LIMIT: workspace_limit = atoi(value); if (workspace_limit > SWM_WS_MAX) workspace_limit = SWM_WS_MAX; else if (workspace_limit < 1) workspace_limit = 1; num_screens = get_screen_count(); for (i = 0; i < num_screens; ++i) ewmh_update_desktops(&screens[i]); break; case SWM_S_WORKSPACE_INDICATOR: if (parse_workspace_indicator(value, &workspace_indicator, emsg)) return (1); break; case SWM_S_WORKSPACE_NAME: if (getenv("SWM_STARTED") != NULL) return (0); n = 0; if (sscanf(value, "ws[%d]:%n", &ws_id, &n) != 1 || n == 0 || value[n] == '\0') { ALLOCSTR(emsg, "invalid syntax: %s", value); return (1); } value += n; ws_id--; if (ws_id < 0 || ws_id >= workspace_limit) { ALLOCSTR(emsg, "invalid workspace: %d", ws_id + 1); return (1); } num_screens = get_screen_count(); for (i = 0; i < num_screens; ++i) { ws = get_workspace(&screens[i], ws_id); if (ws) { free(ws->name); if ((ws->name = strdup(value)) == NULL) err(1, "name: strdup"); ewmh_update_desktop_names(&screens[i]); ewmh_get_desktop_names(&screens[i]); } } break; case SWM_S_FOCUS_MARK_NONE: free(focus_mark_none); focus_mark_none = unescape_value(value); break; case SWM_S_FOCUS_MARK_NORMAL: free(focus_mark_normal); focus_mark_normal = unescape_value(value); break; case SWM_S_FOCUS_MARK_FLOATING: free(focus_mark_floating); focus_mark_floating = unescape_value(value); break; case SWM_S_FOCUS_MARK_FREE: free(focus_mark_free); focus_mark_free = unescape_value(value); break; case SWM_S_FOCUS_MARK_MAXIMIZED: free(focus_mark_maximized); focus_mark_maximized = unescape_value(value); break; case SWM_S_WORKSPACE_MARK_CURRENT: free(workspace_mark_current); workspace_mark_current = unescape_value(value); break; case SWM_S_WORKSPACE_MARK_CURRENT_SUFFIX: free(workspace_mark_current_suffix); workspace_mark_current_suffix = unescape_value(value); break; case SWM_S_WORKSPACE_MARK_URGENT: free(workspace_mark_urgent); workspace_mark_urgent = unescape_value(value); break; case SWM_S_WORKSPACE_MARK_URGENT_SUFFIX: free(workspace_mark_urgent_suffix); workspace_mark_urgent_suffix = unescape_value(value); break; case SWM_S_WORKSPACE_MARK_ACTIVE: free(workspace_mark_active); workspace_mark_active = unescape_value(value); break; case SWM_S_WORKSPACE_MARK_ACTIVE_SUFFIX: free(workspace_mark_active_suffix); workspace_mark_active_suffix = unescape_value(value); break; case SWM_S_WORKSPACE_MARK_EMPTY: free(workspace_mark_empty); workspace_mark_empty = unescape_value(value); break; case SWM_S_WORKSPACE_MARK_EMPTY_SUFFIX: free(workspace_mark_empty_suffix); workspace_mark_empty_suffix = unescape_value(value); break; case SWM_S_STACK_MARK_FLOATING: free(stack_mark_floating); stack_mark_floating = unescape_value(value); break; case SWM_S_STACK_MARK_MAX: free(stack_mark_max); stack_mark_max = unescape_value(value); break; case SWM_S_STACK_MARK_VERTICAL: free(stack_mark_vertical); stack_mark_vertical = unescape_value(value); break; case SWM_S_STACK_MARK_VERTICAL_FLIP: free(stack_mark_vertical_flip); stack_mark_vertical_flip = unescape_value(value); break; case SWM_S_STACK_MARK_HORIZONTAL: free(stack_mark_horizontal); stack_mark_horizontal = unescape_value(value); break; case SWM_S_STACK_MARK_HORIZONTAL_FLIP: free(stack_mark_horizontal_flip); stack_mark_horizontal_flip = unescape_value(value); break; default: ALLOCSTR(emsg, "invalid option"); return (1); } return (0); } int setconfmodkey(const char *selector, const char *value, int flags, char **emsg) { /* Suppress warning. */ (void)selector; (void)flags; if (strncasecmp(value, "Mod1", strlen("Mod1")) == 0) update_modkey(XCB_MOD_MASK_1); else if (strncasecmp(value, "Mod2", strlen("Mod2")) == 0) update_modkey(XCB_MOD_MASK_2); else if (strncasecmp(value, "Mod3", strlen("Mod3")) == 0) update_modkey(XCB_MOD_MASK_3); else if (strncasecmp(value, "Mod4", strlen("Mod4")) == 0) update_modkey(XCB_MOD_MASK_4); else if (strncasecmp(value, "Mod5", strlen("Mod5")) == 0) update_modkey(XCB_MOD_MASK_5); else { ALLOCSTR(emsg, "invalid value: %s", value); return (1); } return (0); } int setconfcancelkey(const char *selector, const char *value, int flags, char **emsg) { xcb_keysym_t ks; char *name, *cp; /* Suppress warning. */ (void)selector; (void)flags; if ((cp = name = strdup(value)) == NULL) err(1, "setconfcancelkey: strdup"); cp += strcspn(cp, SWM_CONF_WHITESPACE) + 1; *cp = '\0'; if ((ks = get_string_keysym(name)) == XCB_NO_SYMBOL) { ALLOCSTR(emsg, "invalid key"); free(name); return (1); } free(name); cancel_key = ks; DNPRINTF(SWM_D_KEY, "cancel_key = %u\n", cancel_key); return (0); } int setconfcolor(const char *selector, const char *value, int flags, char **emsg) { struct swm_screen *s; int first, last, i = 0, num_screens; num_screens = get_screen_count(); /* conf screen indices begin at 1; treat vals <= 0 as 'all screens.' */ if (selector == NULL || strlen(selector) == 0 || (last = atoi(selector) - 1) < 0) { first = 0; last = num_screens - 1; } else { first = last; } if (last >= num_screens) { ALLOCSTR(emsg, "invalid screen index: %d", last + 1); return (1); } for (i = first; i <= last; ++i) { s = &screens[i]; setscreencolor(s, value, flags); /* * Need to sync 'maximized', 'selected' and 'unfocus' colors * with their respective colors if they haven't been customized. */ switch (flags) { case SWM_S_COLOR_FOCUS: if (!s->c[SWM_S_COLOR_FOCUS_MAXIMIZED].manual) setscreencolor(s, value, SWM_S_COLOR_FOCUS_MAXIMIZED); break; case SWM_S_COLOR_UNFOCUS: if (!s->c[SWM_S_COLOR_UNFOCUS_MAXIMIZED].manual) setscreencolor(s, value, SWM_S_COLOR_UNFOCUS_MAXIMIZED); break; case SWM_S_COLOR_FOCUS_FREE: if (!s->c[SWM_S_COLOR_FOCUS_MAXIMIZED_FREE].manual) setscreencolor(s, value, SWM_S_COLOR_FOCUS_MAXIMIZED_FREE); break; case SWM_S_COLOR_UNFOCUS_FREE: if (!s->c[SWM_S_COLOR_UNFOCUS_MAXIMIZED_FREE].manual) setscreencolor(s, value, SWM_S_COLOR_UNFOCUS_MAXIMIZED_FREE); break; case SWM_S_COLOR_BAR: if (!s->c[SWM_S_COLOR_BAR_FONT_SELECTED].manual) setscreencolor(s, value, SWM_S_COLOR_BAR_FONT_SELECTED); if (!s->c[SWM_S_COLOR_BAR_UNFOCUS].manual) setscreencolor(s, value, SWM_S_COLOR_BAR_UNFOCUS); break; case SWM_S_COLOR_BAR_BORDER: if (!s->c[SWM_S_COLOR_BAR_SELECTED].manual) setscreencolor(s, value, SWM_S_COLOR_BAR_SELECTED); break; case SWM_S_COLOR_BAR_FONT: if (!s->c[SWM_S_COLOR_BAR_FONT_UNFOCUS].manual) setscreencolor(s, value, SWM_S_COLOR_BAR_FONT_UNFOCUS); break; } s->c[flags].manual = 1; } return (0); } int setconfcolorlist(const char *selector, const char *value, int flags, char **emsg) { char *b, *str, *sp; switch (flags) { case SWM_S_COLOR_BAR: case SWM_S_COLOR_BAR_UNFOCUS: case SWM_S_COLOR_BAR_FREE: /* Set list of background colors */ if ((sp = str = strdup(value)) == NULL) err(1, "setconfvalue: strdup"); num_bg_colors = 0; while ((b = strsep(&sp, SWM_CONF_DELIMLIST)) != NULL) { while (isblank((unsigned char)*b)) b++; if (*b == '\0') continue; setconfcolor(selector, b, flags + num_bg_colors, emsg); num_bg_colors++; if (num_bg_colors == SWM_BAR_MAX_COLORS) break; } free(str); break; case SWM_S_COLOR_BAR_FONT: case SWM_S_COLOR_BAR_FONT_UNFOCUS: case SWM_S_COLOR_BAR_FONT_FREE: /* Set list of foreground colors */ if ((sp = str = strdup(value)) == NULL) err(1, "setconfvalue: strdup"); num_fg_colors = 0; while ((b = strsep(&sp, SWM_CONF_DELIMLIST)) != NULL) { while (isblank((unsigned char)*b)) b++; if (*b == '\0') continue; setconfcolor(selector, b, flags + num_fg_colors, emsg); num_fg_colors++; if (num_fg_colors == SWM_BAR_MAX_COLORS) break; } free(str); break; } return (0); } int setconfregion(const char *selector, const char *value, int flags, char **emsg) { unsigned int x, y, w, h; int sidx, num_screens, rot; char r[9]; xcb_screen_t *screen; /* suppress unused warnings since vars are needed */ (void)selector; (void)flags; DNPRINTF(SWM_D_CONF, "%s\n", value); num_screens = get_screen_count(); if (sscanf(value, "screen[%d]:%ux%u+%u+%u,%8s", &sidx, &w, &h, &x, &y, r) == 6) { if (strcasecmp(r, "normal") == 0) rot = XCB_RANDR_ROTATION_ROTATE_0; else if (strcasecmp(r, "left") == 0) rot = XCB_RANDR_ROTATION_ROTATE_90; else if (strcasecmp(r, "inverted") == 0) rot = XCB_RANDR_ROTATION_ROTATE_180; else if (strcasecmp(r, "right") == 0) rot = XCB_RANDR_ROTATION_ROTATE_270; else { ALLOCSTR(emsg, "invalid rotation: %s", value); return (1); } } else if (sscanf(value, "screen[%d]:%ux%u+%u+%u", &sidx, &w, &h, &x, &y) == 5) { rot = ROTATION_DEFAULT; } else { ALLOCSTR(emsg, "invalid syntax: %s", value); return (1); } if (sidx < 1 || sidx > num_screens) { ALLOCSTR(emsg, "invalid screen index: %d", sidx); return (1); } sidx--; if ((screen = get_screen(sidx)) == NULL) errx(1, "ERROR: unable to get screen %d.", sidx); if (w < 1 || h < 1) { ALLOCSTR(emsg, "invalid size: %ux%u", w, h); return (1); } if (x > screen->width_in_pixels || y > screen->height_in_pixels || w + x > screen->width_in_pixels || h + y > screen->height_in_pixels) { ALLOCSTR(emsg, "geometry exceeds screen boundary: %ux%u+%u+%u", w, h, x, y); return (1); } new_region(&screens[sidx], x, y, w, h, rot); return (0); } int setautorun(const char *selector, const char *value, int flags, char **emsg) { int ws_id; char *ap, *sp, *str; union arg a; int argc = 0, n; pid_t pid; struct pid_e *p; /* suppress unused warnings since vars are needed */ (void)selector; (void)flags; if (getenv("SWM_STARTED")) return (0); n = 0; if (sscanf(value, "ws[%d]:%n", &ws_id, &n) != 1 || n == 0 || value[n] == '\0') { ALLOCSTR(emsg, "invalid syntax: %s", value); return (1); } value += n; if (ws_id > 0) ws_id--; if (ws_id < -1 || ws_id >= workspace_limit) { ALLOCSTR(emsg, "invalid workspace: %d", ws_id + 1); return (1); } sp = str = expand_tilde(value); /* * This is a little intricate * * If the pid already exists we simply reuse it because it means it was * used before AND not claimed by manage_window. We get away with * altering it in the parent after INSERT because this can not be a race */ a.argv = NULL; while ((ap = strsep(&sp, " \t")) != NULL) { if (*ap == '\0') continue; DNPRINTF(SWM_D_SPAWN, "arg [%s]\n", ap); argc++; if ((a.argv = realloc(a.argv, argc * sizeof(char *))) == NULL) err(1, "setautorun: realloc"); a.argv[argc - 1] = ap; } if ((a.argv = realloc(a.argv, (argc + 1) * sizeof(char *))) == NULL) err(1, "setautorun: realloc"); a.argv[argc] = NULL; if ((pid = fork()) == 0) { spawn(ws_id, &a, true); /* NOTREACHED */ _exit(1); } free(a.argv); free(str); /* parent */ p = find_pid(pid); if (p == NULL) { p = calloc(1, sizeof *p); if (p == NULL) return (1); TAILQ_INSERT_TAIL(&pidlist, p, entry); } p->pid = pid; p->ws = ws_id; return (0); } int setlayout(const char *selector, const char *value, int flags, char **emsg) { struct workspace *ws; int ws_id, i, x, mg, ma, si, ar, n; int st = SWM_V_STACK, num_screens; bool f = false; /* suppress unused warnings since vars are needed */ (void)selector; (void)flags; if (getenv("SWM_STARTED")) return (0); n = 0; if (sscanf(value, "ws[%d]:%d:%d:%d:%d:%n", &ws_id, &mg, &ma, &si, &ar, &n) != 5 || n == 0 || value[n] == '\0') { ALLOCSTR(emsg, "invalid syntax: %s", value); return (1); } value += n; ws_id--; if (ws_id < 0 || ws_id >= workspace_limit) { ALLOCSTR(emsg, "invalid workspace: %d", ws_id + 1); return (1); } if (strcasecmp(value, "vertical") == 0) st = SWM_V_STACK; else if (strcasecmp(value, "vertical_flip") == 0) { st = SWM_V_STACK; f = true; } else if (strcasecmp(value, "horizontal") == 0) st = SWM_H_STACK; else if (strcasecmp(value, "horizontal_flip") == 0) { st = SWM_H_STACK; f = true; } else if (strcasecmp(value, "max") == 0 || strcasecmp(value, "fullscreen") == 0) /* Keep "fullscreen" for backwards compatibility. */ st = SWM_MAX_STACK; else if (strcasecmp(value, "floating") == 0) st = SWM_FLOATING_STACK; else { ALLOCSTR(emsg, "invalid layout: %s", value); return (1); } num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) { ws = get_workspace(&screens[i], ws_id); if (ws == NULL) continue; /* Set layout relative to default rotation. */ uint16_t rot = ws->rotation; rotatews(ws, ROTATION_DEFAULT); ws->cur_layout = &layouts[st]; ws->cur_layout->l_config(ws, SWM_ARG_ID_STACKINIT); ws->always_raise = (ar != 0); if (st == SWM_MAX_STACK) continue; /* master grow */ for (x = 0; x < abs(mg); x++) { ws->cur_layout->l_config(ws, mg >= 0 ? SWM_ARG_ID_MASTERGROW : SWM_ARG_ID_MASTERSHRINK); } /* master add */ for (x = 0; x < abs(ma); x++) { ws->cur_layout->l_config(ws, ma >= 0 ? SWM_ARG_ID_MASTERADD : SWM_ARG_ID_MASTERDEL); } /* stack inc */ for (x = 0; x < abs(si); x++) { ws->cur_layout->l_config(ws, si >= 0 ? SWM_ARG_ID_STACKINC : SWM_ARG_ID_STACKDEC); } /* Apply flip */ if (f) ws->cur_layout->l_config(ws, SWM_ARG_ID_FLIPLAYOUT); /* Reapply rotation. */ rotatews(ws, rot); } return (0); } /* config options */ struct config_option { char *name; int (*func)(const char*, const char*, int, char **); int flags; }; struct config_option configopt[] = { { "autorun", setautorun, 0 }, { "bar_action", setconfvalue, SWM_S_BAR_ACTION }, { "bar_action_expand", setconfvalue, SWM_S_BAR_ACTION_EXPAND }, { "bar_at_bottom", setconfvalue, SWM_S_BAR_AT_BOTTOM }, { "bar_border", setconfcolor, SWM_S_COLOR_BAR_BORDER }, { "bar_border_unfocus", setconfcolor, SWM_S_COLOR_BAR_BORDER_UNFOCUS }, { "bar_border_free", setconfcolor, SWM_S_COLOR_BAR_BORDER_FREE }, { "bar_border_width", setconfvalue, SWM_S_BAR_BORDER_WIDTH }, { "bar_color", setconfcolorlist, SWM_S_COLOR_BAR }, { "bar_color_unfocus", setconfcolorlist, SWM_S_COLOR_BAR_UNFOCUS }, { "bar_color_free", setconfcolorlist, SWM_S_COLOR_BAR_FREE }, { "bar_color_selected", setconfcolor, SWM_S_COLOR_BAR_SELECTED }, { "bar_delay", NULL, 0 }, /* dummy */ { "bar_enabled", setconfvalue, SWM_S_BAR_ENABLED }, { "bar_enabled_ws", setconfvalue, SWM_S_BAR_ENABLED_WS }, { "bar_font", setconfvalue, SWM_S_BAR_FONT }, { "bar_font_color", setconfcolorlist, SWM_S_COLOR_BAR_FONT }, { "bar_font_color_unfocus", setconfcolorlist, SWM_S_COLOR_BAR_FONT_UNFOCUS }, { "bar_font_color_free", setconfcolorlist, SWM_S_COLOR_BAR_FONT_FREE }, { "bar_font_color_selected", setconfcolor, SWM_S_COLOR_BAR_FONT_SELECTED }, { "bar_font_pua", setconfvalue, SWM_S_BAR_FONT_PUA }, { "bar_format", setconfvalue, SWM_S_BAR_FORMAT }, { "bar_justify", setconfvalue, SWM_S_BAR_JUSTIFY }, { "bind", setconfbinding, 0 }, { "border_width", setconfvalue, SWM_S_BORDER_WIDTH }, { "boundary_width", setconfvalue, SWM_S_BOUNDARY_WIDTH }, { "cancelkey", setconfcancelkey, 0 }, { "click_to_raise", setconfvalue, SWM_S_CLICK_TO_RAISE }, { "clock_enabled", setconfvalue, SWM_S_CLOCK_ENABLED }, { "clock_format", setconfvalue, SWM_S_CLOCK_FORMAT }, { "color_focus", setconfcolor, SWM_S_COLOR_FOCUS }, { "color_focus_free", setconfcolor, SWM_S_COLOR_FOCUS_FREE }, { "color_focus_maximized", setconfcolor, SWM_S_COLOR_FOCUS_MAXIMIZED }, { "color_focus_maximized_free", setconfcolor, SWM_S_COLOR_FOCUS_MAXIMIZED_FREE }, { "color_unfocus", setconfcolor, SWM_S_COLOR_UNFOCUS }, { "color_unfocus_free", setconfcolor, SWM_S_COLOR_UNFOCUS_FREE }, { "color_unfocus_maximized", setconfcolor, SWM_S_COLOR_UNFOCUS_MAXIMIZED }, { "color_unfocus_maximized_free", setconfcolor, SWM_S_COLOR_UNFOCUS_MAXIMIZED_FREE }, { "cycle_empty", setconfvalue, SWM_S_CYCLE_EMPTY }, { "cycle_visible", setconfvalue, SWM_S_CYCLE_VISIBLE }, { "dialog_ratio", setconfvalue, SWM_S_DIALOG_RATIO }, { "disable_border", setconfvalue, SWM_S_DISABLE_BORDER }, { "focus_close", setconfvalue, SWM_S_FOCUS_CLOSE }, { "focus_close_wrap", setconfvalue, SWM_S_FOCUS_CLOSE_WRAP }, { "focus_default", setconfvalue, SWM_S_FOCUS_DEFAULT }, { "focus_mode", setconfvalue, SWM_S_FOCUS_MODE }, { "fullscreen_hide_other", setconfvalue, SWM_S_FULLSCREEN_HIDE_OTHER }, { "fullscreen_unfocus", setconfvalue, SWM_S_FULLSCREEN_UNFOCUS }, { "iconic_enabled", setconfvalue, SWM_S_ICONIC_ENABLED }, { "java_workaround", NULL, 0 }, /* dummy */ { "keyboard_mapping", setkeymapping, 0 }, { "layout", setlayout, 0 }, { "max_layout_maximize", setconfvalue, SWM_S_MAX_LAYOUT_MAXIMIZE }, { "maximize_hide_bar", setconfvalue, SWM_S_MAXIMIZE_HIDE_BAR }, { "maximize_hide_other", setconfvalue, SWM_S_MAXIMIZE_HIDE_OTHER }, { "maximized_unfocus", setconfvalue, SWM_S_MAXIMIZED_UNFOCUS }, { "modkey", setconfmodkey, 0 }, { "program", setconfspawn, 0 }, { "quirk", setconfquirk, 0 }, { "region", setconfregion, 0 }, { "region_padding", setconfvalue, SWM_S_REGION_PADDING }, { "screenshot_app", NULL, 0 }, /* dummy */ { "screenshot_enabled", NULL, 0 }, /* dummy */ { "snap_range", setconfvalue, SWM_S_SNAP_RANGE }, { "spawn_position", setconfvalue, SWM_S_SPAWN_ORDER }, { "spawn_term", setconfvalue, SWM_S_SPAWN_TERM }, { "stack_enabled", setconfvalue, SWM_S_STACK_ENABLED }, { "term_width", setconfvalue, SWM_S_TERM_WIDTH }, { "tile_gap", setconfvalue, SWM_S_TILE_GAP }, { "title_class_enabled", setconfvalue, SWM_S_WINDOW_CLASS_ENABLED }, /* For backwards compat. */ { "title_name_enabled", setconfvalue, SWM_S_WINDOW_INSTANCE_ENABLED }, /* For backwards compat. */ { "urgent_collapse", setconfvalue, SWM_S_URGENT_COLLAPSE }, { "urgent_enabled", setconfvalue, SWM_S_URGENT_ENABLED }, { "verbose_layout", setconfvalue, SWM_S_VERBOSE_LAYOUT }, { "warp_focus", setconfvalue, SWM_S_WARP_FOCUS }, { "warp_pointer", setconfvalue, SWM_S_WARP_POINTER }, { "window_class_enabled", setconfvalue, SWM_S_WINDOW_CLASS_ENABLED }, { "window_instance_enabled", setconfvalue, SWM_S_WINDOW_INSTANCE_ENABLED }, { "window_name_enabled", setconfvalue, SWM_S_WINDOW_NAME_ENABLED }, { "workspace_autorotate", setconfvalue, SWM_S_WORKSPACE_AUTOROTATE }, { "workspace_clamp", setconfvalue, SWM_S_WORKSPACE_CLAMP }, { "workspace_limit", setconfvalue, SWM_S_WORKSPACE_LIMIT }, { "workspace_indicator", setconfvalue, SWM_S_WORKSPACE_INDICATOR }, { "name", setconfvalue, SWM_S_WORKSPACE_NAME }, { "focus_mark_none", setconfvalue, SWM_S_FOCUS_MARK_NONE }, { "focus_mark_normal", setconfvalue, SWM_S_FOCUS_MARK_NORMAL }, { "focus_mark_floating", setconfvalue, SWM_S_FOCUS_MARK_FLOATING }, { "focus_mark_free", setconfvalue, SWM_S_FOCUS_MARK_FREE }, { "focus_mark_maximized", setconfvalue, SWM_S_FOCUS_MARK_MAXIMIZED }, { "workspace_mark_current", setconfvalue, SWM_S_WORKSPACE_MARK_CURRENT }, { "workspace_mark_current_suffix", setconfvalue, SWM_S_WORKSPACE_MARK_CURRENT_SUFFIX }, { "workspace_mark_urgent", setconfvalue, SWM_S_WORKSPACE_MARK_URGENT }, { "workspace_mark_urgent_suffix", setconfvalue, SWM_S_WORKSPACE_MARK_URGENT_SUFFIX }, { "workspace_mark_active", setconfvalue, SWM_S_WORKSPACE_MARK_ACTIVE }, { "workspace_mark_active_suffix", setconfvalue, SWM_S_WORKSPACE_MARK_ACTIVE_SUFFIX }, { "workspace_mark_empty", setconfvalue, SWM_S_WORKSPACE_MARK_EMPTY }, { "workspace_mark_empty_suffix", setconfvalue, SWM_S_WORKSPACE_MARK_EMPTY_SUFFIX }, { "stack_mark_floating", setconfvalue, SWM_S_STACK_MARK_FLOATING }, { "stack_mark_max", setconfvalue, SWM_S_STACK_MARK_MAX }, { "stack_mark_vertical", setconfvalue, SWM_S_STACK_MARK_VERTICAL }, { "stack_mark_vertical_flip", setconfvalue, SWM_S_STACK_MARK_VERTICAL_FLIP }, { "stack_mark_horizontal", setconfvalue, SWM_S_STACK_MARK_HORIZONTAL }, { "stack_mark_horizontal_flip", setconfvalue, SWM_S_STACK_MARK_HORIZONTAL_FLIP }, }; void _add_startup_exception(const char *fmt, va_list ap) { if (vasprintf(&startup_exception, fmt, ap) == -1) warn("%s: asprintf", __func__); } void add_startup_exception(const char *fmt, ...) { va_list ap; nr_exceptions++; if (startup_exception) return; /* force bar to be enabled due to exception */ bar_enabled = true; va_start(ap, fmt); _add_startup_exception(fmt, ap); va_end(ap); } int conf_load(const char *filename, int keymapping) { FILE *config; char *line = NULL, *cp, *ce, *optsub, *optval = NULL; char *emsg = NULL; size_t linelen, lineno = 0; int wordlen, i, optidx, count; struct config_option *opt = NULL; DNPRINTF(SWM_D_CONF, "filename: %s, keymapping: %d\n", filename, keymapping); if (filename == NULL) { warnx("conf_load: no filename"); return (1); } DNPRINTF(SWM_D_CONF, "open %s\n", filename); if ((config = fopen(filename, "r")) == NULL) { warn("%s", filename); return (1); } while (!feof(config)) { if (line) free(line); if ((line = fparseln(config, &linelen, &lineno, NULL, FPARSELN_UNESCCOMM | FPARSELN_UNESCCONT)) == NULL) { if (ferror(config)) err(1, "%s", filename); else continue; } cp = line; cp += strspn(cp, " \t\n"); /* eat whitespace */ if (cp[0] == '\0') { /* empty line */ continue; } /* get config option */ wordlen = strcspn(cp, "=[ \t\n"); if (wordlen == 0) { add_startup_exception("%s: line %zd: no option found", filename, lineno); continue; } optidx = -1; for (i = 0; i < LENGTH(configopt); i++) { opt = &configopt[i]; if (strncasecmp(cp, opt->name, wordlen) == 0 && (int)strlen(opt->name) == wordlen) { optidx = i; break; } } if (optidx == -1) { add_startup_exception("%s: line %zd: unknown option " "%.*s", filename, lineno, wordlen, cp); continue; } if (keymapping && opt && strcmp(opt->name, "bind")) { add_startup_exception("%s: line %zd: invalid option " "%.*s", filename, lineno, wordlen, cp); continue; } cp += wordlen; cp += strspn(cp, " \t\n"); /* eat whitespace */ /* get [selector] if any */ optsub = NULL; count = 0; if (*cp == '[') { ++count; /* Get length of selector. */ for (ce = ++cp; *ce != '\0'; ++ce) { /* Find matching (not escaped) bracket. */ if (*ce == ']' && *(ce - 1) != '\\') { --count; break; } } if (count > 0) { add_startup_exception("%s: line %zd: syntax " "error: unterminated selector", filename, lineno); continue; } /* ce points at ']'; terminate optsub. */ *ce = '\0'; optsub = cp; cp = ce + 1; } cp += strspn(cp, "= \t\n"); /* eat trailing */ /* get RHS value */ optval = cp; if (strlen(optval) == 0) { add_startup_exception("%s: line %zd: must supply value " "to %s", filename, lineno, opt->name); continue; } /* trim trailing spaces */ ce = optval + strlen(optval) - 1; while (ce > optval && isspace((unsigned char)*ce)) --ce; *(ce + 1) = '\0'; /* call function to deal with it all */ if (opt->func && opt->func(optsub, optval, opt->flags, &emsg)) { if (emsg) { add_startup_exception("%s: line %zd: %s: %s", filename, lineno, opt->name, emsg); free(emsg); emsg = NULL; } else add_startup_exception("%s: line %zd: %s", filename, lineno, opt->name); } } if (line) free(line); fclose(config); DNPRINTF(SWM_D_CONF, "end\n"); return (0); } static int32_t strtoint32(const char *str, int32_t min, int32_t max, int *fail) { int32_t ret; #if defined(__NetBSD__) int e; ret = strtoi(str, NULL, 10, min, max, &e); *fail = (e != 0); #else const char *errstr; ret = strtonum(str, min, max, &errstr); *fail = (errstr != NULL); #endif return (ret); } pid_t window_get_pid(xcb_window_t win) { pid_t ret = 0; int fail; xcb_get_property_reply_t *pr; pr = xcb_get_property_reply(conn, xcb_get_property(conn, 0, win, a_net_wm_pid, XCB_ATOM_CARDINAL, 0, 1), NULL); if (pr && pr->type == XCB_ATOM_CARDINAL && pr->format == 32) ret = *((pid_t *)xcb_get_property_value(pr)); else { /* tryharder */ free(pr); pr = xcb_get_property_reply(conn, xcb_get_property(conn, 0, win, a_swm_pid, XCB_ATOM_STRING, 0, SWM_PROPLEN), NULL); if (pr && pr->type == XCB_ATOM_STRING && pr->format == 8) { ret = (pid_t)strtoint32(xcb_get_property_value(pr), 0, INT32_MAX, &fail); if (fail) ret = 0; } } free(pr); DNPRINTF(SWM_D_PROP, "pid: %d\n", ret); return (ret); } int get_swm_ws(xcb_window_t id) { int ws_idx = -2, fail; xcb_get_property_reply_t *gpr; gpr = xcb_get_property_reply(conn, xcb_get_property(conn, 0, id, a_swm_ws, XCB_ATOM_STRING, 0, SWM_PROPLEN), NULL); if (gpr && gpr->type == XCB_ATOM_STRING && gpr->format == 8) { ws_idx = strtoint32(xcb_get_property_value(gpr), -1, workspace_limit - 1, &fail); if (fail) ws_idx = -2; } free(gpr); DNPRINTF(SWM_D_PROP, "_SWM_WS: %d\n", ws_idx); return (ws_idx); } int get_ws_id(struct ws_win *win) { xcb_get_property_reply_t *gpr; int wsid = -2; uint32_t val; if (win == NULL) return (-2); gpr = xcb_get_property_reply(conn, xcb_get_property(conn, 0, win->id, ewmh[_NET_WM_DESKTOP].atom, XCB_ATOM_CARDINAL, 0, 1), NULL); if (gpr && gpr->type == XCB_ATOM_CARDINAL && gpr->format == 32) { val = *((uint32_t *)xcb_get_property_value(gpr)); DNPRINTF(SWM_D_PROP, "get _NET_WM_DESKTOP: %#x\n", val); wsid = (val == EWMH_ALL_DESKTOPS ? -1 : (int)val); } free(gpr); if (wsid == -2 && !(win->quirks & SWM_Q_IGNORESPAWNWS)) wsid = get_swm_ws(win->id); if (wsid >= workspace_limit || wsid < -2) wsid = -2; DNPRINTF(SWM_D_PROP, "win %#x, wsid: %d\n", win->id, wsid); return (wsid); } int reparent_window(struct ws_win *win) { xcb_screen_t *s; xcb_void_cookie_t c; xcb_generic_error_t *error; uint32_t wa[3]; if (win_reparented(win)) { DNPRINTF(SWM_D_MISC, "win %#x already reparented.\n", win->id); return (0); } win->frame = xcb_generate_id(conn); DNPRINTF(SWM_D_MISC, "win %#x (f:%#x)\n", win->id, win->frame); if ((s = get_screen(win->s->idx)) == NULL) errx(1, "ERROR: unable to get screen %d.", win->s->idx); wa[0] = s->black_pixel; wa[1] = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_FOCUS_CHANGE; wa[2] = win->s->colormap; xcb_create_window(conn, win->s->depth, win->frame, win->s->root, X(win), Y(win), WIDTH(win), HEIGHT(win), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, win->s->visual, XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP, wa); c = xcb_reparent_window_checked(conn, win->id, win->frame, 0, 0); if ((error = xcb_request_check(conn, c))) { DNPRINTF(SWM_D_MISC, "error:\n"); event_error(error); free(error); /* Abort. */ xcb_destroy_window(conn, win->frame); win->frame = XCB_WINDOW_NONE; unmanage_window(win); return (1); } else { if (win->mapped) { xcb_map_window(conn, win->frame); /* Remap will occur. */ win->unmapping++; win->mapping++; } xcb_change_save_set(conn, XCB_SET_MODE_INSERT, win->id); if (!xinput2_raw) grab_buttons_win(win->id); } return (0); } void unparent_window(struct ws_win *win) { if (!win_reparented(win)) return; if (win->id != XCB_WINDOW_NONE) { xcb_reparent_window(conn, win->id, win->s->root, X(win), Y(win)); if (win->mapped) { /* Remap will occur. */ win->unmapping++; win->mapping++; } xcb_change_save_set(conn, XCB_SET_MODE_DELETE, win->id); } if (win->debug != XCB_WINDOW_NONE) { xcb_destroy_window(conn, win->debug); win->debug = XCB_WINDOW_NONE; } xcb_destroy_window(conn, win->frame); win->frame = XCB_WINDOW_NONE; } struct ws_win * manage_window(xcb_window_t id, int spawn_pos, bool mapping) { struct ws_win *win = NULL, *w; struct swm_screen *s; struct swm_region *r; struct pid_e *p; struct quirk *qp; xcb_get_geometry_reply_t *gr; xcb_get_window_attributes_reply_t *war = NULL; uint32_t i, wa[1], new_flags; int ws_idx, force_ws = -2; char *class, *instance, *name; if (find_bar(id)) { DNPRINTF(SWM_D_MISC, "skip; win %#x is region bar\n", id); goto out; } if (find_region(id)) { DNPRINTF(SWM_D_MISC, "skip; win %#x is region window\n", id); goto out; } if ((win = find_window(id)) != NULL) { DNPRINTF(SWM_D_MISC, "skip; win %#x (f:%#x) already managed\n", win->id, win->frame); goto out; } war = xcb_get_window_attributes_reply(conn, xcb_get_window_attributes(conn, id), NULL); if (war == NULL) { DNPRINTF(SWM_D_EVENT, "skip; window lost\n"); goto out; } if (war->override_redirect) { DNPRINTF(SWM_D_EVENT, "skip; override_redirect\n"); goto out; } if (!mapping && war->map_state == XCB_MAP_STATE_UNMAPPED && get_win_state(id) == XCB_ICCCM_WM_STATE_WITHDRAWN) { DNPRINTF(SWM_D_EVENT, "skip; window withdrawn\n"); goto out; } /* Try to get initial window geometry. */ gr = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, id), NULL); if (gr == NULL) { DNPRINTF(SWM_D_MISC, "get geometry failed\n"); goto out; } /* Create and initialize ws_win object. */ if ((win = calloc(1, sizeof(struct ws_win))) == NULL) err(1, "manage_window: calloc"); if ((win->st = calloc(1, sizeof(struct swm_stackable))) == NULL) err(1, "manage_window: calloc2"); s = find_screen(gr->root); win->st->s = win->s = s; /* this never changes */ win->st->type = STACKABLE_WIN; win->st->win = win; win->id = id; /* Ignore window border if there is one. */ WIDTH(win) = gr->width; HEIGHT(win) = gr->height; X(win) = gr->x + gr->border_width; Y(win) = gr->y + gr->border_width; win->g_float = win->g; /* Window is initially floating. */ win->g_floatref_root = true; win->g_float_xy_valid = false; win->bordered = false; win->maxstackmax = max_layout_maximize; win->mapped = (war->map_state != XCB_MAP_STATE_UNMAPPED); win->mapping = 0; win->unmapping = 0; win->strut = NULL; win->main = win; win->parent = NULL; free(gr); /* Select which X events to monitor and set border pixel color. */ wa[0] = XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY; xcb_change_window_attributes(conn, win->id, XCB_CW_EVENT_MASK, wa); /* Get WM_SIZE_HINTS. */ get_wm_normal_hints(win); win->gravity = win_gravity(win); update_gravity(win); /* Get WM_HINTS. */ get_wm_hints(win); /* Get WM_TRANSIENT_FOR/update parent. */ if (get_wm_transient_for(win)) win->main = find_main_window(win); /* Only updates other wins (not in list yet.) */ update_win_refs(win); /* Redirect focus from any parent windows. */ set_focus_redirect(win); get_wm_protocols(win); ewmh_get_window_type(win); ewmh_get_strut(win); if (swm_debug & SWM_D_MISC) { DNPRINTF(SWM_D_MISC, "window type: "); ewmh_print_window_type(win->type); DPRINTF("\n"); } /* Must be after getting WM_HINTS and WM_PROTOCOLS. */ DNPRINTF(SWM_D_FOCUS, "input_model: %s\n", get_win_input_model_label(win)); /* Automatic quirks based on EWMH. */ if (WINSPLASH(win) || WINDIALOG(win)) win->quirks = SWM_Q_FLOAT; if (WINTOOLBAR(win) || WINUTILITY(win)) win->quirks = SWM_Q_FLOAT | SWM_Q_ANYWHERE; /* Determine initial quirks. */ xcb_icccm_get_wm_class_reply(conn, xcb_icccm_get_wm_class(conn, win->id), &win->ch, NULL); class = win->ch.class_name ? win->ch.class_name : ""; instance = win->ch.instance_name ? win->ch.instance_name : ""; name = get_win_name(win->id); DNPRINTF(SWM_D_CLASS, "class: %s, instance: %s, name: %s\n", class, instance, name); TAILQ_FOREACH(qp, &quirks, entry) { if (regexec(&qp->regex_class, class, 0, NULL, 0) == 0 && regexec(&qp->regex_instance, instance, 0, NULL, 0) == 0 && regexec(&qp->regex_name, name, 0, NULL, 0) == 0) { DNPRINTF(SWM_D_CLASS, "matched quirk: %s:%s:%s " "mask: %#x, ws: %d\n", qp->class, qp->instance, qp->name, qp->quirk, qp->ws); win->quirks = qp->quirk; if (qp->ws >= -1 && qp->ws < workspace_limit) force_ws = qp->ws; } } free(name); /* Reset font sizes (the bruteforce way; no default keybinding). */ if (win->quirks & SWM_Q_XTERM_FONTADJ) { for (i = 0; i < SWM_MAX_FONT_STEPS; i++) fake_keypress(win, XK_KP_Subtract, XCB_MOD_MASK_SHIFT); for (i = 0; i < SWM_MAX_FONT_STEPS; i++) fake_keypress(win, XK_KP_Add, XCB_MOD_MASK_SHIFT); } /* Figure out which workspace the window belongs to. */ if (!win_main(win)) { win->ws = win->main->ws; } else if (!(win->quirks & SWM_Q_IGNOREPID) && (p = find_pid(window_get_pid(win->id))) != NULL) { win->ws = get_workspace(s, p->ws); TAILQ_REMOVE(&pidlist, p, entry); free(p); p = NULL; } else if ((ws_idx = get_ws_id(win)) != -2 && !win_transient(win)) { /* _SWM_WS is set; use that. */ win->ws = get_workspace(s, ws_idx); } else if ((r = get_current_region(s)) && r->ws) win->ws = r->ws; else win->ws = get_workspace(s, 0); if (force_ws != -2) win->ws = get_workspace(s, force_ws); if (win->ws == NULL) win->ws = s->r->ws; /* Failsafe. */ /* WS must be valid before adding to managed list. */ TAILQ_INSERT_TAIL(&s->managed, win, manage_entry); s->managed_count++; /* Set the _NET_WM_DESKTOP atom. */ DNPRINTF(SWM_D_PROP, "set _NET_WM_DESKTOP: %d\n", win->ws->idx); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win->id, ewmh[_NET_WM_DESKTOP].atom, XCB_ATOM_CARDINAL, 32, 1, &win->ws->idx); /* Remove any _SWM_WS now that we set _NET_WM_DESKTOP. */ xcb_delete_property(conn, win->id, a_swm_ws); /* Initial position was specified by the program/user. */ if ((SH_POS(win) && (win->quirks & SWM_Q_ANYWHERE || WINDOCK(win) || WINDESKTOP(win) || ws_floating(win->ws))) || SH_UPOS(win)) win->g_float_xy_valid = true; if (win->ws->r) { /* On MapRequest, update the reference point. */ if (mapping) { win->g_floatref = win->ws->r->g; win->g_floatref_root = false; } /* Make sure window has at least 1px inside its region. */ contain_window(win, get_boundary(win), boundary_width, SWM_CW_ALLSIDES | SWM_CW_HARDBOUNDARY); update_window(win); } /* Figure out where to insert the window in the workspace list. */ if (win->parent && win->ws == win->parent->ws) TAILQ_INSERT_AFTER(&win->parent->ws->winlist, win->parent, win, entry); else if (!win_main(win) && win->ws == win->main->ws) TAILQ_INSERT_AFTER(&win->main->ws->winlist, win->main, win, entry); else if (win->ws->focus && spawn_pos == SWM_STACK_ABOVE) TAILQ_INSERT_AFTER(&win->ws->winlist, win->ws->focus, win, entry); else if (win->ws->focus && spawn_pos == SWM_STACK_BELOW) TAILQ_INSERT_BEFORE(win->ws->focus, win, entry); else switch (spawn_pos) { default: case SWM_STACK_TOP: case SWM_STACK_ABOVE: TAILQ_INSERT_TAIL(&win->ws->winlist, win, entry); break; case SWM_STACK_BOTTOM: case SWM_STACK_BELOW: TAILQ_INSERT_HEAD(&win->ws->winlist, win, entry); } /* The most recent focus, after current focus. */ if (s->focus && (w = TAILQ_FIRST(&s->fl))) TAILQ_INSERT_AFTER(&s->fl, w, win, focus_entry); else TAILQ_INSERT_HEAD(&s->fl, win, focus_entry); /* Same for the priority queue. */ if (s->focus && (w = TAILQ_FIRST(&s->priority))) TAILQ_INSERT_AFTER(&s->priority, w, win, priority_entry); else TAILQ_INSERT_HEAD(&s->priority, win, priority_entry); ewmh_update_client_list(s); /* Get/apply initial _NET_WM_STATE */ ewmh_get_wm_state(win); /* Apply quirks. */ new_flags = win->ewmh_flags; if (win->quirks & SWM_Q_FLOAT) new_flags |= EWMH_F_ABOVE; if (win->quirks & SWM_Q_ANYWHERE) new_flags |= SWM_F_MANUAL; if (win->maxstackmax && ws_maxstack(win->ws)) new_flags |= EWMH_F_MAXIMIZED; ewmh_apply_flags(win, new_flags); ewmh_update_wm_state(win); /* Set initial _NET_WM_ALLOWED_ACTIONS */ ewmh_update_actions(win); update_win_layer_related(win); if (reparent_window(win) == 0) { refresh_stack(s); update_stacking(s); if (win->ws->r && !HIDDEN(win)) map_window(win); else unmap_window(win); update_window(win); DNPRINTF(SWM_D_MISC, "done. win %#x, (x,y) w x h: (%d,%d) " "%d x %d, ws: %d, iconic: %s, transient: %#x\n", win->id, X(win), Y(win), WIDTH(win), HEIGHT(win), win->ws->idx, YESNO(HIDDEN(win)), win->transient_for); } else { /* Failed to manage. */ win = NULL; } out: free(war); return (win); } static const char * get_gravity_label(uint8_t val) { const char *label = NULL; switch(val) { case XCB_GRAVITY_NORTH_WEST: label = "NorthWest"; break; case XCB_GRAVITY_NORTH: label = "North"; break; case XCB_GRAVITY_NORTH_EAST: label = "NorthEast"; break; case XCB_GRAVITY_WEST: label = "West"; break; case XCB_GRAVITY_CENTER: label = "Center"; break; case XCB_GRAVITY_EAST: label = "East"; break; case XCB_GRAVITY_SOUTH_WEST: label = "SouthWest"; break; case XCB_GRAVITY_SOUTH: label = "South"; break; case XCB_GRAVITY_SOUTH_EAST: label = "SouthEast"; break; case XCB_GRAVITY_STATIC: label = "Static"; break; default: label = "Unknown"; break; } return (label); } static void update_gravity(struct ws_win *win) { switch (win->gravity) { case XCB_GRAVITY_NORTH_WEST: win->g_grav.x = win->g_grav.y = win_border(win); break; case XCB_GRAVITY_NORTH: win->g_grav.x = -win->g_float.w / 2; win->g_grav.y = win_border(win); break; case XCB_GRAVITY_NORTH_EAST: win->g_grav.x = -win->g_float.w - win_border(win); win->g_grav.y = win_border(win); break; case XCB_GRAVITY_WEST: win->g_grav.x = win_border(win); win->g_grav.y = -win->g_float.h / 2; break; case XCB_GRAVITY_CENTER: win->g_grav.x = -win->g_float.w / 2; win->g_grav.y = -win->g_float.h / 2; break; case XCB_GRAVITY_EAST: win->g_grav.x = -win->g_float.w - win_border(win); win->g_grav.y = -win->g_float.h / 2; break; case XCB_GRAVITY_SOUTH_WEST: win->g_grav.x = win_border(win); win->g_grav.y = -win->g_float.h - win_border(win); break; case XCB_GRAVITY_SOUTH: win->g_grav.x = -win->g_float.w / 2; win->g_grav.y = -win->g_float.h - win_border(win); break; case XCB_GRAVITY_SOUTH_EAST: win->g_grav.x = -win->g_float.w - win_border(win); win->g_grav.y = -win->g_float.h - win_border(win); break; case XCB_GRAVITY_STATIC: default: win->g_grav.x = win->g_grav.y = 0; break; } DNPRINTF(SWM_D_MISC, "win %#x, g_grav (x,y): (%d,%d) gravity: %s\n", win->id, win->g_grav.x, win->g_grav.y, get_gravity_label(win->gravity)); } void free_window(struct ws_win *win) { DNPRINTF(SWM_D_MISC, "win %#x\n", WINID(win)); if (win == NULL) return; xcb_icccm_get_wm_class_reply_wipe(&win->ch); free(win->st); /* paint memory */ memset(win, 0xff, sizeof *win); /* XXX kill later */ free(win); DNPRINTF(SWM_D_MISC, "done\n"); } void unmanage_window(struct ws_win *win) { struct swm_stackable *st; if (win == NULL) return; DNPRINTF(SWM_D_MISC, "win %#x (f:%#x)\n", win->id, win->frame); kill_refs(win); unparent_window(win); TAILQ_REMOVE(&win->ws->winlist, win, entry); TAILQ_REMOVE(&win->s->fl, win, focus_entry); TAILQ_REMOVE(&win->s->priority, win, priority_entry); TAILQ_REMOVE(&win->s->managed, win, manage_entry); win->s->managed_count--; if (win->strut) { SLIST_REMOVE(&win->s->struts, win->strut, swm_strut, entry); free(win->strut); win->strut = NULL; } SLIST_FOREACH(st, &win->s->stack, entry) if (st == win->st) SLIST_REMOVE(&win->s->stack, win->st, swm_stackable, entry); ewmh_update_client_list(win->s); free_window(win); } const char * get_randr_event_label(xcb_generic_event_t *e) { const char *label = NULL; uint8_t type = XCB_EVENT_RESPONSE_TYPE(e); if ((type - randr_eventbase) == XCB_RANDR_SCREEN_CHANGE_NOTIFY) label = "RRScreenChangeNotify"; else if ((type - randr_eventbase) == XCB_RANDR_NOTIFY) { switch (((xcb_randr_notify_event_t *)e)->subCode) { case XCB_RANDR_NOTIFY_CRTC_CHANGE: label = "RRCrtcChangeNotify"; break; case XCB_RANDR_NOTIFY_OUTPUT_CHANGE: label = "RROutputChangeNotify"; break; case XCB_RANDR_NOTIFY_OUTPUT_PROPERTY: label = "RROutputPropertyNotify"; break; case XCB_RANDR_NOTIFY_PROVIDER_CHANGE: label = "RRProviderChangeNotify"; break; case XCB_RANDR_NOTIFY_PROVIDER_PROPERTY: label = "RRProviderPropertyNotify"; break; case XCB_RANDR_NOTIFY_RESOURCE_CHANGE: label = "RResourceChangeNotify"; break; case XCB_RANDR_NOTIFY_LEASE: label = "RRLeaseNotify"; break; default: label = "RRNotifyUnknown"; } } return (label); } const char * get_event_label(xcb_generic_event_t *e) { const char *label = NULL; uint8_t type = XCB_EVENT_RESPONSE_TYPE(e); if (type <= XCB_MAPPING_NOTIFY) label = xcb_event_get_label(type); else if (type == XCB_GE_GENERIC) { #ifdef SWM_XCB_HAS_XINPUT if (xinput2_support && ((xcb_ge_generic_event_t *)e)->extension == xinput2_opcode) label = get_input_event_label( (xcb_ge_generic_event_t *)e); else #endif label = "Unknown GenericEvent"; } else if (randr_support && ((type - randr_eventbase) == XCB_RANDR_SCREEN_CHANGE_NOTIFY || (type - randr_eventbase) == XCB_RANDR_NOTIFY)) label = get_randr_event_label(e); else label = "Unknown Event"; return (label); } /* events */ void expose(xcb_expose_event_t *e) { struct ws_win *w; struct swm_bar *b; DNPRINTF(SWM_D_EVENT, "win %#x, count: %d\n", e->window, e->count); if (e->count > 0) return; if ((b = find_bar(e->window))) { bar_draw(b); xcb_flush(conn); } else if ((w = find_window(e->window)) && w->frame == e->window) { draw_frame(w); debug_refresh(w); xcb_flush(conn); } DNPRINTF(SWM_D_EVENT, "done\n"); } void focusin(xcb_focus_in_event_t *e) { struct swm_region *r; struct ws_win *win; struct swm_screen *s = NULL; DNPRINTF(SWM_D_EVENT, "win %#x, mode: %s(%u), detail: %s(%u)\n", e->event, get_notify_mode_label(e->mode), e->mode, get_notify_detail_label(e->detail), e->detail); if (e->detail == XCB_NOTIFY_DETAIL_POINTER || e->detail == XCB_NOTIFY_DETAIL_VIRTUAL) return; if (e->mode == XCB_NOTIFY_MODE_GRAB && e->detail == XCB_NOTIFY_DETAIL_INFERIOR) return; if (e->mode == XCB_NOTIFY_MODE_WHILE_GRABBED && e->detail == XCB_NOTIFY_DETAIL_NONLINEAR_VIRTUAL) return; /* Managed window. */ if ((win = find_window(e->event))) { /* Ignore if there is a current focus and its unrelated. */ if (win->mapped && win != win->ws->focus && (win->ws->focus == NULL || win_related(win, win->ws->focus))) { set_focus(win->s, win); draw_frame(get_ws_focus_prev(win->ws)); draw_frame(win); update_stacking(win->s); flush(); } } else { /* Redirect focus if needed. */ r = find_region(e->event); if (r) s = r->s; else s = find_screen(e->event); if (s && !follow_pointer(s)) { win = s->focus; if (win == NULL && s->r_focus) win = get_focus_magic( get_ws_focus(s->r_focus->ws)); if (win) { focus_win_input(win, false); set_focus(s, win); draw_frame(win); draw_frame(get_ws_focus_prev(win->ws)); flush(); } } } DNPRINTF(SWM_D_EVENT, "done\n"); } static void focusout(xcb_focus_out_event_t *e) { DNPRINTF(SWM_D_EVENT, "win %#x, mode: %s(%u), detail: %s(%u)\n", e->event, get_notify_mode_label(e->mode), e->mode, get_notify_detail_label(e->detail), e->detail); } void keypress(xcb_key_press_event_t *e) { struct action *ap; struct binding *bp; xcb_keysym_t keysym; bool replay = true; event_time = e->time; keysym = xcb_key_press_lookup_keysym(syms, e, 0); DNPRINTF(SWM_D_EVENT, "keysym: %u, win (x,y): %#x (%d,%d), detail: %u, " "time: %#x, root (x,y): %#x (%d,%d), child: %#x, state: %u, " "cleaned: %u, same_screen: %s\n", keysym, e->event, e->event_x, e->event_y, e->detail, e->time, e->root, e->root_x, e->root_y, e->child, e->state, CLEANMASK(e->state), YESNO(e->same_screen)); bp = binding_lookup(CLEANMASK(e->state), KEYBIND, keysym); if (bp == NULL) { /* Look for catch-all. */ if ((bp = binding_lookup(ANYMOD, KEYBIND, keysym)) == NULL) goto out; } replay = bp->flags & BINDING_F_REPLAY; if ((ap = &actions[bp->action]) == NULL) goto out; if (bp->action == FN_SPAWN_CUSTOM) spawn_custom(get_current_region(find_screen(e->root)), &ap->args, bp->spawn_name); else if (ap->func) ap->func(find_screen(e->root), bp, &ap->args); replay = replay && !(ap->flags & FN_F_NOREPLAY); out: if (replay) { /* Replay event to event window */ DNPRINTF(SWM_D_EVENT, "replaying\n"); xcb_allow_events(conn, XCB_ALLOW_REPLAY_KEYBOARD, e->time); } else { /* Unfreeze grab events. */ DNPRINTF(SWM_D_EVENT, "unfreezing\n"); xcb_allow_events(conn, XCB_ALLOW_SYNC_KEYBOARD, e->time); } xcb_flush(conn); DNPRINTF(SWM_D_EVENT, "done\n"); } void keyrelease(xcb_key_release_event_t *e) { struct action *ap; struct binding *bp; xcb_keysym_t keysym; event_time = e->time; keysym = xcb_key_release_lookup_keysym(syms, e, 0); DNPRINTF(SWM_D_EVENT, "keysym: %u, win (x,y): %#x (%d,%d), detail: %u, " "time: %#x, root (x,y): %#x (%d,%d), child: %#x, state: %u, " "same_screen: %s\n", keysym, e->event, e->event_x, e->event_y, e->detail, e->time, e->root, e->root_x, e->root_y, e->child, e->state, YESNO(e->same_screen)); bp = binding_lookup(CLEANMASK(e->state), KEYBIND, keysym); if (bp == NULL) /* Look for catch-all. */ bp = binding_lookup(ANYMOD, KEYBIND, keysym); if (bp && (ap = &actions[bp->action]) && !(ap->flags & FN_F_NOREPLAY) && bp->flags & BINDING_F_REPLAY) { /* Replay event to event window */ DNPRINTF(SWM_D_EVENT, "replaying\n"); xcb_allow_events(conn, XCB_ALLOW_REPLAY_KEYBOARD, e->time); } else { /* Unfreeze grab events. */ DNPRINTF(SWM_D_EVENT, "unfreezing\n"); xcb_allow_events(conn, XCB_ALLOW_SYNC_KEYBOARD, e->time); } xcb_flush(conn); DNPRINTF(SWM_D_EVENT, "done\n"); } void buttonpress(xcb_button_press_event_t *e) { struct action *ap; struct binding *bp; struct ws_win *w; xcb_window_t winid; bool replay = true; event_time = e->time; DNPRINTF(SWM_D_EVENT, "win (x,y): %#x (%d,%d), detail: %u, time: %#x, " "root (x,y): %#x (%d,%d), child: %#x, state: %u, same_screen: %s\n", e->event, e->event_x, e->event_y, e->detail, e->time, e->root, e->root_x, e->root_y, e->child, e->state, YESNO(e->same_screen)); if (e->event == e->root) winid = e->child; else winid = e->event; /* Try to find bound action with current modifier state. */ bp = binding_lookup(CLEANMASK(e->state), BTNBIND, e->detail); if (bp == NULL) { /* Look for catch-all. */ bp = binding_lookup(ANYMOD, BTNBIND, e->detail); if (bp == NULL) goto out; } if (xinput2_raw && bp->flags & BINDING_F_REPLAY) goto out; /* Button binding is not pass-through */ click_focus(find_screen(e->root), winid, e->root_x, e->root_y); replay = bp->flags & BINDING_F_REPLAY; if ((ap = &actions[bp->action]) == NULL) goto out; if (bp->action == FN_SPAWN_CUSTOM) spawn_custom(get_current_region(find_screen(e->root)), &ap->args, bp->spawn_name); else if (ap->func) ap->func(find_screen(e->root), bp, &ap->args); replay = replay && !(ap->flags & FN_F_NOREPLAY); out: /* Only release grabs on windows with grabs. */ if ((xinput2_raw && e->event == e->root) || ((w = find_window(winid)) && w->id == e->event)) { if (replay) { DNPRINTF(SWM_D_EVENT, "replaying\n"); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, e->time); } else { DNPRINTF(SWM_D_EVENT, "unfreezing\n"); xcb_allow_events(conn, XCB_ALLOW_SYNC_POINTER, e->time); } } flush(); DNPRINTF(SWM_D_FOCUS, "done\n"); } void buttonrelease(xcb_button_release_event_t *e) { struct action *ap; struct binding *bp; struct ws_win *w; event_time = e->time; DNPRINTF(SWM_D_EVENT, "win (x,y): %#x (%d,%d), detail: %u, time: %#x, " "root (x,y): %#x (%d,%d), child: %#x, state: %u, same_screen: %s\n", e->event, e->event_x, e->event_y, e->detail, e->time, e->root, e->root_x, e->root_y, e->child, e->state, YESNO(e->same_screen)); /* Only release grabs on windows with grabs. */ if ((xinput2_raw && e->event == e->root) || ((w = find_window(e->event)) && w->id == e->event)) { bp = binding_lookup(CLEANMASK(e->state), BTNBIND, e->detail); if (bp == NULL) /* Look for catch-all. */ bp = binding_lookup(ANYMOD, BTNBIND, e->detail); if (bp && bp->flags & BINDING_F_REPLAY && (((ap = &actions[bp->action]) == NULL) || !(ap->flags & FN_F_NOREPLAY))) { DNPRINTF(SWM_D_EVENT, "replaying\n"); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, e->time); } else { DNPRINTF(SWM_D_EVENT, "unfreezing\n"); xcb_allow_events(conn, XCB_ALLOW_SYNC_POINTER, e->time); } xcb_flush(conn); } DNPRINTF(SWM_D_FOCUS, "done\n"); } const char * get_win_input_model_label(struct ws_win *win) { const char *inputmodel; /* * Input Model Input Field WM_TAKE_FOCUS * No Input False Absent * Passive True Absent * Locally Active True Present * Globally Active False Present */ if (accepts_focus(win)) inputmodel = (win->take_focus) ? "Locally Active" : "Passive"; else inputmodel = (win->take_focus) ? "Globally Active" : "No Input"; return (inputmodel); } void print_win_geom(xcb_window_t w) { xcb_get_geometry_reply_t *wa; wa = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, w), NULL); if (wa == NULL) { DNPRINTF(SWM_D_MISC, "win %#x not found\n", w); return; } DNPRINTF(SWM_D_MISC, "win %#x, root: %#x, depth: %u, (x,y) w x h: " "(%d,%d) %d x %d, border: %d\n", w, wa->root, wa->depth, wa->x, wa->y, wa->width, wa->height, wa->border_width); free(wa); } const char * get_stack_mode_label(uint8_t mode) { const char *label; switch (mode) { case XCB_STACK_MODE_ABOVE: label = "Above"; break; case XCB_STACK_MODE_BELOW: label = "Below"; break; case XCB_STACK_MODE_TOP_IF: label = "TopIf"; break; case XCB_STACK_MODE_BOTTOM_IF: label = "BottomIf"; break; case XCB_STACK_MODE_OPPOSITE: label = "Opposite"; break; default: label = "Unknown"; } return (label); } void configurerequest(xcb_configure_request_event_t *e) { struct ws_win *win; struct swm_region *r = NULL; int i = 0; uint32_t wc[7] = {0}; uint16_t mask = 0; bool new = false; if ((win = find_window(e->window)) == NULL) new = true; if (swm_debug & SWM_D_EVENT) { print_win_geom(e->window); DNPRINTF(SWM_D_EVENT, "win %#x, parent: %#x, new: %s, " "value_mask: %u { ", e->window, e->parent, YESNO(new), e->value_mask); if (e->value_mask & XCB_CONFIG_WINDOW_X) DPRINTF("X: %d ", e->x); if (e->value_mask & XCB_CONFIG_WINDOW_Y) DPRINTF("Y: %d ", e->y); if (e->value_mask & XCB_CONFIG_WINDOW_WIDTH) DPRINTF("W: %u ", e->width); if (e->value_mask & XCB_CONFIG_WINDOW_HEIGHT) DPRINTF("H: %u ", e->height); if (e->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) DPRINTF("Border: %u ", e->border_width); if (e->value_mask & XCB_CONFIG_WINDOW_SIBLING) DPRINTF("Sibling: %#x ", e->sibling); if (e->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) DPRINTF("StackMode: %s(%u) ", get_stack_mode_label(e->stack_mode), e->stack_mode); DPRINTF("}\n"); } if (new) { if (e->value_mask & XCB_CONFIG_WINDOW_X) { mask |= XCB_CONFIG_WINDOW_X; wc[i++] = e->x; } if (e->value_mask & XCB_CONFIG_WINDOW_Y) { mask |= XCB_CONFIG_WINDOW_Y; wc[i++] = e->y; } if (e->value_mask & XCB_CONFIG_WINDOW_WIDTH) { mask |= XCB_CONFIG_WINDOW_WIDTH; wc[i++] = e->width; } if (e->value_mask & XCB_CONFIG_WINDOW_HEIGHT) { mask |= XCB_CONFIG_WINDOW_HEIGHT; wc[i++] = e->height; } if (e->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) { mask |= XCB_CONFIG_WINDOW_BORDER_WIDTH; wc[i++] = e->border_width; } if (e->value_mask & XCB_CONFIG_WINDOW_SIBLING) { mask |= XCB_CONFIG_WINDOW_SIBLING; wc[i++] = e->sibling; } if (e->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) { mask |= XCB_CONFIG_WINDOW_STACK_MODE; wc[i++] = e->stack_mode; } if (mask != 0) { xcb_configure_window(conn, e->window, mask, wc); xcb_flush(conn); } } else if ((!MANUAL(win) || win->quirks & SWM_Q_ANYWHERE || ws_floating(win->ws)) && !FULLSCREEN(win) && !MAXIMIZED(win)) { /* Update floating geometry. */ if (win->ws->r) r = win->ws->r; else if (win->ws->old_r) r = win->ws->old_r; /* Assume position is in reference to latest region. */ if (r) { win->g_floatref = r->g; win->g_floatref_root = false; } if (e->value_mask & XCB_CONFIG_WINDOW_X) { win->g_float.x = e->x; win->g_float_xy_valid = true; } if (e->value_mask & XCB_CONFIG_WINDOW_Y) { win->g_float.y = e->y; win->g_float_xy_valid = true; } if (e->value_mask & XCB_CONFIG_WINDOW_WIDTH) win->g_float.w = e->width; if (e->value_mask & XCB_CONFIG_WINDOW_HEIGHT) win->g_float.h = e->height; if (e->value_mask & (XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT)) update_gravity(win); if (!MAXIMIZED(win) && !FULLSCREEN(win) && win_floating(win)) { /* Immediately apply new geometry if mapped. */ if (win->mapped) { xcb_window_t wid = pointer_window; update_floater(win); flush(); if (focus_mode == SWM_FOCUS_FOLLOW && pointer_window != wid) { focus_window(pointer_window); xcb_flush(conn); } } else { config_win(win, e); xcb_flush(conn); } } else { config_win(win, e); xcb_flush(conn); } } else { config_win(win, e); xcb_flush(conn); } DNPRINTF(SWM_D_EVENT, "done\n"); } void configurenotify(xcb_configure_notify_event_t *e) { struct ws_win *win; DNPRINTF(SWM_D_EVENT, "win %#x, event win: %#x, (x,y) WxH: (%d,%d) " "%ux%u, border: %u, above_sibling: %#x, override_redirect: %s\n", e->window, e->event, e->x, e->y, e->width, e->height, e->border_width, e->above_sibling, YESNO(e->override_redirect)); win = find_window(e->window); if (win) { adjust_font(win); if (font_adjusted && win->ws->r) { stack(win->ws->r); update_mapping(win->s); xcb_flush(conn); } } DNPRINTF(SWM_D_EVENT, "done\n"); } void destroynotify(xcb_destroy_notify_event_t *e) { struct ws_win *win; struct workspace *ws; struct swm_screen *s; bool follow; DNPRINTF(SWM_D_EVENT, "win %#x\n", e->window); if ((win = find_window(e->window)) == NULL) { DNPRINTF(SWM_D_EVENT, "ignore; unmanaged.\n"); return; } if (win->frame == e->window) { DNPRINTF(SWM_D_EVENT, "ignore; frame for win %#x\n", win->id); win->frame = XCB_WINDOW_NONE; return; } s = win->s; ws = win->ws; win->id = XCB_WINDOW_NONE; follow = follow_pointer(s); if (win == ws->focus) ws->focus = get_focus_magic(get_focus_other(win)); unmanage_window(win); if (ws->r) { if (refresh_strut(s)) update_layout(s); else stack(ws->r); update_mapping(s); if (!follow) { update_focus(s); center_pointer(ws->r); } bar_draw(ws->r->bar); flush(); /* win can be freed. */ if (follow) { if (pointer_window != XCB_WINDOW_NONE) focus_window(pointer_window); else if (ws->focus == NULL) focus_win(s, get_focus_magic(get_ws_focus(ws))); } } xcb_flush(conn); DNPRINTF(SWM_D_EVENT, "done\n"); } const char * get_notify_detail_label(uint8_t detail) { const char *label; switch (detail) { case XCB_NOTIFY_DETAIL_ANCESTOR: label = "Ancestor"; break; case XCB_NOTIFY_DETAIL_VIRTUAL: label = "Virtual"; break; case XCB_NOTIFY_DETAIL_INFERIOR: label = "Inferior"; break; case XCB_NOTIFY_DETAIL_NONLINEAR: label = "Nonlinear"; break; case XCB_NOTIFY_DETAIL_NONLINEAR_VIRTUAL: label = "NonlinearVirtual"; break; case XCB_NOTIFY_DETAIL_POINTER: label = "Pointer"; break; case XCB_NOTIFY_DETAIL_POINTER_ROOT: label = "PointerRoot"; break; case XCB_NOTIFY_DETAIL_NONE: label = "None"; break; default: label = "Unknown"; } return (label); } const char * get_notify_mode_label(uint8_t mode) { const char *label; switch (mode) { case XCB_NOTIFY_MODE_NORMAL: label = "Normal"; break; case XCB_NOTIFY_MODE_GRAB: label = "Grab"; break; case XCB_NOTIFY_MODE_UNGRAB: label = "Ungrab"; break; case XCB_NOTIFY_MODE_WHILE_GRABBED: label = "WhileGrabbed"; break; default: label = "Unknown"; } return (label); } const char * get_state_mask_label(uint16_t state) { const char *label; switch (state) { case XCB_KEY_BUT_MASK_SHIFT: label = "ShiftMask"; break; case XCB_KEY_BUT_MASK_LOCK: label = "LockMask"; break; case XCB_KEY_BUT_MASK_CONTROL: label = "ControlMask"; break; case XCB_KEY_BUT_MASK_MOD_1: label = "Mod1Mask"; break; case XCB_KEY_BUT_MASK_MOD_2: label = "Mod2Mask"; break; case XCB_KEY_BUT_MASK_MOD_3: label = "Mod3Mask"; break; case XCB_KEY_BUT_MASK_MOD_4: label = "Mod4Mask"; break; case XCB_KEY_BUT_MASK_MOD_5: label = "Mod5Mask"; break; case XCB_KEY_BUT_MASK_BUTTON_1: label = "Button1Mask"; break; case XCB_KEY_BUT_MASK_BUTTON_2: label = "Button2Mask"; break; case XCB_KEY_BUT_MASK_BUTTON_3: label = "Button3Mask"; break; case XCB_KEY_BUT_MASK_BUTTON_4: label = "Button4Mask"; break; case XCB_KEY_BUT_MASK_BUTTON_5: label = "Button5Mask"; break; case 0: label = "None"; break; default: label = "Unknown"; } return (label); } const char * get_wm_state_label(uint32_t state) { const char *label; switch (state) { case XCB_ICCCM_WM_STATE_WITHDRAWN: label = "Withdrawn"; break; case XCB_ICCCM_WM_STATE_NORMAL: label = "Normal"; break; case XCB_ICCCM_WM_STATE_ICONIC: label = "Iconic"; break; default: label = "Unknown"; } return (label); } void enternotify(xcb_enter_notify_event_t *e) { struct ws_win *win; event_time = e->time; pointer_window = e->event; DNPRINTF(SWM_D_FOCUS, "time: %#x, win (x,y): %#x (%d,%d), mode: %s(%d)," " detail: %s(%d), root (x,y): %#x (%d,%d), child: %#x, " "same_screen_focus: %s, state: %s(%d)\n", e->time, e->event, e->event_x, e->event_y, get_notify_mode_label(e->mode), e->mode, get_notify_detail_label(e->detail), e->detail, e->root, e->root_x, e->root_y, e->child, YESNO(e->same_screen_focus), get_state_mask_label(e->state), e->state); if (focus_mode == SWM_FOCUS_MANUAL) { DNPRINTF(SWM_D_EVENT, "ignore; manual focus\n"); return; } else if (focus_mode != SWM_FOCUS_FOLLOW && e->mode == XCB_NOTIFY_MODE_UNGRAB && e->detail != XCB_NOTIFY_DETAIL_ANCESTOR) { DNPRINTF(SWM_D_EVENT, "ignore; ungrab\n"); return; } if (e->event == e->root) { if (e->child == XCB_WINDOW_NONE && e->mode == XCB_NOTIFY_MODE_GRAB && e->detail == XCB_NOTIFY_DETAIL_INFERIOR) { DNPRINTF(SWM_D_EVENT, "ignore; grab inferior\n"); return; } } else { /* Ignore enternotify within managed windows. */ if (e->detail == XCB_NOTIFY_DETAIL_INFERIOR) { DNPRINTF(SWM_D_EVENT, "ignore; enter from inferior\n"); return; } else if (e->detail == XCB_NOTIFY_DETAIL_ANCESTOR) { /* Only handle enternotify on frame. */ win = find_window(e->event); if (win && e->event != win->frame) { DNPRINTF(SWM_D_EVENT, "ignore; enter client " "from ancestor\n"); return; } } } if ((win = find_window(e->event)) && win_free(win)) { set_region(get_pointer_region(win->s)); focus_win(win->s, get_focus_magic(win)); } else focus_window(e->event); xcb_flush(conn); DNPRINTF(SWM_D_EVENT, "done\n"); } void leavenotify(xcb_leave_notify_event_t *e) { event_time = e->time; DNPRINTF(SWM_D_FOCUS, "time: %#x, win (x,y): %#x (%d,%d), mode: %s(%d)," " detail: %s(%d), root (x,y): %#x (%d,%d), child: %#x, " "same_screen_focus: %s, state: %s(%d)\n", e->time, e->event, e->event_x, e->event_y, get_notify_mode_label(e->mode), e->mode, get_notify_detail_label(e->detail), e->detail, e->root, e->root_x, e->root_y, e->child, YESNO(e->same_screen_focus), get_state_mask_label(e->state), e->state); } void mapnotify(xcb_map_notify_event_t *e) { struct ws_win *win; if (!(swm_debug & SWM_D_EVENT)) return; DNPRINTF(SWM_D_EVENT, "event: %#x, win %#x, override_redirect: %u\n", e->event, e->window, e->override_redirect); if (e->event != e->window) { DNPRINTF(SWM_D_EVENT, "not event window.\n"); return; } if ((win = manage_window(e->window, spawn_position, false)) == NULL) { DNPRINTF(SWM_D_EVENT, "unmanaged.\n"); return; } if (win->id != e->window && win->frame != e->window) { DNPRINTF(SWM_D_EVENT, "unknown window.\n"); return; } if (win->mapping > 0) { DNPRINTF(SWM_D_EVENT, "mapping %d\n", win->mapping); win->mapping--; return; } } const char * get_mapping_notify_label(uint8_t request) { const char *label; switch(request) { case XCB_MAPPING_MODIFIER: label = "MappingModifier"; break; case XCB_MAPPING_KEYBOARD: label = "MappingKeyboard"; break; case XCB_MAPPING_POINTER: label = "MappingPointer"; break; default: label = "Unknown"; } return (label); } void mappingnotify(xcb_mapping_notify_event_t *e) { DNPRINTF(SWM_D_EVENT, "request: %s (%u), first_keycode: %u, " "count: %u\n", get_mapping_notify_label(e->request), e->request, e->first_keycode, e->count); if (e->request != XCB_MAPPING_POINTER) { xcb_refresh_keyboard_mapping(syms, e); grabkeys(); } grabbuttons(); DNPRINTF(SWM_D_EVENT, "done\n"); } void click_focus(struct swm_screen *s, xcb_window_t id, int x, int y) { struct swm_region *r, *rr; struct swm_bar *bar; struct ws_win *win, *w; win = find_window(id); if (win) { if (win_free(win)) set_region(region_under(s, x, y)); else if (apply_unfocus(s->r->ws, NULL)) { refresh_stack(s); update_stacking(s); stack(s->r); update_mapping(s); } w = get_focus_magic(win); if (w != win && w == win->ws->focus && win->focus_redirect == win) win->focus_redirect = NULL; focus_win(s, win); if (click_to_raise && !win_prioritized(win)) { prioritize_window(win); refresh_stack(win->s); update_stacking(win->s); } } else { /* Not a managed window, figure out region. */ r = find_region(id); if (r == NULL) { bar = find_bar(id); if (bar) r = bar->r; /* Fallback to pointer location. */ if (r == NULL) r = region_under(s, x, y); } /* If region is empty, set default focus. */ if (r && TAILQ_EMPTY(&r->ws->winlist)) { w = find_window(get_input_focus()); if (w) { rr = w->ws->r; if (rr && rr != r) unfocus_win(rr->ws->focus); } set_input_focus(r->id, false); bar_draw(r->bar); } focus_region(r); } } void focus_window(xcb_window_t id) { struct ws_win *w; DNPRINTF(SWM_D_FOCUS, "id: %#x\n", id); if (id == XCB_WINDOW_NONE) return; if ((w = find_window(id))) focus_win(w->s, get_focus_magic(w)); else focus_window_region(id); } void focus_window_region(xcb_window_t id) { struct swm_region *r; struct swm_bar *b; xcb_get_geometry_reply_t *gr; DNPRINTF(SWM_D_FOCUS, "id: %#x\n", id); r = find_region(id); if (r == NULL && (b = find_bar(id))) r = b->r; if (r == NULL) { gr = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, id), NULL); if (gr == NULL) { DNPRINTF(SWM_D_MISC, "get geometry failed\n"); return; } r = get_pointer_region(find_screen(gr->root)); free(gr); } focus_region(r); } void maprequest(xcb_map_request_event_t *e) { struct swm_screen *s; struct workspace *ws; struct ws_win *win, *w; bool follow, setfocus = false; DNPRINTF(SWM_D_EVENT, "win %#x, parent: %#x\n", e->window, e->parent); if ((win = manage_window(e->window, spawn_position, true)) == NULL) return; s = win->s; ws = win->ws; win->mapped = false; follow = follow_pointer(s); /* Set new focus unless a quirk says otherwise. */ if (!follow && !(win->quirks & SWM_Q_NOFOCUSONMAP)) { w = NULL; if (win->quirks & SWM_Q_FOCUSONMAP_SINGLE) { /* See if other wins of same type are already mapped. */ TAILQ_FOREACH(w, &ws->winlist, entry) { if (w == win || !w->mapped) continue; if (w->ch.class_name && win->ch.class_name && strcmp(w->ch.class_name, win->ch.class_name) == 0 && w->ch.instance_name && win->ch.instance_name && strcmp(w->ch.instance_name, win->ch.instance_name) == 0) break; } } if (w == NULL) setfocus = true; } if (ewmh_apply_flags(win, win->ewmh_flags & ~EWMH_F_HIDDEN)) ewmh_update_wm_state(win); if (setfocus) { set_focus(s, get_focus_magic(win)); draw_frame(get_ws_focus_prev(ws)); } prioritize_window(win); update_win_layer_related(win); apply_unfocus(ws, win); refresh_stack(s); update_stacking(s); if (ws->r) { if (refresh_strut(s)) update_layout(s); else stack(ws->r); update_mapping(s); if (!follow) { update_focus(s); center_pointer(ws->r); } flush(); /* win can be freed. */ if (follow) { if (pointer_window != XCB_WINDOW_NONE) focus_window(pointer_window); else if (ws->focus == NULL && validate_win(win) == 0) focus_win(s, get_focus_magic(win)); } } xcb_flush(conn); DNPRINTF(SWM_D_EVENT, "done\n"); } void motionnotify(xcb_motion_notify_event_t *e) { struct swm_screen *s; struct swm_region *r = NULL; struct ws_win *win; event_time = e->time; DNPRINTF(SWM_D_FOCUS, "time: %#x, win (x,y): %#x (%d,%d), " "detail: %s(%d), root (x,y): %#x (%d,%d), child: %#x, " "same_screen_focus: %s, state: %d\n", e->time, e->event, e->event_x, e->event_y, get_notify_detail_label(e->detail), e->detail, e->root, e->root_x, e->root_y, e->child, YESNO(e->same_screen), e->state); if (focus_mode == SWM_FOCUS_MANUAL) return; if (focus_mode == SWM_FOCUS_FOLLOW && pointer_window != XCB_WINDOW_NONE) focus_window(pointer_window); else { if (e->event == e->root && (win = find_window(e->child))) focus_win(win->s, win); else if ((s = find_screen(e->root)) != NULL) { TAILQ_FOREACH(r, &s->rl, entry) if (X(r) <= e->root_x && e->root_x < MAX_X(r) && Y(r) <= e->root_y && e->root_y < MAX_Y(r)) break; focus_region(r); } } DNPRINTF(SWM_D_EVENT, "done\n"); } void propertynotify(xcb_property_notify_event_t *e) { struct ws_win *win; DNPRINTF(SWM_D_EVENT, "win %#x, atom: %s(%u), time: %#x, state: %u\n", e->window, get_atom_label(e->atom), e->atom, e->time, e->state); event_time = e->time; win = find_window(e->window); if (win == NULL) return; if (e->atom == XCB_ATOM_WM_CLASS || e->atom == XCB_ATOM_WM_NAME) { if (win->ws->r) bar_draw(win->ws->r->bar); } else if (e->atom == XCB_ATOM_WM_HINTS) { get_wm_hints(win); } else if (e->atom == XCB_ATOM_WM_NORMAL_HINTS) { get_wm_normal_hints(win); win->gravity = win_gravity(win); } else if (e->atom == XCB_ATOM_WM_TRANSIENT_FOR) { if (get_wm_transient_for(win)) update_win_refs(win); if (win->main->ws != win->ws) win_to_ws(win, win->main->ws, SWM_WIN_UNFOCUS); update_win_layer_related(win); refresh_stack(win->s); update_stacking(win->s); } else if (e->atom == ewmh[_NET_WM_STRUT_PARTIAL].atom || e->atom == ewmh[_NET_WM_STRUT].atom) { ewmh_get_strut(win); refresh_strut(win->s); update_layout(win->s); } else if (e->atom == a_prot) { get_wm_protocols(win); } xcb_flush(conn); DNPRINTF(SWM_D_EVENT, "done\n"); } void reparentnotify(xcb_reparent_notify_event_t *e) { DNPRINTF(SWM_D_EVENT, "event: %#x, win %#x, parent: %#x, " "(x,y): (%u,%u), override_redirect: %u\n", e->event, e->window, e->parent, e->x, e->y, e->override_redirect); } void unmapnotify(xcb_unmap_notify_event_t *e) { struct swm_screen *s; struct ws_win *win, *nfw = NULL; struct workspace *ws; bool follow; DNPRINTF(SWM_D_EVENT, "event: %#x, win %#x, from_configure: %u\n", e->event, e->window, e->from_configure); if (e->event != e->window) { DNPRINTF(SWM_D_EVENT, "ignore; not event window.\n"); return; } win = find_window(e->window); if (win == NULL || (win->id != e->window && win->frame != e->window)) { DNPRINTF(SWM_D_EVENT, "ignore; unmanaged.\n"); return; } if (win->unmapping > 0) { DNPRINTF(SWM_D_EVENT, "ignore; unmapping %d\n", win->unmapping); win->unmapping--; return; } s = win->s; ws = win->ws; follow = follow_pointer(s); if (win->frame == e->window) { DNPRINTF(SWM_D_EVENT, "unexpected unmap of frame; fixing.\n"); if (win == ws->focus) nfw = win; xcb_map_window(conn, win->frame); } else { DNPRINTF(SWM_D_EVENT, "unexpected unmap; unmanaging.\n"); if (win == ws->focus) nfw = get_focus_other(win); win->mapped = false; /* EWMH advises removal of _NET_WM_DESKTOP and _NET_WM_STATE. */ xcb_delete_property(conn, win->id, ewmh[_NET_WM_DESKTOP].atom); xcb_delete_property(conn, win->id, ewmh[_NET_WM_STATE].atom); set_win_state(win, XCB_ICCCM_WM_STATE_WITHDRAWN); unmanage_window(win); } if (!follow && nfw) set_focus(s, get_focus_magic(nfw)); refresh_stack(s); update_stacking(s); if (ws->r) { if (refresh_strut(s)) update_layout(s); else stack(ws->r); update_mapping(s); if (!follow) { update_focus(s); center_pointer(ws->r); } bar_draw(ws->r->bar); flush(); /* win can be freed. */ if (follow) { if (pointer_window != XCB_WINDOW_NONE) focus_window(pointer_window); else if (ws->focus == NULL && validate_win(nfw) == 0) focus_win(s, get_focus_magic(nfw)); } } xcb_flush(conn); DNPRINTF(SWM_D_EVENT, "done\n"); } const char * get_source_type_label(uint32_t type) { const char *label; switch (type) { case EWMH_SOURCE_TYPE_NONE: label = "None"; break; case EWMH_SOURCE_TYPE_NORMAL: label = "Normal"; break; case EWMH_SOURCE_TYPE_OTHER: label = "Other"; break; default: label = "Invalid"; } return (label); } static uint8_t win_gravity(struct ws_win *win) { if (SH_GRAVITY(win)) return (win->sh.win_gravity); else return (XCB_GRAVITY_NORTH_WEST); } void clientmessage(xcb_client_message_event_t *e) { struct swm_screen *s; struct swm_region *r = NULL; struct workspace *ws; struct ws_win *win; uint32_t vals[4]; xcb_map_request_event_t mre; DNPRINTF(SWM_D_EVENT, "win %#x, atom: %s(%u)\n", e->window, get_atom_label(e->type), e->type); if (e->type == ewmh[_NET_CURRENT_DESKTOP].atom) { s = find_screen(e->window); if (s) { r = get_current_region(s); if (r && (int)e->data.data32[0] < workspace_limit) { ws = get_workspace(s, e->data.data32[0]); if (ws) switch_workspace(r, ws, false); } } return; } else if (e->type == ewmh[_NET_REQUEST_FRAME_EXTENTS].atom) { DNPRINTF(SWM_D_EVENT,"set _NET_FRAME_EXTENTS on window\n"); vals[0] = vals[1] = vals[2] = vals[3] = border_width; xcb_change_property(conn, XCB_PROP_MODE_REPLACE, e->window, a_net_frame_extents, XCB_ATOM_CARDINAL, 32, 4, vals); xcb_flush(conn); return; } win = find_window(e->window); if (win == NULL) { if (e->type == ewmh[_NET_ACTIVE_WINDOW].atom) { /* Manage the window with maprequest. */ DNPRINTF(SWM_D_EVENT, "focus unmanaged; mapping.\n"); mre.window = e->window; mre.parent = XCB_WINDOW_NONE; maprequest(&mre); } return; } s = win->s; if (e->type == ewmh[_NET_ACTIVE_WINDOW].atom) { DNPRINTF(SWM_D_EVENT, "_NET_ACTIVE_WINDOW, source_type: " "%s(%d), timestamp: %#x, active_window: %#x\n", get_source_type_label(e->data.data32[0]), e->data.data32[0], e->data.data32[1], e->data.data32[2]); /* * Allow focus changes that are a result of direct user * action and from applications that use the old EWMH spec. */ if (e->data.data32[0] != EWMH_SOURCE_TYPE_NORMAL || win->quirks & SWM_Q_OBEYAPPFOCUSREQ) { /* Uniconify. */ if (ewmh_apply_flags(win, win->ewmh_flags & ~EWMH_F_HIDDEN)) ewmh_update_wm_state(win); set_focus(s, win); apply_unfocus(win->ws, win); update_win_layer_related(win); prioritize_window(win); refresh_stack(s); update_stacking(s); if (refresh_strut(s)) update_layout(s); else stack(win->ws->r); update_mapping(s); if (win->mapped) update_focus(s); else { set_attention(win); update_bars(s); } } else { set_attention(win); update_bars(s); } } else if (e->type == ewmh[_NET_CLOSE_WINDOW].atom) { DNPRINTF(SWM_D_EVENT, "_NET_CLOSE_WINDOW\n"); if (win->can_delete) client_msg(win, a_delete, 0); else xcb_kill_client(conn, win->id); } else if (e->type == ewmh[_NET_MOVERESIZE_WINDOW].atom) { DNPRINTF(SWM_D_EVENT, "_NET_MOVERESIZE_WINDOW\n"); if ((!MANUAL(win) || win->quirks & SWM_Q_ANYWHERE || ws_floating(win->ws)) && !FULLSCREEN(win) && !MAXIMIZED(win)) { uint8_t grav; /* Update floating geometry. */ if (e->data.data32[0] & (1 << 8)) { /* x */ win->g_float.x = e->data.data32[1]; win->g_float_xy_valid = true; } if (e->data.data32[0] & (1 << 9)) {/* y */ win->g_float.y = e->data.data32[2]; win->g_float_xy_valid = true; } if (e->data.data32[0] & (1 << 10)) /* width */ win->g_float.w = e->data.data32[3]; if (e->data.data32[0] & (1 << 11)) /* height */ win->g_float.h = e->data.data32[4]; /* Use specified gravity just this once. */ grav = win->gravity; if (e->data.data32[0] & 0xff) win->gravity = e->data.data32[0] & 0xff; update_gravity(win); if (win_floating(win)) { load_float_geom(win); update_floater(win); } else config_win(win, NULL); win->gravity = grav; } else config_win(win, NULL); } else if (e->type == ewmh[_NET_RESTACK_WINDOW].atom) { DNPRINTF(SWM_D_EVENT, "_NET_RESTACK_WINDOW\n"); vals[0] = e->data.data32[1]; /* Sibling window. */ vals[1] = e->data.data32[2]; /* Stack mode detail. */ if (win->frame != XCB_WINDOW_NONE) xcb_configure_window(conn, win->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, vals); } else if (e->type == ewmh[_NET_WM_STATE].atom) { DNPRINTF(SWM_D_EVENT, "_NET_WM_STATE\n"); bool origmax = MAXIMIZED(win); ewmh_change_wm_state(win, e->data.data32[1], e->data.data32[0]); if (e->data.data32[2]) ewmh_change_wm_state(win, e->data.data32[2], e->data.data32[0]); if (s->focus == win && HIDDEN(win)) set_focus(s, get_focus_other(win)); if (ws_maxstack(win->ws) && origmax != MAXIMIZED(win)) win->maxstackmax = MAXIMIZED(win); ewmh_update_wm_state(win); update_win_layer_related(win); refresh_stack(s); update_stacking(s); if (refresh_strut(s)) update_layout(s); else stack(win->ws->r); update_mapping(s); } else if (e->type == ewmh[_NET_WM_DESKTOP].atom) { DNPRINTF(SWM_D_EVENT, "_NET_WM_DESKTOP, new_desktop: %d, " "source_type: %s(%d)\n", e->data.data32[0], get_source_type_label(e->data.data32[1]), e->data.data32[1]); DNPRINTF(SWM_D_EVENT, "_NET_WM_DESKTOP\n"); r = win->ws->r; win_to_ws(win, get_workspace(s, e->data.data32[0]), SWM_WIN_UNFOCUS); /* Stack source and destination ws, if mapped. */ if (r != win->ws->r) { if (refresh_strut(s)) update_layout(s); else if (r) { stack(r); bar_draw(r->bar); } if (win->ws->r) { if (win_floating(win)) load_float_geom(win); stack(win->ws->r); bar_draw(win->ws->r->bar); } update_mapping(s); } } else if (e->type == a_change_state) { DNPRINTF(SWM_D_EVENT, "WM_CHANGE_STATE state: %s\n", get_wm_state_label(e->data.data32[0])); if (e->data.data32[0] == XCB_ICCCM_WM_STATE_ICONIC) { /* Iconify. */ ewmh_apply_flags(win, win->ewmh_flags | EWMH_F_HIDDEN); ewmh_update_wm_state(win); update_stacking(s); if (s->focus == win) set_focus(s, get_focus_other(win)); if (refresh_strut(s)) update_layout(s); else stack(win->ws->r); update_mapping(s); } } else if (e->type == ewmh[_NET_WM_MOVERESIZE].atom) { DNPRINTF(SWM_D_EVENT, "_NET_WM_MOVERESIZE\n"); moveresize_win(win, e); } if (focus_mode != SWM_FOCUS_FOLLOW) { update_focus(s); center_pointer(s->r_focus); } flush(); /* win can be freed. */ focus_follow(s, s->r_focus, win); DNPRINTF(SWM_D_EVENT, "done\n"); } const char * get_moveresize_direction_label(uint32_t type) { switch (type) { #define RETSTR(c,l) case c: return l RETSTR(EWMH_WM_MOVERESIZE_SIZE_TOPLEFT, "SIZE_TOPLEFT"); RETSTR(EWMH_WM_MOVERESIZE_SIZE_TOP, "SIZE_TOP"); RETSTR(EWMH_WM_MOVERESIZE_SIZE_TOPRIGHT, "SIZE_TOPRIGHT"); RETSTR(EWMH_WM_MOVERESIZE_SIZE_RIGHT, "SIZE_RIGHT"); RETSTR(EWMH_WM_MOVERESIZE_SIZE_BOTTOMRIGHT, "SIZE_BOTTOMRIGHT"); RETSTR(EWMH_WM_MOVERESIZE_SIZE_BOTTOM, "SIZE_BOTTOM"); RETSTR(EWMH_WM_MOVERESIZE_SIZE_BOTTOMLEFT, "SIZE_BOTTOMLEFT"); RETSTR(EWMH_WM_MOVERESIZE_SIZE_LEFT, "SIZE_LEFT"); RETSTR(EWMH_WM_MOVERESIZE_MOVE, "MOVE"); RETSTR(EWMH_WM_MOVERESIZE_SIZE_KEYBOARD, "SIZE_KEYBOARD"); RETSTR(EWMH_WM_MOVERESIZE_MOVE_KEYBOARD, "MOVE_KEYBOARD"); RETSTR(EWMH_WM_MOVERESIZE_CANCEL, "CANCEL"); #undef RETSTR } return "Invalid"; } void moveresize_win(struct ws_win *win, xcb_client_message_event_t *e) { struct binding b; uint32_t dir = 0; bool move; /* * _NET_WM_MOVERESIZE * window = window to be moved or resized * message_type = _NET_WM_MOVERESIZE * format = 32 * data.l[0] = x_root * data.l[1] = y_root * data.l[2] = direction * data.l[3] = button * data.l[4] = source indication */ DNPRINTF(SWM_D_EVENT, "win %#x, event win: %#x, x_root: %d, y_root: %d," " direction: %s (%u), button: %#x, source type: %s\n", win->id, e->window, e->data.data32[0], e->data.data32[1], get_moveresize_direction_label(e->data.data32[2]), e->data.data32[2], e->data.data32[3], get_source_type_label(e->data.data32[4])); move = false; switch (e->data.data32[2]) { case EWMH_WM_MOVERESIZE_SIZE_TOPLEFT: dir = SWM_SIZE_TOPLEFT; break; case EWMH_WM_MOVERESIZE_SIZE_TOP: dir = SWM_SIZE_TOP; break; case EWMH_WM_MOVERESIZE_SIZE_TOPRIGHT: dir = SWM_SIZE_TOPRIGHT; break; case EWMH_WM_MOVERESIZE_SIZE_RIGHT: dir = SWM_SIZE_RIGHT; break; case EWMH_WM_MOVERESIZE_SIZE_BOTTOMRIGHT: dir = SWM_SIZE_BOTTOMRIGHT; break; case EWMH_WM_MOVERESIZE_SIZE_BOTTOM: dir = SWM_SIZE_BOTTOM; break; case EWMH_WM_MOVERESIZE_SIZE_BOTTOMLEFT: dir = SWM_SIZE_BOTTOMLEFT; break; case EWMH_WM_MOVERESIZE_SIZE_LEFT: dir = SWM_SIZE_LEFT; break; case EWMH_WM_MOVERESIZE_MOVE: move = true; break; case EWMH_WM_MOVERESIZE_SIZE_KEYBOARD: case EWMH_WM_MOVERESIZE_MOVE_KEYBOARD: DNPRINTF(SWM_D_EVENT, "ignore; keyboard move/resize.\n"); return; case EWMH_WM_MOVERESIZE_CANCEL: DNPRINTF(SWM_D_EVENT, "ignore; cancel.\n"); return; default: DNPRINTF(SWM_D_EVENT, "invalid direction.\n"); return; }; b.type = BTNBIND; b.value = e->data.data32[3]; if (move) move_win_pointer(win, &b, e->data.data32[0], e->data.data32[1]); else resize_win_pointer(win, &b, e->data.data32[0], e->data.data32[1], dir, false); DNPRINTF(SWM_D_EVENT, "done.\n"); } int enable_wm(void) { int num_screens, i; const uint32_t val = XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_POINTER_MOTION_HINT | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_PROPERTY_CHANGE; xcb_screen_t *sc; xcb_void_cookie_t ck; xcb_generic_error_t *error; /* this causes an error if some other window manager is running */ num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) { if ((sc = get_screen(i)) == NULL) errx(1, "ERROR: unable to get screen %d.", i); DNPRINTF(SWM_D_INIT, "screen %d, root: %#x\n", i, sc->root); ck = xcb_change_window_attributes_checked(conn, sc->root, XCB_CW_EVENT_MASK, &val); if ((error = xcb_request_check(conn, ck))) { DNPRINTF(SWM_D_INIT, "error_code: %u\n", error->error_code); free(error); return (1); } } return (0); } const char * get_randr_rotation_label(int rot) { const char *label; switch (rot) { case XCB_RANDR_ROTATION_ROTATE_0: label = "normal"; break; case XCB_RANDR_ROTATION_ROTATE_90: label = "left"; break; case XCB_RANDR_ROTATION_ROTATE_180: label = "inverted"; break; case XCB_RANDR_ROTATION_ROTATE_270: label = "right"; break; default: label = "invalid"; } return (label); } void new_region(struct swm_screen *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t rot) { struct swm_region *r = NULL, *n; struct workspace *ws = NULL; int i; uint32_t wa[1]; DNPRINTF(SWM_D_MISC, "screen[%d]:%dx%d+%d+%d,%s\n", s->idx, w, h, x, y, get_randr_rotation_label(rot)); /* remove any conflicting regions */ n = TAILQ_FIRST(&s->rl); while (n) { r = n; n = TAILQ_NEXT(r, entry); if (X(r) < (x + w) && (X(r) + WIDTH(r)) > x && Y(r) < (y + h) && (Y(r) + HEIGHT(r)) > y) { if (r->ws->r != NULL) r->ws->old_r = r->ws->r; r->ws->r = NULL; bar_cleanup(r); xcb_destroy_window(conn, r->id); r->id = XCB_WINDOW_NONE; TAILQ_REMOVE(&s->rl, r, entry); TAILQ_INSERT_TAIL(&s->orl, r, entry); } } /* search old regions for one to reuse */ /* size + location match */ TAILQ_FOREACH(r, &s->orl, entry) if (X(r) == x && Y(r) == y && HEIGHT(r) == h && WIDTH(r) == w) break; /* size match (same axis) */ if (r == NULL) TAILQ_FOREACH(r, &s->orl, entry) if ((r->g.r & ROTATION_VERT) == (rot & ROTATION_VERT) && HEIGHT(r) == h && WIDTH(r) == w) break; /* size match (different axis) */ if (r == NULL) TAILQ_FOREACH(r, &s->orl, entry) if ((r->g.r & ROTATION_VERT) != (rot & ROTATION_VERT) && HEIGHT(r) == w && WIDTH(r) == h) break; if (r != NULL) { TAILQ_REMOVE(&s->orl, r, entry); /* try to use old region's workspace */ if (r->ws->r == NULL) ws = r->ws; } else { if ((r = calloc(1, sizeof(struct swm_region))) == NULL) err(1, "new_region: calloc"); if ((r->st = calloc(1, sizeof(struct swm_stackable))) == NULL) err(1, "new_region: calloc"); } /* if we don't have a workspace already, find one */ if (ws == NULL) { for (i = 0; i < workspace_limit; i++) { ws = get_workspace(s, i); if (ws && ws->r == NULL) break; } } if (ws == NULL) errx(1, "new_region: no free workspaces"); r->st->type = STACKABLE_REGION; r->st->region = r; r->st->layer = SWM_LAYER_REGION; r->st->s = s; X(r) = x; Y(r) = y; WIDTH(r) = w; HEIGHT(r) = h; ROTATION(r) = rot; r->g_usable = r->g; r->bar = NULL; r->s = s; r->ws = ws; r->ws_prior = NULL; ws->r = r; outputs++; TAILQ_INSERT_TAIL(&s->rl, r, entry); if (workspace_autorotate) rotatews(ws, rot); /* Invisible region window to detect pointer events on empty regions. */ r->id = xcb_generate_id(conn); wa[0] = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_POINTER_MOTION_HINT; /* Depth of InputOnly always 0. */ xcb_create_window(conn, 0, r->id, r->s->root, X(r), Y(r), WIDTH(r), HEIGHT(r), 0, XCB_WINDOW_CLASS_INPUT_ONLY, r->s->visual, XCB_CW_EVENT_MASK, wa); /* Make sure region input is at the bottom. */ wa[0] = XCB_STACK_MODE_BELOW; xcb_configure_window(conn, r->id, XCB_CONFIG_WINDOW_STACK_MODE, wa); xcb_map_window(conn, r->id); } void scan_randr(struct swm_screen *s) { #ifdef SWM_XRR_HAS_CRTC int i, j; int ncrtc = 0, nmodes = 0; xcb_randr_get_screen_resources_current_reply_t *srr; xcb_randr_get_crtc_info_reply_t *cir = NULL; xcb_randr_crtc_t *crtc; xcb_randr_mode_info_t *mode; int minrate, currate; #endif /* SWM_XRR_HAS_CRTC */ struct swm_region *r; xcb_screen_t *screen; if (s == NULL) return; DNPRINTF(SWM_D_MISC, "screen: %d\n", s->idx); if ((screen = get_screen(s->idx)) == NULL) errx(1, "ERROR: unable to get screen %d.", s->idx); /* remove any old regions */ while ((r = TAILQ_FIRST(&s->rl)) != NULL) { r->ws->old_r = r->ws->r = NULL; bar_cleanup(r); xcb_destroy_window(conn, r->id); r->id = XCB_WINDOW_NONE; TAILQ_REMOVE(&s->rl, r, entry); TAILQ_INSERT_TAIL(&s->orl, r, entry); } outputs = 0; /* map virtual screens onto physical screens */ #ifdef SWM_XRR_HAS_CRTC if (randr_support) { srr = xcb_randr_get_screen_resources_current_reply(conn, xcb_randr_get_screen_resources_current(conn, s->root), NULL); if (srr == NULL) { new_region(s, 0, 0, screen->width_in_pixels, screen->height_in_pixels, ROTATION_DEFAULT); goto out; } else { ncrtc = srr->num_crtcs; nmodes = srr->num_modes; } minrate = -1; mode = xcb_randr_get_screen_resources_current_modes(srr); crtc = xcb_randr_get_screen_resources_current_crtcs(srr); for (i = 0; i < ncrtc; i++) { currate = SWM_RATE_DEFAULT; cir = xcb_randr_get_crtc_info_reply(conn, xcb_randr_get_crtc_info(conn, crtc[i], XCB_CURRENT_TIME), NULL); if (cir == NULL) continue; if (cir->num_outputs == 0) { free(cir); continue; } if (cir->mode == 0) { new_region(s, 0, 0, screen->width_in_pixels, screen->height_in_pixels, ROTATION_DEFAULT); } else { new_region(s, cir->x, cir->y, cir->width, cir->height, cir->rotation & 0xf); /* Determine the crtc refresh rate. */ for (j = 0; j < nmodes; j++) { if (mode[j].id == cir->mode) { if (mode[j].htotal && mode[j].vtotal) currate = mode[j].dot_clock / (mode[j].htotal * mode[j].vtotal); break; } } if (minrate == -1 || currate < minrate) minrate = currate; } free(cir); } free(srr); s->rate = (minrate > 0) ? minrate : SWM_RATE_DEFAULT; DNPRINTF(SWM_D_MISC, "Screen %d update rate: %d\n", s->idx, s->rate); } #endif /* SWM_XRR_HAS_CRTC */ /* If detection failed, create a single region that spans the screen. */ if (TAILQ_EMPTY(&s->rl)) new_region(s, 0, 0, screen->width_in_pixels, screen->height_in_pixels, ROTATION_DEFAULT); #ifdef SWM_XRR_HAS_CRTC out: #endif /* The screen shouldn't focus on unused regions. */ TAILQ_FOREACH(r, &s->orl, entry) { if (s->r_focus == r) s->r_focus = NULL; } DNPRINTF(SWM_D_MISC, "done.\n"); } void screenchange(xcb_randr_screen_change_notify_event_t *e) { struct swm_screen *s; struct swm_region *r; struct workspace *ws; DNPRINTF(SWM_D_EVENT, "root: %#x\n", e->root); /* silly event doesn't include the screen index */ s = find_screen(e->root); if (s == NULL) errx(1, "screenchange: screen not found."); /* brute force for now, just re-enumerate the regions */ scan_randr(s); if (swm_debug & SWM_D_EVENT) print_win_geom(e->root); /* add bars to all regions */ TAILQ_FOREACH(r, &s->rl, entry) bar_setup(r); /* Stack all regions. */ update_stacking(s); refresh_strut(s); update_layout(s); update_mapping(s); /* Focus region. */ if (s->r_focus) { /* Update bar colors for existing focus. */ r = s->r_focus; s->r_focus = NULL; set_region(r); } else { /* Focus on the first region. */ r = TAILQ_FIRST(&s->rl); if (r) focus_region(r); } /* Unmap workspaces that are no longer visible. */ RB_FOREACH(ws, workspace_tree, &s->workspaces) if (ws->r == NULL) unmap_workspace(ws); ewmh_update_desktops(s); update_bars(s); flush(); DNPRINTF(SWM_D_EVENT, "done.\n"); } void grab_windows(void) { struct workspace *ws; struct ws_win_list *wl; struct ws_win *w; xcb_query_tree_cookie_t qtc; xcb_query_tree_reply_t *qtr; xcb_get_property_reply_t *pr; xcb_window_t *wins = NULL, *cwins = NULL; int i, j, k, n, no, num_screens; DNPRINTF(SWM_D_INIT, "begin\n"); num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) { qtc = xcb_query_tree(conn, screens[i].root); qtr = xcb_query_tree_reply(conn, qtc, NULL); if (qtr == NULL) continue; wins = xcb_query_tree_children(qtr); no = xcb_query_tree_children_length(qtr); /* Try to sort windows according to _NET_CLIENT_LIST. */ pr = xcb_get_property_reply(conn, xcb_get_property(conn, 0, screens[i].root, ewmh[_NET_CLIENT_LIST].atom, XCB_ATOM_WINDOW, 0, UINT32_MAX), NULL); if (pr != NULL) { cwins = xcb_get_property_value(pr); n = xcb_get_property_value_length(pr) / sizeof(xcb_atom_t); for (j = 0; j < n; ++j) { for (k = j; k < no; ++k) { if (wins[k] == cwins[j]) { /* Swap wins j and k. */ wins[k] = wins[j]; wins[j] = cwins[j]; } } } free(pr); } /* Manage windows from bottom to top. */ for (j = 0; j < no; j++) manage_window(wins[j], SWM_STACK_TOP, false); free(qtr); /* Set initial focus state based on manage order and EWMH. */ DNPRINTF(SWM_D_INIT, "set initial focus state\n"); RB_FOREACH(ws, workspace_tree, &screens[i].workspaces) { wl = &ws->winlist; /* Prepare previous focus queue. */ TAILQ_FOREACH(w, wl, entry) set_focus(w->s, w); /* 1. Fullscreen */ TAILQ_FOREACH_REVERSE(w, wl, ws_win_list, entry) if (!HIDDEN(w) && FULLSCREEN(w)) break; /* 2. Maximized */ if (w == NULL) TAILQ_FOREACH_REVERSE(w, wl, ws_win_list, entry) if (!HIDDEN(w) && MAXIMIZED(w)) break; /* 3. Default */ if (w == NULL) w = get_main_window(ws); if (w == NULL) continue; set_focus(w->s, w); DNPRINTF(SWM_D_INIT, "ws %d: focus: %#x\n", j, WINID(ws->focus)); } } DNPRINTF(SWM_D_INIT, "done\n"); } void setup_focus(void) { struct swm_region *r; struct ws_win *w; if (focus_mode == SWM_FOCUS_FOLLOW) { DNPRINTF(SWM_D_INIT, "focus pointer window\n"); r = get_pointer_region(&screens[0]); } else { DNPRINTF(SWM_D_INIT, "focus first region\n"); r = TAILQ_FIRST(&screens[0].rl); } if (r) { if (focus_mode != SWM_FOCUS_FOLLOW || ws_maponfocus(r->ws)) { w = r->ws->focus; if (w == NULL || !(FULLSCREEN(w) || MAXIMIZED(w))) w = get_main_window(r->ws); if (w) { set_focus(w->s, w); if (apply_unfocus(w->ws, w) > 0) { update_stacking(&screens[0]); refresh_strut(&screens[0]); update_layout(&screens[0]); update_mapping(&screens[0]); } } } focus_region(r); flush(); if (focus_mode == SWM_FOCUS_FOLLOW) { if (pointer_window != XCB_WINDOW_NONE) { focus_window(pointer_window); xcb_flush(conn); } } } DNPRINTF(SWM_D_INIT, "done\n"); } void setup_screens(void) { struct swm_screen *s; xcb_pixmap_t pmap; xcb_depth_iterator_t diter; xcb_visualtype_iterator_t viter; xcb_void_cookie_t cmc; xcb_generic_error_t *error; XVisualInfo vtmpl, *vlist; int i, num_screens, vcount; uint32_t wa[3]; xcb_screen_t *screen; num_screens = get_screen_count(); if ((screens = calloc(num_screens, sizeof(struct swm_screen))) == NULL) err(1, "setup_screens: calloc"); /* map physical screens */ for (i = 0; i < num_screens; i++) { DNPRINTF(SWM_D_INIT, "init screen: %d\n", i); s = &screens[i]; s->idx = i; if ((screen = get_screen(i)) == NULL) errx(1, "ERROR: unable to get screen %d.", i); /* Create region for root. */ if ((s->r = calloc(1, sizeof(struct swm_region))) == NULL) err(1, "setup_screens: calloc"); TAILQ_INIT(&s->rl); TAILQ_INIT(&s->orl); RB_INIT(&s->workspaces); TAILQ_INIT(&s->priority); SLIST_INIT(&s->stack); SLIST_INIT(&s->struts); TAILQ_INIT(&s->fl); TAILQ_INIT(&s->managed); s->r->id = XCB_WINDOW_NONE; /* Not needed for root region. */ X(s->r) = 0; Y(s->r) = 0; WIDTH(s->r) = screen->width_in_pixels; HEIGHT(s->r) = screen->height_in_pixels; s->r->s = s; s->r->ws = get_workspace(s, -1); s->r->ws->r = s->r; s->r->ws->cur_layout = &layouts[SWM_FLOATING_STACK]; s->focus = NULL; s->r_focus = NULL; s->rate = SWM_RATE_DEFAULT; s->root = screen->root; s->active_window = screen->root; s->depth = screen->root_depth; s->visual = screen->root_visual; s->colormap = screen->default_colormap; s->xvisual = DefaultVisual(display, i); s->bar_xftfonts[0] = NULL; s->managed_count = 0; DNPRINTF(SWM_D_INIT, "root_depth: %d, screen_depth: %d\n", screen->root_depth, xcb_aux_get_depth(conn, screen)); /* Use the maximum supported screen depth. */ for (diter = xcb_screen_allowed_depths_iterator(screen); diter.rem; xcb_depth_next(&diter)) { if (diter.data->depth > s->depth) { viter = xcb_depth_visuals_iterator(diter.data); if (viter.rem && viter.data->_class == XCB_VISUAL_CLASS_TRUE_COLOR) { s->depth = diter.data->depth; s->visual = viter.data->visual_id; DNPRINTF(SWM_D_INIT, "Found TrueColor " "visual %#x, depth: %d.\n", s->visual, s->depth); } } } /* Create a new colormap if not using the root visual */ if (s->visual != screen->root_visual) { /* Find the corresponding Xlib visual struct for Xft. */ vtmpl.visualid = s->visual; vlist = XGetVisualInfo(display, VisualIDMask, &vtmpl, &vcount); if (vcount > 0) s->xvisual = vlist[0].visual; DNPRINTF(SWM_D_INIT, "Creating new colormap.\n"); s->colormap = xcb_generate_id(conn); cmc = xcb_create_colormap_checked(conn, XCB_COLORMAP_ALLOC_NONE, s->colormap, s->root, s->visual); if ((error = xcb_request_check(conn, cmc))) { DNPRINTF(SWM_D_MISC, "error:\n"); event_error(error); free(error); } } DNPRINTF(SWM_D_INIT, "Using visual %#x, depth: %d.\n", s->visual, xcb_aux_get_depth_of_visual(screen, s->visual)); /* set default colors */ setscreencolor(s, "red", SWM_S_COLOR_FOCUS); setscreencolor(s, "red", SWM_S_COLOR_FOCUS_MAXIMIZED); setscreencolor(s, "rgb:88/88/88", SWM_S_COLOR_UNFOCUS); setscreencolor(s, "rgb:88/88/88", SWM_S_COLOR_UNFOCUS_MAXIMIZED); setscreencolor(s, "yellow", SWM_S_COLOR_FOCUS_FREE); setscreencolor(s, "yellow", SWM_S_COLOR_FOCUS_MAXIMIZED_FREE); setscreencolor(s, "rgb:88/88/00", SWM_S_COLOR_UNFOCUS_FREE); setscreencolor(s, "rgb:88/88/00", SWM_S_COLOR_UNFOCUS_MAXIMIZED_FREE); setscreencolor(s, "rgb:00/80/80", SWM_S_COLOR_BAR_BORDER); setscreencolor(s, "rgb:80/80/00", SWM_S_COLOR_BAR_BORDER_FREE); setscreencolor(s, "rgb:00/40/40", SWM_S_COLOR_BAR_BORDER_UNFOCUS); setscreencolor(s, "black", SWM_S_COLOR_BAR); setscreencolor(s, "black", SWM_S_COLOR_BAR_UNFOCUS); setscreencolor(s, "rgb:40/40/00", SWM_S_COLOR_BAR_FREE); setscreencolor(s, "rgb:00/80/80", SWM_S_COLOR_BAR_SELECTED); setscreencolor(s, "rgb:a0/a0/a0", SWM_S_COLOR_BAR_FONT); setscreencolor(s, "rgb:a0/a0/a0", SWM_S_COLOR_BAR_FONT_UNFOCUS); setscreencolor(s, "rgb:ff/ff/ff", SWM_S_COLOR_BAR_FONT_FREE); setscreencolor(s, "black", SWM_S_COLOR_BAR_FONT_SELECTED); /* set default cursor */ wa[0] = cursors[XC_LEFT_PTR].cid; xcb_change_window_attributes(conn, s->root, XCB_CW_CURSOR, wa); /* Create graphics context. */ /* xcb_create_gc determines root/depth from a drawable. */ pmap = xcb_generate_id(conn); xcb_create_pixmap(conn, s->depth, pmap, s->root, 10, 10); s->gc = xcb_generate_id(conn); wa[0] = wa[1] = screen->black_pixel; wa[2] = 0; xcb_create_gc(conn, s->gc, pmap, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_GRAPHICS_EXPOSURES, wa); xcb_free_pixmap(conn, pmap); /* Create window for input fallback as well as EWMH support. */ s->swmwin = xcb_generate_id(conn); wa[0] = 1; wa[1] = XCB_EVENT_MASK_PROPERTY_CHANGE; xcb_create_window(conn, XCB_COPY_FROM_PARENT, s->swmwin, s->root, -1, -1, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_COPY_FROM_PARENT, XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK, wa); xcb_map_window(conn, s->swmwin); #if defined(SWM_XCB_HAS_XINPUT) && defined(XCB_INPUT_RAW_BUTTON_PRESS) setup_xinput2(s); #endif scan_randr(s); if (randr_support) xcb_randr_select_input(conn, s->root, XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE); } } void setup_extensions(void) { const xcb_query_extension_reply_t *qep; xcb_randr_query_version_reply_t *rqvr; #ifdef SWM_XCB_HAS_XINPUT xcb_input_xi_query_version_reply_t *xiqvr; #endif randr_support = false; qep = xcb_get_extension_data(conn, &xcb_randr_id); if (qep->present) { rqvr = xcb_randr_query_version_reply(conn, xcb_randr_query_version(conn, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION), NULL); if (rqvr) { if (rqvr->major_version >= 1) { randr_support = true; randr_eventbase = qep->first_event; DNPRINTF(SWM_D_INIT, "RandR %u.%u supported." " major_opcode: %u, first_event: %u\n", rqvr->major_version, rqvr->minor_version, qep->major_opcode, qep->first_event); } free(rqvr); } } #ifdef SWM_XCB_HAS_XINPUT xinput2_support = false; xinput2_raw = false; qep = xcb_get_extension_data(conn, &xcb_input_id); if (qep->present) { xiqvr = xcb_input_xi_query_version_reply(conn, xcb_input_xi_query_version(conn, XCB_INPUT_MAJOR_VERSION, XCB_INPUT_MINOR_VERSION), NULL); if (xiqvr) { if (xiqvr->major_version >= 2) { xinput2_support = true; #ifdef XCB_INPUT_RAW_BUTTON_PRESS if (xiqvr->minor_version >= 1 || xiqvr->major_version > 2) xinput2_raw = true; #endif xinput2_opcode = qep->major_opcode; DNPRINTF(SWM_D_INIT, "XInput %u.%u supported. " "major_opcode: %u\n", xiqvr->major_version, xiqvr->minor_version, qep->major_opcode); } free(xiqvr); } } #endif /* SWM_XCB_HAS_XINPUT */ } void setup_globals(void) { if ((clock_format = strdup("%a %b %d %R %Z %Y")) == NULL) err(1, "clock_format: strdup"); if ((focus_mark_none = strdup("")) == NULL) err(1, "focus_mark_none: strdup"); if ((focus_mark_normal = strdup("")) == NULL) err(1, "focus_mark_normal: strdup"); if ((focus_mark_floating = strdup("(f)")) == NULL) err(1, "focus_mark_floating: strdup"); if ((focus_mark_free = strdup("(*)")) == NULL) err(1, "focus_mark_free: strdup"); if ((focus_mark_maximized = strdup("(m)")) == NULL) err(1, "focus_mark_maximized: strdup"); if ((stack_mark_floating = strdup("[~]")) == NULL) err(1, "stack_mark_floating: strdup"); if ((stack_mark_max = strdup("[ ]")) == NULL) err(1, "stack_mark_max: strdup"); if ((stack_mark_vertical = strdup("[|]")) == NULL) err(1, "stack_mark_vertical: strdup"); if ((stack_mark_vertical_flip = strdup("[>]")) == NULL) err(1, "stack_mark_vertical_flip: strdup"); if ((stack_mark_horizontal = strdup("[-]")) == NULL) err(1, "stack_mark_horizontal: strdup"); if ((stack_mark_horizontal_flip = strdup("[v]")) == NULL) err(1, "stack_mark_horizontal_flip: strdup"); if ((syms = xcb_key_symbols_alloc(conn)) == NULL) errx(1, "unable to allocate key symbols."); if ((workspace_mark_current = strdup("*")) == NULL) err(1, "workspace_mark_current: strdup"); if ((workspace_mark_urgent = strdup("!")) == NULL) err(1, "workspace_mark_urgent: strdup"); if ((workspace_mark_active = strdup("^")) == NULL) err(1, "workspace_mark_active: strdup"); if ((workspace_mark_empty = strdup("-")) == NULL) err(1, "workspace_mark_empty: strdup"); a_state = get_atom_from_string("WM_STATE"); a_change_state = get_atom_from_string("WM_CHANGE_STATE"); a_prot = get_atom_from_string("WM_PROTOCOLS"); a_delete = get_atom_from_string("WM_DELETE_WINDOW"); a_net_frame_extents = get_atom_from_string("_NET_FRAME_EXTENTS"); a_net_supported = get_atom_from_string("_NET_SUPPORTED"); a_net_wm_check = get_atom_from_string("_NET_SUPPORTING_WM_CHECK"); a_net_wm_pid = get_atom_from_string("_NET_WM_PID"); a_takefocus = get_atom_from_string("WM_TAKE_FOCUS"); a_utf8_string = get_atom_from_string("UTF8_STRING"); a_swm_pid = get_atom_from_string("_SWM_PID"); a_swm_ws = get_atom_from_string("_SWM_WS"); } void scan_config(void) { struct stat sb; struct passwd *pwd; char conf[PATH_MAX]; char *cfile = NULL, *str = NULL, *ret, *s, *sp; int i; /* To get $HOME */ pwd = getpwuid(getuid()); if (pwd == NULL) errx(1, "invalid user: %d", getuid()); /* XDG search with backwards compatibility. */ for (i = 0; ; i++) { conf[0] = '\0'; switch (i) { case 0: /* 1) $XDG_CONFIG_HOME/spectrwm/spectrwm.conf */ ret = getenv("XDG_CONFIG_HOME"); if (ret && ret[0]) snprintf(conf, sizeof conf, "%s/spectrwm/%s", ret, SWM_CONF_FILE); else /* 2) Default is $HOME/.config */ snprintf(conf, sizeof conf, "%s/.config/spectrwm/%s", pwd->pw_dir, SWM_CONF_FILE); break; case 1: /* 3) $HOME/.spectrwm.conf */ snprintf(conf, sizeof conf, "%s/.%s", pwd->pw_dir, SWM_CONF_FILE); break; case 2: /* 4) $XDG_CONFIG_DIRS (colon-separated set of dirs) */ ret = getenv("XDG_CONFIG_DIRS"); if (ret && ret[0]) { if ((str = strdup(ret)) == NULL) err(1, "xdg strdup"); } else { /* 5) Fallback to default: /etc/xdg */ if (asprintf(&str, "/etc/xdg") == -1) err(1, "xdg asprintf"); } /* Try ./spectrwm/spectrwm.conf under each dir. */ sp = str; while ((s = strsep(&sp, ":")) != NULL) { if (*s == '\0') continue; snprintf(conf, sizeof conf, "%s/spectrwm/%s", s, SWM_CONF_FILE); if (stat(conf, &sb) != -1 && S_ISREG(sb.st_mode)) { /* Found a file. */ cfile = conf; break; } conf[0] = '\0'; } free(str); break; case 3: /* 6) /etc/spectrwm.conf */ snprintf(conf, sizeof conf, "/etc/%s", SWM_CONF_FILE); break; case 4: /* 7) $HOME/.scrotwm.conf */ snprintf(conf, sizeof conf, "%s/.%s", pwd->pw_dir, SWM_CONF_FILE_OLD); break; case 5: /* 8) /etc/scrotwm.conf */ snprintf(conf, sizeof conf, "/etc/%s", SWM_CONF_FILE_OLD); break; default: goto done; } if (cfile == NULL && conf[0] && stat(conf, &sb) != -1 && S_ISREG(sb.st_mode)) cfile = conf; if (cfile) { conf_load(cfile, SWM_CONF_DEFAULT); break; } } done: DNPRINTF(SWM_D_INIT, "done\n"); } void shutdown_cleanup(void) { struct swm_screen *s; struct swm_region *r; struct ws_win *w; struct workspace *ws; int i, j, num_screens; /* disable alarm because the following code may not be interrupted */ alarm(0); if (signal(SIGALRM, SIG_IGN) == SIG_ERR) err(1, "can't disable alarm"); bar_extra_stop(); cursors_cleanup(); clear_quirks(); clear_spawns(); clear_bindings(); clear_atom_names(); teardown_ewmh(); num_screens = get_screen_count(); for (i = 0; i < num_screens; ++i) { s = &screens[i]; set_input_focus(XCB_INPUT_FOCUS_POINTER_ROOT, true); xcb_destroy_window(conn, s->swmwin); if (s->gc != XCB_NONE) xcb_free_gc(conn, s->gc); if (!bar_font_legacy) { XftColorFree(display, s->xvisual, s->colormap, &search_font_color); for (j = 0; j < num_fg_colors; j++) XftColorFree(display, s->xvisual, s->colormap, &bar_fg_colors[j]); for (j = 0; j < num_fg_colors; j++) XftColorFree(display, s->xvisual, s->colormap, &bar_fg_colors_free[j]); for (j = 0; j < num_fg_colors; j++) XftColorFree(display, s->xvisual, s->colormap, &bar_fg_colors_unfocus[j]); for (i = 0; i < num_xftfonts; i++) if (s->bar_xftfonts[i]) XftFontClose(display, s->bar_xftfonts[i]); if (font_pua_index) XftFontClose(display, s->bar_xftfonts[font_pua_index]); } #ifndef __clang_analyzer__ /* Suppress false warnings. */ /* Cleanup window state and memory. */ while ((w = TAILQ_FIRST(&s->managed))) { unmap_window(w); /* Load floating geometry and adjust to root. */ w->g = w->g_float; w->g.x -= w->g_floatref.x; w->g.y -= w->g_floatref.y; update_window(w); if (ws_maxstack(w->ws)) { w->ewmh_flags &= ~EWMH_F_MAXIMIZED; ewmh_update_wm_state(w); } if (w->strut) { SLIST_REMOVE(&w->s->struts, w->strut, swm_strut, entry); free(w->strut); w->strut = NULL; } TAILQ_REMOVE(&s->managed, w, manage_entry); free_window(w); } while ((ws = RB_ROOT(&s->workspaces))) workspace_remove(ws); #endif /* Free region memory. */ while ((r = TAILQ_FIRST(&s->rl)) != NULL) { TAILQ_REMOVE(&s->rl, r, entry); free(r->bar); free(r); } while ((r = TAILQ_FIRST(&s->orl)) != NULL) { TAILQ_REMOVE(&s->orl, r, entry); free(r->bar); free(r); } free(s->r); } free(screens); free(bar_format); free(bar_fonts); free(clock_format); free(startup_exception); free(focus_mark_none); free(focus_mark_normal); free(focus_mark_floating); free(focus_mark_maximized); free(stack_mark_floating); free(stack_mark_max); free(stack_mark_vertical); free(stack_mark_vertical_flip); free(stack_mark_horizontal); free(stack_mark_horizontal_flip); free(workspace_mark_current); free(workspace_mark_current_suffix); free(workspace_mark_urgent); free(workspace_mark_urgent_suffix); free(workspace_mark_active); free(workspace_mark_active_suffix); free(workspace_mark_empty); free(workspace_mark_empty_suffix); if (bar_fs) XFreeFontSet(display, bar_fs); xcb_key_symbols_free(syms); xcb_flush(conn); xcb_aux_sync(conn); xcb_disconnect(conn); } void event_error(xcb_generic_error_t *e) { (void)e; DNPRINTF(SWM_D_EVENT, "%s(%u) from %s(%u), sequence: %u, " "resource_id: %u, minor_code: %u\n", xcb_event_get_error_label(e->error_code), e->error_code, xcb_event_get_request_label(e->major_code), e->major_code, e->sequence, e->resource_id, e->minor_code); } void event_handle(xcb_generic_event_t *evt) { uint8_t type = XCB_EVENT_RESPONSE_TYPE(evt); DNPRINTF(SWM_D_EVENT, "%s(%d), seq %u, sent: %s\n", get_event_label(evt), type, evt->sequence, YESNO(XCB_EVENT_SENT(evt))); if (type <= XCB_MAPPING_NOTIFY) { switch (type) { #define EVENT(type, callback) case type: callback((void *)evt); return EVENT(0, event_error); EVENT(XCB_KEY_PRESS, keypress); EVENT(XCB_KEY_RELEASE, keyrelease); EVENT(XCB_BUTTON_PRESS, buttonpress); EVENT(XCB_BUTTON_RELEASE, buttonrelease); EVENT(XCB_MOTION_NOTIFY, motionnotify); EVENT(XCB_ENTER_NOTIFY, enternotify); EVENT(XCB_LEAVE_NOTIFY, leavenotify); EVENT(XCB_FOCUS_IN, focusin); EVENT(XCB_FOCUS_OUT, focusout); /*EVENT(XCB_KEYMAP_NOTIFY, );*/ EVENT(XCB_EXPOSE, expose); /*EVENT(XCB_GRAPHICS_EXPOSURE, );*/ /*EVENT(XCB_NO_EXPOSURE, );*/ /*EVENT(XCB_VISIBILITY_NOTIFY, );*/ /*EVENT(XCB_CREATE_NOTIFY, );*/ EVENT(XCB_DESTROY_NOTIFY, destroynotify); EVENT(XCB_UNMAP_NOTIFY, unmapnotify); EVENT(XCB_MAP_NOTIFY, mapnotify); EVENT(XCB_MAP_REQUEST, maprequest); EVENT(XCB_REPARENT_NOTIFY, reparentnotify); EVENT(XCB_CONFIGURE_NOTIFY, configurenotify); EVENT(XCB_CONFIGURE_REQUEST, configurerequest); /*EVENT(XCB_GRAVITY_NOTIFY, );*/ /*EVENT(XCB_RESIZE_REQUEST, );*/ /*EVENT(XCB_CIRCULATE_NOTIFY, );*/ /*EVENT(XCB_CIRCULATE_REQUEST, );*/ EVENT(XCB_PROPERTY_NOTIFY, propertynotify); /*EVENT(XCB_SELECTION_CLEAR, );*/ /*EVENT(XCB_SELECTION_REQUEST, );*/ /*EVENT(XCB_SELECTION_NOTIFY, );*/ /*EVENT(XCB_COLORMAP_NOTIFY, );*/ EVENT(XCB_CLIENT_MESSAGE, clientmessage); EVENT(XCB_MAPPING_NOTIFY, mappingnotify); #undef EVENT } #if defined(SWM_XCB_HAS_XINPUT) && defined(XCB_INPUT_RAW_BUTTON_PRESS) } else if (type == XCB_GE_GENERIC) { xcb_ge_generic_event_t *ge; ge = (xcb_ge_generic_event_t *)evt; if (xinput2_support && ge->extension == xinput2_opcode) { if (ge->event_type == XCB_INPUT_RAW_BUTTON_PRESS) rawbuttonpress((void *)evt); } #endif } else if (randr_support && (type - randr_eventbase) == XCB_RANDR_SCREEN_CHANGE_NOTIFY) { screenchange((void *)evt); } } static void usage(void) { fprintf(stderr, "usage: spectrwm [-c file] [-v]\n" " -c FILE load configuration file\n" " -d enable debug mode and logging to stderr\n" " -v display version information and exit\n"); exit(1); } int main(int argc, char *argv[]) { struct pollfd pfd[2]; struct sigaction sact; struct swm_region *r; xcb_generic_event_t *evt; xcb_mapping_notify_event_t *mne; char *cfile = NULL; int ch, i, num_screens, num_readable; bool stdin_ready = false; while ((ch = getopt(argc, argv, "c:dhv")) != -1) { switch (ch) { case 'c': cfile = optarg; break; case 'd': swm_debug = SWM_D_ALL; break; case 'v': fprintf(stderr, "spectrwm %s Build: %s\n", SPECTRWM_VERSION, buildstr); exit(1); break; default: usage(); } } time_started = time(NULL); start_argv = argv; warnx("Welcome to spectrwm V%s Build: %s", SPECTRWM_VERSION, buildstr); if (setlocale(LC_CTYPE, "") == NULL || setlocale(LC_TIME, "") == NULL) warnx("no locale support"); /* handle some signals */ bzero(&sact, sizeof(sact)); sigemptyset(&sact.sa_mask); sact.sa_flags = 0; sact.sa_handler = sighdlr; sigaction(SIGINT, &sact, NULL); sigaction(SIGQUIT, &sact, NULL); sigaction(SIGTERM, &sact, NULL); sigaction(SIGHUP, &sact, NULL); sact.sa_handler = sighdlr; sact.sa_flags = SA_NOCLDSTOP; sigaction(SIGCHLD, &sact, NULL); if ((display = XOpenDisplay(0)) == NULL) errx(1, "unable to open display"); conn = XGetXCBConnection(display); if (xcb_connection_has_error(conn)) errx(1, "unable to get XCB connection"); XSetEventQueueOwner(display, XCBOwnsEventQueue); xcb_prefetch_extension_data(conn, &xcb_randr_id); xcb_grab_server(conn); xcb_aux_sync(conn); /* Flush the event queue. */ while ((evt = get_next_event(false))) { switch (XCB_EVENT_RESPONSE_TYPE(evt)) { case XCB_MAPPING_NOTIFY: /* Need to handle mapping changes during startup. */ mne = (xcb_mapping_notify_event_t *)evt; if (mne->request == XCB_MAPPING_KEYBOARD) xcb_refresh_keyboard_mapping(syms, mne); break; case 0: /* Display errors. */ if (swm_debug & SWM_D_EVENT) event_handle(evt); break; } free(evt); } if (enable_wm()) errx(1, "another window manager is currently running"); cursors_load(); xcb_aux_sync(conn); setup_globals(); setup_extensions(); setup_screens(); setup_ewmh(); setup_keybindings(); setup_btnbindings(); setup_quirks(); setup_spawn(); if (cfile) conf_load(cfile, SWM_CONF_DEFAULT); else scan_config(); setup_marks(); setup_fonts(); validate_spawns(); if (getenv("SWM_STARTED") == NULL) setenv("SWM_STARTED", "YES", 1); /* Setup bars on all regions. */ num_screens = get_screen_count(); for (i = 0; i < num_screens; i++) TAILQ_FOREACH(r, &screens[i].rl, entry) bar_setup(r); #ifdef __OpenBSD__ if (pledge("stdio proc exec", NULL) == -1) err(1, "pledge"); #endif /* Manage existing windows. */ grab_windows(); grabkeys(); grabbuttons(); /* Stack all regions to trigger mapping. */ for (i = 0; i < num_screens; i++) { update_stacking(&screens[i]); refresh_strut(&screens[i]); update_layout(&screens[i]); update_mapping(&screens[i]); } xcb_ungrab_server(conn); flush(); setup_focus(); memset(&pfd, 0, sizeof(pfd)); pfd[0].fd = xcb_get_file_descriptor(conn); pfd[0].events = POLLIN; pfd[1].fd = STDIN_FILENO; pfd[1].events = POLLIN; while (running) { while ((evt = get_next_event(false))) { if (!running) goto done; event_handle(evt); free(evt); } if (search_resp) { search_do_resp(); continue; } num_readable = poll(pfd, bar_extra ? 2 : 1, 1000); if (num_readable > 0) { if (pfd[0].revents & POLLHUP) goto done; if (bar_extra) { if (pfd[1].revents & POLLHUP) bar_extra = false; else if (pfd[1].revents & POLLIN) stdin_ready = true; } } else if (num_readable == -1) { DNPRINTF(SWM_D_MISC, "poll: %s\n", strerror(errno)); } if (restart_wm) restart(NULL, NULL, NULL); if (!running) goto done; if (stdin_ready) { stdin_ready = false; if (bar_extra_update() == 0) continue; } /* Need to ensure the bar(s) are always updated. */ for (i = 0; i < num_screens; i++) update_bars(&screens[i]); xcb_flush(conn); } done: shutdown_cleanup(); return (0); } spectrwm-SPECTRWM_3_5_1/spectrwm.conf000066400000000000000000000135501453034271100175710ustar00rootroot00000000000000# # spectrwm Example Configuration File # # PLEASE READ THE MAN PAGE BEFORE EDITING THIS FILE! # https://htmlpreview.github.io/?https://github.com/conformal/spectrwm/blob/master/spectrwm.html # # All example settings in this file are commented out with a '#'. # See the spectrwm(1) man page for default values. # # NOTE: rgb color values are in hexadecimal! See XQueryColor(3) for details. #workspace_limit = 22 #focus_mode = default #focus_close = previous #focus_close_wrap = 1 #focus_default = last #spawn_position = next #workspace_autorotate = 1 #workspace_clamp = 1 #warp_focus = 1 #warp_pointer = 1 #click_to_raise = 1 # Include mapped workspaces when switching with any of the ws next/prev actions. #cycle_visible = 1 # Window Decoration #border_width = 1 #color_focus = red #color_focus_maximized = yellow #color_unfocus = rgb:88/88/88 #color_unfocus_maximized = rgb:88/88/00 #color_focus_free = cyan #color_focus_maximized_free = magenta #color_unfocus_free = rgb:00/88/88 #color_unfocus_maximized_free = rgb:88/00/88 #region_padding = 0 #tile_gap = 0 # Region containment # Distance window must be dragged/resized beyond the region edge before it is # allowed outside the region. #boundary_width = 50 # Remove window border when bar is disabled and there is only one window in workspace #disable_border = 1 # Bar Settings #bar_enabled = 1 #bar_enabled_ws[1] = 1 #bar_border_width = 1 #bar_border[1] = rgb:00/80/80 #bar_border_unfocus[1] = rgb:00/40/40 #bar_border_free[1] = rgb:80/80/00 #bar_color[1] = black #bar_color_unfocus[1] = black #bar_color_free[1] = rgb:40/40/00 #bar_color_selected[1] = rgb:00/80/80 #bar_font_color[1] = rgb:a0/a0/a0 #bar_font_color_unfocus[1] = rgb:a0/a0/a0 #bar_font_color_free[1] = white #bar_font_color_selected = black #bar_font = xos4 Terminus:pixelsize=14:antialias=true #bar_font_pua = Typicons:pixelsize=14:antialias=true #bar_action = baraction.sh #bar_action_expand = 0 #bar_justify = left #bar_format = +N:+I +S <+D>+4<%a %b %d %R %Z %Y+8<+A+4<+V #workspace_indicator = listcurrent,listactive,markcurrent,printnames #workspace_mark_current = '*' #workspace_mark_current_suffix = '' #workspace_mark_active = '^' #workspace_mark_active_suffix = '' #workspace_mark_empty = '-' #workspace_mark_empty_suffix = '' #workspace_mark_urgent = '!' #workspace_mark_urgent_suffix = '' #bar_at_bottom = 1 #stack_enabled = 1 #stack_mark_horizontal = '[-]' #stack_mark_horizontal_flip = '[v]' #stack_mark_vertical = '[|]' #stack_mark_vertical_flip = '[>]' #stack_mark_max = '[ ]' #stack_mark_floating = '[~]' #focus_mark_none = '' #focus_mark_normal = '' #focus_mark_floating = '(f)' #focus_mark_maximized = '(m)' #focus_mark_free = '(*)' #clock_enabled = 1 #clock_format = %a %b %d %R %Z %Y #iconic_enabled = 0 #fullscreen_hide_other = 0 #maximize_hide_bar = 0 #maximize_hide_other = 0 #window_class_enabled = 0 #window_instance_enabled = 0 #window_name_enabled = 0 #verbose_layout = 1 #urgent_enabled = 1 #urgent_collapse = 0 # Dialog box size ratio when using TRANSSZ quirk; 0.3 < dialog_ratio <= 1.0 #dialog_ratio = 0.6 # Split a non-RandR dual head setup into one region per monitor # (non-standard driver-based multihead is not seen by spectrwm) #region = screen[1]:1280x1024+0+0 #region = screen[1]:1280x1024+1280+0 # Launch applications in a workspace of choice #autorun = ws[1]:xterm #autorun = ws[2]:xombrero http://www.openbsd.org # Customize workspace layout at start #layout = ws[1]:4:0:0:0:vertical #layout = ws[2]:0:0:0:0:horizontal #layout = ws[3]:0:0:0:0:max #layout = ws[4]:4:0:0:0:vertical_flip #layout = ws[5]:0:0:0:0:horizontal_flip #layout = ws[6]:0:0:0:0:floating # Set workspace name at start #name = ws[1]:IRC #name = ws[2]:Email #name = ws[3]:Browse #name = ws[10]:Music # Change the modifier to use when specifying 'MOD' in bindings. # This should come before configuring bindings, not after. # (Mod1: Alt key, Mod4: Windows key, Mod2: Apple key on OSX) #modkey = Mod4 # This allows you to include pre-defined key bindings for your keyboard layout. # All key bindings are cleared before loading bindings in the specified file. #keyboard_mapping = ~/.spectrwm_us.conf # PROGRAMS # Validated default programs: #program[lock] = xlock #program[term] = xterm #program[menu] = dmenu_run $dmenu_bottom -fn $bar_font -nb $bar_color -nf $bar_font_color -sb $bar_color_selected -sf $bar_font_color_selected #program[search] = dmenu $dmenu_bottom -i -fn $bar_font -nb $bar_color -nf $bar_font_color -sb $bar_color_selected -sf $bar_font_color_selected #program[name_workspace] = dmenu $dmenu_bottom -p Workspace -fn $bar_font -nb $bar_color -nf $bar_font_color -sb $bar_color_selected -sf $bar_font_color_selected # To disable validation of the above, free the respective binding(s): #bind[] = MOD+Shift+Delete # disable lock #bind[] = MOD+Shift+Return # disable term #bind[] = MOD+p # disable menu # Optional default programs that will only be validated if you override: #program[screenshot_all] = screenshot.sh full # optional #program[screenshot_wind] = screenshot.sh window # optional #program[initscr] = initscreen.sh # optional # EXAMPLE: Define 'firefox' action and bind to key. #program[firefox] = firefox http://spectrwm.org/ #bind[firefox] = MOD+Shift+b # QUIRKS # Default quirks, remove with: quirk[class:name] = NONE #quirk[MPlayer:xv] = FLOAT + FULLSCREEN + FOCUSPREV #quirk[OpenOffice.org 2.4:VCLSalFrame] = FLOAT #quirk[OpenOffice.org 3.0:VCLSalFrame] = FLOAT #quirk[OpenOffice.org 3.1:VCLSalFrame] = FLOAT #quirk[Firefox-bin:firefox-bin] = TRANSSZ #quirk[Firefox:Dialog] = FLOAT #quirk[Gimp:gimp] = FLOAT + ANYWHERE #quirk[XTerm:xterm] = XTERM_FONTADJ #quirk[xine:Xine Window] = FLOAT + ANYWHERE #quirk[Xitk:Xitk Combo] = FLOAT + ANYWHERE #quirk[xine:xine Panel] = FLOAT + ANYWHERE #quirk[Xitk:Xine Window] = FLOAT + ANYWHERE #quirk[xine:xine Video Fullscreen Window] = FULLSCREEN + FLOAT #quirk[pcb:pcb] = FLOAT spectrwm-SPECTRWM_3_5_1/spectrwm.html000066400000000000000000003231441453034271100176130ustar00rootroot00000000000000 SPECTRWM(1)
SPECTRWM(1) General Commands Manual SPECTRWM(1)

spectrwmwindow manager for X11

spectrwm [-c file] [-v]

file
Specify a configuration file to load instead of scanning for one.
Enable debug mode and logging to stderr.
Print version and exit.

spectrwm is a minimalistic window manager that tries to stay out of the way so that valuable screen real estate can be used for much more important stuff. It has sane defaults and does not require one to learn a language to do any configuration. It was written by hackers for hackers and it strives to be small, compact and fast.

When spectrwm starts up, it reads settings from its configuration file, spectrwm.conf. See the CONFIGURATION FILES section below.

The following notation is used throughout this page:

Meta
Shift
Name
Named key or button

spectrwm is very simple in its use. Most of the actions are initiated via key or pointer button bindings. See the BINDINGS section below for defaults and customizations.

spectrwm looks for the user-configuration file in the following order:

  1. $XDG_CONFIG_HOME/spectrwm/spectrwm.conf
  2. ~/.config/spectrwm/spectrwm.conf (if $XDG_CONFIG_HOME is either not set or empty)
  3. ~/.spectrwm.conf.

If the user-configuration file is not found, spectrwm then looks for the global configuration file in the following order:

  1. $XDG_CONFIG_DIRS/spectrwm/spectrwm.conf (each colon-separated directory in $XDG_CONFIG_DIRS)
  2. /etc/xdg/spectrwm/spectrwm.conf (if $XDG_CONFIG_DIRS is either not set or empty)
  3. /etc/spectrwm.conf

The format of the file is

keyword = setting

For example:

color_focus = red

Enabling or disabling an option is done by using 1 or 0 respectively.

Colors need to be specified per the XQueryColor(3) specification. In addition, alpha transparency may be specified via the format rbga:red/green/blue/alpha (8-bit hex values) For example, to specify a 50% transparent blue status bar background:

bar_color = rgba:00/00/ff/7f

Note that a compositing manager is required for alpha transparency.

Mark option values may be wrapped in single/double quotes to prevent whitespace trimming, specify empty strings, etc. Literal quote/backslash characters can be escaped with a backslash ‘\’, when needed.

Comments begin with a #. When a literal ‘#’ is desired in an option, then it must be escaped with a backslash, i.e. \#

The file supports the following keywords:

Launch an application in a specified workspace at start-of-day. Defined in the format ws[idx]:application, e.g. ws[2]:xterm launches an xterm(1) in workspace 2. Specify ‘ws[-1]’ to launch applications such as desktop managers and panels in free mode to keep them always mapped.

Note that libswmhack.so is required for "spawn-in-workspace" behavior. See the SWMHACK section below for more information, tips, and workarounds if a program fails to spawn in the specified workspace.

External script that populates additional information in the status bar, such as battery life.
Process bar_format character sequences in bar_action output; default is 0.
Place the statusbar at the bottom of each region instead of the top. Default is 0.
[x]
Border color of status bar(s) on screen number x. Default is rgb:00/80/80.
[x]
Border color of a status bar for a focused region on screen number x when a workspace-free window is focused. Default is rgb:80/80/00.
[x]
Border color of status bar(s) for unfocused region(s) on screen number x. Default is rgb:00/40/40.
Set status bar border thickness in pixels. Disable border by setting to 0.
[x]
Background color of status bar(s) on screen number x.

A comma separated list of up to 10 colors can be specified. The first value is used as the default background color. Any of these colors can then be selected as a background color in the status bar through the use of the markup sequence +@bg=n; where n is between 0 and 9.

[x]
Background color of a status bar for a focused region on screen number x when a workspace-free window is focused.

A comma separated list of up to 10 colors can be specified, with the same syntax and behavior as bar_color. Default is rgb:40/40/00.

[x]
Background color for selected menu items on screen number x. Defaults to the value of bar_border.
[x]
Background color of status bar(s) for unfocused region(s) on screen number x.

A comma separated list of up to 10 colors can be specified, with the same syntax and behavior as bar_color for unfocused bar(s). Defaults to the value of bar_color.

Set default bar_toggle state; default is 1.
[x]
Set default bar_toggle_ws state on workspace x; default is 1.
Fonts used in the status bar. Either Xft or X Logical Font Description (XLFD) may be used to specify fonts. Fallback fonts may be specified by separating each font with a comma. If all entries are in XLFD syntax, font set will be used. If at least one entry is Xft, Xft will be used.

The default is to use font set.

If Xft is used, a comma-separated list of up to 10 fonts can be specified. The first entry is the default font. Any font defined here can then be selected in the status bar through the use of the markup sequence +@fn=n; where n is between 0 and 9.

Also note that dmenu(1) prior to 4.6 does not support Xft fonts.

Xft examples:

bar_font = Terminus:style=Regular:pixelsize=14:antialias=true

bar_font = -*-profont-medium-*-*-*-11-*-*-*-*-*-*-*,Terminus:pixelsize=14,-*-clean-medium-*-*-*-12-*-*-*-*-*-*-*

Font set examples:

bar_font = -*-terminus-medium-*-*-*-14-*-*-*-*-*-*-*

bar_font = -*-profont-medium-*-*-*-11-*-*-*-*-*-*-*,-*-terminus-medium-*-*-*-14-*-*-*-*-*-*-*,-*-clean-medium-*-*-*-12-*-*-*-*-*-*-*

To list the available fonts in your system see fc-list(1) or xlsfonts(1) manpages. The xfontsel(1) application can help with the XLFD setting.

[x]
Foreground color of the status bar(s) on screen number x.

A comma separated list of up to 10 colors can be specified. The first value is used as the default foreground color. Any of these colors can then be selected as a foreground color in the status bar through the use of the markup sequence +@fg=n; where n is between 0 and 9.

[x]
Foreground color of status bar(s) for unfocused region(s) on screen number x.

A comma separated list of up to 10 colors can be specified, with the same syntax and behavior as bar_font_color for unfocused bar(s). Defaults to the value of bar_font_color.

[x]
Foreground color for selected menu items on screen number x. Defaults to the value of bar_color.
Specify a font to override the Unicode Private Use Area code points (U+E000 -> U+F8FF, U+F0000 -> U+FFFFD, U+100000 -> U+10FFFD). Some fonts use these code points to provide special icon glyphs. Available only with Xft fonts.
Set the bar format string, overriding clock_format and all of the enabled options. The format is passed through strftime(3) before being used. It may contain the following character sequences:
Pad with a space
Output of the external script
Window class (from WM_CLASS)
Workspace name
Focus status indicator
Workspace index
Workspace list indicator
Number of iconic (minimized) windows in workspace
Screen number
Window class and instance separated by a colon
Region index
Stacking algorithm
Window instance (from WM_CLASS)
Urgency hint
Program version
Number of windows in workspace
Window name (from _NET_WM_NAME/WM_NAME)
Begin new section and reset markup sequence effects.

weight is a positive integer used to allocate horizontal space between 'L', 'C' and 'R' sections (see justify). The default weight is 1.

justify can have the value L, C, R or T. L, C, R are for left, center and right justified sections respectively. A 'T' section will limit its space usage to fit to the text. If no value is specified for a given section, the setting from bar_justify is used.

A literal ‘+
Prefix for text markup sequences

The currently recognized text markup sequences are:

Selects font n (from 0 to 9) from bar_font.
Selects foreground color n (from 0 to 9) from bar_font_color.
Selects background color n (from 0 to 9) from bar_color.
Stops the interpretation of markup sequences. Any markup sequence found after +@stp will appear as normal characters in the status bar.

Note that markup sequences in bar_action script output will only be processed if bar_action_expand is enabled.

All character sequences may limit its output to a specific length, for example +64A. By default, no padding/alignment is done in case the length of the replaced string is less than the specified length (64 in the example). The padding/alignment can be enabled using a '_' character in the sequence. For example: +_64W, +64_W and +_64_W enable padding before (right alignment), after (left alignment), and both before and after (center alignment) window name, respectively. Any characters that do not match the specification are copied as-is.

Justify the status bar text. Possible values are left, center, and right.

Note that if the output is not left justified, it may not be properly aligned in some circumstances, due to the white-spaces in the default static format. See the bar_format option for more details.

[x]
Bind key or button combo to action x. See the BINDINGS section below.
Set window border thickness in pixels. Disable all borders by setting to 0.
Set region containment boundary width in pixels. This is how far a window must be dragged/resized (with the pointer) beyond the region edge before it is allowed outside the region. Disable the window containment effect by setting to 0.
Change the key used as an alternative means of terminating move/resize operations. Default is Escape.

See the BINDINGS section below for details on how to find key names.

Enable or disable raising stacking priority when clicking on a window. Default is 1.
Enable or disable displaying the clock in the status bar. Disable by setting to 0 so a custom clock could be used in the bar_action script.
Border color of the currently focused window that is in free mode. Default is yellow.
Border color of the currently focused maximized window that is in free mode. Defaults to the value of color_focus_free.
Border color of unfocused windows that are in free mode, default is rgb:88/88/00.
Border color of unfocused maximized windows that are in free mode. Defaults to the value of color_unfocus_free.
Border color of the currently focused window. Default is red.
Border color of the currently focused, maximized window. Defaults to the value of color_focus.
Border color of unfocused windows, default is rgb:88/88/88.
Border color of unfocused, maximized windows. Defaults to the value of color_unfocus.
Include workspaces that are mapped when switching with ws_next, ws_prev, ws_next_all, ws_prev_all, ws_next_move, or ws_prev_move. Enable by setting to 1.

Note that mapped workspaces will be swapped unless workspace_clamp is enabled. If warp_focus is also enabled, focus will go to the region where the workspace is mapped.

Some applications have dialogue windows that are too small to be useful. This ratio adjusts the window/region size ratio for transient windows having the TRANSSZ quirk. For example, 0.6 is 60% of the the monitor size if the current region spans the monitor.
Remove border when bar is disabled and there is only one window on the region. Enable by setting to 1. Setting this to always removes the border regardless of the bar being enabled/disabled. Defaults to 0.
Window to put focus when the focused window is closed. Possible values are first, next, previous (default), last and prior. next and previous are relative to the window that is closed. prior is the last focused window in the workspace.
Whether to allow the focus to jump to the last window when the first window is closed or vice versa. Disable by setting to 0.
Window to put focus when no window has been focused. Possible values are first and last (default).
Set the bar_format focus status indicator (+F) string to substitute when no window is focused. Default is ''.
Set the bar_format focus status indicator (+F) string to substitute when a normal (not floating, maximized or free) window is focused. Default is ''.
Set the bar_format focus status indicator (+F) string to substitute when a floating window is focused. Default is '(f)'.
Set the bar_format focus status indicator (+F) string to substitute when a window that is in free mode is focused. Default is '(*)'.
Set the bar_format focus status indicator (+F) string to substitute when a maximized window is focused. Default is '(m)'.
Window focus behavior with respect to the pointer. Possible values:

default
Set window focus on border crossings caused by cursor motion and window interaction.
follow
Set window focus on all cursor border crossings, including workspace switches and changes to layout.
manual
Set window focus on window interaction only.
When a fullscreen window is focused and not in below state, hide unrelated windows in the same workspace. Useful for transparent windows. Defaults to 0.
Set handling when a fullscreen window loses focus. Possible values:

none
Leave window fullscreen. (default)
restore
Exit fullscreen.
iconify
Minimize/hide the window.
float
Exit fullscreen and float window.
below
Set below state on the window.
quick_below
Set below state on the window, unset when refocused.

Note that this option is ignored in max layout.

Display the number of iconic (minimized) windows in the status bar. Enable by setting to 1.
Clear all key bindings (not button bindings) and load new bindings from the specified file. This allows you to load pre-defined key bindings for your keyboard layout. See the KEYBOARD MAPPING FILES section below for a list of keyboard mapping files that have been provided for several keyboard layouts.

Note that /dev/null can be specified if you only want to clear bindings.

Select layout to use at start-of-day. Defined in the format ws[idx]:master_grow:master_add:stack_inc:always_raise:stack_mode, e.g. ws[2]:-4:0:1:0:horizontal sets workspace 2 to the horizontal stack mode, shrinks the master area by 4 ticks and adds one window to the stack, while maintaining default floating window behavior. Possible stack_mode values are vertical, vertical_flip, horizontal, horizontal_flip, max and floating.

See master_grow, master_shrink, master_add, master_del, stack_inc, stack_dec, stack_balance, and always_raise for more information. Note that the stacking options are complicated and have side-effects. One should familiarize oneself with these commands before experimenting with the layout option.

This setting is not retained at restart.

Automatically maximize windows in max layout. Note that automatic maximize behavior is disabled for windows that are unmaximized in max layout. Maximizing the window or resetting the layout with stack_reset enables it again. Enabled by default. Disable by setting to 0.
When set to 1, maximize_toggle will also hide/restore the bar visibility of the affected workspace. Defaults to 0.
When a maximized window is focused and not in below state, hide unrelated windows in the same workspace. Useful for transparent windows. Defaults to 0.
Set handling when a maximized window loses focus. Possible values:

none
Leave window maximized.
restore
Unmaximize window. (default)
iconify
Minimize/hide the window.
float
Unmaximize and float window.
below
Set below state on the window.
quick_below
Set below state on the window, unset when refocused.

Note that this option is ignored in max layout.

Change the current modifier value of MOD in bind entries that come later in the configuration file. For existing bindings, the new value is substituted for the previous value. Possible values are Mod1 (default), Mod2, Mod3, Mod4 and Mod5.

Mod1 is generally the Alt key, Mod2 is the Command key on macOS and Mod4 is the Windows key on a PC. The current modifier key mapping can be found by running xmodmap(1).

Set the name of a workspace at start-of-day. Defined in the format ws[idx]:name, e.g. ws[1]:Console sets the name of workspace 1 to “Console”.
[p]
Define new action to spawn a program p. See the PROGRAMS section below.
[c[:i[:n]]]
Add "quirk" for windows with class c, instance i (optional) and name n (optional). See the QUIRKS section below.
Allocates a custom region, removing any autodetected regions that occupy the same space on the specified logical X screen number. Defined in the format screen[idx]:widthxheight+x+y[,rotation], e.g. screen[1]:800x1200+0+0 or screen[1]:800x1200+0+0,inverted (with optional rotation).

To make a region span multiple monitors, create a region big enough to cover them all, e.g. screen[1]:2048x768+0+0 makes the region span two monitors with 1024x768 resolution sitting one next to the other.

Possible values for the optional rotation argument are normal (default), left, inverted and right. Note that rotation is used by workspace_autorotate.

Pixel width of empty space within region borders. Disable by setting to 0.
Set the distance in pixels a tiled/maximized window must be moved (with the pointer) to unsnap and float freely. Set to 0 to unsnap immediately. Default is 25.
Position in stack to place newly spawned windows. Possible values are first, next, previous and last (default). next and previous are relative to the focused window.
Enable or disable displaying the current stacking algorithm in the status bar.
Set the floating layout mark for the bar_format stacking indicator (+S). Default is '[~]'.
Set the horizontal layout mark for the bar_format stacking indicator (+S). Default is '[-]'.
Set the horizontal_flip layout mark for the bar_format stacking indicator (+S). Default is '[v]'.
Set the max layout mark for the bar_format stacking indicator (+S). Default is '[ ]'.
Set the vertical layout mark for the bar_format stacking indicator (+S). Default is '[|]'.
Set the vertical_flip layout mark for the bar_format stacking indicator (+S). Default is '[>]'.
Set a preferred minimum width for the terminal. If this value is greater than 0, spectrwm will attempt to adjust the font sizes in the terminal to keep the terminal width above this number as the window is resized. Only xterm(1) is currently supported. The xterm(1) binary must not be setuid or setgid, which it is by default on most systems. Users may need to set program[term] (see the PROGRAMS section) to use an alternate copy of the xterm(1) binary without the setgid bit set.
Pixel width of empty space between tiled windows. Negative values cause overlap. Set this to the opposite of border_width to collapse the border between tiles. Disable by setting to 0.
Minimizes the space consumed by the urgency hint indicator by removing the placeholders for non-urgent workspaces, the trailing space when there are urgent windows and the default leading space. Enable by setting to 1.
Enable or disable the urgency hint indicator in the status bar. Note that many terminal emulators require an explicit setting for the bell character to trigger urgency on the window. In xterm(1), for example, one needs to add the following line to .Xdefaults:
xterm.bellIsUrgent: true
Enable or disable displaying the current master window count and stack column/row count in the status bar. Enable by setting to 1. See master_add, master_del, stack_inc and stack_dec for more information.
Focus on the target window/workspace/region when clamped. For example, when attempting to switch to a workspace that is mapped on another region and workspace_clamp is enabled, focus on the region with the target workspace. Enable by setting to 1.
Centers the pointer on the focused window when using bindings to change focus, switch workspaces, change regions, etc. Enable by setting to 1.
Enable or disable displaying the window class name (from WM_CLASS) in the status bar. Enable by setting to 1.
Enable or disable displaying the window instance name (from WM_CLASS) in the status bar. Enable by setting to 1.
Enable or disable displaying the window display name (from _NET_WM_NAME/WM_NAME) in the status bar. Enable by setting to 1.

To prevent excessively large window names from pushing the remaining text off the bar, it is limited to 64 characters, by default. See the bar_format option for more details.

When moving workspaces across regions, auto-rotate vertical/horizontal layouts based on rotation data from xrandr(1). Enable by setting to 1.
Prevents workspaces from being swapped when attempting to switch to a workspace that is mapped to another region. Use warp_focus if you want to focus on the region containing the workspace and warp_pointer if you want to also send the pointer. Enable by setting to 1.
Configure the status bar workspace indicator. One or more of the following options may be specified in a comma-separated list:

listcurrent
Include the current workspace.
listactive
Include workspaces with windows.
listempty
Include empty workspaces.
listnamed
Include named workspaces.
listurgent
Include workspaces with urgent window(s).
listall
Include all workspaces.
hidecurrent
Always exclude the current workspace from the list.
markcurrent
Indicate the current workspace if it is in the list.
markactive
Indicate workspaces in the list that are active.
markempty
Indicate workspaces in the list that are empty.
markurgent
Indicate workspaces in the list that contain urgent window(s).
printnames
Display the names of named workspaces in the list.
noindexes
Hide the index of the workspaces.

The default is listcurrent,listactive,markcurrent,printnames

Note that markup sequences can be used to style the workspace indicator. For example, to change the color of the current workspace:

workspace_mark_current = '+@fg=1;'
workspace_mark_current_suffix = '+@fg=0;'
Set the total number of workspaces available. Minimum is 1, maximum is 22, default is 10.
Set the string inserted before active workspaces in the workspace_indicator. Default is '^'.
Set the string inserted after active workspaces in the workspace_indicator. Default is '' (empty string).
Set the string inserted before the current workspace in the workspace_indicator. Default is '*'.
Set the string inserted after the current workspace in the workspace_indicator. Default is '' (empty string).
Set the string inserted before empty workspaces in the workspace_indicator. Default is '-'.
Set the string inserted after empty workspaces in the workspace_indicator. Default is '' (empty string).
Set the string inserted before urgent workspaces in the workspace_indicator. Default is '!'.
Set the string inserted after urgent workspaces in the workspace_indicator. Default is '' (empty string).

Master area is on the left and stack area is on the right. Additional windows are vertically tiled in stack area.
vertical flipped
Same as above but stack and master areas are swapped.
Master area is on the top and stack area is on the bottom. Additional windows are horizontally tiled in stack area.
horizontal flipped
Same as above but stack and master areas are swapped.
The focused window occupies the whole region, except for the bar (if enabled).
Windows are untiled and can be resized and positioned.

These can be set/unset by the corresponding toggle actions listed in the BINDINGS section below.

The window is stacked above others and is not in a tile; it may be freely resized and positioned.
The window is floating, but stacked below others.
The window occupies the work area of the region (area that excludes space reserved by the bar, docks/panels, etc.) By default, focusing another window removes the maximized state of the window. See maximized_unfocus to configure unfocused behavior.
The window occupies the whole region. By default, focusing another window does not remove the fullscreen state of the window. See fullscreen_unfocus to configure unfocused behavior.
The window is floating, but not bound by regions, workspaces or their layouts. It is always mapped, unless iconified, and may be resized and positioned anywhere.

spectrwm allows you to define custom actions to launch programs of your choice and then bind them the same as with built-in actions. See the BINDINGS section below.

Custom programs in the configuration file are specified as follows:

program[action] = progpath [arg [arg ...]]

action is any identifier that does not conflict with a built-in action or keyword, progpath is the desired program, and arg is zero or more arguments to the program.

With the exception of '~' expansion, program calls are executed as-is without any interpretation. A shell can be called to execute shell commands. (e.g. sh -c 'command string').

Remember that when using ‘#’ in your program call, it must be escaped with a backslash, i.e. \#

The following argument variables are replaced with values at the time the program is spawned:

Example:

program[ff] = /usr/local/bin/firefox http://spectrwm.org/
bind[ff] = MOD+Shift+b # Now M-S-b launches firefox

To cancel the previous, unbind it:

bind[] = MOD+Shift+b

Default programs:

xterm
xlock
dmenu_run $dmenu_bottom -fn $bar_font -nb $bar_color -nf $bar_font_color -sb $bar_color_selected -sf $bar_font_color_selected
dmenu $dmenu_bottom -i -fn $bar_font -nb $bar_color -nf $bar_font_color -sb $bar_color_selected -sf $bar_font_color_selected
dmenu $dmenu_bottom -p Workspace -fn $bar_font -nb $bar_color -nf $bar_font_color -sb $bar_color_selected -sf $bar_font_color_selected
initscreen.sh # optional
screenshot.sh full # optional
screenshot.sh window # optional

Note that optional default programs will not be validated unless overridden. If a default program fails validation, you can resolve the exception by installing the program, modifying the program call or disabling the program by freeing the respective binding.

For example, to override lock:

program[lock] = xscreensaver-command -lock

To unbind lock and prevent it from being validated:

bind[] = MOD+Shift+Delete

Note that when a program is spawned, spectrwm aims to place its windows in its spawn workspace. See the SWMHACK section below for more information, tips, and workarounds if a program fails to spawn in the correct workspace.

spectrwm provides many functions (or actions) accessed via key or pointer button bindings.

The default bindings are listed below:

Button1
focus
Button1
move
Button3
resize
Button3
resize_centered
Return
term
menu
quit
restart
unbound
restart_of_day
Space
cycle_layout
M-S-\
flip_layout
unbound
prior_layout
unbound
layout_vertical
unbound
layout_horizontal
unbound
layout_max
unbound
layout_floating
Space
stack_reset
unbound
stack_balance
master_shrink
master_grow
master_add
master_del
stack_inc
stack_dec
Return
swap_main
, M-TAB
focus_next
, M-S-TAB
focus_prev
focus_main
M-`
focus_free
focus_prior
focus_urgent
swap_next
swap_prev
bar_toggle
bar_toggle_ws
wind_del
wind_kill
1-9,0,F1-F12
ws_⟨1-22
1-9,0,F1-F12
mvws_⟨1-22
Keypad 1-9
rg_⟨1-9
Keypad 1-9
mvrg_⟨1-9
unbound
mvrg_next
unbound
mvrg_prev
unbound
ws_empty
unbound
ws_empty_move
Right
ws_next
Left
ws_prev
Up
ws_next_all
Down
ws_prev_all
ws_prior
Down
ws_prev_move
Up
ws_next_move
Right
rg_next
Left
rg_prev
unbound
rg_move_next
unbound
rg_move_prev
screenshot_all
screenshot_wind
version
float_toggle
below_toggle
M-S-`
free_toggle
Delete
lock
initscr
iconify
uniconify
maximize_toggle
fullscreen_toggle
raise
always_raise
button2
width_shrink
width_grow
height_shrink
height_grow
move_left
move_right
move_up
move_down
name_workspace
search_workspace
search_win
debug_toggle (debug mode only)
dumpwins (debug mode only)

The action names and descriptions are listed below:

Focus window/region under pointer.
Move window with pointer while binding is pressed.
Resize window with pointer while binding is pressed.
Same as resize but keep window centered.
Spawn a new terminal (see PROGRAMS above).
Menu (see PROGRAMS above).
Quit spectrwm.
Restart spectrwm.
Same as restart but configuration file is loaded in full.
Switch to the next layout.
Swap the master and stacking areas.
Switch to the last used layout.
Switch to vertical layout.
Switch to horizontal layout.
Switch to max layout.
Switch to floating layout.
Reset layout.
Balance master/stacking area.
Shrink master area.
Grow master area.
Add windows to master area.
Remove windows from master area.
Add columns/rows to stacking area.
Remove columns/rows from stacking area.
Move current window to master area.
Focus next window in workspace.
Focus previous window in workspace.
Focus on main window in workspace.
Focus last focused window in workspace.
Focus on a window in free mode, if any.
Focus on next window with the urgency hint flag set. The workspace is switched if needed.
Swap with next window in workspace.
Swap with previous window in workspace.
Toggle overall visibility of status bars.
Toggle status bar on current workspace.
Delete current window.
Kill the program that created the current window.
n
Switch to workspace n, where n is 1 through workspace_limit.
n
Move current window to workspace n, where n is 1 through workspace_limit.
n
Focus on region n, where n is 1 through 9.
n
Move current window to region n, where n is 1 through 9.
Move current window to workspace in next region.
Move current window to workspace in previous region.
Switch to the first empty workspace.
Switch to the first empty workspace and move current window.
Switch to next workspace with a window in it.
Switch to previous workspace with a window in it.
Switch to next workspace.
Switch to previous workspace.
Switch to next workspace with the current window.
Switch to previous workspace with the current window.
Switch to last visited workspace.
Switch to next region.
Switch to previous region.
Switch to next region and move current workspace.
Switch to previous region and move current workspace.
Take screenshot of entire screen (if enabled) (see PROGRAMS above).
Take screenshot of selected window (if enabled) (see PROGRAMS above).
Toggle version in status bar.
Toggle focused window between tiled and floating.
Toggle below state on current window.
Toggle focused window between workspace mode and free mode.
Lock screen (see PROGRAMS above).
Reinitialize physical screens (see PROGRAMS above).
Minimize (unmap) currently focused window.
Restore (map) window returned by dmenu(1) selection.
Toggle maximization of focused window.
Toggle fullscreen state of focused window.
Raise the current window.
When set tiled windows are allowed to obscure floating windows.
Fake a middle mouse button click (Button2).
Shrink the width of a floating window.
Grow the width of a floating window.
Shrink the height of a floating window.
Grow the height of a floating window.
Move a floating window a step to the left.
Move a floating window a step to the right.
Move a floating window a step upwards.
Move a floating window a step downwards.
Name the current workspace.
Search for a workspace.
Search the windows in the current workspace.
Toggles debug overlay. (debug mode only)
Dump current window/focus/stacking state to debug log. (debug mode only)

Custom bindings in the configuration file are specified as follows:

bind[action] = combo

action is one of the actions listed above (or empty to unbind) and combo is in the form of zero or more modifier keys and/or special arguments (Mod1, Shift, Control, MOD, etc.) and a normal key (b, Space, etc) or a button (Button1 .. Button255), separated by ‘+’. Multiple key/button combinations may be bound to the same action.

Special arguments:

Substituted for the currently defined modkey.
Select all modifier combinations not handled by another binding.
Reprocess binding press/release events for other programs to handle. Unavailable for move, resize and resize_centered.

MOD example:

bind[reset] = Mod4+q # bind Windows-key + q to reset
bind[] = Mod1+q # unbind Alt + q
bind[move] = MOD+Button3 # Bind move to M-Button3
bind[] = MOD+Button1 # Unbind default move binding.

ANYMOD example:

bind[focus] = ANYMOD+Button3
bind[move] = MOD+Button3

In the above example, M-Button3⟩ initiates move and ⟨Button3⟩ pressed with any other combination of modifiers sets focus to the window/region under the pointer.

REPLAY example:

bind[focus] = REPLAY+Button3

In the above example, when ⟨Button3⟩ is pressed without any modifier(s), focus is set to the window under the pointer and the button press is passed to the window.

To bind non-latin characters such as å or π you must enter the xkb character name instead of the character itself. Run xev(1), focus the window and press the specific key and in the terminal output read the symbol name. In the following example for å:

KeyPress event, serial 41, synthetic NO, window 0x2600001,
    root 0x15a, subw 0x0, time 106213808, (11,5), root:(359,823),
    state 0x0, keycode 24 (keysym 0xe5, aring), same_screen YES,
    XLookupString gives 2 bytes: (c3 a5) "å"
    XmbLookupString gives 2 bytes: (c3 a5) "å"
    XFilterEvent returns: False

The xkb name is aring. In other words, in spectrwm.conf add:

bind[program] = MOD+aring

To clear all default keyboard bindings and specify your own, see the keyboard_mapping option.

Keyboard mapping files for several keyboard layouts are listed below. These files can be used with the keyboard_mapping setting to load pre-defined key bindings for the specified keyboard layout.

Czech Republic keyboard layout
Spanish keyboard layout
French keyboard layout
Swiss French keyboard layout
Swedish keyboard layout
United States keyboard layout

spectrwm provides "quirks" which handle windows that must be treated specially in a tiling window manager, such as some dialogs and fullscreen apps.

The default quirks are described below:

Firefox-bin:firefox-bin
TRANSSZ
Firefox:Dialog
FLOAT
Gimp:gimp
FLOAT + ANYWHERE
MPlayer:xv
FLOAT + FULLSCREEN + FOCUSPREV
OpenOffice.org 2.4:VCLSalFrame
FLOAT
OpenOffice.org 3.1:VCLSalFrame
FLOAT
pcb:pcb
FLOAT
xine:Xine Window
FLOAT + ANYWHERE
xine:xine Panel
FLOAT + ANYWHERE
xine:xine Video Fullscreen Window
FULLSCREEN + FLOAT
Xitk:Xitk Combo
FLOAT + ANYWHERE
Xitk:Xine Window
FLOAT + ANYWHERE
XTerm:xterm
XTERM_FONTADJ

The quirks themselves are described below:

ANYWHERE
Allow window to position itself, uncentered.
FLOAT
This window should not be tiled, but allowed to float freely.
FOCUSONMAP_SINGLE
When the window first appears on the screen, change focus to the window if there are no other windows on the workspace with the same WM_CLASS class/instance value. Has no effect when focus_mode is set to follow.
FOCUSPREV
On exit force focus on previously focused application not previous application in the stack.
FULLSCREEN
Remove border to allow window to use full region size.
IGNOREPID
Ignore the PID when determining the initial workspace for a new window. Especially useful for terminal windows that share a process.
IGNORESPAWNWS
Ignore the spawn workspace when determining the initial workspace for a new window.
MINIMALBORDER
Remove border when window is unfocused and floating.
NOFOCUSCYCLE
Remove from normal focus cycle (focus_prev or focus_next). The window can still be focused using search_win.
NOFOCUSONMAP
Do not change focus to the window when it first appears on the screen. Has no effect when focus_mode is set to follow.
OBEYAPPFOCUSREQ
When an application requests focus on the window via a _NET_ACTIVE_WINDOW client message (source indication of 1), comply with the request. Note that a source indication of 0 (unspecified) or 2 (pager) are always obeyed.
TRANSSZ
Adjusts size on transient windows that are too small using dialog_ratio (see CONFIGURATION FILES).
WS[n]
Force a new window to appear on workspace n. Specify -1 to put the window into free mode so that it is mapped independent of workspaces and regions.
XTERM_FONTADJ
Adjust xterm(1) fonts when resizing.

Custom quirks in the configuration file are specified as follows:

quirk[class[:instance[:name]]] = quirk [+ quirk ...]

class, instance (optional) and name (optional) are patterns used to determine which window(s) the quirk(s) apply to and quirk is one of the quirks from the list above.

Note that patterns are interpreted as POSIX Extended Regular Expressions. Any ':', '[' or ']' must be escaped with '\'. See regex(7) for more information on POSIX Extended Regular Expressions.

For example:

quirk[MPlayer] = FLOAT + FULLSCREEN + FOCUSPREV # Float all windows having a class of 'MPlayer'
quirk[.*] = FLOAT # Float all windows by default.
quirk[.*:.*:.*] = FLOAT # Same as above.
quirk[Firefox:Navigator] = FLOAT # Float all Firefox browser windows.
quirk[::Console] = FLOAT # Float windows with WM_CLASS not set and a window name of 'Console'.
quirk[\[0-9\].*:.*:\[\[\:alnum\:\]\]*] = FLOAT # Float windows with WM_CLASS class beginning with a number, any WM_CLASS instance and a _NET_WM_NAME/WM_NAME either blank or containing alphanumeric characters without spaces.
quirk[pcb:pcb] = NONE # remove existing quirk

You can obtain class, instance and name by running xprop(1) and then clicking on the desired window. In the following example the main window of Firefox was clicked:

$ xprop | grep -E "^(WM_CLASS|_NET_WM_NAME|WM_NAME)"
WM_CLASS(STRING) = "Navigator", "Firefox"
WM_NAME(STRING) = "spectrwm - ConformalOpenSource"
_NET_WM_NAME(UTF8_STRING) = "spectrwm - ConformalOpenSource"

Note that xprop(1) displays WM_CLASS as:

WM_CLASS(STRING) = "<instance>", "<class>"

In the example above the quirk entry would be:

quirk[Firefox:Navigator] = FLOAT

spectrwm also automatically assigns quirks to windows based on the value of the window's _NET_WM_WINDOW_TYPE property as follows:

_NET_WM_WINDOW_TYPE_TOOLBAR
FLOAT + ANYWHERE
_NET_WM_WINDOW_TYPE_UTILITY
FLOAT + ANYWHERE
_NET_WM_WINDOW_TYPE_SPLASH
FLOAT
_NET_WM_WINDOW_TYPE_DIALOG
FLOAT

In all other cases, no automatic quirks are assigned to the window. Quirks specified in the configuration file override the automatic quirks.

spectrwm partially implements the Extended Window Manager Hints (EWMH) specification. This enables controlling windows as well as spectrwm itself from external scripts and programs. This is achieved by spectrwm responding to certain ClientMessage events. From the terminal these events can be conveniently sent using tools such as wmctrl(1) and xdotool(1). For the actual format of these ClientMessage events, see the EWMH specification.

The id of the currently focused window is stored in the _NET_ACTIVE_WINDOW property of the root window. This can be used for example to retrieve the title of the currently active window with xprop(1) and grep(1):

$ WINDOWID=`xprop -root _NET_ACTIVE_WINDOW | grep -o "0x.*"`
$ xprop -id $WINDOWID _NET_WM_NAME | grep -o "\".*\""

A window can be focused by sending a _NET_ACTIVE_WINDOW client message to the root window. For example, using wmctrl(1) to send the message (assuming 0x4a0000b is the id of the window to be focused):

$ wmctrl -i -a 0x4a0000b

Windows can be closed by sending a _NET_CLOSE_WINDOW client message to the root window. For example, using wmctrl(1) to send the message (assuming 0x4a0000b is the id of the window to be closed):

$ wmctrl -i -c 0x4a0000b

Windows can be floated and un-floated by adding or removing the _NET_WM_STATE_ABOVE atom from the _NET_WM_STATE property of the window. This can be achieved by sending a _NET_WM_STATE client message to the root window. For example, the following toggles the floating state of a window using wmctrl(1) to send the message (assuming 0x4a0000b is the id of the window to be floated or un-floated):

$ wmctrl -i -r 0x4a0000b -b toggle,above

Windows can also be iconified and un-iconified by substituting _NET_WM_STATE_HIDDEN for _NET_WM_STATE_ABOVE in the previous example:

$ wmctrl -i -r 0x4a0000b -b toggle,hidden

Floating windows can also be resized and moved by sending a _NET_MOVERESIZE_WINDOW client message to the root window. For example, using wmctrl(1) to send the message (assuming 0x4a0000b is the id of the window to be resize/moved):

$ wmctrl -i -r 0x4a0000b -e 0,100,50,640,480

This moves the window to (100,50) and resizes it to 640x480.

Any _NET_MOVERESIZE_WINDOW events received for stacked windows are ignored.

When spawning a program via autorun or a binding, spectrwm aims to place the program's windows (if any) in its spawn workspace. To accomplish this "spawn-in-workspace" behavior, spectrwm must determine the intended spawn workspace when managing a new window. Since it cannot be done with X11 alone, libswmhack.so is included to make this feature possible.

When a program is spawned, spectrwm automatically sets LD_PRELOAD and _SWM_WS in the program's spawn environment to enable libswmhack.so when it is executed. Note that LD_PRELOAD is the path to libswmhack.so and _SWM_WS is the spawn workspace for any windows created by the program.

When running programs from terminals, scripts, etc, the inherited environment may need to be configured. It is possible to override the spawn workspace by setting _SWM_WS to a different value. Alternatively, _SWM_WS can be unset(1) or set to a blank value to disable "spawn-in-workspace" behavior. Note that workspaces are counted from 0. ‘-1’ can be specified to put windows into workspace-free mode.

For example, to play a video with mpv(1) on workspace 10 without changing the spawn workspace in the environment:

$ _SWM_WS=9 mpv video.mkv

Play the video in free mode so that it remains mapped when switching workspaces.

$ _SWM_WS=-1 mpv video.mkv

Disable "spawn-in-workspace" in the environment so that new windows map on whichever workspace happens to be focused.

$ unset _SWM_WS

Change the environment to spawn programs in free mode.

$ export _SWM_WS=-1

When spawning a program that creates windows via a daemon, ensure the daemon is started with the correct LD_PRELOAD in its environment.

For example, when starting urxvtd(1) via xinit(1), LD_PRELOAD must be specified.

LD_PRELOAD=/usr/lib/libswmhack.so.0.0 urxvtd -q -o -f

Note that some operating systems may ignore LD_PRELOAD if certain conditions are not met. It is advised to check the man page of ld.so.

In situations where libswmhack.so cannot be used, it is possible to use a quirk to spawn a program in a specific workspace.

e.g. launch an xterm(1) in workspace 2 on startup:

autorun = ws[2]:xterm -name ws2
quirk[XTerm:ws2] = WS[2]

Note that XCB programs are currently unsupported by libswmhack.so.

Sending spectrwm a HUP signal will restart it.

~/.spectrwm.conf
spectrwm user specific settings.
/etc/spectrwm.conf
spectrwm global settings.

spectrwm was inspired by xmonad & dwm.

spectrwm was written by:

Marco Peereboom <marco@peereboom.us>
 
Ryan Thomas McBride <mcbride@countersiege.com>
 
Darrin Chandler <dwchandler@stilyagin.com>
 
Pierre-Yves Ritschard <pyr@spootnik.org>
 
Tuukka Kataja <stuge@xor.fi>
 
Jason L. Wright <jason@thought.net>
 
Reginald Kennedy <rk@rejii.com>
 
Lawrence Teo <lteo@lteo.net>
 
Tiago Cunha <tcunha@gmx.com>
 
David Hill <dhill@mindcry.org>
 
November 25, 2023
spectrwm-SPECTRWM_3_5_1/spectrwm_cz.conf000066400000000000000000000077611453034271100202740ustar00rootroot00000000000000# Key bindings for Czech Republic (cz) keyboards # unbind with: bind[] = bind[bar_toggle] = MOD+b bind[bar_toggle_ws] = MOD+Shift+b bind[below_toggle] = MOD+Shift+t bind[button2] = MOD+v bind[cycle_layout] = MOD+space bind[flip_layout] = MOD+Shift+backslash bind[float_toggle] = MOD+t bind[focus_free] = MOD+semicolon bind[focus_main] = MOD+m bind[focus_next] = MOD+j bind[focus_next] = MOD+Tab bind[focus_prev] = MOD+k bind[focus_prev] = MOD+Shift+Tab bind[focus_prior] = MOD+Shift+a bind[focus_urgent] = MOD+u bind[free_toggle] = MOD+Shift+semicolon bind[fullscreen_toggle] = MOD+Shift+e bind[height_grow] = MOD+Shift+dead_acute bind[height_shrink] = MOD+Shift+equal bind[iconify] = MOD+w bind[initscr] = MOD+Shift+i #bind[layout_floating] = #bind[layout_horizontal] = #bind[layout_max] = #bind[layout_vertical] = bind[lock] = MOD+Shift+Delete bind[master_add] = MOD+comma bind[master_del] = MOD+period bind[master_grow] = MOD+l bind[master_shrink] = MOD+h bind[maximize_toggle] = MOD+e bind[menu] = MOD+p bind[move_down] = MOD+Shift+parenright bind[move_left] = MOD+uacute bind[move_right] = MOD+parenright bind[move_up] = MOD+Shift+uacute bind[mvrg_1] = MOD+Shift+KP_End bind[mvrg_2] = MOD+Shift+KP_Down bind[mvrg_3] = MOD+Shift+KP_Next bind[mvrg_4] = MOD+Shift+KP_Left bind[mvrg_5] = MOD+Shift+KP_Begin bind[mvrg_6] = MOD+Shift+KP_Right bind[mvrg_7] = MOD+Shift+KP_Home bind[mvrg_8] = MOD+Shift+KP_Up bind[mvrg_9] = MOD+Shift+KP_Prior #bind[mvrg_next] = #bind[mvrg_prev] = bind[mvws_1] = MOD+Shift+plus bind[mvws_2] = MOD+Shift+ecaron bind[mvws_3] = MOD+Shift+scaron bind[mvws_4] = MOD+Shift+ccaron bind[mvws_5] = MOD+Shift+rcaron bind[mvws_6] = MOD+Shift+zcaron bind[mvws_7] = MOD+Shift+yacute bind[mvws_8] = MOD+Shift+aacute bind[mvws_9] = MOD+Shift+iacute bind[mvws_10] = MOD+Shift+eacute bind[mvws_11] = MOD+Shift+F1 bind[mvws_12] = MOD+Shift+F2 bind[mvws_13] = MOD+Shift+F3 bind[mvws_14] = MOD+Shift+F4 bind[mvws_15] = MOD+Shift+F5 bind[mvws_16] = MOD+Shift+F6 bind[mvws_17] = MOD+Shift+F7 bind[mvws_18] = MOD+Shift+F8 bind[mvws_19] = MOD+Shift+F9 bind[mvws_20] = MOD+Shift+F10 bind[mvws_21] = MOD+Shift+F11 bind[mvws_22] = MOD+Shift+F12 bind[name_workspace] = MOD+Shift+slash bind[quit] = MOD+Shift+q bind[raise] = MOD+r bind[raise_toggle] = MOD+Shift+r bind[restart] = MOD+q #bind[restart_of_day] = bind[rg_1] = MOD+KP_End bind[rg_2] = MOD+KP_Down bind[rg_3] = MOD+KP_Next bind[rg_4] = MOD+KP_Left bind[rg_5] = MOD+KP_Begin bind[rg_6] = MOD+KP_Right bind[rg_7] = MOD+KP_Home bind[rg_8] = MOD+KP_Up bind[rg_9] = MOD+KP_Prior #bind[rg_move_next] = #bind[rg_move_prev] = bind[rg_next] = MOD+Shift+Right bind[rg_prev] = MOD+Shift+Left bind[screenshot_all] = MOD+s bind[screenshot_wind] = MOD+Shift+s bind[search_win] = MOD+f bind[search_workspace] = MOD+slash #bind[stack_balance] = bind[stack_dec] = MOD+Shift+period bind[stack_inc] = MOD+Shift+comma bind[stack_reset] = MOD+Shift+space bind[swap_main] = MOD+Return bind[swap_next] = MOD+Shift+j bind[swap_prev] = MOD+Shift+k bind[term] = MOD+Shift+Return bind[uniconify] = MOD+Shift+w bind[version] = MOD+Shift+v bind[width_grow] = MOD+dead_acute bind[width_shrink] = MOD+equal bind[wind_del] = MOD+x bind[wind_kill] = MOD+Shift+x bind[ws_1] = MOD+plus bind[ws_2] = MOD+ecaron bind[ws_3] = MOD+scaron bind[ws_4] = MOD+ccaron bind[ws_5] = MOD+rcaron bind[ws_6] = MOD+zcaron bind[ws_7] = MOD+yacute bind[ws_8] = MOD+aacute bind[ws_9] = MOD+iacute bind[ws_10] = MOD+eacute bind[ws_11] = MOD+F1 bind[ws_12] = MOD+F2 bind[ws_13] = MOD+F3 bind[ws_14] = MOD+F4 bind[ws_15] = MOD+F5 bind[ws_16] = MOD+F6 bind[ws_17] = MOD+F7 bind[ws_18] = MOD+F8 bind[ws_19] = MOD+F9 bind[ws_20] = MOD+F10 bind[ws_21] = MOD+F11 bind[ws_22] = MOD+F12 #bind[ws_empty] = #bind[ws_empty_move] = bind[ws_next] = MOD+Right bind[ws_next_all] = MOD+Up bind[ws_next_move] = MOD+Shift+Up bind[ws_prev] = MOD+Left bind[ws_prev_all] = MOD+Down bind[ws_prev_move] = MOD+Shift+Down bind[ws_prior] = MOD+a bind[debug_toggle] = MOD+d bind[dumpwins] = MOD+Shift+d spectrwm-SPECTRWM_3_5_1/spectrwm_es.conf000066400000000000000000000076201453034271100202610ustar00rootroot00000000000000# Key bindings for Spanish (es) keyboards # unbind with: bind[] = bind[bar_toggle] = MOD+b bind[bar_toggle_ws] = MOD+Shift+b bind[below_toggle] = MOD+Shift+t bind[button2] = MOD+v bind[cycle_layout] = MOD+space bind[flip_layout] = MOD+Shift+backslash bind[float_toggle] = MOD+t bind[focus_free] = MOD+masculine bind[focus_main] = MOD+m bind[focus_next] = MOD+j bind[focus_next] = MOD+Tab bind[focus_prev] = MOD+k bind[focus_prev] = MOD+Shift+Tab bind[focus_prior] = MOD+Shift+a bind[focus_urgent] = MOD+u bind[free_toggle] = MOD+Shift+masculine bind[fullscreen_toggle] = MOD+Shift+e bind[height_grow] = MOD+Shift+exclamdown bind[height_shrink] = MOD+Shift+apostrophe bind[iconify] = MOD+w bind[initscr] = MOD+Shift+i #bind[layout_floating] = #bind[layout_horizontal] = #bind[layout_max] = #bind[layout_vertical] = bind[lock] = MOD+Shift+Delete bind[master_add] = MOD+comma bind[master_del] = MOD+period bind[master_grow] = MOD+l bind[master_shrink] = MOD+h bind[maximize_toggle] = MOD+e bind[menu] = MOD+p bind[move_down] = MOD+Shift+plus bind[move_left] = MOD+dead_grave bind[move_right] = MOD+plus bind[move_up] = MOD+Shift+dead_grave bind[mvrg_1] = MOD+Shift+KP_End bind[mvrg_2] = MOD+Shift+KP_Down bind[mvrg_3] = MOD+Shift+KP_Next bind[mvrg_4] = MOD+Shift+KP_Left bind[mvrg_5] = MOD+Shift+KP_Begin bind[mvrg_6] = MOD+Shift+KP_Right bind[mvrg_7] = MOD+Shift+KP_Home bind[mvrg_8] = MOD+Shift+KP_Up bind[mvrg_9] = MOD+Shift+KP_Prior #bind[mvrg_next] = #bind[mvrg_prev] = bind[mvws_1] = MOD+Shift+1 bind[mvws_2] = MOD+Shift+2 bind[mvws_3] = MOD+Shift+3 bind[mvws_4] = MOD+Shift+4 bind[mvws_5] = MOD+Shift+5 bind[mvws_6] = MOD+Shift+6 bind[mvws_7] = MOD+Shift+7 bind[mvws_8] = MOD+Shift+8 bind[mvws_9] = MOD+Shift+9 bind[mvws_10] = MOD+Shift+0 bind[mvws_11] = MOD+Shift+F1 bind[mvws_12] = MOD+Shift+F2 bind[mvws_13] = MOD+Shift+F3 bind[mvws_14] = MOD+Shift+F4 bind[mvws_15] = MOD+Shift+F5 bind[mvws_16] = MOD+Shift+F6 bind[mvws_17] = MOD+Shift+F7 bind[mvws_18] = MOD+Shift+F8 bind[mvws_19] = MOD+Shift+F9 bind[mvws_20] = MOD+Shift+F10 bind[mvws_21] = MOD+Shift+F11 bind[mvws_22] = MOD+Shift+F12 bind[name_workspace] = MOD+Shift+slash bind[quit] = MOD+Shift+q bind[raise] = MOD+r bind[raise_toggle] = MOD+Shift+r bind[restart] = MOD+q #bind[restart_of_day] = bind[rg_1] = MOD+KP_End bind[rg_2] = MOD+KP_Down bind[rg_3] = MOD+KP_Next bind[rg_4] = MOD+KP_Left bind[rg_5] = MOD+KP_Begin bind[rg_6] = MOD+KP_Right bind[rg_7] = MOD+KP_Home bind[rg_8] = MOD+KP_Up bind[rg_9] = MOD+KP_Prior #bind[rg_move_next] = #bind[rg_move_prev] = bind[rg_next] = MOD+Shift+Right bind[rg_prev] = MOD+Shift+Left bind[screenshot_all] = MOD+s bind[screenshot_wind] = MOD+Shift+s bind[search_win] = MOD+f bind[search_workspace] = MOD+slash #bind[stack_balance] = bind[stack_dec] = MOD+Shift+period bind[stack_inc] = MOD+Shift+comma bind[stack_reset] = MOD+Shift+space bind[swap_main] = MOD+Return bind[swap_next] = MOD+Shift+j bind[swap_prev] = MOD+Shift+k bind[term] = MOD+Shift+Return bind[uniconify] = MOD+Shift+w bind[version] = MOD+Shift+v bind[width_grow] = MOD+exclamdown bind[width_shrink] = MOD+apostrophe bind[wind_del] = MOD+x bind[wind_kill] = MOD+Shift+x bind[ws_1] = MOD+1 bind[ws_2] = MOD+2 bind[ws_3] = MOD+3 bind[ws_4] = MOD+4 bind[ws_5] = MOD+5 bind[ws_6] = MOD+6 bind[ws_7] = MOD+7 bind[ws_8] = MOD+8 bind[ws_9] = MOD+9 bind[ws_10] = MOD+0 bind[ws_11] = MOD+F1 bind[ws_12] = MOD+F2 bind[ws_13] = MOD+F3 bind[ws_14] = MOD+F4 bind[ws_15] = MOD+F5 bind[ws_16] = MOD+F6 bind[ws_17] = MOD+F7 bind[ws_18] = MOD+F8 bind[ws_19] = MOD+F9 bind[ws_20] = MOD+F10 bind[ws_21] = MOD+F11 bind[ws_22] = MOD+F12 #bind[ws_empty] = #bind[ws_empty_move] = bind[ws_next] = MOD+Right bind[ws_next_all] = MOD+Up bind[ws_next_move] = MOD+Shift+Up bind[ws_prev] = MOD+Left bind[ws_prev_all] = MOD+Down bind[ws_prev_move] = MOD+Shift+Down bind[ws_prior] = MOD+a bind[debug_toggle] = MOD+d bind[dumpwins] = MOD+Shift+d spectrwm-SPECTRWM_3_5_1/spectrwm_fr.conf000066400000000000000000000100151453034271100202510ustar00rootroot00000000000000# Key bindings for French (fr) keyboards # unbind with: bind[] = bind[bar_toggle] = MOD+b bind[bar_toggle_ws] = MOD+Shift+b bind[below_toggle] = MOD+Shift+t bind[button2] = MOD+v bind[cycle_layout] = MOD+space bind[flip_layout] = MOD+Shift+dollar bind[float_toggle] = MOD+t bind[focus_free] = MOD+twosuperior bind[focus_main] = MOD+m bind[focus_next] = MOD+j bind[focus_next] = MOD+Tab bind[focus_prev] = MOD+k bind[focus_prev] = MOD+Shift+Tab bind[focus_prior] = MOD+Shift+a bind[focus_urgent] = MOD+u bind[free_toggle] = MOD+Shift+twosuperior bind[fullscreen_toggle] = MOD+Shift+e bind[height_grow] = MOD+Shift+equal bind[height_shrink] = MOD+Shift+parenright bind[iconify] = MOD+w bind[initscr] = MOD+Shift+i #bind[layout_floating] = #bind[layout_horizontal] = #bind[layout_max] = #bind[layout_vertical] = bind[lock] = MOD+Shift+Delete bind[master_add] = MOD+comma bind[master_del] = MOD+semicolon bind[master_grow] = MOD+l bind[master_shrink] = MOD+h bind[maximize_toggle] = MOD+e bind[menu] = MOD+p bind[move_down] = MOD+Shift+asterisk bind[move_left] = MOD+ugrave bind[move_right] = MOD+asterisk bind[move_up] = MOD+Shift+ugrave bind[mvrg_1] = MOD+Shift+KP_End bind[mvrg_2] = MOD+Shift+KP_Down bind[mvrg_3] = MOD+Shift+KP_Next bind[mvrg_4] = MOD+Shift+KP_Left bind[mvrg_5] = MOD+Shift+KP_Begin bind[mvrg_6] = MOD+Shift+KP_Right bind[mvrg_7] = MOD+Shift+KP_Home bind[mvrg_8] = MOD+Shift+KP_Up bind[mvrg_9] = MOD+Shift+KP_Prior #bind[mvrg_next] = #bind[mvrg_prev] = bind[mvws_1] = MOD+Shift+ampersand bind[mvws_2] = MOD+Shift+eacute bind[mvws_3] = MOD+Shift+quotedbl bind[mvws_4] = MOD+Shift+apostrophe bind[mvws_5] = MOD+Shift+parenleft bind[mvws_6] = MOD+Shift+minus bind[mvws_7] = MOD+Shift+egrave bind[mvws_8] = MOD+Shift+underscore bind[mvws_9] = MOD+Shift+ccedilla bind[mvws_10] = MOD+Shift+agrave bind[mvws_11] = MOD+Shift+F1 bind[mvws_12] = MOD+Shift+F2 bind[mvws_13] = MOD+Shift+F3 bind[mvws_14] = MOD+Shift+F4 bind[mvws_15] = MOD+Shift+F5 bind[mvws_16] = MOD+Shift+F6 bind[mvws_17] = MOD+Shift+F7 bind[mvws_18] = MOD+Shift+F8 bind[mvws_19] = MOD+Shift+F9 bind[mvws_20] = MOD+Shift+F10 bind[mvws_21] = MOD+Shift+F11 bind[mvws_22] = MOD+Shift+F12 bind[name_workspace] = MOD+Shift+colon bind[quit] = MOD+Shift+q bind[raise] = MOD+r bind[raise_toggle] = MOD+Shift+r bind[restart] = MOD+q #bind[restart_of_day] = bind[rg_1] = MOD+KP_End bind[rg_2] = MOD+KP_Down bind[rg_3] = MOD+KP_Next bind[rg_4] = MOD+KP_Left bind[rg_5] = MOD+KP_Begin bind[rg_6] = MOD+KP_Right bind[rg_7] = MOD+KP_Home bind[rg_8] = MOD+KP_Up bind[rg_9] = MOD+KP_Prior #bind[rg_move_next] = #bind[rg_move_prev] = bind[rg_next] = MOD+Shift+Right bind[rg_prev] = MOD+Shift+Left bind[screenshot_all] = MOD+s bind[screenshot_wind] = MOD+Shift+s bind[search_win] = MOD+f bind[search_workspace] = MOD+colon #bind[stack_balance] = bind[stack_dec] = MOD+Shift+semicolon bind[stack_inc] = MOD+Shift+comma bind[stack_reset] = MOD+Shift+space bind[swap_main] = MOD+Return bind[swap_next] = MOD+Shift+j bind[swap_prev] = MOD+Shift+k bind[term] = MOD+Shift+Return bind[uniconify] = MOD+Shift+w bind[version] = MOD+Shift+v bind[width_grow] = MOD+equal bind[width_shrink] = MOD+minus bind[wind_del] = MOD+x bind[wind_kill] = MOD+Shift+x bind[ws_1] = MOD+ampersand bind[ws_2] = MOD+eacute bind[ws_3] = MOD+quotedbl bind[ws_4] = MOD+apostrophe bind[ws_5] = MOD+parenleft bind[ws_6] = MOD+minus bind[ws_7] = MOD+egrave bind[ws_8] = MOD+underscore bind[ws_9] = MOD+ccedilla bind[ws_10] = MOD+agrave bind[ws_11] = MOD+F1 bind[ws_12] = MOD+F2 bind[ws_13] = MOD+F3 bind[ws_14] = MOD+F4 bind[ws_15] = MOD+F5 bind[ws_16] = MOD+F6 bind[ws_17] = MOD+F7 bind[ws_18] = MOD+F8 bind[ws_19] = MOD+F9 bind[ws_20] = MOD+F10 bind[ws_21] = MOD+F11 bind[ws_22] = MOD+F12 #bind[ws_empty] = #bind[ws_empty_move] = bind[ws_next] = MOD+Right bind[ws_next_all] = MOD+Up bind[ws_next_move] = MOD+Shift+Up bind[ws_prev] = MOD+Left bind[ws_prev_all] = MOD+Down bind[ws_prev_move] = MOD+Shift+Down bind[ws_prior] = MOD+a bind[debug_toggle] = MOD+d bind[dumpwins] = MOD+Shift+d spectrwm-SPECTRWM_3_5_1/spectrwm_fr_ch.conf000066400000000000000000000076101453034271100207320ustar00rootroot00000000000000# Key bindings for Swiss French (FR_CH) keyboards # unbind with: bind[] = bind[bar_toggle] = MOD+b bind[bar_toggle_ws] = MOD+Shift+b bind[below_toggle] = MOD+Shift+t bind[button2] = MOD+v bind[cycle_layout] = MOD+space bind[flip_layout] = MOD+Shift+backslash bind[float_toggle] = MOD+t bind[focus_free] = MOD+section bind[focus_main] = MOD+m bind[focus_next] = MOD+j bind[focus_next] = MOD+Tab bind[focus_prev] = MOD+k bind[focus_prev] = MOD+Shift+Tab bind[focus_prior] = MOD+Shift+a bind[focus_urgent] = MOD+u bind[free_toggle] = MOD+Shift+section bind[fullscreen_toggle] = MOD+Shift+e bind[height_grow] = MOD+Shift+egrave bind[height_shrink] = MOD+Shift+minus bind[iconify] = MOD+w bind[initscr] = MOD+Shift+i #bind[layout_floating] = #bind[layout_horizontal] = #bind[layout_max] = #bind[layout_vertical] = bind[lock] = MOD+Shift+Delete bind[master_add] = MOD+comma bind[master_del] = MOD+period bind[master_grow] = MOD+l bind[master_shrink] = MOD+h bind[maximize_toggle] = MOD+e bind[menu] = MOD+p bind[move_down] = MOD+Shift+agrave bind[move_left] = MOD+eacute bind[move_right] = MOD+agrave bind[move_up] = MOD+Shift+eacute bind[mvrg_1] = MOD+Shift+KP_End bind[mvrg_2] = MOD+Shift+KP_Down bind[mvrg_3] = MOD+Shift+KP_Next bind[mvrg_4] = MOD+Shift+KP_Left bind[mvrg_5] = MOD+Shift+KP_Begin bind[mvrg_6] = MOD+Shift+KP_Right bind[mvrg_7] = MOD+Shift+KP_Home bind[mvrg_8] = MOD+Shift+KP_Up bind[mvrg_9] = MOD+Shift+KP_Prior #bind[mvrg_next] = #bind[mvrg_prev] = bind[mvws_1] = MOD+Shift+1 bind[mvws_2] = MOD+Shift+2 bind[mvws_3] = MOD+Shift+3 bind[mvws_4] = MOD+Shift+4 bind[mvws_5] = MOD+Shift+5 bind[mvws_6] = MOD+Shift+6 bind[mvws_7] = MOD+Shift+7 bind[mvws_8] = MOD+Shift+8 bind[mvws_9] = MOD+Shift+9 bind[mvws_10] = MOD+Shift+0 bind[mvws_11] = MOD+Shift+F1 bind[mvws_12] = MOD+Shift+F2 bind[mvws_13] = MOD+Shift+F3 bind[mvws_14] = MOD+Shift+F4 bind[mvws_15] = MOD+Shift+F5 bind[mvws_16] = MOD+Shift+F6 bind[mvws_17] = MOD+Shift+F7 bind[mvws_18] = MOD+Shift+F8 bind[mvws_19] = MOD+Shift+F9 bind[mvws_20] = MOD+Shift+F10 bind[mvws_21] = MOD+Shift+F11 bind[mvws_22] = MOD+Shift+F12 bind[name_workspace] = MOD+Shift+apostrophe bind[quit] = MOD+Shift+q bind[raise] = MOD+r bind[raise_toggle] = MOD+Shift+r bind[restart] = MOD+q #bind[restart_of_day] = bind[rg_1] = MOD+KP_End bind[rg_2] = MOD+KP_Down bind[rg_3] = MOD+KP_Next bind[rg_4] = MOD+KP_Left bind[rg_5] = MOD+KP_Begin bind[rg_6] = MOD+KP_Right bind[rg_7] = MOD+KP_Home bind[rg_8] = MOD+KP_Up bind[rg_9] = MOD+KP_Prior #bind[rg_move_next] = #bind[rg_move_prev] = bind[rg_next] = MOD+Shift+Right bind[rg_prev] = MOD+Shift+Left bind[screenshot_all] = MOD+s bind[screenshot_wind] = MOD+Shift+s bind[search_win] = MOD+f bind[search_workspace] = MOD+apostrophe #bind[stack_balance] = bind[stack_dec] = MOD+Shift+period bind[stack_inc] = MOD+Shift+comma bind[stack_reset] = MOD+Shift+space bind[swap_main] = MOD+Return bind[swap_next] = MOD+Shift+j bind[swap_prev] = MOD+Shift+k bind[term] = MOD+Shift+Return bind[uniconify] = MOD+Shift+w bind[version] = MOD+Shift+v bind[width_grow] = MOD+egrave bind[width_shrink] = MOD+minus bind[wind_del] = MOD+x bind[wind_kill] = MOD+Shift+x bind[ws_1] = MOD+1 bind[ws_2] = MOD+2 bind[ws_3] = MOD+3 bind[ws_4] = MOD+4 bind[ws_5] = MOD+5 bind[ws_6] = MOD+6 bind[ws_7] = MOD+7 bind[ws_8] = MOD+8 bind[ws_9] = MOD+9 bind[ws_10] = MOD+0 bind[ws_11] = MOD+F1 bind[ws_12] = MOD+F2 bind[ws_13] = MOD+F3 bind[ws_14] = MOD+F4 bind[ws_15] = MOD+F5 bind[ws_16] = MOD+F6 bind[ws_17] = MOD+F7 bind[ws_18] = MOD+F8 bind[ws_19] = MOD+F9 bind[ws_20] = MOD+F10 bind[ws_21] = MOD+F11 bind[ws_22] = MOD+F12 #bind[ws_empty] = #bind[ws_empty_move] = bind[ws_next] = MOD+Right bind[ws_next_all] = MOD+Up bind[ws_next_move] = MOD+Shift+Up bind[ws_prev] = MOD+Left bind[ws_prev_all] = MOD+Down bind[ws_prev_move] = MOD+Shift+Down bind[ws_prior] = MOD+a bind[debug_toggle] = MOD+d bind[dumpwins] = MOD+Shift+d spectrwm-SPECTRWM_3_5_1/spectrwm_se.conf000066400000000000000000000076131453034271100202630ustar00rootroot00000000000000# Key bindings for Swedish (se) keyboards # unbind with: bind[] = bind[bar_toggle] = MOD+b bind[bar_toggle_ws] = MOD+Shift+b bind[below_toggle] = MOD+Shift+t bind[button2] = MOD+v bind[cycle_layout] = MOD+space bind[flip_layout] = MOD+Shift+apostrophe bind[float_toggle] = MOD+t bind[focus_free] = MOD+section bind[focus_main] = MOD+m bind[focus_next] = MOD+j bind[focus_next] = MOD+Tab bind[focus_prev] = MOD+k bind[focus_prev] = MOD+Shift+Tab bind[focus_prior] = MOD+Shift+a bind[focus_urgent] = MOD+u bind[free_toggle] = MOD+Shift+section bind[fullscreen_toggle] = MOD+Shift+e bind[height_grow] = MOD+Shift+dead_acute bind[height_shrink] = MOD+Shift+plus bind[iconify] = MOD+w bind[initscr] = MOD+Shift+i #bind[layout_floating] = #bind[layout_horizontal] = #bind[layout_max] = #bind[layout_vertical] = bind[lock] = MOD+Shift+Delete bind[master_add] = MOD+comma bind[master_del] = MOD+period bind[master_grow] = MOD+l bind[master_shrink] = MOD+h bind[maximize_toggle] = MOD+e bind[menu] = MOD+p bind[move_down] = MOD+Shift+dead_diaeresis bind[move_left] = MOD+aring bind[move_right] = MOD+dead_diaeresis bind[move_up] = MOD+Shift+aring bind[mvrg_1] = MOD+Shift+KP_End bind[mvrg_2] = MOD+Shift+KP_Down bind[mvrg_3] = MOD+Shift+KP_Next bind[mvrg_4] = MOD+Shift+KP_Left bind[mvrg_5] = MOD+Shift+KP_Begin bind[mvrg_6] = MOD+Shift+KP_Right bind[mvrg_7] = MOD+Shift+KP_Home bind[mvrg_8] = MOD+Shift+KP_Up bind[mvrg_9] = MOD+Shift+KP_Prior #bind[mvrg_next] = #bind[mvrg_prev] = bind[mvws_1] = MOD+Shift+1 bind[mvws_2] = MOD+Shift+2 bind[mvws_3] = MOD+Shift+3 bind[mvws_4] = MOD+Shift+4 bind[mvws_5] = MOD+Shift+5 bind[mvws_6] = MOD+Shift+6 bind[mvws_7] = MOD+Shift+7 bind[mvws_8] = MOD+Shift+8 bind[mvws_9] = MOD+Shift+9 bind[mvws_10] = MOD+Shift+0 bind[mvws_11] = MOD+Shift+F1 bind[mvws_12] = MOD+Shift+F2 bind[mvws_13] = MOD+Shift+F3 bind[mvws_14] = MOD+Shift+F4 bind[mvws_15] = MOD+Shift+F5 bind[mvws_16] = MOD+Shift+F6 bind[mvws_17] = MOD+Shift+F7 bind[mvws_18] = MOD+Shift+F8 bind[mvws_19] = MOD+Shift+F9 bind[mvws_20] = MOD+Shift+F10 bind[mvws_21] = MOD+Shift+F11 bind[mvws_22] = MOD+Shift+F12 bind[name_workspace] = MOD+Shift+minus bind[quit] = MOD+Shift+q bind[raise] = MOD+r bind[raise_toggle] = MOD+Shift+r bind[restart] = MOD+q #bind[restart_of_day] = bind[rg_1] = MOD+KP_End bind[rg_2] = MOD+KP_Down bind[rg_3] = MOD+KP_Next bind[rg_4] = MOD+KP_Left bind[rg_5] = MOD+KP_Begin bind[rg_6] = MOD+KP_Right bind[rg_7] = MOD+KP_Home bind[rg_8] = MOD+KP_Up bind[rg_9] = MOD+KP_Prior #bind[rg_move_next] = #bind[rg_move_prev] = bind[rg_next] = MOD+Shift+Right bind[rg_prev] = MOD+Shift+Left bind[screenshot_all] = MOD+s bind[screenshot_wind] = MOD+Shift+s bind[search_win] = MOD+f bind[search_workspace] = MOD+minus #bind[stack_balance] = bind[stack_dec] = MOD+Shift+period bind[stack_inc] = MOD+Shift+comma bind[stack_reset] = MOD+Shift+space bind[swap_main] = MOD+Return bind[swap_next] = MOD+Shift+j bind[swap_prev] = MOD+Shift+k bind[term] = MOD+Shift+Return bind[uniconify] = MOD+Shift+w bind[version] = MOD+Shift+v bind[width_grow] = MOD+dead_acute bind[width_shrink] = MOD+plus bind[wind_del] = MOD+x bind[wind_kill] = MOD+Shift+x bind[ws_1] = MOD+1 bind[ws_2] = MOD+2 bind[ws_3] = MOD+3 bind[ws_4] = MOD+4 bind[ws_5] = MOD+5 bind[ws_6] = MOD+6 bind[ws_7] = MOD+7 bind[ws_8] = MOD+8 bind[ws_9] = MOD+9 bind[ws_10] = MOD+0 bind[ws_11] = MOD+F1 bind[ws_12] = MOD+F2 bind[ws_13] = MOD+F3 bind[ws_14] = MOD+F4 bind[ws_15] = MOD+F5 bind[ws_16] = MOD+F6 bind[ws_17] = MOD+F7 bind[ws_18] = MOD+F8 bind[ws_19] = MOD+F9 bind[ws_20] = MOD+F10 bind[ws_21] = MOD+F11 bind[ws_22] = MOD+F12 #bind[ws_empty] = #bind[ws_empty_move] = bind[ws_next] = MOD+Right bind[ws_next_all] = MOD+Up bind[ws_next_move] = MOD+Shift+Up bind[ws_prev] = MOD+Left bind[ws_prev_all] = MOD+Down bind[ws_prev_move] = MOD+Shift+Down bind[ws_prior] = MOD+a bind[debug_toggle] = MOD+d bind[dumpwins] = MOD+Shift+d spectrwm-SPECTRWM_3_5_1/spectrwm_us.conf000066400000000000000000000076141453034271100203040ustar00rootroot00000000000000# Key bindings for United States (us) keyboards # unbind with: bind[] = bind[bar_toggle] = MOD+b bind[bar_toggle_ws] = MOD+Shift+b bind[below_toggle] = MOD+Shift+t bind[button2] = MOD+v bind[cycle_layout] = MOD+space bind[flip_layout] = MOD+Shift+backslash bind[float_toggle] = MOD+t bind[focus_free] = MOD+grave bind[focus_main] = MOD+m bind[focus_next] = MOD+j bind[focus_next] = MOD+Tab bind[focus_prev] = MOD+k bind[focus_prev] = MOD+Shift+Tab bind[focus_prior] = MOD+Shift+a bind[focus_urgent] = MOD+u bind[free_toggle] = MOD+Shift+grave bind[fullscreen_toggle] = MOD+Shift+e bind[height_grow] = MOD+Shift+equal bind[height_shrink] = MOD+Shift+minus bind[iconify] = MOD+w bind[initscr] = MOD+Shift+i #bind[layout_floating] = #bind[layout_horizontal] = #bind[layout_max] = #bind[layout_vertical] = bind[lock] = MOD+Shift+Delete bind[master_add] = MOD+comma bind[master_del] = MOD+period bind[master_grow] = MOD+l bind[master_shrink] = MOD+h bind[maximize_toggle] = MOD+e bind[menu] = MOD+p bind[move_down] = MOD+Shift+bracketright bind[move_left] = MOD+bracketleft bind[move_right] = MOD+bracketright bind[move_up] = MOD+Shift+bracketleft bind[mvrg_1] = MOD+Shift+KP_End bind[mvrg_2] = MOD+Shift+KP_Down bind[mvrg_3] = MOD+Shift+KP_Next bind[mvrg_4] = MOD+Shift+KP_Left bind[mvrg_5] = MOD+Shift+KP_Begin bind[mvrg_6] = MOD+Shift+KP_Right bind[mvrg_7] = MOD+Shift+KP_Home bind[mvrg_8] = MOD+Shift+KP_Up bind[mvrg_9] = MOD+Shift+KP_Prior #bind[mvrg_next] = #bind[mvrg_prev] = bind[mvws_1] = MOD+Shift+1 bind[mvws_2] = MOD+Shift+2 bind[mvws_3] = MOD+Shift+3 bind[mvws_4] = MOD+Shift+4 bind[mvws_5] = MOD+Shift+5 bind[mvws_6] = MOD+Shift+6 bind[mvws_7] = MOD+Shift+7 bind[mvws_8] = MOD+Shift+8 bind[mvws_9] = MOD+Shift+9 bind[mvws_10] = MOD+Shift+0 bind[mvws_11] = MOD+Shift+F1 bind[mvws_12] = MOD+Shift+F2 bind[mvws_13] = MOD+Shift+F3 bind[mvws_14] = MOD+Shift+F4 bind[mvws_15] = MOD+Shift+F5 bind[mvws_16] = MOD+Shift+F6 bind[mvws_17] = MOD+Shift+F7 bind[mvws_18] = MOD+Shift+F8 bind[mvws_19] = MOD+Shift+F9 bind[mvws_20] = MOD+Shift+F10 bind[mvws_21] = MOD+Shift+F11 bind[mvws_22] = MOD+Shift+F12 bind[name_workspace] = MOD+Shift+slash bind[quit] = MOD+Shift+q bind[raise] = MOD+r bind[raise_toggle] = MOD+Shift+r bind[restart] = MOD+q #bind[restart_of_day] = bind[rg_1] = MOD+KP_End bind[rg_2] = MOD+KP_Down bind[rg_3] = MOD+KP_Next bind[rg_4] = MOD+KP_Left bind[rg_5] = MOD+KP_Begin bind[rg_6] = MOD+KP_Right bind[rg_7] = MOD+KP_Home bind[rg_8] = MOD+KP_Up bind[rg_9] = MOD+KP_Prior #bind[rg_move_next] = #bind[rg_move_prev] = bind[rg_next] = MOD+Shift+Right bind[rg_prev] = MOD+Shift+Left bind[screenshot_all] = MOD+s bind[screenshot_wind] = MOD+Shift+s bind[search_win] = MOD+f bind[search_workspace] = MOD+slash #bind[stack_balance] = bind[stack_dec] = MOD+Shift+period bind[stack_inc] = MOD+Shift+comma bind[stack_reset] = MOD+Shift+space bind[swap_main] = MOD+Return bind[swap_next] = MOD+Shift+j bind[swap_prev] = MOD+Shift+k bind[term] = MOD+Shift+Return bind[uniconify] = MOD+Shift+w bind[version] = MOD+Shift+v bind[width_grow] = MOD+equal bind[width_shrink] = MOD+minus bind[wind_del] = MOD+x bind[wind_kill] = MOD+Shift+x bind[ws_1] = MOD+1 bind[ws_2] = MOD+2 bind[ws_3] = MOD+3 bind[ws_4] = MOD+4 bind[ws_5] = MOD+5 bind[ws_6] = MOD+6 bind[ws_7] = MOD+7 bind[ws_8] = MOD+8 bind[ws_9] = MOD+9 bind[ws_10] = MOD+0 bind[ws_11] = MOD+F1 bind[ws_12] = MOD+F2 bind[ws_13] = MOD+F3 bind[ws_14] = MOD+F4 bind[ws_15] = MOD+F5 bind[ws_16] = MOD+F6 bind[ws_17] = MOD+F7 bind[ws_18] = MOD+F8 bind[ws_19] = MOD+F9 bind[ws_20] = MOD+F10 bind[ws_21] = MOD+F11 bind[ws_22] = MOD+F12 #bind[ws_empty] = #bind[ws_empty_move] = bind[ws_next] = MOD+Right bind[ws_next_all] = MOD+Up bind[ws_next_move] = MOD+Shift+Up bind[ws_prev] = MOD+Left bind[ws_prev_all] = MOD+Down bind[ws_prev_move] = MOD+Shift+Down bind[ws_prior] = MOD+a bind[debug_toggle] = MOD+d bind[dumpwins] = MOD+Shift+d spectrwm-SPECTRWM_3_5_1/version.h000066400000000000000000000022531453034271100167120ustar00rootroot00000000000000/* * Copyright (c) 2011 Conformal Systems LLC * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef SPECTRWM_VERSION_H #define SPECTRWM_VERSION_H #define SPECTRWM_STR(x) #x #define SPECTRWM_STRINGIZE(x) SPECTRWM_STR(x) #define SPECTRWM_MAJOR 3 #define SPECTRWM_MINOR 5 #define SPECTRWM_PATCH 1 #define SPECTRWM_VERSION SPECTRWM_STRINGIZE(SPECTRWM_MAJOR) "." \ SPECTRWM_STRINGIZE(SPECTRWM_MINOR) "." \ SPECTRWM_STRINGIZE(SPECTRWM_PATCH) #endif /* SPECTRWM_VERSION_H */