Repository: baskerville/bspwm Branch: master Commit: c5cf7d3943f9 Files: 88 Total size: 523.1 KB Directory structure: gitextract_k_h94np_/ ├── .editorconfig ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── Sourcedeps ├── VERSION ├── contrib/ │ ├── bash_completion │ ├── fish_completion │ ├── freedesktop/ │ │ └── bspwm.desktop │ └── zsh_completion ├── doc/ │ ├── CHANGELOG.md │ ├── CONTRIBUTING.md │ ├── INSTALL.md │ ├── MISC.md │ ├── TODO.md │ ├── bspwm.1 │ └── bspwm.1.asciidoc ├── examples/ │ ├── bspwmrc │ ├── external_rules/ │ │ ├── bspwmrc │ │ └── external_rules │ ├── overlapping_borders/ │ │ └── bspwmrc │ ├── panel/ │ │ ├── bspwmrc │ │ ├── panel │ │ ├── panel_bar │ │ ├── panel_colors │ │ ├── profile │ │ └── sxhkdrc │ ├── receptacles/ │ │ ├── README.md │ │ ├── extract_canvas │ │ └── induce_rules │ └── sxhkdrc ├── src/ │ ├── bspc.c │ ├── bspwm.c │ ├── bspwm.h │ ├── common.h │ ├── desktop.c │ ├── desktop.h │ ├── events.c │ ├── events.h │ ├── ewmh.c │ ├── ewmh.h │ ├── geometry.c │ ├── geometry.h │ ├── helpers.c │ ├── helpers.h │ ├── history.c │ ├── history.h │ ├── jsmn.c │ ├── jsmn.h │ ├── messages.c │ ├── messages.h │ ├── monitor.c │ ├── monitor.h │ ├── parse.c │ ├── parse.h │ ├── pointer.c │ ├── pointer.h │ ├── query.c │ ├── query.h │ ├── restore.c │ ├── restore.h │ ├── rule.c │ ├── rule.h │ ├── settings.c │ ├── settings.h │ ├── stack.c │ ├── stack.h │ ├── subscribe.c │ ├── subscribe.h │ ├── tree.c │ ├── tree.h │ ├── types.h │ ├── window.c │ └── window.h └── tests/ ├── Makefile ├── README.md ├── desktop/ │ ├── swap │ └── transfer ├── node/ │ ├── flags │ ├── insertion │ ├── receptacle │ ├── removal │ ├── swap │ └── transfer ├── prelude ├── run └── test_window.c ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # EditorConfig: https://editorconfig.org # Top-most EditorConfig file root = true # Unix-style newlines with a newline ending every file [*] insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 indent_style = tab indent_size = 4 ================================================ FILE: .gitignore ================================================ tags bspwm bspc *.o tests/test_window ================================================ FILE: LICENSE ================================================ Copyright (c) 2012, Bastien Dejean 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: Makefile ================================================ VERCMD ?= git describe --tags 2> /dev/null VERSION := $(shell $(VERCMD) || cat VERSION) CPPFLAGS += -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" CFLAGS += -std=c99 -pedantic -Wall -Wextra -DJSMN_STRICT LDFLAGS ?= LDLIBS = $(LDFLAGS) -lm -lxcb -lxcb-util -lxcb-keysyms -lxcb-icccm -lxcb-ewmh -lxcb-randr -lxcb-xinerama -lxcb-shape PREFIX ?= /usr/local BINPREFIX ?= $(PREFIX)/bin MANPREFIX ?= $(PREFIX)/share/man DOCPREFIX ?= $(PREFIX)/share/doc/bspwm BASHCPL ?= $(PREFIX)/share/bash-completion/completions FISHCPL ?= $(PREFIX)/share/fish/vendor_completions.d ZSHCPL ?= $(PREFIX)/share/zsh/site-functions MD_DOCS = README.md doc/CHANGELOG.md doc/CONTRIBUTING.md doc/INSTALL.md doc/MISC.md doc/TODO.md XSESSIONS ?= $(PREFIX)/share/xsessions WM_SRC = bspwm.c helpers.c geometry.c jsmn.c settings.c monitor.c desktop.c tree.c stack.c history.c \ events.c pointer.c window.c messages.c parse.c query.c restore.c rule.c ewmh.c subscribe.c WM_OBJ := $(WM_SRC:.c=.o) CLI_SRC = bspc.c helpers.c CLI_OBJ := $(CLI_SRC:.c=.o) all: bspwm bspc debug: CFLAGS += -O0 -g debug: bspwm bspc VPATH=src include Sourcedeps $(WM_OBJ) $(CLI_OBJ): Makefile bspwm: $(WM_OBJ) bspc: $(CLI_OBJ) install: mkdir -p "$(DESTDIR)$(BINPREFIX)" cp -pf bspwm "$(DESTDIR)$(BINPREFIX)" cp -pf bspc "$(DESTDIR)$(BINPREFIX)" mkdir -p "$(DESTDIR)$(MANPREFIX)"/man1 cp -p doc/bspwm.1 "$(DESTDIR)$(MANPREFIX)"/man1 cp -Pp doc/bspc.1 "$(DESTDIR)$(MANPREFIX)"/man1 mkdir -p "$(DESTDIR)$(BASHCPL)" cp -p contrib/bash_completion "$(DESTDIR)$(BASHCPL)"/bspc mkdir -p "$(DESTDIR)$(FISHCPL)" cp -p contrib/fish_completion "$(DESTDIR)$(FISHCPL)"/bspc.fish mkdir -p "$(DESTDIR)$(ZSHCPL)" cp -p contrib/zsh_completion "$(DESTDIR)$(ZSHCPL)"/_bspc mkdir -p "$(DESTDIR)$(DOCPREFIX)" cp -p $(MD_DOCS) "$(DESTDIR)$(DOCPREFIX)" mkdir -p "$(DESTDIR)$(DOCPREFIX)"/examples cp -pr examples/* "$(DESTDIR)$(DOCPREFIX)"/examples mkdir -p "$(DESTDIR)$(XSESSIONS)" cp -p contrib/freedesktop/bspwm.desktop "$(DESTDIR)$(XSESSIONS)" uninstall: rm -f "$(DESTDIR)$(BINPREFIX)"/bspwm rm -f "$(DESTDIR)$(BINPREFIX)"/bspc rm -f "$(DESTDIR)$(MANPREFIX)"/man1/bspwm.1 rm -f "$(DESTDIR)$(MANPREFIX)"/man1/bspc.1 rm -f "$(DESTDIR)$(BASHCPL)"/bspc rm -f "$(DESTDIR)$(FISHCPL)"/bspc.fish rm -f "$(DESTDIR)$(ZSHCPL)"/_bspc rm -rf "$(DESTDIR)$(DOCPREFIX)" rm -f "$(DESTDIR)$(XSESSIONS)"/bspwm.desktop doc: a2x -v -d manpage -f manpage -a revnumber=$(VERSION) doc/bspwm.1.asciidoc clean: rm -f $(WM_OBJ) $(CLI_OBJ) bspwm bspc .PHONY: all debug install uninstall doc clean ================================================ FILE: README.md ================================================ ## Description *bspwm* is a tiling window manager that represents windows as the leaves of a full binary tree. It only responds to X events, and the messages it receives on a dedicated socket. *bspc* is a program that writes messages on *bspwm*'s socket. *bspwm* doesn't handle any keyboard or pointer inputs: a third party program (e.g. *sxhkd*) is needed in order to translate keyboard and pointer events to *bspc* invocations. The outlined architecture is the following: ``` PROCESS SOCKET sxhkd --------> bspc <------> bspwm ``` ## Configuration The default configuration file is `$XDG_CONFIG_HOME/bspwm/bspwmrc`: this is simply a shell script that calls *bspc*. An argument is passed to that script to indicate whether is was executed after a restart (`$1 -gt 0`) or not (`$1 -eq 0`). Keyboard and pointer bindings are defined with [sxhkd](https://github.com/baskerville/sxhkd). Example configuration files can be found in the [examples](examples) directory. ## Monitors, desktops and windows *bspwm* holds a list of monitors. A monitor is just a rectangle that contains desktops. A desktop is just a pointer to a tree. Monitors only show the tree of one desktop at a time (their focused desktop). The tree is a partition of a monitor's rectangle into smaller rectangular regions. Each node in a tree either has zero or two children. Each internal node is responsible for splitting a rectangle in half. A split is defined by two parameters: the type (horizontal or vertical) and the ratio (a real number *r* such that *0 < r < 1*). Each leaf node holds exactly one window. ## Insertion modes When *bspwm* receives a new window, it inserts it into a window tree at the specified insertion point (a leaf) using the insertion mode specified for that insertion point. The insertion mode tells *bspwm* how it should alter the tree in order to insert new windows on a given insertion point. By default the insertion point is the focused window and its insertion mode is *automatic*. ### Manual mode The user can specify a region in the insertion point where the next new window should appear by sending a *node -p|--presel-dir DIR* message to *bspwm*. The *DIR* argument allows to specify how the insertion point should be split (horizontally or vertically) and if the new window should be the first or the second child of the new internal node (the insertion point will become its *brother*). After doing so the insertion point goes into *manual* mode. Let's consider the following scenario: ``` a a a / \ / \ / \ 1 b ---> c b ---> c b ^ / \ / \ / \ / \ / \ 2 3 4 1 2 3 d 1 2 3 ^ / \ 5 4 ^ +-----------------------+ +-----------------------+ +-----------------------+ | | | | | | | | | | | | 2 | | 4 | 2 | | 5 | 4 | 2 | | | | | ^ | | | ^ | | | | 1 |-----------| |-----------|-----------| |-----------|-----------| | ^ | | | | | | | | | | 3 | | 1 | 3 | | 1 | 3 | | | | | | | | | | +-----------------------+ +-----------------------+ +-----------------------+ X Y Z ``` In state *X*, the insertion point is *1*. We send the following message to *bspwm*: *node -p north*. Then add a new window: *4*, this leads to state *Y*: the new internal node, *c* becomes *a*'s first child. Finally we send another message: *node -p west* and add window *5*. The ratio of the preselection (that ends up being the ratio of the split of the new internal node) can be changed with the *node -o|--presel-ratio* message. ### Automatic mode The *automatic* mode, as opposed to the *manual* mode, doesn't require any user choice. The way the new window is inserted is determined by the value of the automatic scheme and the initial polarity settings. #### Longest side scheme When the value of the automatic scheme is `longest_side`, the window will be attached as if the insertion point was in manual mode and the split direction was chosen based on the dimensions of the tiling rectangle and the initial polarity. Let's consider the following scenario, where the initial polarity is set to `second_child`: ``` 1 a a ^ / \ / \ ---> 1 2 ---> 1 b ^ / \ 2 3 ^ +-----------------------+ +-----------------------+ +-----------------------+ | | | | | | | | | | | | | | | 2 | | | | | | | | | | 1 | | 1 | 2 | | 1 |-----------| | ^ | | | ^ | | | | | | | | | | | 3 | | | | | | | | ^ | +-----------------------+ +-----------------------+ +-----------------------+ X Y Z ``` In state *X*, a new window is added. Since *1* is wide, it gets split vertically and *2* is added as *a*'s second child given the initial polarity. This leads to *Y* where we insert window *3*. *2* is tall and is therefore split horizontally. *3* is once again added as *b*'s second child. #### Alternate scheme When the value of the automatic scheme is `alternate`, the window will be attached as if the insertion point was in manual mode and the split direction was chosen based on the split type of the insertion point's parent and the initial polarity. If the parent is split horizontally, the insertion point will be split vertically and vice versa. #### Spiral scheme When the value of the automatic scheme is `spiral`, the window will *take the space* of the insertion point. Let's dive into the details with the following scenario: ``` a a a / \ / \ / \ 1 b ---> 1 c ---> 1 d / \ / \ / \ 2 3 4 b 5 c ^ ^ / \ ^ / \ 3 2 b 4 / \ 3 2 +-----------------------+ +-----------------------+ +-----------------------+ | | | | | | | | | | | 2 | | | 4 | | | 5 | | | ^ | | | ^ | | | ^ | | 1 |-----------| | 1 |-----------| | 1 |-----------| | | | | | | | | | 3 | | | | 3 | | | 3 | 2 | | |-----| 4 | | | | | | | | | | 2 | | +-----------------------+ +-----------------------+ +-----------------------+ X Y Z ``` In state *X*, the insertion point, *2* is in automatic mode. When we add a new window, *4*, the whole tree rooted at *b* is reattached, as the second child of a new internal node, *c*. The splitting parameters of *b* (type: *horizontal*, ratio: *½*) are copied to *c* and *b* is rotated by 90° clockwise. The tiling rectangle of *4* in state *Y* is equal to the tiling rectangle of *2* in state *X*. Then the insertion of *5*, with *4* as insertion point, leads to *Z*. The *spiral* automatic scheme generates window spirals that rotate clockwise (resp. anti-clockwise) if the insertion point is the first (resp. second) child of its parent. ## Supported protocols and standards - The RandR and Xinerama protocols. - A subset of the EWMH and ICCCM standards. ## Community Want to get in touch with other *bspwm* users or you need help? Join us on our: - Subreddit at [r/bspwm](https://www.reddit.com/r/bspwm/). - IRC channel at `#bspwm` on `irc.libera.chat` (maintained by [Emanuele Torre](https://github.com/emanuele6) / emanuele6 on IRC). - Matrix room at https://matrix.to/#/#bspwm:matrix.org ================================================ FILE: Sourcedeps ================================================ bspc.o: bspc.c common.h helpers.h bspwm.o: bspwm.c bspwm.h common.h desktop.h events.h ewmh.h helpers.h history.h messages.h monitor.h pointer.h rule.h settings.h subscribe.h types.h window.h desktop.o: desktop.c bspwm.h desktop.h ewmh.h helpers.h history.h monitor.h query.h settings.h subscribe.h tree.h types.h window.h events.o: events.c bspwm.h events.h ewmh.h helpers.h monitor.h pointer.h query.h settings.h subscribe.h tree.h types.h window.h ewmh.o: ewmh.c bspwm.h ewmh.h helpers.h settings.h tree.h types.h geometry.o: geometry.c geometry.h helpers.h types.h helpers.o: helpers.c bspwm.h helpers.h types.h history.o: history.c bspwm.h helpers.h query.h tree.h types.h jsmn.o: jsmn.c jsmn.h messages.o: messages.c bspwm.h common.h desktop.h helpers.h jsmn.h messages.h monitor.h parse.h pointer.h query.h restore.h rule.h settings.h subscribe.h tree.h types.h window.h monitor.o: monitor.c bspwm.h desktop.h ewmh.h geometry.h helpers.h monitor.h pointer.h query.h settings.h subscribe.h tree.h types.h window.h parse.o: parse.c helpers.h parse.h subscribe.h types.h pointer.o: pointer.c bspwm.h events.h helpers.h monitor.h pointer.h query.h settings.h stack.h subscribe.h tree.h types.h window.h query.o: query.c bspwm.h desktop.h helpers.h history.h monitor.h parse.h query.h subscribe.h tree.h types.h window.h restore.o: restore.c bspwm.h desktop.h ewmh.h helpers.h history.h jsmn.h monitor.h parse.h pointer.h query.h restore.h settings.h stack.h subscribe.h tree.h types.h window.h rule.o: rule.c bspwm.h ewmh.h helpers.h parse.h rule.h settings.h subscribe.h types.h window.h settings.o: settings.c bspwm.h helpers.h settings.h types.h stack.o: stack.c bspwm.h ewmh.h helpers.h stack.h subscribe.h tree.h types.h window.h subscribe.o: subscribe.c bspwm.h desktop.h helpers.h settings.h subscribe.h types.h tree.o: tree.c bspwm.h desktop.h ewmh.h geometry.h helpers.h history.h monitor.h pointer.h query.h settings.h stack.h subscribe.h tree.h types.h window.h window.o: window.c bspwm.h ewmh.h geometry.h helpers.h monitor.h parse.h pointer.h query.h rule.h settings.h stack.h subscribe.h tree.h types.h window.h ================================================ FILE: VERSION ================================================ 0.9.12 ================================================ FILE: contrib/bash_completion ================================================ _bspc() { local commands='node desktop monitor query rule wm subscribe config quit' local settings='external_rules_command status_prefix normal_border_color active_border_color focused_border_color presel_feedback_color border_width window_gap top_padding right_padding bottom_padding left_padding top_monocle_padding right_monocle_padding bottom_monocle_padding left_monocle_padding split_ratio automatic_scheme removal_adjustment initial_polarity directional_focus_tightness presel_feedback borderless_monocle gapless_monocle single_monocle borderless_singleton pointer_motion_interval pointer_modifier pointer_action1 pointer_action2 pointer_action3 click_to_focus swallow_first_click focus_follows_pointer pointer_follows_focus pointer_follows_monitor mapping_events_count ignore_ewmh_focus ignore_ewmh_fullscreen ignore_ewmh_struts center_pseudo_tiled honor_size_hints remove_disabled_monitors remove_unplugged_monitors merge_overlapping_monitors' COMPREPLY=() if [[ $COMP_CWORD -ge 1 ]] ; then local current_word="${COMP_WORDS[COMP_CWORD]}" if [[ $COMP_CWORD -eq 1 ]] ; then COMPREPLY=( $(compgen -W "$commands" -- "$current_word") ) return 0 else local second_word=${COMP_WORDS[1]} case $second_word in config) if [[ $COMP_CWORD -eq 2 ]] ; then COMPREPLY=( $(compgen -W "$settings" -- "$current_word") ) return 0 fi ;; esac fi fi } complete -F _bspc bspc # vim: set ft=sh: ================================================ FILE: contrib/fish_completion ================================================ function __fish_bspc_needs_command set cmd (commandline -opc) [ (count $cmd) -eq 1 -a $cmd[1] = 'bspc' ]; and return 0 return 1 end function __fish_bspc_using_command set cmd (commandline -opc) [ (count $cmd) -gt 1 ]; and [ $argv[1] = $cmd[2] ]; and return 0 return 1 end complete -f -c bspc -n '__fish_bspc_needs_command' -a 'node desktop monitor query rule wm subscribe config quit' complete -f -c bspc -n '__fish_bspc_using_command config' -a 'external_rules_command status_prefix normal_border_color active_border_color focused_border_color presel_feedback_color border_width window_gap top_padding right_padding bottom_padding left_padding top_monocle_padding right_monocle_padding bottom_monocle_padding left_monocle_padding split_ratio automatic_scheme removal_adjustment initial_polarity directional_focus_tightness presel_feedback borderless_monocle gapless_monocle single_monocle borderless_singleton pointer_motion_interval pointer_modifier pointer_action1 pointer_action2 pointer_action3 click_to_focus swallow_first_click focus_follows_pointer pointer_follows_focus pointer_follows_monitor mapping_events_count ignore_ewmh_focus ignore_ewmh_fullscreen ignore_ewmh_struts center_pseudo_tiled honor_size_hints remove_disabled_monitors remove_unplugged_monitors merge_overlapping_monitors' ================================================ FILE: contrib/freedesktop/bspwm.desktop ================================================ [Desktop Entry] Name=bspwm Comment=Binary space partitioning window manager Exec=bspwm Type=Application DesktopNames=bspwm ================================================ FILE: contrib/zsh_completion ================================================ #compdef bspc _bspc_selector() { [[ ${@[(r)--]} = '--' ]] && shift ${@[(i)--]} local -a completions=() completions_display=() local index=0 name id sel_type="$1" shift 1 case $sel_type in (node) compset -P '*[#.:/@]' ;; (desktop) compset -P '*[#.:]' ;; (monitor) compset -P '*[#.]' ;; (*) return 1 ;; esac case "$sel_type $IPREFIX" in (desktop*:) local ipfx=${${IPREFIX##*@}%:} while do if completions=("${(@f)$(bspc query --names -D -m ${ipfx} 2> /dev/null)}") ;then until ((++index > $#completions)) do completions[index]="^${index}:${completions[index]}" done completions+='focused' _describe "${sel_type} selector" completions $@ -S '' -J ${sel_type} break else completions=() [ -n "$ipfx[(r)#]" ] && ipfx="${ipfx#*#}" || break fi done ;| (node*[^@/:.]) bspc query -N -n .window 2> /dev/null | while read id ;do id=${id//:/\\:} if which xdotool &> /dev/null ;then name=$(xdotool getwindowname $id 2> /dev/null) elif which xprop &> /dev/null ;then name=$(xprop -id $id -notype WM_NAME 2> /dev/null) && [[ "$name" = 'WM_NAME ='* ]] && name="${${name#*\"}%\"*}" || name="" else name="install xdotool or xprop to see window titles here" fi completions+="$id:$name" done ;| ((desktop|monitor)*[^:.]) local max_name_len=0 max_index_len i local -a snames names ids bspc query -${(U)sel_type[1]} 2> /dev/null | while read id ;do ((index++)) name=$(bspc query --names -${(U)sel_type[1]} -${sel_type[1]} $id 2> /dev/null) [[ "$name" == *[:.!]* ]] && sel_name="%${name//:/\\:}" || sel_name="$name" ((max_name_len = $#sel_name > max_name_len ? $#sel_name : max_name_len)) ids+="${id}" snames+="${sel_name}" names+="${name}" done max_index_len=$(($#index + 1)) ((max_name_len >= max_name_len)) && ((max_name_len=max_name_len + 1)) for ((i = 1 ; i <= $#ids ; i++)) ;do (($#ids[i] <= max_name_len)) && completions_display+="${(r($max_name_len+1))ids[i]}:${names[i]}" || completions_display+="${ids[i]}:${names[i]}" completions+="${ids[i]}:${names[i]}" done for ((i = 1 ; i <= $#ids ; i++)) ;do completions+="${snames[i]}:${names[i]}" completions_display+="${(r($max_name_len))snames[i]}:${names[i]}" done for ((i = 1 ; i <= $#ids ; i++)) ;do completions+="^${i}:${names[i]}" completions_display+="^${i}:${names[i]}" done ;| (node*('@'(*':'|*'/'|))) _describe 'node path' jump -S '/' -r "#. ${quote}" -J nodes ;| (node*'/') ;; (*'.') _bspc_prefix '!' "${sel_type} modifiers" ${sel_type}_mod $@ -J ${sel_type}_mod ;| ((desktop|monitor)*@) ;& (*[^:.@/]) if (( $#completions_display)) ;then _describe "${sel_type} selector" ${sel_type}_desc $@ -S '.' -r ". \n:#${quote}\-" -J ${sel_type} \ -- completions_display completions $@ -S '.' -r ". \n:#${quote}\-" -J ${sel_type} else _describe "${sel_type} selector" ${sel_type}_desc $@ -S '.' -r ". \n:#${quote}\-" -J ${sel_type} \ -- completions $@ -S '.' -r ". \n:#${quote}\-" -J ${sel_type} fi ;| (node*@*'#'*) ;; (node*@*) _bspc_selector desktop -S ':' -qr ".#\-\n ${quote}" ;; (desktop*) _bspc_selector monitor -S ':' -r ".#\-\n ${quote}" ;; esac } _bspc_prefix(){ [[ ${@[(r)--]} = '--' ]] && shift ${@[(i)--]} [[ "$PREFIX[1]" == "$1" ]] && local a="-n" b || local b="-n" a _describe $@[2,-1] $a -- $@[3,-1] $b -p "$1" } _bspc_query_names() { [[ ${@[(r)--]} = '--' ]] && shift ${@[(i)--]} local -a items=("${(@f)$(bspc query $2 --names 2> /dev/null)}") || return local c for c in '\' ':' '[' '(' '*' items=("${(@)items//$c/\\$c}") _values -w "$1" "${items[@]}" } _bspc() { local -a commands=(node desktop monitor query rule wm subscribe config quit) \ resize_handle=(top bottom top_left top_right bottom_left bottom_right left right) \ node_state=(tiled pseudo_tiled floating fullscreen) \ flag=(hidden sticky private locked marked urgent) \ layer=(below normal above) \ dir=(north west south east) \ cycle_dir=(next prev) local -a jump=($dir first second brother parent 1 2) \ node_desc=($dir $cycle_dir any last newest older newer focused pointed biggest smallest) \ node_mod=($node_state $flag $layer focused automatic local \ active leaf window same_class descendant_of ancestor_of) \ desktop_desc=($cycle_dir any last newest older newer focused) \ desktop_mod=(focused occupied local urgent) \ monitor_desc=($dir $cycle_dir any last newest older newer focused pointed primary) \ monitor_mod=(focused occupied) \ presel_dir=($dir cancel) local quote="${compstate[quote]}" context state state_descr line typeset -A opt_args compset -n 2 compset -S "${quote}" if ((CURRENT==1)) ;then _describe 'command or domain' commands return fi case $words[1] in (node) ((CURRENT==2)) && _bspc_selector node ((CURRENT>2)) && [[ "$words[2]" != '-'* ]] && compset -n 2 ((CURRENT>2)) && [[ "$words[CURRENT-2]" =~ "^-(m|d|n|s|-to-(monitor|desktop|node)|-swap)$" ]] && _values 'option' '--follow[If passed, the focused node will stay focused]' _arguments -C \ '*'{-a,--activate}'[Activate the selected or given node]::node selector:_bspc_selector -- node'\ '*'{-B,--balance}'[Adjust the split ratios of the tree rooted at the selected node so that all windows occupy the same area]'\ '*'{-C,--circulate}'[Circulate the windows of the tree rooted at the selected node]:direction:(forward backward)'\ '*'{-c,--close}'[Close the windows rooted at the selected node]'\ '*'{-d,--to-desktop}'[Send the selected node to the given desktop]:desktop selector:_bspc_selector -- desktop'\ '*'{-E,--equalize}'[Reset the split ratios of the tree rooted at the selected node to their default value]'\ '*'{-F,--flip}'[Flip the the tree rooted at selected node]: :(horizontal vertical)'\ '*'{-f,--focus}'[Focus the selected or given node]::node selector:_bspc_selector -- node'\ '*'{-g,--flag}'[Set or toggle the given flag for the selected node]: :-> flag'\ '*'{-i,--insert-receptacle}'[Insert a receptacle node at the selected node]'\ '*'{-k,--kill}'[Kill the windows rooted at the selected node]'\ '*'{-l,--layer}"[Set the stacking layer of the selected window]:stacking layer:($layer)"\ '*'{-m,--to-monitor}'[Send the selected node to the given monitor]:monitor selector:_bspc_selector -- monitor'\ '*'{-n,--to-node}'[Transplant the selected node to the given node]:node selector:_bspc_selector -- node'\ '*'{-o,--presel-ratio}'[Set the splitting ratio of the preselection area]:preselect ratio: ( )'\ '*'{-p,--presel-dir}'[Preselect the splitting area of the selected node or cancel the preselection]: :_bspc_prefix -- "~" preselect presel_dir'\ '*'{-y,--type}'[Set the splitting type of the selected node]: :(horizontal vertical)'\ '*'{-r,--ratio}'[Set the splitting ratio of the selected node (0 < ratio < 1)]: :( )'\ '*'{-R,--rotate}'[Rotate the tree rooted at the selected node]:angle:(90 270 180)'\ '*'{-s,--swap}'[Swap the selected node with the given node]:node selector:_bspc_selector -- node'\ '*'{-t,--state}'[Set the state of the selected window]: :_bspc_prefix -- "~" "node state" node_state '\ '*'{-v,--move}'[Move the selected window by dx pixels horizontally and dy pixels vertically]:dx:( ):dy:( )'\ '*'{-z,--resize}"[Resize the selected window by moving the given handle by dx pixels horizontally and dy pixels vertically]:handle:($resize_handle):dx:( ):dy:( )" [ "$state" = flag ] && _values 'flag' "${flag[@]:#urgent}::set flag:(on off)" ;; (desktop) ((CURRENT==2)) && _bspc_selector desktop ((CURRENT>2)) && [[ "$words[2]" != '-'* ]] && compset -n 2 ((CURRENT>2)) && [[ "$words[CURRENT-2]" =~ "^-m|-s|--monitor|--swap$" ]] && _values 'option' '--follow[If passed, the focused desktop will stay focused]' _arguments \ '*'{-a,--activate}'[Activate the selected or given desktop]:: :_bspc_selector -- desktop'\ '*'{-b,--bubble}"[Bubble the selected desktop in the given direction]:direction:($cycle_dir)"\ '*'{-f,--focus}'[Focus the selected or given desktop]:: :_bspc_selector -- desktop'\ '*'{-l,--layout}"[Set or cycle the layout of the selected desktop]:desktop layout:($cycle_dir monocle tiled)"\ '*'{-m,--to-monitor}'[Send the selected desktop to the given monitor]: :_bspc_selector -- monitor'\ '*'{-n,--rename}'[Rename the selected desktop]:desktop name:( )'\ '*'{-r,--remove}'[Remove the selected desktop]'\ '*'{-s,--swap}'[Swap the selected desktop with the given desktop]: :_bspc_selector -- desktop' ;; (monitor) ((CURRENT==2)) && _bspc_selector monitor ((CURRENT>2)) && [[ "$words[2]" != '-'* ]] && compset -n 2 _arguments \ '*'{-a,--add-desktops}'[Create desktops with the given names in the selected monitor]:*:add desktops:( )'\ '*'{-d,--reset-desktops}'[Rename, add or remove desktops]:*: :->desktops'\ '*'{-f,--focus}'[Focus the selected or given monitor]:: :_bspc_selector -- monitor'\ '*'{-g,--rectangle}'[Set the rectangle of the selected monitor]:WxH+X+Y:( )'\ '*'{-n,--rename}'[Rename the selected monitor]: :( )'\ '*'{-o,--reorder-desktops}'[Reorder the desktops of the selected monitor]:*:reorder desktops:_bspc_query_names -- desktops -D'\ '*'{-r,--remove}'[Remove the selected monitor]'\ '*'{-s,--swap}'[Swap the selected monitor with the given monitor]: :_bspc_selector -- monitor' ;; (query) local -a cmds_no_names=('-T' '--tree' '-N' '--nodes') local -a cmds=($cmds_no_names '-D' '--desktops' '-M' '--monitors') _arguments \ '*'{-d,--desktop}'[Constrain matches to the selected desktop]: :_bspc_selector -- desktop'\ '*'{-m,--monitor}'[Constrain matches to the selected monitor]: :_bspc_selector -- monitor'\ '*'{-n,--node}'[Constrain matches to the selected node]: :_bspc_selector -- node'\ "($cmds_no_names --names)--names[Print names instead of IDs. Can only be used with -M and -D]"\ "($cmds --names)"{-N,--nodes}'[List the IDs of the matching nodes]'\ "($cmds --names)"{-T,--tree}'[Print a JSON representation of the matching item]'\ "($cmds)"{-D,--desktops}'[List the IDs (or names) of the matching desktops]'\ "($cmds)"{-M,--monitors}'[List the IDs (or names) of the matching monitors]' ;; (wm) _arguments \ '*'{-d,--dump-state}'[Dump the current world state on standard output]'\ '*'{-l,--load-state}'[Load a world state from the given file]:load state from file:_files'\ '*'{-a,--add-monitor}'[Add a monitor for the given name and rectangle]:add monitor:( )'\ '*'{-O,--reorder-monitors}'[Reorder the list of monitors to match the given order]:*: :_bspc_query_names -- monitors -M'\ '*'{-o,--adopt-orphans}'[Manage all the unmanaged windows remaining from a previous session]'\ '*'{-h,--record-history}'[Enable or disable the recording of node focus history]:history:(on off)'\ '*'{-g,--get-status}'[Print the current status information]'\ '*'{-r,--restart}'[Restart the window manager]' ;; (subscribe) if [[ "$words[CURRENT-1]" != (-c|--count) ]] ;then _values -w "options" \ '(-f --fifo)'{-f,--fifo}'[Print a path to a FIFO from which events can be read and return]'\ '(-c --count)'{-c,--count}'[Stop the corresponding bspc process after having received specified count of events]' _values -w -S "_" events all report pointer_action \ "monitor:: :(add rename remove swap focus geometry)"\ "desktop:: :(add rename remove swap transfer focus activate layout)"\ "node:: :(add remove swap transfer focus activate presel stack geometry state flag layer)" fi ;; (rule) local -a completions by_index local index=0 target settings class id instance json _arguments -C \ {-a,--add}'[Create a new rule]:*: :->add'\ {-r,--remove}'[Remove the given rules]:*: :->remove'\ '(-l --list)'{-l,--list}'[List the rules]' compset -N "-([ar]|-add|-remove)" case $state$CURRENT in (add1) compset -P '*:' bspc query -N -n '.window' 2> /dev/null | while read id ; do json=$(bspc query -T -n $id 2>/dev/null) || continue [[ "$json[1]" = '{' ]] || continue class=${${json##*\"className\":\"}%%\",\"*} instance=${${json##*\"instanceName\":\"}%%\",\"*} [[ "$class[1]" != '{' && "$instance[1]" != '{' ]] || continue if [ -n "$IPREFIX" ] ;then [[ "$IPREFIX" == ("${class}:"|('\'|)'*:') ]] && completions[(r)$instance]=$instance else class=${class%%:/\\:} completions[(r)$class]="$class" fi done ;; (add*) _values -w 'add rule' {border,focus,follow,manage,center}': :(on off)'\ '(--one-shot)-o'\ '(-o)--one-shot'\ 'monitor: :_bspc_selector -- monitor'\ 'desktop: :_bspc_selector -- desktop'\ 'node: :_bspc_selector -- node'\ 'rectangle: :( )'\ 'split_ratio:split ratio:( )'\ "split_dir:split direction:(${dir})"\ "state:state:(${node_state})"\ "${flag[@]:#urgent}:set flag:(on off)"\ "layer:layer:(${layer})" return ;; (remove*) compset -P '*:' bspc rule -l 2> /dev/null | while IFS=" " read target settings ;do by_index+="^$((++index)):${target} ${settings}" if [ -n "$IPREFIX" ] ;then completions+="${target#*:}" else completions+="${target%:*}" fi done [[ -z "$IPREFIX" ]] && _describe 'remove rule by position' '(head tail)' -J by_index -- by_index -J by_index ;; (*) return ;; esac completions[(r)\*]=* [ -n "$IPREFIX" ] && _describe 'match window instance' completions || _describe 'match window class' completions -q -S ':' ;; (config) local -a {look,behaviour,input}{_bool,} look_bool=(presel_feedback borderless_monocle gapless_monocle borderless_singleton) look=({normal,active,focused}_border_color {top,right,bottom,left}_padding {top,right,bottom,left}_monocle_padding presel_feedback_color border_width window_gap) behaviour_bool=(single_monocle removal_adjustment ignore_ewmh_focus ignore_ewmh_struts center_pseudo_tiled honor_size_hints remove_disabled_monitors remove_unplugged_monitors merge_overlapping_monitors) behaviour=(mapping_events_count ignore_ewmh_fullscreen external_rules_command split_ratio automatic_scheme initial_polarity directional_focus_tightness status_prefix) input_bool=(swallow_first_click focus_follows_pointer pointer_follows_{focus,monitor}) input=(click_to_focus pointer_motion_interval pointer_modifier pointer_action{1,2,3}) if [[ "$CURRENT" == (2|3) ]];then _arguments \ '-d[Set settings for the selected desktop]: :_bspc_selector -- desktop'\ '-m[Set settings for the selected monitor]: :_bspc_selector -- monitor'\ '-n[Set settings for the selected node]: :_bspc_selector -- node' fi if [[ "${words[2]}" == -* ]] ;then (( CURRENT == 3 )) && return if (( CURRENT > 3 )) ;then compset -n 3 fi fi if ((CURRENT==2)) ;then _describe 'look' look -J look -- look_bool -J look _describe 'input' input -J input -- input_bool -J input _describe 'behaviour' behaviour -J behaviour -- behaviour_bool -J behaviour elif ((CURRENT==3)) ;then setting=$words[2] case $setting in (ignore_ewmh_fullscreen) _values -S "," "set $setting" all none "enter:: :(exit)" "exit:: :(enter)" ;; (initial_polarity) _values "set $setting" first_child second_child ;; (pointer_action(1|2|3)) _values "set $setting" move resize_side resize_corner focus none ;; (pointer_modifier) _values "set $setting" shift control lock mod1 mod2 mod3 mod4 mod5 ;; (directional_focus_tightness) _values "set $setting" low high ;; (click_to_focus) _values "set $setting" any button1 button2 button3 none ;; (*) [[ -n $look_bool[(r)$setting] ]] || [[ -n $behaviour_bool[(r)$setting] ]] || [[ -n $input_bool[(r)$setting] ]] && _values "set $setting" true false ;; esac fi ;; esac } _bspc "$@" ================================================ FILE: doc/CHANGELOG.md ================================================ # From 0.9.11 to 0.9.12 - Fix a regression introduced by 6082d8b (#1533). # From 0.9.10 to 0.9.11 ## Fixes - Set the input focus before unmapping windows (#811). - Arrange across all desktops when handling structs (#1165). - Don't set the wrong border color when swapping nodes. - Honor `pointer_follows_focus` when swapping nodes. - Update EWMH's current desktop when adding a desktop (#1179). - Initialize the destination location early (#1196). - Handle standard output closure last (#1207). - Discard colons within references (#1218). - Use separate references in `cmd_query` (#1235). - `DESKTOP_SEL`: discard hashes within `MONITOR_SEL` (#1267). - Discard colons within refs in `desktop_from_desc` (#1278). - Propagate the size constraints toward the root. - Set `CLOEXEC` on the sockets except when restarting (#1298). - Properly update the sticky count in `transfer_*` (#1199). - Don't remove non-receptacles in `kill_node` (#1268). - Handle empty insertion point in the `node_add` message (#1306). - Windows sometimes not appearing (#935). - Refocus the focused window when receiving a FOCUS_IN event for root (#1160). - Always return `1` when `execvp()` fails (#1393). - Avoid unnecessary relayouts for unchanged values (#1415). - Account for vacant nodes when adjusting ratios (#1431). - Notify subscribers when a desktop's status changes (#1437). - Account for border width in configure requests (#863). - Unset `pointer_follows_focus` when refocusing the focused node (#1481). - Fix segfault caused by non-null-terminated string (#1503). ## Changes - Allow negated window modifiers to match non-windows (#1232). - Handle sticky nodes in `swap_desktops` (#1298). - Don't include pointer events in the node mask. - Turn `honor_size_hints` into a node setting (#1447). - Set `_NET_WM_WINDOW_TYPE` on monitor root window (#1453). - Revamp signal handling (#1480). - Set the desktop name in the xsession file (#1493). ## Additions - Add layout modifiers to desktop selectors : `.tiled`, `.monocle`, `.user_tiled`, `.user_monocle`. - Add new setting: `borderless_singleton`. - Restore the last window state with `node -t ~`. - Emit life cycle events for receptacles. - Allow setting a node's splitting type (#1291). - Allow cycling the splitting type of a node. - Add `--print-socket-path` option (#1367). - Allow escaping colons in rule tokenization (#1071). # From 0.9.9 to 0.9.10 ## Additions - New node descriptor: `first_ancestor`. - New node modifiers: `horizontal`, `vertical`. ## Changes - The node descriptors `next` and `prev` might now return any node. The previous behavior can be emulated by appending `.!hidden.window`. - The node descriptors `pointed`, `biggest` and `smallest` now return leaves (in particular `pointed` will now return the *id* of a pointed receptacle). The previous behavior can be emulated by appending `.window`. - The *query* command now handles all the possible descriptor-free constraints (for example, `query -N -d .active` now works as expected). - The rules can now match against the window's names (`WM_NAME`). - The configuration script now receives an argument to indicate whether is was executed after a restart or not. - The *intermediate consequences* passed to the external rules command are now in resolved form to avoid unwanted code execution. # From 0.9.8 to 0.9.9 - Fix a memory allocation bug in the implementation of `wm --restart`. - Honor `single_monocle` when the `hidden` flag is toggled. # From 0.9.7 to 0.9.8 - Fix a potential infinite loop. - Fix two bugs having to do with `single_monocle`. - Honor `removal_adjustment` for the spiral automatic insertion scheme. # From 0.9.6 to 0.9.7 This release fixes a bug in the behavior of `single_monocle`. # From 0.9.4 to 0.9.6 ## Additions - New *wm* command: `--restart`. It was already possible to restart `bspwm` without loosing the current state through `--{dump,load}-state`, but this command will also keep the existing subscribers intact. - New settings: `automatic_scheme`, `removal_adjustment`. The automatic insertion mode now provides three ways of inserting a new node: `spiral`, `longest_side` (the default) and `alternate`. Those schemes are described in the README. - New settings: `ignore_ewmh_struts`, `presel_feedback`, `{top,right,bottom,left}_monocle_padding`. - New node descriptor: `smallest`. - New desktop modifier: `active`. ## Changes - The `focused` and `active` modifiers now mean the same thing across every object. - Fullscreen windows are no longer sent to the `above` layer. Within the same layer, fullscreen windows are now above floating windows. If you want a floating window to be above a fullscreen window, you'll need to rely on layers. - Pseudo-tiled windows now shrink automatically. ## Removals - The `paddingless_monocle` setting was removed (and subsumed). The effect of `paddingless_monocle` can now be achieved with: ```shell for side in top right bottom left; do bspc config ${side}_monocle_padding -$(bspc config ${side}_padding) done ``` # From 0.9.3 to 0.9.4 ## Changes - The following events: `node_{manage,unmanage}` are now `node_{add,remove}`. ## Additions - New monitor/desktop/node descriptors: `any`, `newest`. - New node flag: `marked`. - New monitor descriptor: `pointed`. - New *wm* command: `--reorder-monitors`. - Receptacles are now described in the manual. - New `--follow` option added to `node -{m,d,n,s}` and `desktop -{m,s}`. - The *subscribe* command now has the following options: `--fifo`, `--count`. - New settings: `ignore_ewmh_fullscreen`, `mapping_events_count`. # From 0.9.2 to 0.9.3 ## Changes - *click_to_focus* is now a button name. Specifying a boolean is deprecated but will still work (`true` is equivalent to `button1`). ## Additions - `node -r` now accepts a relative fraction argument. - An option was added to `query -{M,D,N}` in order to output names instead of IDs: `--names`. - New rule consequence: `rectangle=WxH+X+Y`. - New settings: `swallow_first_click` and `directional_focus_tightness`. # From 0.9.1 to 0.9.2 ## Changes - Monitors, desktops and nodes have unique IDs, `bspc query -{N,D,M}` returns IDs and events reference objects by ID instead of name. - `bspc` fails verbosely and only returns a single non-zero exit code. - The `DIR` descriptor is based on [right-window](https://github.com/ntrrgc/right-window). - The `CYCLE_DIR` descriptor isn't limited to the current desktop/monitor anymore. (You can emulate the previous behavior by appending a `.local` modifier to the selector.) - `bspc query -{N,D,M}` accepts an optional reference argument used by certain descriptors/modifiers. - Monitors are ordered visually by default. - The following settings: `border_width`, `window_gap` and `*_padding` behave as expected. - External rules also receives the monitor, desktop and node selectors computed from the built-in rules stage as subsequent arguments. - The `focus_follows_pointer` setting is implemented via enter notify events. ## Additions - Nodes can be hidden/shown via the new `hidden` flag. - Node receptacles can be inserted with `node -i`. An example is given in `git show e8aa679`. - Non-tiled nodes can be moved/resized via `node -{v,z}`. - The reference of a selector can be set via the `{NODE,DESKTOP,MONITOR}_SEL#` prefix, example: `bspc node 0x0080000c#south -c` will close the node at the south of `0x0080000c`. - Node descriptors: ``, `pointed`. - Node modifiers: `hidden`, `descendant_of`, `ancestor_of`, `window`, `active`. Example: `bspc query -N 0x00400006 -n .descendant_of` returns the descendants of `0x00400006`. - Desktop descriptor: ``. - Monitor descriptor: ``. - Settings: `pointer_motion_interval`, `pointer_modifier`, `pointer_action{1,2,3}`, `click_to_focus`, `honor_size_hints`. - Event: `pointer_action`. - ICCCM/EWMH atoms: `WM_STATE`, `_NET_WM_STRUT_PARTIAL`. - `bspc` shell completions for `fish`. ## Removals - The `pointer` domain. Pointer actions are handled internally. You need to remove any binding that uses this domain from your `sxhkdrc`. - Settings: `history_aware_focus`, `focus_by_distance`. Both settings are merged into the new `DIR` implementation. - `monitor -r|--remove-desktops`: use `desktop -r|--remove` instead. - `wm -r|--remove-monitor`: use `monitor -r|--remove` instead. # From 0.9 to 0.9.1 ## Overview All the commands that acted on leaves can now be applied on internal nodes (including focusing and preselection). Consequently, the *window* domain is now a *node* domain. Please note that some commands are applied to the leaves of the tree rooted at the selected node and not to the node itself. ## Changes - All the commands that started with `window` now start with `node`. - `-W|--windows`, `-w|--window`, `-w|--to-window` are now `-N|--nodes`, `-n|--node`, `-n|--to-node`. - We now use cardinal directions: `west,south,north,east` instead of `left,down,up,right` (in fact the latter is just plain wrong: the `up,down` axis is perpendicular to the screen). - The `WINDOW_SEL` becomes `NODE_SEL` and now contains a `PATH` specifier to select internal nodes. - The `control` domain is renamed to `wm`. - `restore -{T,H,S}` was unified into `wm -l|--load-state` and `query -{T,H,S}` into `wm -d|--dump-state`. - `control --subscribe` becomes `subscribe`. - `node --toggle` (previously `window --toggle`) is split into `node --state` and `node --flag`. - The preselection direction (resp. ratio) is now set with `node --presel-dir|-p` (resp. `node --presel-ratio|-o`). - The following desktop commands: `--rotate`, `--flip`, `--balance`, `--equalize`, `--circulate` are now node commands. - `query -T ...` outputs JSON. - `query -{M,D,N}`: the descriptor part of the selector is now optional (e.g.: `query -D -d .urgent`). - Many new modifiers were added, some were renamed. The opposite of a modifier is now expressed with the `!` prefix (e.g.: `like` becomes `same_class`, `unlike` becomes `!same_class`, etc.). - Modifiers can now be applied to any descriptor (e.g.: `query -N -n 0x80000d.floating`). - `wm -l` (previously `restore -T`) will now destroy the existing tree and restore from scratch instead of relying on existing monitors and desktops. - `subscribe` (previously `control --subscribe`) now accepts arguments and can receive numerous events from different domains (see the *EVENTS* section of the manual). - `rule -a`: it is now possible to specify the class name *and* instance name (e.g.: `rule -a Foo:bar`). - `presel_border_color` is now `presel_feedback_color`. - `bspwm -v` yields an accurate version. - The monitors are sorted, by default, according to the natural visual hierarchy. ## Additions ### Settings - `single_monocle`. - `paddingless_monocle`. ### Commands - `{node,desktop} --activate`. - `node --layer`. - `desktop --bubble`. - `wm {--add-monitor,--remove-monitor}`. - `monitor --rectangle`. ## Removals ### Commands - `desktop --toggle` - `desktop --cancel-presel` - `control --toggle-visibility`. ### Settings - `apply_floating_atom`. - `auto_alternate`. - `auto_cancel`. - `focused_locked_border_color` - `active_locked_border_color` - `normal_locked_border_color` - `focused_sticky_border_color` - `active_sticky_border_color` - `normal_sticky_border_color` - `focused_private_border_color` - `active_private_border_color` - `normal_private_border_color` - `urgent_border_color` ## Message Translation Guide 0.9 | 0.9.1 -----------------------------------------|---------------------------------- `{left,down,up,right}` | `{west,south,north,east}` `window -r` | `node -o` (`node -r` also exists) `window -e DIR RATIO` | `node @DIR -r RATIO` `window -R DIR DEG` | `node @DIR -R DEG` `window -w` | `node -n` `desktop DESKTOP_SEL -R DEG` | `node @DESKTOP_SEL:/ -R DEG` `desktop DESKTOP_SEL -E` | `node @DESKTOP_SEL:/ -E` `desktop DESKTOP_SEL -B` | `node @DESKTOP_SEL:/ -B` `desktop DESKTOP_SEL -C forward|backward`| `node @DESKTOP_SEL:/ -C forward|backward` `desktop DESKTOP_SEL --cancel-presel` | `bspc query -N -d DESKTOP_SEL | xargs -I id -n 1 bspc node id -p cancel` `window -t floating` | `node -t ~floating` `query -W -w` | `query -N -n .leaf` `query -{T,H,S}` | `wm -d` `restore -{T,H,S}` | `wm -l` ================================================ FILE: doc/CONTRIBUTING.md ================================================ ## Issues Always provide the following information when submitting an issue: - Output of `bspwm -v`. - Content of `bspwmrc`. - Steps to reproduce the problem. ## Pull Requests ### Requirements You must be comfortable with [C][1], [XCB][2] and [Git][3]. ### Coding Style I follow the [Linux Coding Style][4] with the following variations: - [Indent with tabs, align with spaces][5]. - Always use braces when using control structures. An [EditorConfig][6] is included for convinience. [1]: https://www.bell-labs.com/usr/dmr/www/cbook/ [2]: https://xcb.freedesktop.org/tutorial/ [3]: http://git-scm.com/documentation [4]: https://www.kernel.org/doc/Documentation/process/coding-style.rst [5]: http://lea.verou.me/2012/01/why-tabs-are-clearly-superior/ [6]: https://editorconfig.org ## Donations [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=RHTYMMB9SHP68) ================================================ FILE: doc/INSTALL.md ================================================ # Dependencies - libxcb - xcb-util - xcb-util-wm # Installation $ make # make install # Removal # make uninstall # Packages - Arch Linux - [bspwm-git](https://aur.archlinux.org/packages/bspwm-git) - [bspwm (x86_64)](https://www.archlinux.org/packages/community/x86_64/bspwm) - [bspwm (i686)](https://www.archlinux.org/packages/community/i686/bspwm) - Debian - [bspwm](https://tracker.debian.org/pkg/bspwm) - [wiki page](https://wiki.debian.org/bspwm) - Gentoo Linux - [bspwm](https://packages.gentoo.org/packages/x11-wm/bspwm) - [bspwm-git](https://github.com/milomouse/ebuilds) - [FreeBSD](https://www.freshports.org/x11-wm/bspwm) - [Void Linux](https://github.com/voidlinux/documentation/wiki/bspwm) - [BunsenLabs](https://forums.bunsenlabs.org/viewtopic.php?id=567) - [Manjaro Linux](https://forum.manjaro.org/index.php?topic=16994.0) - [Chakra](https://chakraos.org/ccr/packages.php?ID=6537) - [Exherbo](http://git.exherbo.org/summer/packages/x11-wm/bspwm) ================================================ FILE: doc/MISC.md ================================================ # Mathematical background The main data structure is a full binary tree. A binary tree is *full* if each of its node has either two or zero children. If a node has two children it is an internal node, otherwise a leaf. Fundamental theorem: Let I be the number of internal nodes and L the number of leaves, then: L = I + 1 (It can be proved by induction on the number of internal nodes.) This means that when we add a leaf to the tree (when a window is created), we must also add one internal node. ================================================ FILE: doc/TODO.md ================================================ - Add zoom feature (view point distinct from root). - Use BSD `sys/{queue/tree}.h` for {list,tree} structures? ================================================ FILE: doc/bspwm.1 ================================================ '\" t .\" Title: bspwm .\" Author: [see the "Author" section] .\" Generator: DocBook XSL Stylesheets vsnapshot .\" Date: 10/08/2025 .\" Manual: Bspwm Manual .\" Source: Bspwm 0.9.12 .\" Language: English .\" .TH "BSPWM" "1" "10/08/2025" "Bspwm 0\&.9\&.12" "Bspwm Manual" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .\" http://bugs.debian.org/507673 .\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- .\" disable hyphenation .nh .\" disable justification (adjust text to left margin only) .ad l .\" ----------------------------------------------------------------- .\" * MAIN CONTENT STARTS HERE * .\" ----------------------------------------------------------------- .SH "NAME" bspwm \- Binary space partitioning window manager .SH "SYNOPSIS" .sp \fBbspwm\fR [\fB\-h\fR|\fB\-v\fR|\fB\-c\fR \fICONFIG_PATH\fR] .sp \fBbspc \-\-print\-socket\-path\fR .sp \fBbspc\fR \fIDOMAIN\fR [\fISELECTOR\fR] \fICOMMANDS\fR .sp \fBbspc\fR \fICOMMAND\fR [\fIOPTIONS\fR] [\fIARGUMENTS\fR] .SH "DESCRIPTION" .sp \fBbspwm\fR is a tiling window manager that represents windows as the leaves of a full binary tree\&. .sp It is controlled and configured via \fBbspc\fR\&. .SH "OPTIONS" .PP \fB\-h\fR .RS 4 Print the synopsis and exit\&. .RE .PP \fB\-v\fR .RS 4 Print the version and exit\&. .RE .PP \fB\-c\fR \fICONFIG_PATH\fR .RS 4 Use the given configuration file\&. .RE .PP \fB\-\-print\-socket\-path\fR .RS 4 Print the \fBbspwm\fR socket path and exit\&. .RE .SH "COMMON DEFINITIONS" .sp .if n \{\ .RS 4 .\} .nf DIR := north | west | south | east CYCLE_DIR := next | prev .fi .if n \{\ .RE .\} .SH "SELECTORS" .sp Selectors are used to select a target node, desktop, or monitor\&. A selector can either describe the target relatively or name it globally\&. .sp Selectors consist of an optional reference, a descriptor and any number of non\-conflicting modifiers as follows: .sp .if n \{\ .RS 4 .\} .nf [REFERENCE#]DESCRIPTOR(\&.MODIFIER)* .fi .if n \{\ .RE .\} .sp The relative targets are computed in relation to the given reference (the default reference value is \fBfocused\fR)\&. .sp An exclamation mark can be prepended to any modifier in order to reverse its meaning\&. .sp The following characters cannot be used in monitor or desktop names: \fB#\fR, \fB:\fR, \fB\&.\fR\&. .sp The special selector \fB%\fR can be used to select a monitor or a desktop with an invalid name\&. .SS "Node" .sp Select a node\&. .sp .if n \{\ .RS 4 .\} .nf NODE_SEL := [NODE_SEL#](DIR|CYCLE_DIR|PATH|any|first_ancestor|last|newest| older|newer|focused|pointed|biggest|smallest| )[\&.[!]focused][\&.[!]active][\&.[!]automatic][\&.[!]local] [\&.[!]leaf][\&.[!]window][\&.[!]STATE][\&.[!]FLAG][\&.[!]LAYER][\&.[!]SPLIT_TYPE] [\&.[!]same_class][\&.[!]descendant_of][\&.[!]ancestor_of] STATE := tiled|pseudo_tiled|floating|fullscreen FLAG := hidden|sticky|private|locked|marked|urgent LAYER := below|normal|above SPLIT_TYPE := horizontal|vertical PATH := @[DESKTOP_SEL:][[/]JUMP](/JUMP)* JUMP := first|1|second|2|brother|parent|DIR .fi .if n \{\ .RE .\} .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBDescriptors\fR .RS 4 .PP \fIDIR\fR .RS 4 Selects the window in the given (spacial) direction relative to the reference node\&. .RE .PP \fICYCLE_DIR\fR .RS 4 Selects the node in the given (cyclic) direction relative to the reference node within a depth\-first in\-order traversal of the tree\&. .RE .PP \fIPATH\fR .RS 4 Selects the node at the given path\&. .RE .PP any .RS 4 Selects the first node that matches the given selectors\&. .RE .PP first_ancestor .RS 4 Selects the first ancestor of the reference node that matches the given selectors\&. .RE .PP last .RS 4 Selects the previously focused node relative to the reference node\&. .RE .PP newest .RS 4 Selects the newest node in the history of the focused node\&. .RE .PP older .RS 4 Selects the node older than the reference node in the history\&. .RE .PP newer .RS 4 Selects the node newer than the reference node in the history\&. .RE .PP focused .RS 4 Selects the currently focused node\&. .RE .PP pointed .RS 4 Selects the leaf under the pointer\&. .RE .PP biggest .RS 4 Selects the biggest leaf\&. .RE .PP smallest .RS 4 Selects the smallest leaf\&. .RE .PP .RS 4 Selects the node with the given ID\&. .RE .RE .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBPath Jumps\fR .RS 4 .sp The initial node is the focused node (or the root if the path starts with \fI/\fR) of the reference desktop (or the selected desktop if the path has a \fIDESKTOP_SEL\fR prefix)\&. .PP 1|first .RS 4 Jumps to the first child\&. .RE .PP 2|second .RS 4 Jumps to the second child\&. .RE .PP brother .RS 4 Jumps to the brother node\&. .RE .PP parent .RS 4 Jumps to the parent node\&. .RE .PP \fIDIR\fR .RS 4 Jumps to the node holding the edge in the given direction\&. .RE .RE .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBModifiers\fR .RS 4 .PP [!]focused .RS 4 Only consider the focused node\&. .RE .PP [!]active .RS 4 Only consider nodes that are the focused node of their desktop\&. .RE .PP [!]automatic .RS 4 Only consider nodes in automatic insertion mode\&. See also \fB\-\-presel\-dir\fR under \fBNode\fR in the \fBDOMAINS\fR section below\&. .RE .PP [!]local .RS 4 Only consider nodes in the reference desktop\&. .RE .PP [!]leaf .RS 4 Only consider leaf nodes\&. .RE .PP [!]window .RS 4 Only consider nodes that hold a window\&. .RE .PP [!](tiled|pseudo_tiled|floating|fullscreen) .RS 4 Only consider windows in the given state\&. .RE .PP [!]same_class .RS 4 Only consider windows that have the same class as the reference window\&. .RE .PP [!]descendant_of .RS 4 Only consider nodes that are descendants of the reference node\&. .RE .PP [!]ancestor_of .RS 4 Only consider nodes that are ancestors of the reference node\&. .RE .PP [!](hidden|sticky|private|locked|marked|urgent) .RS 4 Only consider windows that have the given flag set\&. .RE .PP [!](below|normal|above) .RS 4 Only consider windows in the given layer\&. .RE .PP [!](horizontal|vertical) .RS 4 Only consider nodes with the given split type\&. .RE .RE .SS "Desktop" .sp Select a desktop\&. .sp .if n \{\ .RS 4 .\} .nf DESKTOP_SEL := [DESKTOP_SEL#](CYCLE_DIR|any|last|newest|older|newer| [MONITOR_SEL:](focused|^)| |)[\&.[!]focused][\&.[!]active] [\&.[!]occupied][\&.[!]urgent][\&.[!]local] [\&.[!]LAYOUT][\&.[!]user_LAYOUT] LAYOUT := tiled|monocle .fi .if n \{\ .RE .\} .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBDescriptors\fR .RS 4 .PP \fICYCLE_DIR\fR .RS 4 Selects the desktop in the given direction relative to the reference desktop\&. .RE .PP any .RS 4 Selects the first desktop that matches the given selectors\&. .RE .PP last .RS 4 Selects the previously focused desktop relative to the reference desktop\&. .RE .PP newest .RS 4 Selects the newest desktop in the history of the focused desktops\&. .RE .PP older .RS 4 Selects the desktop older than the reference desktop in the history\&. .RE .PP newer .RS 4 Selects the desktop newer than the reference desktop in the history\&. .RE .PP focused .RS 4 Selects the currently focused desktop\&. .RE .PP ^ .RS 4 Selects the nth desktop\&. If \fBMONITOR_SEL\fR is given, selects the nth desktop on the selected monitor\&. .RE .PP .RS 4 Selects the desktop with the given ID\&. .RE .PP .RS 4 Selects the desktop with the given name\&. .RE .RE .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBModifiers\fR .RS 4 .PP [!]focused .RS 4 Only consider the focused desktop\&. .RE .PP [!]active .RS 4 Only consider desktops that are the focused desktop of their monitor\&. .RE .PP [!]occupied .RS 4 Only consider occupied desktops\&. .RE .PP [!]urgent .RS 4 Only consider urgent desktops\&. .RE .PP [!]local .RS 4 Only consider desktops inside the reference monitor\&. .RE .PP [!](tiled|monocle) .RS 4 Only consider desktops with the given layout\&. .RE .PP [!](user_tiled|user_monocle) .RS 4 Only consider desktops which have the given layout as userLayout\&. .RE .RE .SS "Monitor" .sp Select a monitor\&. .sp .if n \{\ .RS 4 .\} .nf MONITOR_SEL := [MONITOR_SEL#](DIR|CYCLE_DIR|any|last|newest|older|newer| focused|pointed|primary|^| |)[\&.[!]focused][\&.[!]occupied] .fi .if n \{\ .RE .\} .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBDescriptors\fR .RS 4 .PP \fIDIR\fR .RS 4 Selects the monitor in the given (spacial) direction relative to the reference monitor\&. .RE .PP \fICYCLE_DIR\fR .RS 4 Selects the monitor in the given (cyclic) direction relative to the reference monitor\&. .RE .PP any .RS 4 Selects the first monitor that matches the given selectors\&. .RE .PP last .RS 4 Selects the previously focused monitor relative to the reference monitor\&. .RE .PP newest .RS 4 Selects the newest monitor in the history of the focused monitors\&. .RE .PP older .RS 4 Selects the monitor older than the reference monitor in the history\&. .RE .PP newer .RS 4 Selects the monitor newer than the reference monitor in the history\&. .RE .PP focused .RS 4 Selects the currently focused monitor\&. .RE .PP pointed .RS 4 Selects the monitor under the pointer\&. .RE .PP primary .RS 4 Selects the primary monitor\&. .RE .PP ^ .RS 4 Selects the nth monitor\&. .RE .PP .RS 4 Selects the monitor with the given ID\&. .RE .PP .RS 4 Selects the monitor with the given name\&. .RE .RE .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBModifiers\fR .RS 4 .PP [!]focused .RS 4 Only consider the focused monitor\&. .RE .PP [!]occupied .RS 4 Only consider monitors where the focused desktop is occupied\&. .RE .RE .SH "WINDOW STATES" .PP tiled .RS 4 Its size and position are determined by the window tree\&. .RE .PP pseudo_tiled .RS 4 A tiled window that automatically shrinks but doesn\(cqt stretch beyond its floating size\&. .RE .PP floating .RS 4 Can be moved/resized freely\&. Although it doesn\(cqt use any tiling space, it is still part of the window tree\&. .RE .PP fullscreen .RS 4 Fills its monitor rectangle and has no borders\&. .RE .SH "NODE FLAGS" .PP hidden .RS 4 Is hidden and doesn\(cqt occupy any tiling space\&. .RE .PP sticky .RS 4 Stays in the focused desktop of its monitor\&. .RE .PP private .RS 4 Tries to keep the same tiling position/size\&. .RE .PP locked .RS 4 Ignores the \fBnode \-\-close\fR message\&. .RE .PP marked .RS 4 Is marked (useful for deferred actions)\&. A marked node becomes unmarked after being sent on a preselected node\&. .RE .PP urgent .RS 4 Has its urgency hint set\&. This flag is set externally\&. .RE .SH "STACKING LAYERS" .sp There\(cqs three stacking layers: BELOW, NORMAL and ABOVE\&. .sp In each layer, the windows are orderered as follows: tiled & pseudo\-tiled < floating < fullscreen\&. .SH "RECEPTACLES" .sp A leaf node that doesn\(cqt hold any window is called a receptacle\&. When a node is inserted on a receptacle in automatic mode, it will replace the receptacle\&. A receptacle can be inserted on a node, preselected and killed\&. Receptacles can therefore be used to build a tree whose leaves are receptacles\&. Using the appropriate rules, one can then send windows on the leaves of this tree\&. This feature is used in \fIexamples/receptacles\fR to store and recreate layouts\&. .SH "DOMAINS" .SS "Node" .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBGeneral Syntax\fR .RS 4 .sp node [\fINODE_SEL\fR] \fICOMMANDS\fR .sp If \fINODE_SEL\fR is omitted, \fBfocused\fR is assumed\&. .RE .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBCommands\fR .RS 4 .PP \fB\-f\fR, \fB\-\-focus\fR [\fINODE_SEL\fR] .RS 4 Focus the selected or given node\&. .RE .PP \fB\-a\fR, \fB\-\-activate\fR [\fINODE_SEL\fR] .RS 4 Activate the selected or given node\&. .RE .PP \fB\-d\fR, \fB\-\-to\-desktop\fR \fIDESKTOP_SEL\fR [\fB\-\-follow\fR] .RS 4 Send the selected node to the given desktop\&. If \fB\-\-follow\fR is passed, the focused node will stay focused\&. .RE .PP \fB\-m\fR, \fB\-\-to\-monitor\fR \fIMONITOR_SEL\fR [\fB\-\-follow\fR] .RS 4 Send the selected node to the given monitor\&. If \fB\-\-follow\fR is passed, the focused node will stay focused\&. .RE .PP \fB\-n\fR, \fB\-\-to\-node\fR \fINODE_SEL\fR [\fB\-\-follow\fR] .RS 4 Send the selected node on the given node\&. If \fB\-\-follow\fR is passed, the focused node will stay focused\&. .RE .PP \fB\-s\fR, \fB\-\-swap\fR \fINODE_SEL\fR [\fB\-\-follow\fR] .RS 4 Swap the selected node with the given node\&. If \fB\-\-follow\fR is passed, the focused node will stay focused\&. .RE .PP \fB\-p\fR, \fB\-\-presel\-dir\fR [~]\fIDIR\fR|cancel .RS 4 Preselect the splitting area of the selected node (or cancel the preselection)\&. If \fB~\fR is prepended to \fIDIR\fR and the current preselection direction matches \fIDIR\fR, then the argument is interpreted as \fBcancel\fR\&. A node with a preselected area is said to be in "manual insertion mode"\&. .RE .PP \fB\-o\fR, \fB\-\-presel\-ratio\fR \fIRATIO\fR .RS 4 Set the splitting ratio of the preselection area\&. .RE .PP \fB\-v\fR, \fB\-\-move\fR \fIdx\fR \fIdy\fR .RS 4 Move the selected window by \fIdx\fR pixels horizontally and \fIdy\fR pixels vertically\&. .RE .PP \fB\-z\fR, \fB\-\-resize\fR top|left|bottom|right|top_left|top_right|bottom_right|bottom_left \fIdx\fR \fIdy\fR .RS 4 Resize the selected window by moving the given handle by \fIdx\fR pixels horizontally and \fIdy\fR pixels vertically\&. .RE .PP \fB\-y\fR, \fB\-\-type\fR \fICYCLE_DIR\fR|horizontal|vertical .RS 4 Set or cycle the splitting type of the selected node\&. .RE .PP \fB\-r\fR, \fB\-\-ratio\fR \fIRATIO\fR|(+|\-)(\fIPIXELS\fR|\fIFRACTION\fR) .RS 4 Set the splitting ratio of the selected node (0 < \fIRATIO\fR < 1)\&. .RE .PP \fB\-R\fR, \fB\-\-rotate\fR \fI90|270|180\fR .RS 4 Rotate the tree rooted at the selected node\&. .RE .PP \fB\-F\fR, \fB\-\-flip\fR \fIhorizontal|vertical\fR .RS 4 Flip the tree rooted at selected node\&. .RE .PP \fB\-E\fR, \fB\-\-equalize\fR .RS 4 Reset the split ratios of the tree rooted at the selected node to their default value\&. .RE .PP \fB\-B\fR, \fB\-\-balance\fR .RS 4 Adjust the split ratios of the tree rooted at the selected node so that all windows occupy the same area\&. .RE .PP \fB\-C\fR, \fB\-\-circulate\fR forward|backward .RS 4 Circulate the windows of the tree rooted at the selected node\&. .RE .PP \fB\-t\fR, \fB\-\-state\fR ~|[~]\fISTATE\fR .RS 4 Set the state of the selected window\&. If \fB~\fR is present and the current state matches \fISTATE\fR, then the argument is interpreted as its last state\&. If the argument is just \fB~\fR with \fISTATE\fR omitted, then the state of the selected window is set to its last state\&. .RE .PP \fB\-g\fR, \fB\-\-flag\fR hidden|sticky|private|locked|marked[=on|off] .RS 4 Set or toggle the given flag for the selected node\&. .RE .PP \fB\-l\fR, \fB\-\-layer\fR below|normal|above .RS 4 Set the stacking layer of the selected window\&. .RE .PP \fB\-i\fR, \fB\-\-insert\-receptacle\fR .RS 4 Insert a receptacle node at the selected node\&. .RE .PP \fB\-c\fR, \fB\-\-close\fR .RS 4 Close the windows rooted at the selected node\&. .RE .PP \fB\-k\fR, \fB\-\-kill\fR .RS 4 Kill the windows rooted at the selected node\&. .RE .RE .SS "Desktop" .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBGeneral Syntax\fR .RS 4 .sp desktop [\fIDESKTOP_SEL\fR] \fICOMMANDS\fR .sp If \fIDESKTOP_SEL\fR is omitted, \fBfocused\fR is assumed\&. .RE .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBCOMMANDS\fR .RS 4 .PP \fB\-f\fR, \fB\-\-focus\fR [\fIDESKTOP_SEL\fR] .RS 4 Focus the selected or given desktop\&. .RE .PP \fB\-a\fR, \fB\-\-activate\fR [\fIDESKTOP_SEL\fR] .RS 4 Activate the selected or given desktop\&. .RE .PP \fB\-m\fR, \fB\-\-to\-monitor\fR \fIMONITOR_SEL\fR [\fB\-\-follow\fR] .RS 4 Send the selected desktop to the given monitor\&. If \fB\-\-follow\fR is passed, the focused desktop will stay focused\&. .RE .PP \fB\-s\fR, \fB\-\-swap\fR \fIDESKTOP_SEL\fR [\fB\-\-follow\fR] .RS 4 Swap the selected desktop with the given desktop\&. If \fB\-\-follow\fR is passed, the focused desktop will stay focused\&. .RE .PP \fB\-l\fR, \fB\-\-layout\fR \fICYCLE_DIR\fR|monocle|tiled .RS 4 Set or cycle the layout of the selected desktop\&. .RE .PP \fB\-n\fR, \fB\-\-rename\fR .RS 4 Rename the selected desktop\&. .RE .PP \fB\-b\fR, \fB\-\-bubble\fR \fICYCLE_DIR\fR .RS 4 Bubble the selected desktop in the given direction\&. .RE .PP \fB\-r\fR, \fB\-\-remove\fR .RS 4 Remove the selected desktop\&. .RE .RE .SS "Monitor" .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBGeneral Syntax\fR .RS 4 .sp monitor [\fIMONITOR_SEL\fR] \fICOMMANDS\fR .sp If \fIMONITOR_SEL\fR is omitted, \fBfocused\fR is assumed\&. .RE .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBCommands\fR .RS 4 .PP \fB\-f\fR, \fB\-\-focus\fR [\fIMONITOR_SEL\fR] .RS 4 Focus the selected or given monitor\&. .RE .PP \fB\-s\fR, \fB\-\-swap\fR \fIMONITOR_SEL\fR .RS 4 Swap the selected monitor with the given monitor\&. .RE .PP \fB\-a\fR, \fB\-\-add\-desktops\fR \&... .RS 4 Create desktops with the given names in the selected monitor\&. .RE .PP \fB\-o\fR, \fB\-\-reorder\-desktops\fR \&... .RS 4 Reorder the desktops of the selected monitor to match the given order\&. .RE .PP \fB\-d\fR, \fB\-\-reset\-desktops\fR \&... .RS 4 Rename, add or remove desktops depending on whether the number of given names is equal, superior or inferior to the number of existing desktops\&. .RE .PP \fB\-g\fR, \fB\-\-rectangle\fR WxH+X+Y .RS 4 Set the rectangle of the selected monitor\&. .RE .PP \fB\-n\fR, \fB\-\-rename\fR .RS 4 Rename the selected monitor\&. .RE .PP \fB\-r\fR, \fB\-\-remove\fR .RS 4 Remove the selected monitor\&. .RE .RE .SS "Query" .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBGeneral Syntax\fR .RS 4 .sp query \fICOMMANDS\fR [\fIOPTIONS\fR] .RE .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBCommands\fR .RS 4 .sp The optional selectors are references\&. .PP \fB\-N\fR, \fB\-\-nodes\fR [\fINODE_SEL\fR] .RS 4 List the IDs of the matching nodes\&. .RE .PP \fB\-D\fR, \fB\-\-desktops\fR [\fIDESKTOP_SEL\fR] .RS 4 List the IDs (or names) of the matching desktops\&. .RE .PP \fB\-M\fR, \fB\-\-monitors\fR [\fIMONITOR_SEL\fR] .RS 4 List the IDs (or names) of the matching monitors\&. .RE .PP \fB\-T\fR, \fB\-\-tree\fR .RS 4 Print a JSON representation of the matching item\&. .RE .RE .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBOptions\fR .RS 4 .PP \fB\-m\fR,\fB\-\-monitor\fR [\fIMONITOR_SEL\fR|\fIMONITOR_MODIFIERS\fR], \fB\-d\fR,\fB\-\-desktop\fR [\fIDESKTOP_SEL\fR|\fIDESKTOP_MODIFIERS\fR], \fB\-n\fR,\fB\-\-node\fR [\fINODE_SEL\fR|\fINODE_MODIFIERS\fR] .RS 4 Constrain matches to the selected monitors, desktops or nodes\&. .RE .PP \fB\-\-names\fR .RS 4 Print names instead of IDs\&. Can only be used with \fI\-M\fR and \fI\-D\fR\&. .RE .RE .SS "Wm" .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBGeneral Syntax\fR .RS 4 .sp wm \fICOMMANDS\fR .RE .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBCommands\fR .RS 4 .PP \fB\-d\fR, \fB\-\-dump\-state\fR .RS 4 Dump the current world state on standard output\&. .RE .PP \fB\-l\fR, \fB\-\-load\-state\fR .RS 4 Load a world state from the given file\&. The path must be absolute\&. .RE .PP \fB\-a\fR, \fB\-\-add\-monitor\fR WxH+X+Y .RS 4 Add a monitor for the given name and rectangle\&. .RE .PP \fB\-O\fR, \fB\-\-reorder\-monitors\fR \&... .RS 4 Reorder the list of monitors to match the given order\&. .RE .PP \fB\-o\fR, \fB\-\-adopt\-orphans\fR .RS 4 Manage all the unmanaged windows remaining from a previous session\&. .RE .PP \fB\-h\fR, \fB\-\-record\-history\fR on|off .RS 4 Enable or disable the recording of node focus history\&. .RE .PP \fB\-g\fR, \fB\-\-get\-status\fR .RS 4 Print the current status information\&. .RE .PP \fB\-r\fR, \fB\-\-restart\fR .RS 4 Restart the window manager .RE .RE .SS "Rule" .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBGeneral Syntax\fR .RS 4 .sp rule \fICOMMANDS\fR .RE .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBCommands\fR .RS 4 .PP \fB\-a\fR, \fB\-\-add\fR (|*)[:(|*)[:(|*)]] [\fB\-o\fR|\fB\-\-one\-shot\fR] [monitor=MONITOR_SEL|desktop=DESKTOP_SEL|node=NODE_SEL] [state=STATE] [layer=LAYER] [honor_size_hints=(true|false|tiled|floating)] [split_dir=DIR] [split_ratio=RATIO] [(hidden|sticky|private|locked|marked|center|follow|manage|focus|border)=(on|off)] [rectangle=WxH+X+Y] .RS 4 Create a new rule\&. Colons in the \fIinstance_name\fR, \fIclass_name\fR, or \fIname\fR fields can be escaped with a backslash\&. .RE .PP \fB\-r\fR, \fB\-\-remove\fR ^|head|tail|(|*)[:(|*)[:(|*)]]\&... .RS 4 Remove the given rules\&. .RE .PP \fB\-l\fR, \fB\-\-list\fR .RS 4 List the rules\&. .RE .RE .SS "Config" .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBGeneral Syntax\fR .RS 4 .PP config [\-m \fIMONITOR_SEL\fR|\-d \fIDESKTOP_SEL\fR|\-n \fINODE_SEL\fR] [] .RS 4 Get or set the value of \&. .RE .RE .SS "Subscribe" .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBGeneral Syntax\fR .RS 4 .PP subscribe [\fIOPTIONS\fR] (all|report|monitor|desktop|node|\&...)* .RS 4 Continuously print events\&. See the \fBEVENTS\fR section for the description of each event\&. .RE .RE .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBOptions\fR .RS 4 .PP \fB\-f\fR, \fB\-\-fifo\fR .RS 4 Print a path to a FIFO from which events can be read and return\&. .RE .PP \fB\-c\fR, \fB\-\-count\fR \fICOUNT\fR .RS 4 Stop the corresponding \fBbspc\fR process after having received \fICOUNT\fR events\&. .RE .RE .SS "Quit" .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBGeneral Syntax\fR .RS 4 .PP quit [] .RS 4 Quit with an optional exit status\&. .RE .RE .SH "EXIT CODES" .sp If the server can\(cqt handle a message, \fBbspc\fR will return with a non\-zero exit code\&. .SH "SETTINGS" .sp Colors are in the form \fI#RRGGBB\fR, booleans are \fItrue\fR, \fIon\fR, \fIfalse\fR or \fIoff\fR\&. .sp All the boolean settings are \fIfalse\fR by default unless stated otherwise\&. .SS "Global Settings" .PP \fInormal_border_color\fR .RS 4 Color of the border of an unfocused window\&. .RE .PP \fIactive_border_color\fR .RS 4 Color of the border of a focused window of an unfocused monitor\&. .RE .PP \fIfocused_border_color\fR .RS 4 Color of the border of a focused window of a focused monitor\&. .RE .PP \fIpresel_feedback_color\fR .RS 4 Color of the \fBnode \-\-presel\-{dir,ratio}\fR message feedback area\&. .RE .PP \fIsplit_ratio\fR .RS 4 Default split ratio\&. .RE .PP \fIstatus_prefix\fR .RS 4 Prefix prepended to each of the status lines\&. .RE .PP \fIexternal_rules_command\fR .RS 4 Absolute path to the command used to retrieve rule consequences\&. The command will receive the following arguments: window ID, class name, instance name, and intermediate consequences\&. The output of that command must have the following format: \fBkey1=value1 key2=value2 \&...\fR (the valid key/value pairs are given in the description of the \fIrule\fR command)\&. .RE .PP \fIautomatic_scheme\fR .RS 4 The insertion scheme used when the insertion point is in automatic mode\&. Accept the following values: \fBlongest_side\fR, \fBalternate\fR, \fBspiral\fR\&. .RE .PP \fIinitial_polarity\fR .RS 4 On which child should a new window be attached when adding a window on a single window tree in automatic mode\&. Accept the following values: \fBfirst_child\fR, \fBsecond_child\fR\&. .RE .PP \fIdirectional_focus_tightness\fR .RS 4 The tightness of the algorithm used to decide whether a window is on the \fIDIR\fR side of another window\&. Accept the following values: \fBhigh\fR, \fBlow\fR\&. .RE .PP \fIremoval_adjustment\fR .RS 4 Adjust the brother when unlinking a node from the tree in accordance with the automatic insertion scheme\&. .RE .PP \fIpresel_feedback\fR .RS 4 Draw the preselection feedback area\&. Defaults to \fItrue\fR\&. .RE .PP \fIborderless_monocle\fR .RS 4 Remove borders of tiled windows for the \fBmonocle\fR desktop layout\&. .RE .PP \fIgapless_monocle\fR .RS 4 Remove gaps of tiled windows for the \fBmonocle\fR desktop layout\&. .RE .PP \fItop_monocle_padding\fR, \fIright_monocle_padding\fR, \fIbottom_monocle_padding\fR, \fIleft_monocle_padding\fR .RS 4 Padding space added at the sides of the screen for the \fBmonocle\fR desktop layout\&. .RE .PP \fIsingle_monocle\fR .RS 4 Set the desktop layout to \fBmonocle\fR if there\(cqs only one tiled window in the tree\&. .RE .PP \fIborderless_singleton\fR .RS 4 Remove borders of the only window on the only monitor regardless its layout\&. .RE .PP \fIpointer_motion_interval\fR .RS 4 The minimum interval, in milliseconds, between two motion notify events\&. .RE .PP \fIpointer_modifier\fR .RS 4 Keyboard modifier used for moving or resizing windows\&. Accept the following values: \fBshift\fR, \fBcontrol\fR, \fBlock\fR, \fBmod1\fR, \fBmod2\fR, \fBmod3\fR, \fBmod4\fR, \fBmod5\fR\&. .RE .PP \fIpointer_action1\fR, \fIpointer_action2\fR, \fIpointer_action3\fR .RS 4 Action performed when pressing \fIpointer_modifier\fR + \fIbutton\fR\&. Accept the following values: \fBmove\fR, \fBresize_side\fR, \fBresize_corner\fR, \fBfocus\fR, \fBnone\fR\&. .RE .PP \fIclick_to_focus\fR .RS 4 Button used for focusing a window (or a monitor)\&. The possible values are: \fBbutton1\fR, \fBbutton2\fR, \fBbutton3\fR, \fBany\fR, \fBnone\fR\&. Defaults to \fBbutton1\fR\&. .RE .PP \fIswallow_first_click\fR .RS 4 Don\(cqt replay the click that makes a window focused if \fIclick_to_focus\fR isn\(cqt \fBnone\fR\&. .RE .PP \fIfocus_follows_pointer\fR .RS 4 Focus the window under the pointer\&. .RE .PP \fIpointer_follows_focus\fR .RS 4 When focusing a window, put the pointer at its center\&. .RE .PP \fIpointer_follows_monitor\fR .RS 4 When focusing a monitor, put the pointer at its center\&. .RE .PP \fImapping_events_count\fR .RS 4 Handle the next \fBmapping_events_count\fR mapping notify events\&. A negative value implies that every event needs to be handled\&. .RE .PP \fIignore_ewmh_focus\fR .RS 4 Ignore EWMH focus requests coming from applications\&. .RE .PP \fIignore_ewmh_fullscreen\fR .RS 4 Block the fullscreen state transitions that originate from an EWMH request\&. The possible values are: \fBnone\fR, \fBall\fR, or a comma separated list of the following values: \fBenter\fR, \fBexit\fR\&. .RE .PP \fIignore_ewmh_struts\fR .RS 4 Ignore strut hinting from clients requesting to reserve space (i\&.e\&. task bars)\&. .RE .PP \fIcenter_pseudo_tiled\fR .RS 4 Center pseudo tiled windows into their tiling rectangles\&. Defaults to \fItrue\fR\&. .RE .PP \fIremove_disabled_monitors\fR .RS 4 Consider disabled monitors as disconnected\&. .RE .PP \fIremove_unplugged_monitors\fR .RS 4 Remove unplugged monitors\&. .RE .PP \fImerge_overlapping_monitors\fR .RS 4 Merge overlapping monitors (the bigger remains)\&. .RE .SS "Monitor and Desktop Settings" .PP \fItop_padding\fR, \fIright_padding\fR, \fIbottom_padding\fR, \fIleft_padding\fR .RS 4 Padding space added at the sides of the monitor or desktop\&. .RE .SS "Desktop Settings" .PP \fIwindow_gap\fR .RS 4 Size of the gap that separates windows\&. .RE .SS "Node Settings" .PP \fIborder_width\fR .RS 4 Window border width\&. .RE .PP \fIhonor_size_hints\fR .RS 4 If \fItrue\fR, apply ICCCM window size hints to all windows\&. If \fIfloating\fR, only apply them to floating and pseudo tiled windows\&. If \fItiled\fR, only apply them to tiled windows\&. If \fIfalse\fR, don\(cqt apply them\&. Defaults to \fIfalse\fR\&. .RE .SH "POINTER BINDINGS" .PP \fIclick_to_focus\fR .RS 4 Focus the window (or the monitor) under the pointer if the value isn\(cqt \fBnone\fR\&. .RE .PP \fIpointer_modifier\fR + \fIbutton1\fR .RS 4 Move the window under the pointer\&. .RE .PP \fIpointer_modifier\fR + \fIbutton2\fR .RS 4 Resize the window under the pointer by dragging the nearest side\&. .RE .PP \fIpointer_modifier\fR + \fIbutton3\fR .RS 4 Resize the window under the pointer by dragging the nearest corner\&. .RE .sp The behavior of \fIpointer_modifier\fR + \fIbutton\fR can be modified through the \fIpointer_action\fR setting\&. .SH "EVENTS" .PP \fIreport\fR .RS 4 See the next section for the description of the format\&. .RE .PP \fImonitor_add \fR .RS 4 A monitor is added\&. .RE .PP \fImonitor_rename \fR .RS 4 A monitor is renamed\&. .RE .PP \fImonitor_remove \fR .RS 4 A monitor is removed\&. .RE .PP \fImonitor_swap \fR .RS 4 A monitor is swapped\&. .RE .PP \fImonitor_focus \fR .RS 4 A monitor is focused\&. .RE .PP \fImonitor_geometry \fR .RS 4 The geometry of a monitor changed\&. .RE .PP \fIdesktop_add \fR .RS 4 A desktop is added\&. .RE .PP \fIdesktop_rename \fR .RS 4 A desktop is renamed\&. .RE .PP \fIdesktop_remove \fR .RS 4 A desktop is removed\&. .RE .PP \fIdesktop_swap \fR .RS 4 A desktop is swapped\&. .RE .PP \fIdesktop_transfer \fR .RS 4 A desktop is transferred\&. .RE .PP \fIdesktop_focus \fR .RS 4 A desktop is focused\&. .RE .PP \fIdesktop_activate \fR .RS 4 A desktop is activated\&. .RE .PP \fIdesktop_layout tiled|monocle\fR .RS 4 The layout of a desktop changed\&. .RE .PP \fInode_add \fR .RS 4 A node is added\&. .RE .PP \fInode_remove \fR .RS 4 A node is removed\&. .RE .PP \fInode_swap \fR .RS 4 A node is swapped\&. .RE .PP \fInode_transfer \fR .RS 4 A node is transferred\&. .RE .PP \fInode_focus \fR .RS 4 A node is focused\&. .RE .PP \fInode_activate \fR .RS 4 A node is activated\&. .RE .PP \fInode_presel (dir DIR|ratio RATIO|cancel)\fR .RS 4 A node is preselected\&. .RE .PP \fInode_stack below|above \fR .RS 4 A node is stacked below or above another node\&. .RE .PP \fInode_geometry \fR .RS 4 The geometry of a window changed\&. .RE .PP \fInode_state tiled|pseudo_tiled|floating|fullscreen on|off\fR .RS 4 The state of a window changed\&. .RE .PP \fInode_flag hidden|sticky|private|locked|marked|urgent on|off\fR .RS 4 One of the flags of a node changed\&. .RE .PP \fInode_layer below|normal|above\fR .RS 4 The layer of a window changed\&. .RE .PP \fIpointer_action move|resize_corner|resize_side begin|end\fR .RS 4 A pointer action occurred\&. .RE .sp Please note that \fBbspwm\fR initializes monitors before it reads messages on its socket, therefore the initial monitor events can\(cqt be received\&. .SH "REPORT FORMAT" .sp Each report event message is composed of items separated by colons\&. .sp Each item has the form \fI\fR where \fI\fR is the first character of the item\&. .PP \fIM\fR .RS 4 Focused monitor\&. .RE .PP \fIm\fR .RS 4 Unfocused monitor\&. .RE .PP \fIO\fR .RS 4 Occupied focused desktop\&. .RE .PP \fIo\fR .RS 4 Occupied unfocused desktop\&. .RE .PP \fIF\fR .RS 4 Free focused desktop\&. .RE .PP \fIf\fR .RS 4 Free unfocused desktop\&. .RE .PP \fIU\fR .RS 4 Urgent focused desktop\&. .RE .PP \fIu\fR .RS 4 Urgent unfocused desktop\&. .RE .PP \fIL(T|M)\fR .RS 4 Layout of the focused desktop of a monitor\&. .RE .PP \fIT(T|P|F|=|@)\fR .RS 4 State of the focused node of a focused desktop\&. .RE .PP \fIG(S?P?L?M?)\fR .RS 4 Active flags of the focused node of a focused desktop\&. .RE .SH "ENVIRONMENT VARIABLES" .PP \fIBSPWM_SOCKET\fR .RS 4 The path of the socket used for the communication between \fBbspc\fR and \fBbspwm\fR\&. If it isn\(cqt defined, then the following path is used: \fI/tmp/bspwm__\-socket\fR\&. .RE .SH "CONTRIBUTORS" .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Steven Allen .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Thomas Adam .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Ivan Kanakarakis .RE .SH "AUTHOR" .sp Bastien Dejean ================================================ FILE: doc/bspwm.1.asciidoc ================================================ :man source: Bspwm :man version: {revnumber} :man manual: Bspwm Manual bspwm(1) ======== Name ---- bspwm - Binary space partitioning window manager Synopsis -------- *bspwm* [*-h*|*-v*|*-c* 'CONFIG_PATH'] *bspc --print-socket-path* *bspc* 'DOMAIN' ['SELECTOR'] 'COMMANDS' *bspc* 'COMMAND' ['OPTIONS'] ['ARGUMENTS'] Description ----------- *bspwm* is a tiling window manager that represents windows as the leaves of a full binary tree. It is controlled and configured via *bspc*. Options ------- *-h*:: Print the synopsis and exit. *-v*:: Print the version and exit. *-c* 'CONFIG_PATH':: Use the given configuration file. *--print-socket-path*:: Print the *bspwm* socket path and exit. Common Definitions ------------------ ---- DIR := north | west | south | east CYCLE_DIR := next | prev ---- Selectors --------- Selectors are used to select a target node, desktop, or monitor. A selector can either describe the target relatively or name it globally. Selectors consist of an optional reference, a descriptor and any number of non-conflicting modifiers as follows: [REFERENCE#]DESCRIPTOR(.MODIFIER)* The relative targets are computed in relation to the given reference (the default reference value is *focused*). An exclamation mark can be prepended to any modifier in order to reverse its meaning. The following characters cannot be used in monitor or desktop names: *#*, *:*, *.*. The special selector *%* can be used to select a monitor or a desktop with an invalid name. Node ~~~~ Select a node. ---- NODE_SEL := [NODE_SEL#](DIR|CYCLE_DIR|PATH|any|first_ancestor|last|newest| older|newer|focused|pointed|biggest|smallest| )[.[!]focused][.[!]active][.[!]automatic][.[!]local] [.[!]leaf][.[!]window][.[!]STATE][.[!]FLAG][.[!]LAYER][.[!]SPLIT_TYPE] [.[!]same_class][.[!]descendant_of][.[!]ancestor_of] STATE := tiled|pseudo_tiled|floating|fullscreen FLAG := hidden|sticky|private|locked|marked|urgent LAYER := below|normal|above SPLIT_TYPE := horizontal|vertical PATH := @[DESKTOP_SEL:][[/]JUMP](/JUMP)* JUMP := first|1|second|2|brother|parent|DIR ---- Descriptors ^^^^^^^^^^^ 'DIR':: Selects the window in the given (spacial) direction relative to the reference node. 'CYCLE_DIR':: Selects the node in the given (cyclic) direction relative to the reference node within a depth-first in-order traversal of the tree. 'PATH':: Selects the node at the given path. any:: Selects the first node that matches the given selectors. first_ancestor:: Selects the first ancestor of the reference node that matches the given selectors. last:: Selects the previously focused node relative to the reference node. newest:: Selects the newest node in the history of the focused node. older:: Selects the node older than the reference node in the history. newer:: Selects the node newer than the reference node in the history. focused:: Selects the currently focused node. pointed:: Selects the leaf under the pointer. biggest:: Selects the biggest leaf. smallest:: Selects the smallest leaf. :: Selects the node with the given ID. Path Jumps ^^^^^^^^^^ The initial node is the focused node (or the root if the path starts with '/') of the reference desktop (or the selected desktop if the path has a 'DESKTOP_SEL' prefix). 1|first:: Jumps to the first child. 2|second:: Jumps to the second child. brother:: Jumps to the brother node. parent:: Jumps to the parent node. 'DIR':: Jumps to the node holding the edge in the given direction. Modifiers ^^^^^^^^^ [!]focused:: Only consider the focused node. [!]active:: Only consider nodes that are the focused node of their desktop. [!]automatic:: Only consider nodes in automatic insertion mode. See also *--presel-dir* under *Node* in the *DOMAINS* section below. [!]local:: Only consider nodes in the reference desktop. [!]leaf:: Only consider leaf nodes. [!]window:: Only consider nodes that hold a window. [!](tiled|pseudo_tiled|floating|fullscreen):: Only consider windows in the given state. [!]same_class:: Only consider windows that have the same class as the reference window. [!]descendant_of:: Only consider nodes that are descendants of the reference node. [!]ancestor_of:: Only consider nodes that are ancestors of the reference node. [!](hidden|sticky|private|locked|marked|urgent):: Only consider windows that have the given flag set. [!](below|normal|above):: Only consider windows in the given layer. [!](horizontal|vertical):: Only consider nodes with the given split type. Desktop ~~~~~~~ Select a desktop. ---- DESKTOP_SEL := [DESKTOP_SEL#](CYCLE_DIR|any|last|newest|older|newer| [MONITOR_SEL:](focused|^)| |)[.[!]focused][.[!]active] [.[!]occupied][.[!]urgent][.[!]local] [.[!]LAYOUT][.[!]user_LAYOUT] LAYOUT := tiled|monocle ---- Descriptors ^^^^^^^^^^^ 'CYCLE_DIR':: Selects the desktop in the given direction relative to the reference desktop. any:: Selects the first desktop that matches the given selectors. last:: Selects the previously focused desktop relative to the reference desktop. newest:: Selects the newest desktop in the history of the focused desktops. older:: Selects the desktop older than the reference desktop in the history. newer:: Selects the desktop newer than the reference desktop in the history. focused:: Selects the currently focused desktop. ^:: Selects the nth desktop. If *MONITOR_SEL* is given, selects the nth desktop on the selected monitor. :: Selects the desktop with the given ID. :: Selects the desktop with the given name. Modifiers ^^^^^^^^^ [!]focused:: Only consider the focused desktop. [!]active:: Only consider desktops that are the focused desktop of their monitor. [!]occupied:: Only consider occupied desktops. [!]urgent:: Only consider urgent desktops. [!]local:: Only consider desktops inside the reference monitor. [!](tiled|monocle):: Only consider desktops with the given layout. [!](user_tiled|user_monocle):: Only consider desktops which have the given layout as userLayout. Monitor ~~~~~~~ Select a monitor. ---- MONITOR_SEL := [MONITOR_SEL#](DIR|CYCLE_DIR|any|last|newest|older|newer| focused|pointed|primary|^| |)[.[!]focused][.[!]occupied] ---- Descriptors ^^^^^^^^^^^ 'DIR':: Selects the monitor in the given (spacial) direction relative to the reference monitor. 'CYCLE_DIR':: Selects the monitor in the given (cyclic) direction relative to the reference monitor. any:: Selects the first monitor that matches the given selectors. last:: Selects the previously focused monitor relative to the reference monitor. newest:: Selects the newest monitor in the history of the focused monitors. older:: Selects the monitor older than the reference monitor in the history. newer:: Selects the monitor newer than the reference monitor in the history. focused:: Selects the currently focused monitor. pointed:: Selects the monitor under the pointer. primary:: Selects the primary monitor. ^:: Selects the nth monitor. :: Selects the monitor with the given ID. :: Selects the monitor with the given name. Modifiers ^^^^^^^^^ [!]focused:: Only consider the focused monitor. [!]occupied:: Only consider monitors where the focused desktop is occupied. Window States ------------- tiled:: Its size and position are determined by the window tree. pseudo_tiled:: A tiled window that automatically shrinks but doesn't stretch beyond its floating size. floating:: Can be moved/resized freely. Although it doesn't use any tiling space, it is still part of the window tree. fullscreen:: Fills its monitor rectangle and has no borders. Node Flags ---------- hidden:: Is hidden and doesn't occupy any tiling space. sticky:: Stays in the focused desktop of its monitor. private:: Tries to keep the same tiling position/size. locked:: Ignores the *node --close* message. marked:: Is marked (useful for deferred actions). A marked node becomes unmarked after being sent on a preselected node. urgent:: Has its urgency hint set. This flag is set externally. Stacking Layers -------------- There's three stacking layers: BELOW, NORMAL and ABOVE. In each layer, the windows are orderered as follows: tiled & pseudo-tiled < floating < fullscreen. Receptacles ----------- A leaf node that doesn't hold any window is called a receptacle. When a node is inserted on a receptacle in automatic mode, it will replace the receptacle. A receptacle can be inserted on a node, preselected and killed. Receptacles can therefore be used to build a tree whose leaves are receptacles. Using the appropriate rules, one can then send windows on the leaves of this tree. This feature is used in 'examples/receptacles' to store and recreate layouts. Domains ------- Node ~~~~ General Syntax ^^^^^^^^^^^^^^ node ['NODE_SEL'] 'COMMANDS' If 'NODE_SEL' is omitted, *focused* is assumed. Commands ^^^^^^^^ *-f*, *--focus* ['NODE_SEL']:: Focus the selected or given node. *-a*, *--activate* ['NODE_SEL']:: Activate the selected or given node. *-d*, *--to-desktop* 'DESKTOP_SEL' [*--follow*]:: Send the selected node to the given desktop. If *--follow* is passed, the focused node will stay focused. *-m*, *--to-monitor* 'MONITOR_SEL' [*--follow*]:: Send the selected node to the given monitor. If *--follow* is passed, the focused node will stay focused. *-n*, *--to-node* 'NODE_SEL' [*--follow*]:: Send the selected node on the given node. If *--follow* is passed, the focused node will stay focused. *-s*, *--swap* 'NODE_SEL' [*--follow*]:: Swap the selected node with the given node. If *--follow* is passed, the focused node will stay focused. *-p*, *--presel-dir* \[~]'DIR'|cancel:: Preselect the splitting area of the selected node (or cancel the preselection). If *~* is prepended to 'DIR' and the current preselection direction matches 'DIR', then the argument is interpreted as *cancel*. A node with a preselected area is said to be in "manual insertion mode". *-o*, *--presel-ratio* 'RATIO':: Set the splitting ratio of the preselection area. *-v*, *--move* 'dx' 'dy':: Move the selected window by 'dx' pixels horizontally and 'dy' pixels vertically. *-z*, *--resize* top|left|bottom|right|top_left|top_right|bottom_right|bottom_left 'dx' 'dy':: Resize the selected window by moving the given handle by 'dx' pixels horizontally and 'dy' pixels vertically. *-y*, *--type* 'CYCLE_DIR'|horizontal|vertical:: Set or cycle the splitting type of the selected node. *-r*, *--ratio* 'RATIO'|(+|-)('PIXELS'|'FRACTION'):: Set the splitting ratio of the selected node (0 < 'RATIO' < 1). *-R*, *--rotate* '90|270|180':: Rotate the tree rooted at the selected node. *-F*, *--flip* 'horizontal|vertical':: Flip the tree rooted at selected node. *-E*, *--equalize*:: Reset the split ratios of the tree rooted at the selected node to their default value. *-B*, *--balance*:: Adjust the split ratios of the tree rooted at the selected node so that all windows occupy the same area. *-C*, *--circulate* forward|backward:: Circulate the windows of the tree rooted at the selected node. *-t*, *--state* \~|\[~]'STATE':: Set the state of the selected window. If *\~* is present and the current state matches 'STATE', then the argument is interpreted as its last state. If the argument is just *~* with 'STATE' omitted, then the state of the selected window is set to its last state. *-g*, *--flag* hidden|sticky|private|locked|marked[=on|off]:: Set or toggle the given flag for the selected node. *-l*, *--layer* below|normal|above:: Set the stacking layer of the selected window. *-i*, *--insert-receptacle*:: Insert a receptacle node at the selected node. *-c*, *--close*:: Close the windows rooted at the selected node. *-k*, *--kill*:: Kill the windows rooted at the selected node. Desktop ~~~~~~~ General Syntax ^^^^^^^^^^^^^^ desktop ['DESKTOP_SEL'] 'COMMANDS' If 'DESKTOP_SEL' is omitted, *focused* is assumed. COMMANDS ^^^^^^^^ *-f*, *--focus* ['DESKTOP_SEL']:: Focus the selected or given desktop. *-a*, *--activate* ['DESKTOP_SEL']:: Activate the selected or given desktop. *-m*, *--to-monitor* 'MONITOR_SEL' [*--follow*]:: Send the selected desktop to the given monitor. If *--follow* is passed, the focused desktop will stay focused. *-s*, *--swap* 'DESKTOP_SEL' [*--follow*]:: Swap the selected desktop with the given desktop. If *--follow* is passed, the focused desktop will stay focused. *-l*, *--layout* 'CYCLE_DIR'|monocle|tiled:: Set or cycle the layout of the selected desktop. *-n*, *--rename* :: Rename the selected desktop. *-b*, *--bubble* 'CYCLE_DIR':: Bubble the selected desktop in the given direction. *-r*, *--remove*:: Remove the selected desktop. Monitor ~~~~~~~ General Syntax ^^^^^^^^^^^^^^ monitor ['MONITOR_SEL'] 'COMMANDS' If 'MONITOR_SEL' is omitted, *focused* is assumed. Commands ^^^^^^^^ *-f*, *--focus* ['MONITOR_SEL']:: Focus the selected or given monitor. *-s*, *--swap* 'MONITOR_SEL':: Swap the selected monitor with the given monitor. *-a*, *--add-desktops* ...:: Create desktops with the given names in the selected monitor. *-o*, *--reorder-desktops* ...:: Reorder the desktops of the selected monitor to match the given order. *-d*, *--reset-desktops* ...:: Rename, add or remove desktops depending on whether the number of given names is equal, superior or inferior to the number of existing desktops. *-g*, *--rectangle* WxH+X+Y:: Set the rectangle of the selected monitor. *-n*, *--rename* :: Rename the selected monitor. *-r*, *--remove*:: Remove the selected monitor. Query ~~~~~ General Syntax ^^^^^^^^^^^^^^ query 'COMMANDS' ['OPTIONS'] Commands ^^^^^^^^ The optional selectors are references. *-N*, *--nodes* ['NODE_SEL']:: List the IDs of the matching nodes. *-D*, *--desktops* ['DESKTOP_SEL']:: List the IDs (or names) of the matching desktops. *-M*, *--monitors* ['MONITOR_SEL']:: List the IDs (or names) of the matching monitors. *-T*, *--tree*:: Print a JSON representation of the matching item. Options ^^^^^^^ *-m*,*--monitor* ['MONITOR_SEL'|'MONITOR_MODIFIERS']:: *-d*,*--desktop* ['DESKTOP_SEL'|'DESKTOP_MODIFIERS']:: *-n*,*--node* ['NODE_SEL'|'NODE_MODIFIERS']:: Constrain matches to the selected monitors, desktops or nodes. *--names*:: Print names instead of IDs. Can only be used with '-M' and '-D'. Wm ~~ General Syntax ^^^^^^^^^^^^^^ wm 'COMMANDS' Commands ^^^^^^^^ *-d*, *--dump-state*:: Dump the current world state on standard output. *-l*, *--load-state* :: Load a world state from the given file. The path must be absolute. *-a*, *--add-monitor* WxH+X+Y:: Add a monitor for the given name and rectangle. *-O*, *--reorder-monitors* ...:: Reorder the list of monitors to match the given order. *-o*, *--adopt-orphans*:: Manage all the unmanaged windows remaining from a previous session. *-h*, *--record-history* on|off:: Enable or disable the recording of node focus history. *-g*, *--get-status*:: Print the current status information. *-r*, *--restart*:: Restart the window manager Rule ~~~~ General Syntax ^^^^^^^^^^^^^^ rule 'COMMANDS' Commands ^^^^^^^^ *-a*, *--add* (|\*)[:(|\*)[:(|\*)]] [*-o*|*--one-shot*] [monitor=MONITOR_SEL|desktop=DESKTOP_SEL|node=NODE_SEL] [state=STATE] [layer=LAYER] [honor_size_hints=(true|false|tiled|floating)] [split_dir=DIR] [split_ratio=RATIO] [(hidden|sticky|private|locked|marked|center|follow|manage|focus|border)=(on|off)] [rectangle=WxH+X+Y]:: Create a new rule. Colons in the 'instance_name', 'class_name', or 'name' fields can be escaped with a backslash. *-r*, *--remove* ^|head|tail|(|\*)[:(|\*)[:(|*)]]...:: Remove the given rules. *-l*, *--list*:: List the rules. Config ~~~~~~ General Syntax ^^^^^^^^^^^^^^ config [-m 'MONITOR_SEL'|-d 'DESKTOP_SEL'|-n 'NODE_SEL'] []:: Get or set the value of . Subscribe ~~~~~~~~~ General Syntax ^^^^^^^^^^^^^^ subscribe ['OPTIONS'] (all|report|monitor|desktop|node|...)*:: Continuously print events. See the *EVENTS* section for the description of each event. Options ^^^^^^^ *-f*, *--fifo*:: Print a path to a FIFO from which events can be read and return. *-c*, *--count* 'COUNT':: Stop the corresponding *bspc* process after having received 'COUNT' events. Quit ~~~~ General Syntax ^^^^^^^^^^^^^^ quit []:: Quit with an optional exit status. Exit Codes ---------- If the server can't handle a message, *bspc* will return with a non-zero exit code. Settings -------- Colors are in the form '#RRGGBB', booleans are 'true', 'on', 'false' or 'off'. All the boolean settings are 'false' by default unless stated otherwise. Global Settings ~~~~~~~~~~~~~~~ 'normal_border_color':: Color of the border of an unfocused window. 'active_border_color':: Color of the border of a focused window of an unfocused monitor. 'focused_border_color':: Color of the border of a focused window of a focused monitor. 'presel_feedback_color':: Color of the *node --presel-{dir,ratio}* message feedback area. 'split_ratio':: Default split ratio. 'status_prefix':: Prefix prepended to each of the status lines. 'external_rules_command':: Absolute path to the command used to retrieve rule consequences. The command will receive the following arguments: window ID, class name, instance name, and intermediate consequences. The output of that command must have the following format: *key1=value1 key2=value2 ...* (the valid key/value pairs are given in the description of the 'rule' command). 'automatic_scheme':: The insertion scheme used when the insertion point is in automatic mode. Accept the following values: *longest_side*, *alternate*, *spiral*. 'initial_polarity':: On which child should a new window be attached when adding a window on a single window tree in automatic mode. Accept the following values: *first_child*, *second_child*. 'directional_focus_tightness':: The tightness of the algorithm used to decide whether a window is on the 'DIR' side of another window. Accept the following values: *high*, *low*. 'removal_adjustment':: Adjust the brother when unlinking a node from the tree in accordance with the automatic insertion scheme. 'presel_feedback':: Draw the preselection feedback area. Defaults to 'true'. 'borderless_monocle':: Remove borders of tiled windows for the *monocle* desktop layout. 'gapless_monocle':: Remove gaps of tiled windows for the *monocle* desktop layout. 'top_monocle_padding':: 'right_monocle_padding':: 'bottom_monocle_padding':: 'left_monocle_padding':: Padding space added at the sides of the screen for the *monocle* desktop layout. 'single_monocle':: Set the desktop layout to *monocle* if there's only one tiled window in the tree. 'borderless_singleton':: Remove borders of the only window on the only monitor regardless its layout. 'pointer_motion_interval':: The minimum interval, in milliseconds, between two motion notify events. 'pointer_modifier':: Keyboard modifier used for moving or resizing windows. Accept the following values: *shift*, *control*, *lock*, *mod1*, *mod2*, *mod3*, *mod4*, *mod5*. 'pointer_action1':: 'pointer_action2':: 'pointer_action3':: Action performed when pressing 'pointer_modifier' + 'button'. Accept the following values: *move*, *resize_side*, *resize_corner*, *focus*, *none*. 'click_to_focus':: Button used for focusing a window (or a monitor). The possible values are: *button1*, *button2*, *button3*, *any*, *none*. Defaults to *button1*. 'swallow_first_click':: Don't replay the click that makes a window focused if 'click_to_focus' isn't *none*. 'focus_follows_pointer':: Focus the window under the pointer. 'pointer_follows_focus':: When focusing a window, put the pointer at its center. 'pointer_follows_monitor':: When focusing a monitor, put the pointer at its center. 'mapping_events_count':: Handle the next *mapping_events_count* mapping notify events. A negative value implies that every event needs to be handled. 'ignore_ewmh_focus':: Ignore EWMH focus requests coming from applications. 'ignore_ewmh_fullscreen':: Block the fullscreen state transitions that originate from an EWMH request. The possible values are: *none*, *all*, or a comma separated list of the following values: *enter*, *exit*. 'ignore_ewmh_struts':: Ignore strut hinting from clients requesting to reserve space (i.e. task bars). 'center_pseudo_tiled':: Center pseudo tiled windows into their tiling rectangles. Defaults to 'true'. 'remove_disabled_monitors':: Consider disabled monitors as disconnected. 'remove_unplugged_monitors':: Remove unplugged monitors. 'merge_overlapping_monitors':: Merge overlapping monitors (the bigger remains). Monitor and Desktop Settings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 'top_padding':: 'right_padding':: 'bottom_padding':: 'left_padding':: Padding space added at the sides of the monitor or desktop. Desktop Settings ~~~~~~~~~~~~~~~~ 'window_gap':: Size of the gap that separates windows. Node Settings ~~~~~~~~~~~~~ 'border_width':: Window border width. 'honor_size_hints':: If 'true', apply ICCCM window size hints to all windows. If 'floating', only apply them to floating and pseudo tiled windows. If 'tiled', only apply them to tiled windows. If 'false', don't apply them. Defaults to 'false'. Pointer Bindings ---------------- 'click_to_focus':: Focus the window (or the monitor) under the pointer if the value isn't *none*. 'pointer_modifier' + 'button1':: Move the window under the pointer. 'pointer_modifier' + 'button2':: Resize the window under the pointer by dragging the nearest side. 'pointer_modifier' + 'button3':: Resize the window under the pointer by dragging the nearest corner. The behavior of 'pointer_modifier' + 'button' can be modified through the 'pointer_action' setting. Events ------ 'report':: See the next section for the description of the format. 'monitor_add ':: A monitor is added. 'monitor_rename ':: A monitor is renamed. 'monitor_remove ':: A monitor is removed. 'monitor_swap ':: A monitor is swapped. 'monitor_focus ':: A monitor is focused. 'monitor_geometry ':: The geometry of a monitor changed. 'desktop_add ':: A desktop is added. 'desktop_rename ':: A desktop is renamed. 'desktop_remove ':: A desktop is removed. 'desktop_swap ':: A desktop is swapped. 'desktop_transfer ':: A desktop is transferred. 'desktop_focus ':: A desktop is focused. 'desktop_activate ':: A desktop is activated. 'desktop_layout tiled|monocle':: The layout of a desktop changed. 'node_add ':: A node is added. 'node_remove ':: A node is removed. 'node_swap ':: A node is swapped. 'node_transfer ':: A node is transferred. 'node_focus ':: A node is focused. 'node_activate ':: A node is activated. 'node_presel (dir DIR|ratio RATIO|cancel)':: A node is preselected. 'node_stack below|above ':: A node is stacked below or above another node. 'node_geometry ':: The geometry of a window changed. 'node_state tiled|pseudo_tiled|floating|fullscreen on|off':: The state of a window changed. 'node_flag hidden|sticky|private|locked|marked|urgent on|off':: One of the flags of a node changed. 'node_layer below|normal|above':: The layer of a window changed. 'pointer_action move|resize_corner|resize_side begin|end':: A pointer action occurred. Please note that *bspwm* initializes monitors before it reads messages on its socket, therefore the initial monitor events can't be received. Report Format ------------- Each report event message is composed of items separated by colons. Each item has the form '' where '' is the first character of the item. 'M':: Focused monitor. 'm':: Unfocused monitor. 'O':: Occupied focused desktop. 'o':: Occupied unfocused desktop. 'F':: Free focused desktop. 'f':: Free unfocused desktop. 'U':: Urgent focused desktop. 'u':: Urgent unfocused desktop. 'L(T|M)':: Layout of the focused desktop of a monitor. 'T(T|P|F|=|@)':: State of the focused node of a focused desktop. 'G(S?P?L?M?)':: Active flags of the focused node of a focused desktop. Environment Variables --------------------- 'BSPWM_SOCKET':: The path of the socket used for the communication between *bspc* and *bspwm*. If it isn't defined, then the following path is used: '/tmp/bspwm__-socket'. Contributors ------------ * Steven Allen * Thomas Adam * Ivan Kanakarakis Author ------ Bastien Dejean ================================================ FILE: examples/bspwmrc ================================================ #! /bin/sh pgrep -x sxhkd > /dev/null || sxhkd & bspc monitor -d I II III IV V VI VII VIII IX X bspc config border_width 2 bspc config window_gap 12 bspc config split_ratio 0.52 bspc config borderless_monocle true bspc config gapless_monocle true bspc rule -a Gimp desktop='^8' state=floating follow=on bspc rule -a Chromium desktop='^2' bspc rule -a mplayer2 state=floating bspc rule -a Kupfer.py focus=on bspc rule -a Screenkey manage=off ================================================ FILE: examples/external_rules/bspwmrc ================================================ #! /bin/sh bspc config external_rules_command "$(which external_rules)" ================================================ FILE: examples/external_rules/external_rules ================================================ #! /bin/sh wid=$1 class=$2 instance=$3 consequences=$4 if [ "$instance" = fontforge ] ; then title=$(xtitle "$wid") case "$title" in Layers|Tools|Warning) echo "focus=off" ;; esac fi case "$class" in Lutris|Liferea) eval "$consequences" [ "$state" ] || echo "state=pseudo_tiled" ;; esac ================================================ FILE: examples/overlapping_borders/bspwmrc ================================================ #! /bin/sh BW=3 bspc config border_width $BW bspc config window_gap -$BW for side in top right bottom left ; do bspc config ${side}_padding $BW done ================================================ FILE: examples/panel/bspwmrc ================================================ #! /bin/sh pgrep -x panel > /dev/null || panel & ================================================ FILE: examples/panel/panel ================================================ #! /bin/sh if xdo id -a "$PANEL_WM_NAME" > /dev/null ; then printf "%s\n" "The panel is already running." >&2 exit 1 fi trap 'trap - TERM; kill 0' INT TERM QUIT EXIT [ -e "$PANEL_FIFO" ] && rm "$PANEL_FIFO" mkfifo "$PANEL_FIFO" xtitle -sf 'T%s\n' > "$PANEL_FIFO" & clock -sf 'S%a %H:%M' > "$PANEL_FIFO" & bspc subscribe report > "$PANEL_FIFO" & . panel_colors panel_bar < "$PANEL_FIFO" | lemonbar -a 32 -u 2 -n "$PANEL_WM_NAME" -g x$PANEL_HEIGHT -f "$PANEL_FONT" -F "$COLOR_DEFAULT_FG" -B "$COLOR_DEFAULT_BG" | sh & wid=$(xdo id -m -a "$PANEL_WM_NAME") xdo above -t "$(xdo id -N Bspwm -n root | sort | head -n 1)" "$wid" wait ================================================ FILE: examples/panel/panel_bar ================================================ #! /bin/sh # # Example panel for lemonbar . panel_colors num_mon=$(bspc query -M | wc -l) while read -r line ; do case $line in S*) # clock output sys="%{F$COLOR_SYS_FG}%{B$COLOR_SYS_BG} ${line#?} %{B-}%{F-}" ;; T*) # xtitle output title="%{F$COLOR_TITLE_FG}%{B$COLOR_TITLE_BG} ${line#?} %{B-}%{F-}" ;; W*) # bspwm's state wm= IFS=':' set -- ${line#?} while [ $# -gt 0 ] ; do item=$1 name=${item#?} case $item in [mM]*) case $item in m*) # monitor FG=$COLOR_MONITOR_FG BG=$COLOR_MONITOR_BG on_focused_monitor= ;; M*) # focused monitor FG=$COLOR_FOCUSED_MONITOR_FG BG=$COLOR_FOCUSED_MONITOR_BG on_focused_monitor=1 ;; esac [ $num_mon -lt 2 ] && shift && continue wm="${wm}%{F${FG}}%{B${BG}}%{A:bspc monitor -f ${name}:} ${name} %{A}%{B-}%{F-}" ;; [fFoOuU]*) case $item in f*) # free desktop FG=$COLOR_FREE_FG BG=$COLOR_FREE_BG UL=$BG ;; F*) if [ "$on_focused_monitor" ] ; then # focused free desktop FG=$COLOR_FOCUSED_FREE_FG BG=$COLOR_FOCUSED_FREE_BG UL=$BG else # active free desktop FG=$COLOR_FREE_FG BG=$COLOR_FREE_BG UL=$COLOR_FOCUSED_FREE_BG fi ;; o*) # occupied desktop FG=$COLOR_OCCUPIED_FG BG=$COLOR_OCCUPIED_BG UL=$BG ;; O*) if [ "$on_focused_monitor" ] ; then # focused occupied desktop FG=$COLOR_FOCUSED_OCCUPIED_FG BG=$COLOR_FOCUSED_OCCUPIED_BG UL=$BG else # active occupied desktop FG=$COLOR_OCCUPIED_FG BG=$COLOR_OCCUPIED_BG UL=$COLOR_FOCUSED_OCCUPIED_BG fi ;; u*) # urgent desktop FG=$COLOR_URGENT_FG BG=$COLOR_URGENT_BG UL=$BG ;; U*) if [ "$on_focused_monitor" ] ; then # focused urgent desktop FG=$COLOR_FOCUSED_URGENT_FG BG=$COLOR_FOCUSED_URGENT_BG UL=$BG else # active urgent desktop FG=$COLOR_URGENT_FG BG=$COLOR_URGENT_BG UL=$COLOR_FOCUSED_URGENT_BG fi ;; esac wm="${wm}%{F${FG}}%{B${BG}}%{U${UL}}%{+u}%{A:bspc desktop -f ${name}:} ${name} %{A}%{B-}%{F-}%{-u}" ;; [LTG]*) # layout, state and flags wm="${wm}%{F$COLOR_STATE_FG}%{B$COLOR_STATE_BG} ${name} %{B-}%{F-}" ;; esac shift done ;; esac printf "%s\n" "%{l}${wm}%{c}${title}%{r}${sys}" done ================================================ FILE: examples/panel/panel_colors ================================================ COLOR_DEFAULT_FG="#a7a5a5" COLOR_DEFAULT_BG="#333232" COLOR_MONITOR_FG="#8dbcdf" COLOR_MONITOR_BG="#333232" COLOR_FOCUSED_MONITOR_FG="#b1d0e8" COLOR_FOCUSED_MONITOR_BG="#144b6c" COLOR_FREE_FG="#737171" COLOR_FREE_BG="#333232" COLOR_FOCUSED_FREE_FG="#000000" COLOR_FOCUSED_FREE_BG="#504e4e" COLOR_OCCUPIED_FG="#a7a5a5" COLOR_OCCUPIED_BG="#333232" COLOR_FOCUSED_OCCUPIED_FG="#d6d3d2" COLOR_FOCUSED_OCCUPIED_BG="#504e4e" COLOR_URGENT_FG="#f15d66" COLOR_URGENT_BG="#333232" COLOR_FOCUSED_URGENT_FG="#501d1f" COLOR_FOCUSED_URGENT_BG="#d5443e" COLOR_STATE_FG="#89b09c" COLOR_STATE_BG="#333232" COLOR_TITLE_FG="#a8a2c0" COLOR_TITLE_BG="#333232" COLOR_SYS_FG="#b1a57d" COLOR_SYS_BG="#333232" ================================================ FILE: examples/panel/profile ================================================ PANEL_FIFO=/tmp/panel-fifo PANEL_HEIGHT=24 PANEL_FONT="-*-fixed-*-*-*-*-10-*-*-*-*-*-*-*" PANEL_WM_NAME=bspwm_panel export PANEL_FIFO PANEL_HEIGHT PANEL_FONT PANEL_WM_NAME ================================================ FILE: examples/panel/sxhkdrc ================================================ super + alt + Escape pkill -x panel; bspc quit ================================================ FILE: examples/receptacles/README.md ================================================ The scripts present in this directory can be used to store and recreate layouts. Both scripts take a JSON state (output of `bspc wm -d`) as input or argument. - `extract_canvas [state.json]` outputs a new JSON state where each leaf window is replaced by a receptacle. - `induce_rules [state.json]` outputs a list of commands that, if executed, will create rules to place each window in the corresponding receptacle. ================================================ FILE: examples/receptacles/extract_canvas ================================================ #! /usr/bin/env python3 import sys import json source = open(sys.argv[1]) if len(sys.argv) > 1 else sys.stdin state = json.load(source) def nullify_clients(node): if node is None: return elif node['client'] is None: nullify_clients(node['firstChild']) nullify_clients(node['secondChild']) else: node['client'] = None state['clientsCount'] = 0 state['focusHistory'] = [] state['stackingList'] = [] for monitor in state['monitors']: for desktop in monitor['desktops']: desktop['focusedNodeId'] = 0 nullify_clients(desktop['root']) print(json.dumps(state)) ================================================ FILE: examples/receptacles/induce_rules ================================================ #! /usr/bin/env python3 import sys import json source = open(sys.argv[1]) if len(sys.argv) > 1 else sys.stdin state = json.load(source) def print_rules(prefix, node, path): if node is None: return elif node['client'] is None: print_rules(prefix, node['firstChild'], path+['1']) print_rules(prefix, node['secondChild'], path+['2']) else: client = node['client'] print('bspc rule -a {}:{} -o node={}{}'.format(client['className'], client['instanceName'], prefix, '/'.join(path))) for i, monitor in enumerate(state['monitors']): for j, desktop in enumerate(monitor['desktops']): print_rules('@^{}:^{}:/'.format(i+1, j+1), desktop['root'], []) ================================================ FILE: examples/sxhkdrc ================================================ # # wm independent hotkeys # # terminal emulator super + Return urxvt # program launcher super + @space dmenu_run # make sxhkd reload its configuration files: super + Escape pkill -USR1 -x sxhkd # # bspwm hotkeys # # quit/restart bspwm super + alt + {q,r} bspc {quit,wm -r} # close and kill super + {_,shift + }w bspc node -{c,k} # alternate between the tiled and monocle layout super + m bspc desktop -l next # send the newest marked node to the newest preselected node super + y bspc node newest.marked.local -n newest.!automatic.local # swap the current node and the biggest window super + g bspc node -s biggest.window # # state/flags # # set the window state super + {t,shift + t,s,f} bspc node -t {tiled,pseudo_tiled,floating,fullscreen} # set the node flags super + ctrl + {m,x,y,z} bspc node -g {marked,locked,sticky,private} # # focus/swap # # focus the node in the given direction super + {_,shift + }{h,j,k,l} bspc node -{f,s} {west,south,north,east} # focus the node for the given path jump super + {p,b,comma,period} bspc node -f @{parent,brother,first,second} # focus the next/previous window in the current desktop super + {_,shift + }c bspc node -f {next,prev}.local.!hidden.window # focus the next/previous desktop in the current monitor super + bracket{left,right} bspc desktop -f {prev,next}.local # focus the last node/desktop super + {grave,Tab} bspc {node,desktop} -f last # focus the older or newer node in the focus history super + {o,i} bspc wm -h off; \ bspc node {older,newer} -f; \ bspc wm -h on # focus or send to the given desktop super + {_,shift + }{1-9,0} bspc {desktop -f,node -d} '^{1-9,10}' # # preselect # # preselect the direction super + ctrl + {h,j,k,l} bspc node -p {west,south,north,east} # preselect the ratio super + ctrl + {1-9} bspc node -o 0.{1-9} # cancel the preselection for the focused node super + ctrl + space bspc node -p cancel # cancel the preselection for the focused desktop super + ctrl + shift + space bspc query -N -d | xargs -I id -n 1 bspc node id -p cancel # # move/resize # # expand a window by moving one of its side outward super + alt + {h,j,k,l} bspc node -z {left -20 0,bottom 0 20,top 0 -20,right 20 0} # contract a window by moving one of its side inward super + alt + shift + {h,j,k,l} bspc node -z {right -20 0,top 0 20,bottom 0 -20,left 20 0} # move a floating window super + {Left,Down,Up,Right} bspc node -v {-20 0,0 20,0 -20,20 0} ================================================ FILE: src/bspc.c ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include "helpers.h" #include "common.h" int main(int argc, char *argv[]) { int sock_fd; struct sockaddr_un sock_address; char msg[BUFSIZ], rsp[BUFSIZ]; if (argc < 2) { err("No arguments given.\n"); } sock_address.sun_family = AF_UNIX; char *sp; sp = getenv(SOCKET_ENV_VAR); if (sp != NULL) { snprintf(sock_address.sun_path, sizeof(sock_address.sun_path), "%s", sp); } else { char *host = NULL; int dn = 0, sn = 0; if (xcb_parse_display(NULL, &host, &dn, &sn) != 0) { snprintf(sock_address.sun_path, sizeof(sock_address.sun_path), SOCKET_PATH_TPL, host, dn, sn); } free(host); } if (streq(argv[1], "--print-socket-path")) { printf("%s\n", sock_address.sun_path); return EXIT_SUCCESS; } if ((sock_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { err("Failed to create the socket.\n"); } if (connect(sock_fd, (struct sockaddr *) &sock_address, sizeof(sock_address)) == -1) { err("Failed to connect to the socket.\n"); } argc--, argv++; int msg_len = 0; for (int offset = 0, rem = sizeof(msg), n = 0; argc > 0 && rem > 0; offset += n, rem -= n, argc--, argv++) { n = snprintf(msg + offset, rem, "%s%c", *argv, 0); msg_len += n; } if (send(sock_fd, msg, msg_len, 0) == -1) { err("Failed to send the data.\n"); } int ret = EXIT_SUCCESS, nb; struct pollfd fds[] = { {sock_fd, POLLIN, 0}, {STDOUT_FILENO, POLLHUP, 0}, }; while (poll(fds, 2, -1) > 0) { if (fds[0].revents & POLLIN) { if ((nb = recv(sock_fd, rsp, sizeof(rsp)-1, 0)) > 0) { rsp[nb] = '\0'; if (rsp[0] == FAILURE_MESSAGE[0]) { ret = EXIT_FAILURE; fprintf(stderr, "%s", rsp + 1); fflush(stderr); } else { fprintf(stdout, "%s", rsp); fflush(stdout); } } else { break; } } if (fds[1].revents & (POLLERR | POLLHUP)) { break; } } close(sock_fd); return ret; } ================================================ FILE: src/bspwm.c ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "types.h" #include "desktop.h" #include "monitor.h" #include "settings.h" #include "messages.h" #include "pointer.h" #include "events.h" #include "common.h" #include "window.h" #include "history.h" #include "ewmh.h" #include "rule.h" #include "restore.h" #include "query.h" #include "bspwm.h" xcb_connection_t *dpy; int default_screen, screen_width, screen_height; uint32_t clients_count; xcb_screen_t *screen; xcb_window_t root; char config_path[MAXLEN]; monitor_t *mon; monitor_t *mon_head; monitor_t *mon_tail; monitor_t *pri_mon; history_t *history_head; history_t *history_tail; history_t *history_needle; rule_t *rule_head; rule_t *rule_tail; stacking_list_t *stack_head; stacking_list_t *stack_tail; subscriber_list_t *subscribe_head; subscriber_list_t *subscribe_tail; pending_rule_t *pending_rule_head; pending_rule_t *pending_rule_tail; xcb_window_t meta_window; motion_recorder_t motion_recorder; xcb_atom_t WM_STATE; xcb_atom_t WM_TAKE_FOCUS; xcb_atom_t WM_DELETE_WINDOW; int exit_status; bool auto_raise; bool sticky_still; bool hide_sticky; bool record_history; volatile sig_atomic_t running; bool restart; bool randr; int main(int argc, char *argv[]) { fd_set descriptors; char socket_path[MAXLEN]; char state_path[MAXLEN] = {0}; int run_level = 0; config_path[0] = '\0'; int sock_fd = -1, cli_fd, dpy_fd, max_fd, n; struct sockaddr_un sock_address; char msg[BUFSIZ] = {0}; xcb_generic_event_t *event; char *end; int opt; struct sigaction sigact; while ((opt = getopt(argc, argv, "hvc:s:o:")) != -1) { switch (opt) { case 'h': printf(WM_NAME " [-h|-v|-c CONFIG_PATH]\n"); exit(EXIT_SUCCESS); break; case 'v': printf("%s\n", VERSION); exit(EXIT_SUCCESS); break; case 'c': snprintf(config_path, sizeof(config_path), "%s", optarg); break; case 's': run_level |= 1; snprintf(state_path, sizeof(state_path), "%s", optarg); break; case 'o': run_level |= 2; sock_fd = strtol(optarg, &end, 0); if (*end != '\0') { sock_fd = -1; } break; } } if (config_path[0] == '\0') { char *config_home = getenv(CONFIG_HOME_ENV); if (config_home != NULL) { snprintf(config_path, sizeof(config_path), "%s/%s/%s", config_home, WM_NAME, CONFIG_NAME); } else { snprintf(config_path, sizeof(config_path), "%s/%s/%s/%s", getenv("HOME"), ".config", WM_NAME, CONFIG_NAME); } } dpy = xcb_connect(NULL, &default_screen); if (!check_connection(dpy)) { exit(EXIT_FAILURE); } load_settings(); setup(); if (state_path[0] != '\0') { restore_state(state_path); unlink(state_path); } dpy_fd = xcb_get_file_descriptor(dpy); if (sock_fd == -1) { char *sp = getenv(SOCKET_ENV_VAR); if (sp != NULL) { snprintf(socket_path, sizeof(socket_path), "%s", sp); } else { char *host = NULL; int dn = 0, sn = 0; if (xcb_parse_display(NULL, &host, &dn, &sn) != 0) { snprintf(socket_path, sizeof(socket_path), SOCKET_PATH_TPL, host, dn, sn); } free(host); } sock_address.sun_family = AF_UNIX; if (snprintf(sock_address.sun_path, sizeof(sock_address.sun_path), "%s", socket_path) < 0) { err("Couldn't write the socket path.\n"); } sock_fd = socket(AF_UNIX, SOCK_STREAM, 0); if (sock_fd == -1) { err("Couldn't create the socket.\n"); } unlink(socket_path); if (bind(sock_fd, (struct sockaddr *) &sock_address, sizeof(sock_address)) == -1) { err("Couldn't bind a name to the socket.\n"); } if (listen(sock_fd, SOMAXCONN) == -1) { err("Couldn't listen to the socket.\n"); } } fcntl(sock_fd, F_SETFD, FD_CLOEXEC | fcntl(sock_fd, F_GETFD)); sigact.sa_handler = sig_handler; sigemptyset(&sigact.sa_mask); sigact.sa_flags = SA_RESTART; sigaction(SIGCHLD, &sigact, NULL); sigaction(SIGINT, &sigact, NULL); sigaction(SIGHUP, &sigact, NULL); sigaction(SIGTERM, &sigact, NULL); /* We avoid using SIG_IGN with SIGPIPE because that would be preserved across exec. */ sigaction(SIGPIPE, &sigact, NULL); run_config(run_level); running = true; while (running) { xcb_flush(dpy); FD_ZERO(&descriptors); FD_SET(sock_fd, &descriptors); FD_SET(dpy_fd, &descriptors); max_fd = MAX(sock_fd, dpy_fd); for (pending_rule_t *pr = pending_rule_head; pr != NULL; pr = pr->next) { FD_SET(pr->fd, &descriptors); if (pr->fd > max_fd) { max_fd = pr->fd; } } if (select(max_fd + 1, &descriptors, NULL, NULL, NULL) > 0) { pending_rule_t *pr = pending_rule_head; while (pr != NULL) { pending_rule_t *next = pr->next; if (FD_ISSET(pr->fd, &descriptors)) { if (manage_window(pr->win, pr->csq, pr->fd)) { for (event_queue_t *eq = pr->event_head; eq != NULL; eq = eq->next) { handle_event(&eq->event); } } remove_pending_rule(pr); } pr = next; } if (FD_ISSET(sock_fd, &descriptors)) { cli_fd = accept(sock_fd, NULL, 0); if (cli_fd > 0 && (n = recv(cli_fd, msg, sizeof(msg)-1, 0)) > 0) { msg[n] = '\0'; FILE *rsp = fdopen(cli_fd, "w"); if (rsp != NULL) { handle_message(msg, n, rsp); } else { warn("Can't open the client socket as file.\n"); close(cli_fd); } } } if (FD_ISSET(dpy_fd, &descriptors)) { xcb_aux_sync(dpy); while ((event = xcb_poll_for_event(dpy)) != NULL) { handle_event(event); free(event); } } } if (!check_connection(dpy)) { running = false; } prune_dead_subscribers(); } if (restart) { char *host = NULL; int dn = 0, sn = 0; if (xcb_parse_display(NULL, &host, &dn, &sn) != 0) { snprintf(state_path, sizeof(state_path), STATE_PATH_TPL, host, dn, sn); } free(host); FILE *f = fopen(state_path, "w"); query_state(f); fclose(f); } cleanup(); ungrab_buttons(); xcb_ewmh_connection_wipe(ewmh); xcb_destroy_window(dpy, meta_window); xcb_destroy_window(dpy, motion_recorder.id); free(ewmh); xcb_flush(dpy); xcb_disconnect(dpy); if (restart) { fcntl(sock_fd, F_SETFD, ~FD_CLOEXEC & fcntl(sock_fd, F_GETFD)); int rargc; for (rargc = 0; rargc < argc; rargc++) { if (streq("-s", argv[rargc])) { break; } } int len = rargc + 5; char **rargv = malloc(len * sizeof(char *)); for (int i = 0; i < rargc; i++) { rargv[i] = argv[i]; } char sock_fd_arg[SMALEN]; snprintf(sock_fd_arg, sizeof(sock_fd_arg), "%i", sock_fd); rargv[rargc] = "-s"; rargv[rargc + 1] = state_path; rargv[rargc + 2] = "-o"; rargv[rargc + 3] = sock_fd_arg; rargv[rargc + 4] = 0; execvp(*rargv, rargv); exit_status = 1; free(rargv); } close(sock_fd); unlink(socket_path); return exit_status; } void init(void) { clients_count = 0; mon = mon_head = mon_tail = pri_mon = NULL; history_head = history_tail = history_needle = NULL; rule_head = rule_tail = NULL; stack_head = stack_tail = NULL; subscribe_head = subscribe_tail = NULL; pending_rule_head = pending_rule_tail = NULL; auto_raise = sticky_still = hide_sticky = record_history = true; randr_base = 0; exit_status = 0; restart = false; } void setup(void) { init(); ewmh_init(); pointer_init(); screen = xcb_setup_roots_iterator(xcb_get_setup(dpy)).data; if (screen == NULL) { err("Can't acquire the default screen.\n"); } root = screen->root; register_events(); screen_width = screen->width_in_pixels; screen_height = screen->height_in_pixels; meta_window = xcb_generate_id(dpy); xcb_create_window(dpy, XCB_COPY_FROM_PARENT, meta_window, root, -1, -1, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_COPY_FROM_PARENT, XCB_NONE, NULL); xcb_icccm_set_wm_class(dpy, meta_window, sizeof(META_WINDOW_IC), META_WINDOW_IC); motion_recorder.id = xcb_generate_id(dpy); motion_recorder.sequence = 0; motion_recorder.enabled = false; uint32_t values[] = {XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_POINTER_MOTION}; xcb_create_window(dpy, XCB_COPY_FROM_PARENT, motion_recorder.id, root, 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values); xcb_icccm_set_wm_class(dpy, motion_recorder.id, sizeof(MOTION_RECORDER_IC), MOTION_RECORDER_IC); xcb_atom_t net_atoms[] = {ewmh->_NET_SUPPORTED, ewmh->_NET_SUPPORTING_WM_CHECK, ewmh->_NET_DESKTOP_NAMES, ewmh->_NET_DESKTOP_VIEWPORT, ewmh->_NET_NUMBER_OF_DESKTOPS, ewmh->_NET_CURRENT_DESKTOP, ewmh->_NET_CLIENT_LIST, ewmh->_NET_ACTIVE_WINDOW, ewmh->_NET_CLOSE_WINDOW, ewmh->_NET_WM_STRUT_PARTIAL, ewmh->_NET_WM_DESKTOP, ewmh->_NET_WM_STATE, ewmh->_NET_WM_STATE_HIDDEN, ewmh->_NET_WM_STATE_FULLSCREEN, ewmh->_NET_WM_STATE_BELOW, ewmh->_NET_WM_STATE_ABOVE, ewmh->_NET_WM_STATE_STICKY, ewmh->_NET_WM_STATE_DEMANDS_ATTENTION, ewmh->_NET_WM_WINDOW_TYPE, ewmh->_NET_WM_WINDOW_TYPE_DOCK, ewmh->_NET_WM_WINDOW_TYPE_DESKTOP, ewmh->_NET_WM_WINDOW_TYPE_NOTIFICATION, ewmh->_NET_WM_WINDOW_TYPE_DIALOG, ewmh->_NET_WM_WINDOW_TYPE_UTILITY, ewmh->_NET_WM_WINDOW_TYPE_TOOLBAR}; xcb_ewmh_set_supported(ewmh, default_screen, LENGTH(net_atoms), net_atoms); ewmh_set_supporting(meta_window); #define GETATOM(a) \ get_atom(#a, &a); GETATOM(WM_STATE) GETATOM(WM_DELETE_WINDOW) GETATOM(WM_TAKE_FOCUS) #undef GETATOM const xcb_query_extension_reply_t *qep = xcb_get_extension_data(dpy, &xcb_randr_id); if (qep->present && update_monitors()) { randr = true; randr_base = qep->first_event; xcb_randr_select_input(dpy, root, XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE); } else { randr = false; warn("Couldn't retrieve monitors via RandR.\n"); bool xinerama_is_active = false; if (xcb_get_extension_data(dpy, &xcb_xinerama_id)->present) { xcb_xinerama_is_active_reply_t *xia = xcb_xinerama_is_active_reply(dpy, xcb_xinerama_is_active(dpy), NULL); if (xia != NULL) { xinerama_is_active = xia->state; free(xia); } } if (xinerama_is_active) { xcb_xinerama_query_screens_reply_t *xsq = xcb_xinerama_query_screens_reply(dpy, xcb_xinerama_query_screens(dpy), NULL); xcb_xinerama_screen_info_t *xsi = xcb_xinerama_query_screens_screen_info(xsq); int n = xcb_xinerama_query_screens_screen_info_length(xsq); for (int i = 0; i < n; i++) { xcb_xinerama_screen_info_t info = xsi[i]; xcb_rectangle_t rect = (xcb_rectangle_t) {info.x_org, info.y_org, info.width, info.height}; monitor_t *m = make_monitor(NULL, &rect, XCB_NONE); add_monitor(m); add_desktop(m, make_desktop(NULL, XCB_NONE)); } free(xsq); } else { warn("Xinerama is inactive.\n"); xcb_rectangle_t rect = (xcb_rectangle_t) {0, 0, screen_width, screen_height}; monitor_t *m = make_monitor(NULL, &rect, XCB_NONE); add_monitor(m); add_desktop(m, make_desktop(NULL, XCB_NONE)); } } ewmh_update_number_of_desktops(); ewmh_update_desktop_names(); ewmh_update_desktop_viewport(); ewmh_update_current_desktop(); xcb_get_input_focus_reply_t *ifo = xcb_get_input_focus_reply(dpy, xcb_get_input_focus(dpy), NULL); if (ifo != NULL && (ifo->focus == XCB_INPUT_FOCUS_POINTER_ROOT || ifo->focus == XCB_NONE)) { clear_input_focus(); } free(ifo); } void register_events(void) { uint32_t values[] = {ROOT_EVENT_MASK}; xcb_generic_error_t *e = xcb_request_check(dpy, xcb_change_window_attributes_checked(dpy, root, XCB_CW_EVENT_MASK, values)); if (e != NULL) { free(e); xcb_ewmh_connection_wipe(ewmh); free(ewmh); xcb_disconnect(dpy); err("Another window manager is already running.\n"); } } void cleanup(void) { mon = NULL; while (mon_head != NULL) { remove_monitor(mon_head); } while (rule_head != NULL) { remove_rule(rule_head); } while (subscribe_head != NULL) { remove_subscriber(subscribe_head); } while (pending_rule_head != NULL) { remove_pending_rule(pending_rule_head); } empty_history(); } bool check_connection (xcb_connection_t *dpy) { int xerr; if ((xerr = xcb_connection_has_error(dpy)) != 0) { warn("The server closed the connection: "); switch (xerr) { case XCB_CONN_ERROR: warn("socket, pipe or stream error.\n"); break; case XCB_CONN_CLOSED_EXT_NOTSUPPORTED: warn("unsupported extension.\n"); break; case XCB_CONN_CLOSED_MEM_INSUFFICIENT: warn("not enough memory.\n"); break; case XCB_CONN_CLOSED_REQ_LEN_EXCEED: warn("request length exceeded.\n"); break; case XCB_CONN_CLOSED_PARSE_ERR: warn("can't parse display string.\n"); break; case XCB_CONN_CLOSED_INVALID_SCREEN: warn("invalid screen.\n"); break; case XCB_CONN_CLOSED_FDPASSING_FAILED: warn("failed to pass FD.\n"); break; default: warn("unknown error.\n"); break; } return false; } else { return true; } } void sig_handler(int sig) { if (sig == SIGCHLD) { while (waitpid(-1, 0, WNOHANG) > 0) ; } else if (sig == SIGINT || sig == SIGHUP || sig == SIGTERM) { running = false; } } /* Adapted from i3wm */ uint32_t get_color_pixel(const char *color) { unsigned int red, green, blue; if (sscanf(color + 1, "%02x%02x%02x", &red, &green, &blue) == 3) { /* We set the first 8 bits high to have 100% opacity in case of a 32 bit * color depth visual. */ return (0xFF << 24) | (red << 16 | green << 8 | blue); } else { return screen->black_pixel; } } ================================================ FILE: src/bspwm.h ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef BSPWM_BSPWM_H #define BSPWM_BSPWM_H #include #include "types.h" #define WM_NAME "bspwm" #define CONFIG_NAME WM_NAME "rc" #define CONFIG_HOME_ENV "XDG_CONFIG_HOME" #define RUNTIME_DIR_ENV "XDG_RUNTIME_DIR" #define STATE_PATH_TPL "/tmp/bspwm%s_%i_%i-state" #define ROOT_EVENT_MASK (XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_FOCUS_CHANGE) #define CLIENT_EVENT_MASK (XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_FOCUS_CHANGE) #define BSPWM_CLASS_NAME "Bspwm" #define META_WINDOW_IC "wm\0" BSPWM_CLASS_NAME #define ROOT_WINDOW_IC "root\0" BSPWM_CLASS_NAME #define PRESEL_FEEDBACK_I "presel_feedback" #define PRESEL_FEEDBACK_IC PRESEL_FEEDBACK_I "\0" BSPWM_CLASS_NAME #define MOTION_RECORDER_I "motion_recorder" #define MOTION_RECORDER_IC MOTION_RECORDER_I "\0" BSPWM_CLASS_NAME typedef struct { xcb_window_t id; uint16_t sequence; bool enabled; } motion_recorder_t; extern xcb_connection_t *dpy; extern int default_screen, screen_width, screen_height; extern uint32_t clients_count; extern xcb_screen_t *screen; extern xcb_window_t root; extern char config_path[MAXLEN]; extern monitor_t *mon; extern monitor_t *mon_head; extern monitor_t *mon_tail; extern monitor_t *pri_mon; extern history_t *history_head; extern history_t *history_tail; extern history_t *history_needle; extern rule_t *rule_head; extern rule_t *rule_tail; extern stacking_list_t *stack_head; extern stacking_list_t *stack_tail; extern subscriber_list_t *subscribe_head; extern subscriber_list_t *subscribe_tail; extern pending_rule_t *pending_rule_head; extern pending_rule_t *pending_rule_tail; extern xcb_window_t meta_window; extern motion_recorder_t motion_recorder; extern xcb_atom_t WM_STATE; extern xcb_atom_t WM_TAKE_FOCUS; extern xcb_atom_t WM_DELETE_WINDOW; extern int exit_status; extern bool auto_raise; extern bool sticky_still; extern bool hide_sticky; extern bool record_history; extern volatile sig_atomic_t running; extern bool restart; extern bool randr; void init(void); void setup(void); void register_events(void); void cleanup(void); bool check_connection (xcb_connection_t *dpy); void sig_handler(int sig); uint32_t get_color_pixel(const char *color); #endif ================================================ FILE: src/common.h ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef BSPWM_COMMON_H #define BSPWM_COMMON_H #define SOCKET_PATH_TPL "/tmp/bspwm%s_%i_%i-socket" #define SOCKET_ENV_VAR "BSPWM_SOCKET" #define FAILURE_MESSAGE "\x07" #endif ================================================ FILE: src/desktop.c ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include "bspwm.h" #include "ewmh.h" #include "history.h" #include "monitor.h" #include "query.h" #include "tree.h" #include "window.h" #include "desktop.h" #include "subscribe.h" #include "settings.h" bool activate_desktop(monitor_t *m, desktop_t *d) { if (d != NULL && m == mon) { return false; } if (d == NULL) { d = m->desk; if (d == NULL) { d = history_last_desktop(m, NULL); } if (d == NULL) { d = m->desk_head; } } if (d == NULL || d == m->desk) { return false; } if (m->sticky_count > 0 && m->desk != NULL) { transfer_sticky_nodes(m, m->desk, m, d, m->desk->root); } show_desktop(d); hide_desktop(m->desk); m->desk = d; history_add(m, d, NULL, false); put_status(SBSC_MASK_DESKTOP_ACTIVATE, "desktop_activate 0x%08X 0x%08X\n", m->id, d->id); put_status(SBSC_MASK_REPORT); return true; } bool find_closest_desktop(coordinates_t *ref, coordinates_t *dst, cycle_dir_t dir, desktop_select_t *sel) { monitor_t *m = ref->monitor; desktop_t *d = ref->desktop; d = (dir == CYCLE_PREV ? d->prev : d->next); #define HANDLE_BOUNDARIES(m, d) \ if (d == NULL) { \ m = (dir == CYCLE_PREV ? m->prev : m->next); \ if (m == NULL) { \ m = (dir == CYCLE_PREV ? mon_tail : mon_head); \ } \ d = (dir == CYCLE_PREV ? m->desk_tail : m->desk_head); \ } HANDLE_BOUNDARIES(m, d) while (d != ref->desktop) { coordinates_t loc = {m, d, NULL}; if (desktop_matches(&loc, ref, sel)) { *dst = loc; return true; } d = (dir == CYCLE_PREV ? d->prev : d->next); HANDLE_BOUNDARIES(m, d) } #undef HANDLE_BOUNDARIES return false; } bool find_any_desktop(coordinates_t *ref, coordinates_t *dst, desktop_select_t *sel) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { coordinates_t loc = {m, d, NULL}; if (desktop_matches(&loc, ref, sel)) { *dst = loc; return true; } } } return false; } bool set_layout(monitor_t *m, desktop_t *d, layout_t l, bool user) { if ((user && d->user_layout == l) || (!user && d->layout == l)) { return false; } layout_t old_layout = d->layout; if (user) { d->user_layout = l; } else { d->layout = l; } if (user && (!single_monocle || tiled_count(d->root, true) > 1)) { d->layout = l; } if (d->layout != old_layout) { handle_presel_feedbacks(m, d); if (user) { arrange(m, d); } put_status(SBSC_MASK_DESKTOP_LAYOUT, "desktop_layout 0x%08X 0x%08X %s\n", m->id, d->id, LAYOUT_STR(d->layout)); if (d == m->desk) { put_status(SBSC_MASK_REPORT); } } return true; } void handle_presel_feedbacks(monitor_t *m, desktop_t *d) { if (m->desk != d) { return; } if (d->layout == LAYOUT_MONOCLE) { hide_presel_feedbacks(m, d, d->root); } else { show_presel_feedbacks(m, d, d->root); } } bool transfer_desktop(monitor_t *ms, monitor_t *md, desktop_t *d, bool follow) { if (ms == NULL || md == NULL || d == NULL || ms == md) { return false; } bool d_was_active = (d == ms->desk); bool ms_was_focused = (ms == mon); unsigned int sc = (ms->sticky_count > 0 && d_was_active) ? sticky_count(d->root) : 0; unlink_desktop(ms, d); ms->sticky_count -= sc; if ((!follow || !d_was_active || !ms_was_focused) && md->desk != NULL) { hide_sticky = false; hide_desktop(d); hide_sticky = true; } insert_desktop(md, d); md->sticky_count += sc; history_remove(d, NULL, false); if (d_was_active) { if (follow) { if (activate_desktop(ms, NULL)) { activate_node(ms, ms->desk, NULL); } if (ms_was_focused) { focus_node(md, d, d->focus); } } else { if (ms_was_focused) { focus_node(ms, ms->desk, NULL); } else if (activate_desktop(ms, NULL)) { activate_node(ms, ms->desk, NULL); } } } if (sc > 0) { if (ms->desk != NULL) { transfer_sticky_nodes(md, d, ms, ms->desk, d->root); } else if (d != md->desk) { transfer_sticky_nodes(md, d, md, md->desk, d->root); } } adapt_geometry(&ms->rectangle, &md->rectangle, d->root); arrange(md, d); if ((!follow || !d_was_active || !ms_was_focused) && md->desk == d) { if (md == mon) { focus_node(md, d, d->focus); } else { activate_node(md, d, d->focus); } } ewmh_update_wm_desktops(); ewmh_update_desktop_names(); ewmh_update_desktop_viewport(); ewmh_update_current_desktop(); put_status(SBSC_MASK_DESKTOP_TRANSFER, "desktop_transfer 0x%08X 0x%08X 0x%08X\n", ms->id, d->id, md->id); put_status(SBSC_MASK_REPORT); return true; } desktop_t *make_desktop(const char *name, uint32_t id) { desktop_t *d = calloc(1, sizeof(desktop_t)); snprintf(d->name, sizeof(d->name), "%s", name == NULL ? DEFAULT_DESK_NAME : name); if (id == XCB_NONE) { d->id = xcb_generate_id(dpy); } d->prev = d->next = NULL; d->root = d->focus = NULL; d->user_layout = LAYOUT_TILED; d->layout = single_monocle ? LAYOUT_MONOCLE : LAYOUT_TILED; d->padding = (padding_t) PADDING; d->window_gap = window_gap; d->border_width = border_width; return d; } void rename_desktop(monitor_t *m, desktop_t *d, const char *name) { put_status(SBSC_MASK_DESKTOP_RENAME, "desktop_rename 0x%08X 0x%08X %s %s\n", m->id, d->id, d->name, name); snprintf(d->name, sizeof(d->name), "%s", name); put_status(SBSC_MASK_REPORT); ewmh_update_desktop_names(); } void insert_desktop(monitor_t *m, desktop_t *d) { if (m->desk == NULL) { m->desk = d; m->desk_head = d; m->desk_tail = d; } else { m->desk_tail->next = d; d->prev = m->desk_tail; m->desk_tail = d; } } void add_desktop(monitor_t *m, desktop_t *d) { put_status(SBSC_MASK_DESKTOP_ADD, "desktop_add 0x%08X 0x%08X %s\n", m->id, d->id, d->name); d->border_width = m->border_width; d->window_gap = m->window_gap; insert_desktop(m, d); ewmh_update_current_desktop(); ewmh_update_number_of_desktops(); ewmh_update_desktop_names(); ewmh_update_desktop_viewport(); ewmh_update_wm_desktops(); put_status(SBSC_MASK_REPORT); } desktop_t *find_desktop_in(uint32_t id, monitor_t *m) { if (m == NULL) { return NULL; } for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { if (d->id == id) { return d; } } return NULL; } void unlink_desktop(monitor_t *m, desktop_t *d) { desktop_t *prev = d->prev; desktop_t *next = d->next; if (prev != NULL) { prev->next = next; } if (next != NULL) { next->prev = prev; } if (m->desk_head == d) { m->desk_head = next; } if (m->desk_tail == d) { m->desk_tail = prev; } if (m->desk == d) { m->desk = NULL; } d->prev = d->next = NULL; } void remove_desktop(monitor_t *m, desktop_t *d) { put_status(SBSC_MASK_DESKTOP_REMOVE, "desktop_remove 0x%08X 0x%08X\n", m->id, d->id); remove_node(m, d, d->root); unlink_desktop(m, d); history_remove(d, NULL, false); free(d); ewmh_update_current_desktop(); ewmh_update_number_of_desktops(); ewmh_update_desktop_names(); ewmh_update_desktop_viewport(); if (mon != NULL && m->desk == NULL) { if (m == mon) { focus_node(m, NULL, NULL); } else { activate_desktop(m, NULL); if (m->desk != NULL) { activate_node(m, m->desk, m->desk->focus); } } } put_status(SBSC_MASK_REPORT); } void merge_desktops(monitor_t *ms, desktop_t *ds, monitor_t *md, desktop_t *dd) { if (ds == NULL || dd == NULL || ds == dd) { return; } /* TODO: Handle sticky nodes. */ transfer_node(ms, ds, ds->root, md, dd, dd->focus, false); } bool swap_desktops(monitor_t *m1, desktop_t *d1, monitor_t *m2, desktop_t *d2, bool follow) { if (d1 == NULL || d2 == NULL || d1 == d2) { return false; } put_status(SBSC_MASK_DESKTOP_SWAP, "desktop_swap 0x%08X 0x%08X 0x%08X 0x%08X\n", m1->id, d1->id, m2->id, d2->id); bool d1_was_active = (m1->desk == d1); bool d2_was_active = (m2->desk == d2); bool d1_was_focused = (mon->desk == d1); bool d2_was_focused = (mon->desk == d2); desktop_t *d1_stickies = NULL; desktop_t *d2_stickies = NULL; if (m1->sticky_count > 0 && d1 == m1->desk && sticky_count(d1->root) > 0) { d1_stickies = make_desktop(NULL, XCB_NONE); insert_desktop(m1, d1_stickies); transfer_sticky_nodes(m1, d1, m1, d1_stickies, d1->root); } if (m2->sticky_count > 0 && d2 == m2->desk && sticky_count(d2->root) > 0) { d2_stickies = make_desktop(NULL, XCB_NONE); insert_desktop(m2, d2_stickies); transfer_sticky_nodes(m2, d2, m2, d2_stickies, d2->root); } if (m1 != m2) { if (m1->desk == d1) { m1->desk = d2; } if (m1->desk_head == d1) { m1->desk_head = d2; } if (m1->desk_tail == d1) { m1->desk_tail = d2; } if (m2->desk == d2) { m2->desk = d1; } if (m2->desk_head == d2) { m2->desk_head = d1; } if (m2->desk_tail == d2) { m2->desk_tail = d1; } } else { if (m1->desk == d1) { m1->desk = d2; } else if (m1->desk == d2) { m1->desk = d1; } if (m1->desk_head == d1) { m1->desk_head = d2; } else if (m1->desk_head == d2) { m1->desk_head = d1; } if (m1->desk_tail == d1) { m1->desk_tail = d2; } else if (m1->desk_tail == d2) { m1->desk_tail = d1; } } desktop_t *p1 = d1->prev; desktop_t *n1 = d1->next; desktop_t *p2 = d2->prev; desktop_t *n2 = d2->next; if (p1 != NULL && p1 != d2) { p1->next = d2; } if (n1 != NULL && n1 != d2) { n1->prev = d2; } if (p2 != NULL && p2 != d1) { p2->next = d1; } if (n2 != NULL && n2 != d1) { n2->prev = d1; } d1->prev = p2 == d1 ? d2 : p2; d1->next = n2 == d1 ? d2 : n2; d2->prev = p1 == d2 ? d1 : p1; d2->next = n1 == d2 ? d1 : n1; if (m1 != m2) { adapt_geometry(&m1->rectangle, &m2->rectangle, d1->root); adapt_geometry(&m2->rectangle, &m1->rectangle, d2->root); history_remove(d1, NULL, false); history_remove(d2, NULL, false); arrange(m1, d2); arrange(m2, d1); } if (d1_stickies != NULL) { transfer_sticky_nodes(m1, d1_stickies, m1, d2, d1_stickies->root); unlink_desktop(m1, d1_stickies); free(d1_stickies); } if (d2_stickies != NULL) { transfer_sticky_nodes(m2, d2_stickies, m2, d1, d2_stickies->root); unlink_desktop(m2, d2_stickies); free(d2_stickies); } if (d1_was_active && !d2_was_active) { if ((!follow && m1 != m2) || !d1_was_focused) { hide_desktop(d1); } show_desktop(d2); } else if (!d1_was_active && d2_was_active) { show_desktop(d1); if ((!follow && m1 != m2) || !d2_was_focused) { hide_desktop(d2); } } if (follow || m1 == m2) { if (d1_was_focused) { focus_node(m2, d1, d1->focus); } else if (d1_was_active) { activate_node(m2, d1, d1->focus); } if (d2_was_focused) { focus_node(m1, d2, d2->focus); } else if (d2_was_active) { activate_node(m1, d2, d2->focus); } } else { if (d1_was_focused) { focus_node(m1, d2, d2->focus); } else if (d1_was_active) { activate_node(m1, d2, d2->focus); } if (d2_was_focused) { focus_node(m2, d1, d1->focus); } else if (d2_was_active) { activate_node(m2, d1, d1->focus); } } ewmh_update_wm_desktops(); ewmh_update_desktop_names(); ewmh_update_desktop_viewport(); ewmh_update_current_desktop(); put_status(SBSC_MASK_REPORT); return true; } void show_desktop(desktop_t *d) { if (d == NULL) { return; } show_node(d, d->root); } void hide_desktop(desktop_t *d) { if (d == NULL) { return; } hide_node(d, d->root); } bool is_urgent(desktop_t *d) { for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) { if (n->client == NULL) { continue; } if (n->client->urgent) { return true; } } return false; } ================================================ FILE: src/desktop.h ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef BSPWM_DESKTOP_H #define BSPWM_DESKTOP_H #define DEFAULT_DESK_NAME "Desktop" bool activate_desktop(monitor_t *m, desktop_t *d); bool find_closest_desktop(coordinates_t *ref, coordinates_t *dst, cycle_dir_t dir, desktop_select_t *sel); bool find_any_desktop(coordinates_t *ref, coordinates_t *dst, desktop_select_t *sel); bool set_layout(monitor_t *m, desktop_t *d, layout_t l, bool user); void handle_presel_feedbacks(monitor_t *m, desktop_t *d); bool transfer_desktop(monitor_t *ms, monitor_t *md, desktop_t *d, bool follow); desktop_t *make_desktop(const char *name, uint32_t id); void rename_desktop(monitor_t *m, desktop_t *d, const char *name); void insert_desktop(monitor_t *m, desktop_t *d); void add_desktop(monitor_t *m, desktop_t *d); desktop_t *find_desktop_in(uint32_t id, monitor_t *m); void unlink_desktop(monitor_t *m, desktop_t *d); void remove_desktop(monitor_t *m, desktop_t *d); void merge_desktops(monitor_t *ms, desktop_t *ds, monitor_t *md, desktop_t *dd); bool swap_desktops(monitor_t *m1, desktop_t *d1, monitor_t *m2, desktop_t *d2, bool follow); void show_desktop(desktop_t *d); void hide_desktop(desktop_t *d); bool is_urgent(desktop_t *d); #endif ================================================ FILE: src/events.c ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "bspwm.h" #include "ewmh.h" #include "monitor.h" #include "query.h" #include "settings.h" #include "subscribe.h" #include "tree.h" #include "window.h" #include "pointer.h" #include "rule.h" #include "events.h" uint8_t randr_base; void handle_event(xcb_generic_event_t *evt) { uint8_t resp_type = XCB_EVENT_RESPONSE_TYPE(evt); switch (resp_type) { case XCB_MAP_REQUEST: map_request(evt); break; case XCB_DESTROY_NOTIFY: destroy_notify(evt); break; case XCB_UNMAP_NOTIFY: unmap_notify(evt); break; case XCB_CLIENT_MESSAGE: client_message(evt); break; case XCB_CONFIGURE_REQUEST: configure_request(evt); break; case XCB_CONFIGURE_NOTIFY: configure_notify(evt); break; case XCB_PROPERTY_NOTIFY: property_notify(evt); break; case XCB_ENTER_NOTIFY: enter_notify(evt); break; case XCB_MOTION_NOTIFY: motion_notify(evt); break; case XCB_BUTTON_PRESS: button_press(evt); break; case XCB_FOCUS_IN: focus_in(evt); break; case XCB_MAPPING_NOTIFY: mapping_notify(evt); break; case 0: process_error(evt); break; default: if (randr && resp_type == randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY) { update_monitors(); } break; } } void map_request(xcb_generic_event_t *evt) { xcb_map_request_event_t *e = (xcb_map_request_event_t *) evt; schedule_window(e->window); } void configure_request(xcb_generic_event_t *evt) { xcb_configure_request_event_t *e = (xcb_configure_request_event_t *) evt; coordinates_t loc; bool is_managed = locate_window(e->window, &loc); client_t *c = (is_managed ? loc.node->client : NULL); uint16_t width, height; if (!is_managed) { uint16_t mask = 0; uint32_t values[7]; unsigned short i = 0; if (e->value_mask & XCB_CONFIG_WINDOW_X) { mask |= XCB_CONFIG_WINDOW_X; values[i++] = e->x; } if (e->value_mask & XCB_CONFIG_WINDOW_Y) { mask |= XCB_CONFIG_WINDOW_Y; values[i++] = e->y; } if (e->value_mask & XCB_CONFIG_WINDOW_WIDTH) { mask |= XCB_CONFIG_WINDOW_WIDTH; values[i++] = e->width; } if (e->value_mask & XCB_CONFIG_WINDOW_HEIGHT) { mask |= XCB_CONFIG_WINDOW_HEIGHT; values[i++] = e->height; } if (e->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) { mask |= XCB_CONFIG_WINDOW_BORDER_WIDTH; values[i++] = e->border_width; } if (e->value_mask & XCB_CONFIG_WINDOW_SIBLING) { mask |= XCB_CONFIG_WINDOW_SIBLING; values[i++] = e->sibling; } if (e->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) { mask |= XCB_CONFIG_WINDOW_STACK_MODE; values[i++] = e->stack_mode; } xcb_configure_window(dpy, e->window, mask, values); } else if (IS_FLOATING(c)) { width = c->floating_rectangle.width; height = c->floating_rectangle.height; if (e->value_mask & XCB_CONFIG_WINDOW_X) { c->floating_rectangle.x = e->x; } if (e->value_mask & XCB_CONFIG_WINDOW_Y) { c->floating_rectangle.y = e->y; } if (e->value_mask & XCB_CONFIG_WINDOW_WIDTH) { width = e->width; } if (e->value_mask & XCB_CONFIG_WINDOW_HEIGHT) { height = e->height; } apply_size_hints(c, &width, &height); c->floating_rectangle.width = width; c->floating_rectangle.height = height; xcb_rectangle_t r = c->floating_rectangle; r.x -= c->border_width; r.y -= c->border_width; window_move_resize(e->window, r.x, r.y, r.width, r.height); put_status(SBSC_MASK_NODE_GEOMETRY, "node_geometry 0x%08X 0x%08X 0x%08X %ux%u+%i+%i\n", loc.monitor->id, loc.desktop->id, e->window, r.width, r.height, r.x, r.y); monitor_t *m = monitor_from_client(c); if (m != loc.monitor) { transfer_node(loc.monitor, loc.desktop, loc.node, m, m->desk, m->desk->focus, false); } } else { if (c->state == STATE_PSEUDO_TILED) { width = c->floating_rectangle.width; height = c->floating_rectangle.height; if (e->value_mask & XCB_CONFIG_WINDOW_WIDTH) { width = e->width; } if (e->value_mask & XCB_CONFIG_WINDOW_HEIGHT) { height = e->height; } apply_size_hints(c, &width, &height); if (width != c->floating_rectangle.width || height != c->floating_rectangle.height) { c->floating_rectangle.width = width; c->floating_rectangle.height = height; arrange(loc.monitor, loc.desktop); } } xcb_configure_notify_event_t evt; unsigned int bw = c->border_width; xcb_rectangle_t r = IS_FULLSCREEN(c) ? loc.monitor->rectangle : c->tiled_rectangle; evt.response_type = XCB_CONFIGURE_NOTIFY; evt.event = e->window; evt.window = e->window; evt.above_sibling = XCB_NONE; evt.x = r.x; evt.y = r.y; evt.width = r.width; evt.height = r.height; evt.border_width = bw; evt.override_redirect = false; xcb_send_event(dpy, false, e->window, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (const char *) &evt); } } void configure_notify(xcb_generic_event_t *evt) { xcb_configure_notify_event_t *e = (xcb_configure_notify_event_t *) evt; if (e->window == root) { screen_width = e->width; screen_height = e->height; } } void destroy_notify(xcb_generic_event_t *evt) { xcb_destroy_notify_event_t *e = (xcb_destroy_notify_event_t *) evt; unmanage_window(e->window); } void unmap_notify(xcb_generic_event_t *evt) { xcb_unmap_notify_event_t *e = (xcb_unmap_notify_event_t *) evt; if (e->window == motion_recorder.id) { /* Unmapping the motion recorder in `query_pointer` will produce * unwanted enter notify events. We can filter those events because * their sequence number is the same as the sequence number of the * related unmap notify event. This is a technique used by i3-wm to * filter enter notify events. */ motion_recorder.sequence = e->sequence; return; } /* Filter out destroyed windows */ if (!window_exists(e->window)) { return; } set_window_state(e->window, XCB_ICCCM_WM_STATE_WITHDRAWN); unmanage_window(e->window); } void property_notify(xcb_generic_event_t *evt) { xcb_property_notify_event_t *e = (xcb_property_notify_event_t *) evt; if (!ignore_ewmh_struts && e->atom == ewmh->_NET_WM_STRUT_PARTIAL && ewmh_handle_struts(e->window)) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { arrange(m, d); } } } if (e->atom != XCB_ATOM_WM_HINTS && e->atom != XCB_ATOM_WM_NORMAL_HINTS) { return; } coordinates_t loc; if (!locate_window(e->window, &loc)) { for (pending_rule_t *pr = pending_rule_head; pr != NULL; pr = pr->next) { if (pr->win == e->window) { postpone_event(pr, evt); break; } } return; } if (e->atom == XCB_ATOM_WM_HINTS) { xcb_icccm_wm_hints_t hints; if (xcb_icccm_get_wm_hints_reply(dpy, xcb_icccm_get_wm_hints(dpy, e->window), &hints, NULL) == 1 && (hints.flags & XCB_ICCCM_WM_HINT_X_URGENCY)) set_urgent(loc.monitor, loc.desktop, loc.node, xcb_icccm_wm_hints_get_urgency(&hints)); } else if (e->atom == XCB_ATOM_WM_NORMAL_HINTS) { client_t *c = loc.node->client; if (xcb_icccm_get_wm_normal_hints_reply(dpy, xcb_icccm_get_wm_normal_hints(dpy, e->window), &c->size_hints, NULL) == 1) { arrange(loc.monitor, loc.desktop); } } } void client_message(xcb_generic_event_t *evt) { xcb_client_message_event_t *e = (xcb_client_message_event_t *) evt; if (e->type == ewmh->_NET_CURRENT_DESKTOP) { coordinates_t loc; if (ewmh_locate_desktop(e->data.data32[0], &loc)) { focus_node(loc.monitor, loc.desktop, loc.desktop->focus); } return; } coordinates_t loc; if (!locate_window(e->window, &loc)) { for (pending_rule_t *pr = pending_rule_head; pr != NULL; pr = pr->next) { if (pr->win == e->window) { postpone_event(pr, evt); break; } } return; } if (e->type == ewmh->_NET_WM_STATE) { handle_state(loc.monitor, loc.desktop, loc.node, e->data.data32[1], e->data.data32[0]); handle_state(loc.monitor, loc.desktop, loc.node, e->data.data32[2], e->data.data32[0]); } else if (e->type == ewmh->_NET_ACTIVE_WINDOW) { if ((ignore_ewmh_focus && e->data.data32[0] == XCB_EWMH_CLIENT_SOURCE_TYPE_NORMAL) || loc.node == mon->desk->focus) { return; } focus_node(loc.monitor, loc.desktop, loc.node); } else if (e->type == ewmh->_NET_WM_DESKTOP) { coordinates_t dloc; if (ewmh_locate_desktop(e->data.data32[0], &dloc)) { transfer_node(loc.monitor, loc.desktop, loc.node, dloc.monitor, dloc.desktop, dloc.desktop->focus, false); } } else if (e->type == ewmh->_NET_CLOSE_WINDOW) { close_node(loc.node); } } void focus_in(xcb_generic_event_t *evt) { xcb_focus_in_event_t *e = (xcb_focus_in_event_t *) evt; if (e->mode == XCB_NOTIFY_MODE_GRAB || e->mode == XCB_NOTIFY_MODE_UNGRAB || e->detail == XCB_NOTIFY_DETAIL_POINTER || e->detail == XCB_NOTIFY_DETAIL_POINTER_ROOT || e->detail == XCB_NOTIFY_DETAIL_NONE) { return; } if (mon->desk->focus != NULL) { if (e->event == mon->desk->focus->id) { return; } if (e->event == root) { /* Some clients expect the window manager to refocus the focused window in this case */ bool pff = pointer_follows_focus; pointer_follows_focus = false; focus_node(mon, mon->desk, mon->desk->focus); pointer_follows_focus = pff; return; } } coordinates_t loc; if (locate_window(e->event, &loc)) { // prevent input focus stealing update_input_focus(); } } void button_press(xcb_generic_event_t *evt) { xcb_button_press_event_t *e = (xcb_button_press_event_t *) evt; bool replay = false; for (unsigned int i = 0; i < LENGTH(BUTTONS); i++) { if (e->detail != BUTTONS[i]) { continue; } if ((click_to_focus == (int8_t) XCB_BUTTON_INDEX_ANY || click_to_focus == (int8_t) BUTTONS[i]) && cleaned_mask(e->state) == XCB_NONE) { bool pff = pointer_follows_focus; bool pfm = pointer_follows_monitor; pointer_follows_focus = false; pointer_follows_monitor = false; replay = !grab_pointer(ACTION_FOCUS) || !swallow_first_click; pointer_follows_focus = pff; pointer_follows_monitor = pfm; } else { grab_pointer(pointer_actions[i]); } } xcb_allow_events(dpy, replay ? XCB_ALLOW_REPLAY_POINTER : XCB_ALLOW_SYNC_POINTER, e->time); xcb_flush(dpy); } void enter_notify(xcb_generic_event_t *evt) { xcb_enter_notify_event_t *e = (xcb_enter_notify_event_t *) evt; xcb_window_t win = e->event; if (e->mode != XCB_NOTIFY_MODE_NORMAL || e->detail == XCB_NOTIFY_DETAIL_INFERIOR) { return; } /* Ignore the enter notify events that we generated by unmapping the motion * recorder window in `query_pointer`. */ if (motion_recorder.enabled && motion_recorder.sequence == e->sequence) { return; } if (win == mon->root || (mon->desk->focus != NULL && (win == mon->desk->focus->id || (mon->desk->focus->presel != NULL && win == mon->desk->focus->presel->feedback)))) { return; } update_motion_recorder(); } void motion_notify(xcb_generic_event_t *evt) { xcb_motion_notify_event_t *e = (xcb_motion_notify_event_t *) evt; static uint16_t last_motion_x = 0, last_motion_y = 0; static xcb_timestamp_t last_motion_time = 0; int64_t dtime = e->time - last_motion_time; /* Ignore unintentional pointer motions. */ if (dtime > 1000) { last_motion_time = e->time; last_motion_x = e->event_x; last_motion_y = e->event_y; return; } int mdist = abs(e->event_x - last_motion_x) + abs(e->event_y - last_motion_y); if (mdist < 10) { return; } disable_motion_recorder(); xcb_window_t win = XCB_NONE; query_pointer(&win, NULL); coordinates_t loc; bool pff = pointer_follows_focus; bool pfm = pointer_follows_monitor; pointer_follows_focus = false; pointer_follows_monitor = false; auto_raise = false; if (locate_window(win, &loc)) { if (loc.monitor->desk == loc.desktop && loc.node != mon->desk->focus) { focus_node(loc.monitor, loc.desktop, loc.node); } } else { xcb_point_t pt = {e->root_x, e->root_y}; monitor_t *m = monitor_from_point(pt); if (m != NULL && m != mon) { focus_node(m, m->desk, m->desk->focus); } } pointer_follows_focus = pff; pointer_follows_monitor = pfm; auto_raise = true; } void handle_state(monitor_t *m, desktop_t *d, node_t *n, xcb_atom_t state, unsigned int action) { if (state == ewmh->_NET_WM_STATE_FULLSCREEN) { if (action == XCB_EWMH_WM_STATE_ADD && (ignore_ewmh_fullscreen & STATE_TRANSITION_ENTER) == 0) { set_state(m, d, n, STATE_FULLSCREEN); } else if (action == XCB_EWMH_WM_STATE_REMOVE && (ignore_ewmh_fullscreen & STATE_TRANSITION_EXIT) == 0) { if (n->client->state == STATE_FULLSCREEN) { set_state(m, d, n, n->client->last_state); } } else if (action == XCB_EWMH_WM_STATE_TOGGLE) { client_state_t next_state = IS_FULLSCREEN(n->client) ? n->client->last_state : STATE_FULLSCREEN; if ((next_state == STATE_FULLSCREEN && (ignore_ewmh_fullscreen & STATE_TRANSITION_ENTER) == 0) || (next_state != STATE_FULLSCREEN && (ignore_ewmh_fullscreen & STATE_TRANSITION_EXIT) == 0)) { set_state(m, d, n, next_state); } } arrange(m, d); } else if (state == ewmh->_NET_WM_STATE_BELOW) { if (action == XCB_EWMH_WM_STATE_ADD) { set_layer(m, d, n, LAYER_BELOW); } else if (action == XCB_EWMH_WM_STATE_REMOVE) { if (n->client->layer == LAYER_BELOW) { set_layer(m, d, n, n->client->last_layer); } } else if (action == XCB_EWMH_WM_STATE_TOGGLE) { set_layer(m, d, n, n->client->layer == LAYER_BELOW ? n->client->last_layer : LAYER_BELOW); } } else if (state == ewmh->_NET_WM_STATE_ABOVE) { if (action == XCB_EWMH_WM_STATE_ADD) { set_layer(m, d, n, LAYER_ABOVE); } else if (action == XCB_EWMH_WM_STATE_REMOVE) { if (n->client->layer == LAYER_ABOVE) { set_layer(m, d, n, n->client->last_layer); } } else if (action == XCB_EWMH_WM_STATE_TOGGLE) { set_layer(m, d, n, n->client->layer == LAYER_ABOVE ? n->client->last_layer : LAYER_ABOVE); } } else if (state == ewmh->_NET_WM_STATE_HIDDEN) { if (action == XCB_EWMH_WM_STATE_ADD) { set_hidden(m, d, n, true); } else if (action == XCB_EWMH_WM_STATE_REMOVE) { set_hidden(m, d, n, false); } else if (action == XCB_EWMH_WM_STATE_TOGGLE) { set_hidden(m, d, n, !n->hidden); } } else if (state == ewmh->_NET_WM_STATE_STICKY) { if (action == XCB_EWMH_WM_STATE_ADD) { set_sticky(m, d, n, true); } else if (action == XCB_EWMH_WM_STATE_REMOVE) { set_sticky(m, d, n, false); } else if (action == XCB_EWMH_WM_STATE_TOGGLE) { set_sticky(m, d, n, !n->sticky); } } else if (state == ewmh->_NET_WM_STATE_DEMANDS_ATTENTION) { if (action == XCB_EWMH_WM_STATE_ADD) { set_urgent(m, d, n, true); } else if (action == XCB_EWMH_WM_STATE_REMOVE) { set_urgent(m, d, n, false); } else if (action == XCB_EWMH_WM_STATE_TOGGLE) { set_urgent(m, d, n, !n->client->urgent); } #define HANDLE_WM_STATE(s) \ } else if (state == ewmh->_NET_WM_STATE_##s) { \ if (action == XCB_EWMH_WM_STATE_ADD) { \ n->client->wm_flags |= WM_FLAG_##s; \ } else if (action == XCB_EWMH_WM_STATE_REMOVE) { \ n->client->wm_flags &= ~WM_FLAG_##s; \ } else if (action == XCB_EWMH_WM_STATE_TOGGLE) { \ n->client->wm_flags ^= WM_FLAG_##s; \ } \ ewmh_wm_state_update(n); HANDLE_WM_STATE(MODAL) HANDLE_WM_STATE(MAXIMIZED_VERT) HANDLE_WM_STATE(MAXIMIZED_HORZ) HANDLE_WM_STATE(SHADED) HANDLE_WM_STATE(SKIP_TASKBAR) HANDLE_WM_STATE(SKIP_PAGER) } #undef HANDLE_WM_STATE } void mapping_notify(xcb_generic_event_t *evt) { if (mapping_events_count == 0) { return; } xcb_mapping_notify_event_t *e = (xcb_mapping_notify_event_t *) evt; if (e->request == XCB_MAPPING_POINTER) { return; } if (mapping_events_count > 0) { mapping_events_count--; } ungrab_buttons(); grab_buttons(); } void process_error(xcb_generic_event_t *evt) { xcb_request_error_t *e = (xcb_request_error_t *) evt; /* Ignore unavoidable failed requests */ if (e->error_code == ERROR_CODE_BAD_WINDOW) { return; } warn("Failed request: %s, %s: 0x%08X.\n", xcb_event_get_request_label(e->major_opcode), xcb_event_get_error_label(e->error_code), e->bad_value); } ================================================ FILE: src/events.h ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef BSPWM_EVENTS_H #define BSPWM_EVENTS_H #include #include #define ERROR_CODE_BAD_WINDOW 3 extern uint8_t randr_base; static const xcb_button_index_t BUTTONS[] = {XCB_BUTTON_INDEX_1, XCB_BUTTON_INDEX_2, XCB_BUTTON_INDEX_3}; void handle_event(xcb_generic_event_t *evt); void map_request(xcb_generic_event_t *evt); void configure_request(xcb_generic_event_t *evt); void configure_notify(xcb_generic_event_t *evt); void destroy_notify(xcb_generic_event_t *evt); void unmap_notify(xcb_generic_event_t *evt); void property_notify(xcb_generic_event_t *evt); void client_message(xcb_generic_event_t *evt); void focus_in(xcb_generic_event_t *evt); void button_press(xcb_generic_event_t *evt); void enter_notify(xcb_generic_event_t *evt); void motion_notify(xcb_generic_event_t *evt); void handle_state(monitor_t *m, desktop_t *d, node_t *n, xcb_atom_t state, unsigned int action); void mapping_notify(xcb_generic_event_t *evt); void process_error(xcb_generic_event_t *evt); #endif ================================================ FILE: src/ewmh.c ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include "bspwm.h" #include "settings.h" #include "tree.h" #include "ewmh.h" xcb_ewmh_connection_t *ewmh; void ewmh_init(void) { ewmh = calloc(1, sizeof(xcb_ewmh_connection_t)); if (xcb_ewmh_init_atoms_replies(ewmh, xcb_ewmh_init_atoms(dpy, ewmh), NULL) == 0) { err("Can't initialize EWMH atoms.\n"); } } void ewmh_update_active_window(void) { xcb_window_t win = ((mon->desk->focus == NULL || mon->desk->focus->client == NULL) ? XCB_NONE : mon->desk->focus->id); xcb_ewmh_set_active_window(ewmh, default_screen, win); } void ewmh_update_number_of_desktops(void) { uint32_t desktops_count = 0; for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { desktops_count++; } } xcb_ewmh_set_number_of_desktops(ewmh, default_screen, desktops_count); } uint32_t ewmh_get_desktop_index(desktop_t *d) { uint32_t i = 0; for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *cd = m->desk_head; cd != NULL; cd = cd->next, i++) { if (d == cd) { return i; } } } return 0; } bool ewmh_locate_desktop(uint32_t i, coordinates_t *loc) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next, i--) { if (i == 0) { loc->monitor = m; loc->desktop = d; loc->node = NULL; return true; } } } return false; } void ewmh_update_current_desktop(void) { if (mon == NULL) { return; } uint32_t i = ewmh_get_desktop_index(mon->desk); xcb_ewmh_set_current_desktop(ewmh, default_screen, i); } void ewmh_set_wm_desktop(node_t *n, desktop_t *d) { uint32_t i = ewmh_get_desktop_index(d); for (node_t *f = first_extrema(n); f != NULL; f = next_leaf(f, n)) { if (f->client == NULL) { continue; } xcb_ewmh_set_wm_desktop(ewmh, f->id, i); } } void ewmh_update_wm_desktops(void) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { uint32_t i = ewmh_get_desktop_index(d); for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) { if (n->client == NULL) { continue; } xcb_ewmh_set_wm_desktop(ewmh, n->id, i); } } } } void ewmh_update_desktop_names(void) { char names[MAXLEN]; unsigned int i, j; uint32_t names_len; i = 0; for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { for (j = 0; d->name[j] != '\0' && (i + j) < sizeof(names); j++) { names[i + j] = d->name[j]; } i += j; if (i < sizeof(names)) { names[i++] = '\0'; } } } if (i < 1) { xcb_ewmh_set_desktop_names(ewmh, default_screen, 0, NULL); return; } names_len = i - 1; xcb_ewmh_set_desktop_names(ewmh, default_screen, names_len, names); } void ewmh_update_desktop_viewport(void) { uint32_t desktops_count = 0; for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { desktops_count++; } } if (desktops_count == 0) { xcb_ewmh_set_desktop_viewport(ewmh, default_screen, 0, NULL); return; } xcb_ewmh_coordinates_t coords[desktops_count]; uint16_t desktop = 0; for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { coords[desktop++] = (xcb_ewmh_coordinates_t){m->rectangle.x, m->rectangle.y}; } } xcb_ewmh_set_desktop_viewport(ewmh, default_screen, desktop, coords); } bool ewmh_handle_struts(xcb_window_t win) { xcb_ewmh_wm_strut_partial_t struts; bool changed = false; if (xcb_ewmh_get_wm_strut_partial_reply(ewmh, xcb_ewmh_get_wm_strut_partial(ewmh, win), &struts, NULL) == 1) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { xcb_rectangle_t rect = m->rectangle; if (rect.x < (int16_t) struts.left && (int16_t) struts.left < (rect.x + rect.width - 1) && (int16_t) struts.left_end_y >= rect.y && (int16_t) struts.left_start_y < (rect.y + rect.height)) { int dx = struts.left - rect.x; if (m->padding.left < 0) { m->padding.left += dx; } else { m->padding.left = MAX(dx, m->padding.left); } changed = true; } if ((rect.x + rect.width) > (int16_t) (screen_width - struts.right) && (int16_t) (screen_width - struts.right) > rect.x && (int16_t) struts.right_end_y >= rect.y && (int16_t) struts.right_start_y < (rect.y + rect.height)) { int dx = (rect.x + rect.width) - screen_width + struts.right; if (m->padding.right < 0) { m->padding.right += dx; } else { m->padding.right = MAX(dx, m->padding.right); } changed = true; } if (rect.y < (int16_t) struts.top && (int16_t) struts.top < (rect.y + rect.height - 1) && (int16_t) struts.top_end_x >= rect.x && (int16_t) struts.top_start_x < (rect.x + rect.width)) { int dy = struts.top - rect.y; if (m->padding.top < 0) { m->padding.top += dy; } else { m->padding.top = MAX(dy, m->padding.top); } changed = true; } if ((rect.y + rect.height) > (int16_t) (screen_height - struts.bottom) && (int16_t) (screen_height - struts.bottom) > rect.y && (int16_t) struts.bottom_end_x >= rect.x && (int16_t) struts.bottom_start_x < (rect.x + rect.width)) { int dy = (rect.y + rect.height) - screen_height + struts.bottom; if (m->padding.bottom < 0) { m->padding.bottom += dy; } else { m->padding.bottom = MAX(dy, m->padding.bottom); } changed = true; } } } return changed; } void ewmh_update_client_list(bool stacking) { if (clients_count == 0) { xcb_ewmh_set_client_list(ewmh, default_screen, 0, NULL); xcb_ewmh_set_client_list_stacking(ewmh, default_screen, 0, NULL); return; } xcb_window_t wins[clients_count]; unsigned int i = 0; if (stacking) { for (stacking_list_t *s = stack_head; s != NULL; s = s->next) { wins[i++] = s->node->id; } xcb_ewmh_set_client_list_stacking(ewmh, default_screen, clients_count, wins); } else { for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) { if (n->client == NULL) { continue; } wins[i++] = n->id; } } } xcb_ewmh_set_client_list(ewmh, default_screen, clients_count, wins); } } void ewmh_wm_state_update(node_t *n) { client_t *c = n->client; size_t count = 0; uint32_t values[12]; #define HANDLE_WM_STATE(s) \ if (WM_FLAG_##s & c->wm_flags) { \ values[count++] = ewmh->_NET_WM_STATE_##s; \ } HANDLE_WM_STATE(MODAL) HANDLE_WM_STATE(STICKY) HANDLE_WM_STATE(MAXIMIZED_VERT) HANDLE_WM_STATE(MAXIMIZED_HORZ) HANDLE_WM_STATE(SHADED) HANDLE_WM_STATE(SKIP_TASKBAR) HANDLE_WM_STATE(SKIP_PAGER) HANDLE_WM_STATE(HIDDEN) HANDLE_WM_STATE(FULLSCREEN) HANDLE_WM_STATE(ABOVE) HANDLE_WM_STATE(BELOW) HANDLE_WM_STATE(DEMANDS_ATTENTION) #undef HANDLE_WM_STATE xcb_ewmh_set_wm_state(ewmh, n->id, count, values); } void ewmh_set_supporting(xcb_window_t win) { pid_t wm_pid = getpid(); xcb_ewmh_set_supporting_wm_check(ewmh, root, win); xcb_ewmh_set_supporting_wm_check(ewmh, win, win); xcb_ewmh_set_wm_name(ewmh, win, strlen(WM_NAME), WM_NAME); xcb_ewmh_set_wm_pid(ewmh, win, wm_pid); } ================================================ FILE: src/ewmh.h ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef BSPWM_EWMH_H #define BSPWM_EWMH_H #include extern xcb_ewmh_connection_t *ewmh; void ewmh_init(void); void ewmh_update_active_window(void); void ewmh_update_number_of_desktops(void); uint32_t ewmh_get_desktop_index(desktop_t *d); bool ewmh_locate_desktop(uint32_t i, coordinates_t *loc); void ewmh_update_current_desktop(void); void ewmh_set_wm_desktop(node_t *n, desktop_t *d); void ewmh_update_wm_desktops(void); void ewmh_update_desktop_names(void); void ewmh_update_desktop_viewport(void); bool ewmh_handle_struts(xcb_window_t win); void ewmh_update_client_list(bool stacking); void ewmh_wm_state_update(node_t *n); void ewmh_set_supporting(xcb_window_t win); #endif ================================================ FILE: src/geometry.c ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "types.h" #include "settings.h" #include "geometry.h" bool is_inside(xcb_point_t p, xcb_rectangle_t r) { return (p.x >= r.x && p.x < (r.x + r.width) && p.y >= r.y && p.y < (r.y + r.height)); } /* Returns true if a contains b */ bool contains(xcb_rectangle_t a, xcb_rectangle_t b) { return (a.x <= b.x && (a.x + a.width) >= (b.x + b.width) && a.y <= b.y && (a.y + a.height) >= (b.y + b.height)); } unsigned int area(xcb_rectangle_t r) { return r.width * r.height; } /* Distance between the `dir` edge of `r1` and the `opposite(dir)` edge of `r2`. */ uint32_t boundary_distance(xcb_rectangle_t r1, xcb_rectangle_t r2, direction_t dir) { xcb_point_t r1_max = {r1.x + r1.width - 1, r1.y + r1.height - 1}; xcb_point_t r2_max = {r2.x + r2.width - 1, r2.y + r2.height - 1}; switch (dir) { case DIR_NORTH: return r2_max.y > r1.y ? r2_max.y - r1.y : r1.y - r2_max.y; break; case DIR_WEST: return r2_max.x > r1.x ? r2_max.x - r1.x : r1.x - r2_max.x; break; case DIR_SOUTH: return r2.y < r1_max.y ? r1_max.y - r2.y : r2.y - r1_max.y; break; case DIR_EAST: return r2.x < r1_max.x ? r1_max.x - r2.x : r2.x - r1_max.x; break; default: return UINT32_MAX; } } /* Is `r2` on the `dir` side of `r1`? */ bool on_dir_side(xcb_rectangle_t r1, xcb_rectangle_t r2, direction_t dir) { xcb_point_t r1_max = {r1.x + r1.width - 1, r1.y + r1.height - 1}; xcb_point_t r2_max = {r2.x + r2.width - 1, r2.y + r2.height - 1}; /* Eliminate rectangles on the opposite side */ switch (directional_focus_tightness) { case TIGHTNESS_LOW: switch (dir) { case DIR_NORTH: if (r2.y > r1_max.y) { return false; } break; case DIR_WEST: if (r2.x > r1_max.x) { return false; } break; case DIR_SOUTH: if (r2_max.y < r1.y) { return false; } break; case DIR_EAST: if (r2_max.x < r1.x) { return false; } break; default: return false; } break; case TIGHTNESS_HIGH: switch (dir) { case DIR_NORTH: if (r2.y >= r1.y) { return false; } break; case DIR_WEST: if (r2.x >= r1.x) { return false; } break; case DIR_SOUTH: if (r2_max.y <= r1_max.y) { return false; } break; case DIR_EAST: if (r2_max.x <= r1_max.x) { return false; } break; default: return false; } break; default: return false; } /* Is there a shared vertical/horizontal range? */ switch (dir) { case DIR_NORTH: case DIR_SOUTH: return (r2.x >= r1.x && r2.x <= r1_max.x) || (r2_max.x >= r1.x && r2_max.x <= r1_max.x) || (r1.x > r2.x && r1.x < r2_max.x); break; case DIR_WEST: case DIR_EAST: return (r2.y >= r1.y && r2.y <= r1_max.y) || (r2_max.y >= r1.y && r2_max.y <= r1_max.y) || (r1.y > r2.y && r1_max.y < r2_max.y); break; default: return false; } } bool rect_eq(xcb_rectangle_t a, xcb_rectangle_t b) { return (a.x == b.x && a.y == b.y && a.width == b.width && a.height == b.height); } int rect_cmp(xcb_rectangle_t r1, xcb_rectangle_t r2) { if (r1.y >= (r2.y + r2.height)) { return 1; } else if (r2.y >= (r1.y + r1.height)) { return -1; } else { if (r1.x >= (r2.x + r2.width)) { return 1; } else if (r2.x >= (r1.x + r1.width)) { return -1; } else { return area(r2) - area(r1); } } } ================================================ FILE: src/geometry.h ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef BSPWM_GEOMETRY_H #define BSPWM_GEOMETRY_H #include #include bool is_inside(xcb_point_t p, xcb_rectangle_t r); bool contains(xcb_rectangle_t a, xcb_rectangle_t b); unsigned int area(xcb_rectangle_t r); uint32_t boundary_distance(xcb_rectangle_t r1, xcb_rectangle_t r2, direction_t dir); bool on_dir_side(xcb_rectangle_t r1, xcb_rectangle_t r2, direction_t dir); bool rect_eq(xcb_rectangle_t a, xcb_rectangle_t b); int rect_cmp(xcb_rectangle_t r1, xcb_rectangle_t r2); #endif ================================================ FILE: src/helpers.c ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include "bspwm.h" void warn(char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); } __attribute__((noreturn)) void err(char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); exit(EXIT_FAILURE); } char *read_string(const char *file_path, size_t *tlen) { if (file_path == NULL) { return NULL; } int fd = open(file_path, O_RDONLY); if (fd == -1) { perror("Read file: open"); return NULL; } char buf[BUFSIZ], *content = NULL; size_t len = sizeof(buf); if ((content = calloc(len, sizeof(char))) == NULL) { perror("Read file: calloc"); goto end; } int nb; *tlen = 0; while (true) { nb = read(fd, buf, sizeof(buf)); if (nb < 0) { perror("Restore tree: read"); free(content); content = NULL; goto end; } else if (nb == 0) { break; } else { *tlen += nb; if (*tlen > len) { len *= 2; char *rcontent = realloc(content, len * sizeof(char)); if (rcontent == NULL) { perror("Read file: realloc"); free(content); content = NULL; goto end; } else { content = rcontent; } } strncpy(content + (*tlen - nb), buf, nb); } } end: close(fd); return content; } char *copy_string(char *str, size_t len) { char *cpy = calloc(1, ((len+1) * sizeof(char))); if (cpy == NULL) { perror("Copy string: calloc"); return NULL; } strncpy(cpy, str, len); cpy[len] = '\0'; return cpy; } char *mktempfifo(const char *template) { int tempfd; char *runtime_dir = getenv(RUNTIME_DIR_ENV); if (runtime_dir == NULL) { runtime_dir = "/tmp"; } char *fifo_path = malloc(strlen(runtime_dir)+1+strlen(template)+1); if (fifo_path == NULL) { return NULL; } sprintf(fifo_path, "%s/%s", runtime_dir, template); if ((tempfd = mkstemp(fifo_path)) == -1) { free(fifo_path); return NULL; } close(tempfd); unlink(fifo_path); if (mkfifo(fifo_path, 0666) == -1) { free(fifo_path); return NULL; } return fifo_path; } int asprintf(char **buf, const char *fmt, ...) { va_list args; va_start(args, fmt); int size = vasprintf(buf, fmt, args); va_end(args); return size; } int vasprintf(char **buf, const char *fmt, va_list args) { va_list tmp; va_copy(tmp, args); int size = vsnprintf(NULL, 0, fmt, tmp); va_end(tmp); if (size < 0) { return -1; } *buf = malloc(size + 1); if (*buf == NULL) { return -1; } size = vsprintf(*buf, fmt, args); return size; } bool is_hex_color(const char *color) { if (color[0] != '#' || strlen(color) != 7) { return false; } for (int i = 1; i < 7; i++) { if (!isxdigit(color[i])) { return false; } } return true; } char *tokenize_with_escape(struct tokenize_state *state, const char *s, char sep) { if (s != NULL) { // first call state->in_escape = false; state->pos = s; state->len = strlen(s) + 1; } char *outp = calloc(state->len, 1); char *ret = outp; if (!outp) return NULL; char cur; while (*state->pos) { --state->len; cur = *state->pos++; if (state->in_escape) { *outp++ = cur; state->in_escape = false; continue; } if (cur == '\\') { state->in_escape = !state->in_escape; } else if (cur == sep) { return ret; } else { *outp++ = cur; } } return ret; } ================================================ FILE: src/helpers.h ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef BSPWM_HELPERS_H #define BSPWM_HELPERS_H #include #include #include #include #include #include #define LENGTH(x) (sizeof(x) / sizeof(*x)) #define MAX(A, B) ((A) > (B) ? (A) : (B)) #define MIN(A, B) ((A) < (B) ? (A) : (B)) #define IS_TILED(c) (c->state == STATE_TILED || c->state == STATE_PSEUDO_TILED) #define IS_FLOATING(c) (c->state == STATE_FLOATING) #define IS_FULLSCREEN(c) (c->state == STATE_FULLSCREEN) #define IS_RECEPTACLE(n) (is_leaf(n) && n->client == NULL) #define BOOL_STR(A) ((A) ? "true" : "false") #define ON_OFF_STR(A) ((A) ? "on" : "off") #define LAYOUT_STR(A) ((A) == LAYOUT_TILED ? "tiled" : "monocle") #define LAYOUT_CHR(A) ((A) == LAYOUT_TILED ? 'T' : 'M') #define CHILD_POL_STR(A) ((A) == FIRST_CHILD ? "first_child" : "second_child") #define AUTO_SCM_STR(A) ((A) == SCHEME_LONGEST_SIDE ? "longest_side" : ((A) == SCHEME_ALTERNATE ? "alternate" : "spiral")) #define TIGHTNESS_STR(A) ((A) == TIGHTNESS_HIGH ? "high" : "low") #define SPLIT_TYPE_STR(A) ((A) == TYPE_HORIZONTAL ? "horizontal" : "vertical") #define SPLIT_MODE_STR(A) ((A) == MODE_AUTOMATIC ? "automatic" : "manual") #define SPLIT_DIR_STR(A) ((A) == DIR_NORTH ? "north" : ((A) == DIR_WEST ? "west" : ((A) == DIR_SOUTH ? "south" : "east"))) #define STATE_STR(A) ((A) == STATE_TILED ? "tiled" : ((A) == STATE_FLOATING ? "floating" : ((A) == STATE_FULLSCREEN ? "fullscreen" : "pseudo_tiled"))) #define STATE_CHR(A) ((A) == STATE_TILED ? 'T' : ((A) == STATE_FLOATING ? 'F' : ((A) == STATE_FULLSCREEN ? '=' : 'P'))) #define LAYER_STR(A) ((A) == LAYER_BELOW ? "below" : ((A) == LAYER_NORMAL ? "normal" : "above")) #define HSH_MODE_STR(A) ((A) == HONOR_SIZE_HINTS_TILED ? "tiled" : ((A) == HONOR_SIZE_HINTS_FLOATING ? "floating" : BOOL_STR(A))) #define XCB_CONFIG_WINDOW_X_Y (XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y) #define XCB_CONFIG_WINDOW_WIDTH_HEIGHT (XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT) #define XCB_CONFIG_WINDOW_X_Y_WIDTH_HEIGHT (XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT) #define SHOULD_HONOR_SIZE_HINTS(hsh, state) \ ((hsh) && \ (((hsh) == HONOR_SIZE_HINTS_YES && \ (state) != STATE_FULLSCREEN) || \ ((hsh) == HONOR_SIZE_HINTS_TILED && \ (state) == STATE_TILED) || \ ((hsh) == HONOR_SIZE_HINTS_FLOATING && \ ((state) == STATE_FLOATING || (state) == STATE_PSEUDO_TILED)))) #define MAXLEN 256 #define SMALEN 32 #define INIT_CAP 8 #define cleaned_mask(m) (m & ~(num_lock | scroll_lock | caps_lock)) #define streq(s1, s2) (strcmp((s1), (s2)) == 0) #define unsigned_subtract(a, b) \ do { \ if (b > a) { \ a = 0; \ } else { \ a -= b; \ } \ } while (false) void warn(char *fmt, ...); void err(char *fmt, ...); char *read_string(const char *file_path, size_t *tlen); char *copy_string(char *str, size_t len); char *mktempfifo(const char *template); int asprintf(char **buf, const char *fmt, ...); int vasprintf(char **buf, const char *fmt, va_list args); bool is_hex_color(const char *color); struct tokenize_state { bool in_escape; const char *pos; size_t len; }; char *tokenize_with_escape(struct tokenize_state *state, const char *s, char sep); #endif ================================================ FILE: src/history.c ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include "bspwm.h" #include "tree.h" #include "query.h" #include "history.h" history_t *make_history(monitor_t *m, desktop_t *d, node_t *n) { history_t *h = calloc(1, sizeof(history_t)); h->loc = (coordinates_t) {m, d, n}; h->prev = h->next = NULL; h->latest = true; return h; } void history_add(monitor_t *m, desktop_t *d, node_t *n, bool focused) { if (!record_history) { return; } if (focused) { history_needle = NULL; } history_t *h = make_history(m, d, n); if (history_head == NULL) { history_head = history_tail = h; } else if ((n != NULL && history_tail->loc.node != n) || (n == NULL && d != history_tail->loc.desktop)) { history_t *ip = focused ? history_tail : NULL; for (history_t *hh = history_tail; hh != NULL; hh = hh->prev) { if ((n != NULL && hh->loc.node == n) || (n == NULL && d == hh->loc.desktop)) { hh->latest = false; } if (ip == NULL && ((n != NULL && hh->loc.desktop == d) || (n == NULL && hh->loc.monitor == m))) { ip = hh; } } if (ip != NULL) { history_insert_after(h, ip); } else { ip = history_head; if (n != NULL) { for (history_t *hh = history_head; hh != NULL; hh = hh->next) { if (hh->latest && hh->loc.monitor == m) { ip = hh; break; } } } history_insert_before(h, ip); } } else { free(h); } } // Inserts `a` after `b`. void history_insert_after(history_t *a, history_t *b) { a->next = b->next; if (b->next != NULL) { b->next->prev = a; } b->next = a; a->prev = b; if (history_tail == b) { history_tail = a; } } // Inserts `a` before `b`. void history_insert_before(history_t *a, history_t *b) { a->prev = b->prev; if (b->prev != NULL) { b->prev->next = a; } b->prev = a; a->next = b; if (history_head == b) { history_head = a; } } void history_remove(desktop_t *d, node_t *n, bool deep) { /* removing from the newest to the oldest is required */ /* for maintaining the *latest* attribute */ history_t *b = history_tail; while (b != NULL) { if ((n != NULL && ((deep && is_descendant(b->loc.node, n)) || (!deep && b->loc.node == n))) || (n == NULL && d == b->loc.desktop)) { history_t *a = b->next; history_t *c = b->prev; if (a != NULL) { /* remove duplicate entries */ while (c != NULL && ((a->loc.node != NULL && a->loc.node == c->loc.node) || (a->loc.node == NULL && a->loc.desktop == c->loc.desktop))) { history_t *p = c->prev; if (history_head == c) { history_head = history_tail; } if (history_needle == c) { history_needle = history_tail; } free(c); c = p; } a->prev = c; } if (c != NULL) { c->next = a; } if (history_tail == b) { history_tail = c; } if (history_head == b) { history_head = a; } if (history_needle == b) { history_needle = c; } free(b); b = c; } else { b = b->prev; } } } void empty_history(void) { history_t *h = history_head; while (h != NULL) { history_t *next = h->next; free(h); h = next; } history_head = history_tail = NULL; } node_t *history_last_node(desktop_t *d, node_t *n) { for (history_t *h = history_tail; h != NULL; h = h->prev) { if (h->latest && h->loc.node != NULL && !h->loc.node->hidden && !is_descendant(h->loc.node, n) && h->loc.desktop == d) { return h->loc.node; } } return NULL; } desktop_t *history_last_desktop(monitor_t *m, desktop_t *d) { for (history_t *h = history_tail; h != NULL; h = h->prev) { if (h->latest && h->loc.desktop != d && h->loc.monitor == m) { return h->loc.desktop; } } return NULL; } monitor_t *history_last_monitor(monitor_t *m) { for (history_t *h = history_tail; h != NULL; h = h->prev) { if (h->latest && h->loc.monitor != m) { return h->loc.monitor; } } return NULL; } bool history_find_newest_node(coordinates_t *ref, coordinates_t *dst, node_select_t *sel) { for (history_t *h = history_tail; h != NULL; h = h->prev) { if (h->loc.node == NULL || h->loc.node->hidden || !node_matches(&h->loc, ref, sel)) { continue; } *dst = h->loc; return true; } return false; } bool history_find_node(history_dir_t hdi, coordinates_t *ref, coordinates_t *dst, node_select_t *sel) { if (history_needle == NULL || record_history) { history_needle = history_tail; } history_t *h; for (h = history_needle; h != NULL; h = (hdi == HISTORY_OLDER ? h->prev : h->next)) { if (!h->latest || h->loc.node == NULL || h->loc.node == ref->node || h->loc.node->hidden || !node_matches(&h->loc, ref, sel)) { continue; } if (!record_history) { history_needle = h; } *dst = h->loc; return true; } return false; } bool history_find_newest_desktop(coordinates_t *ref, coordinates_t *dst, desktop_select_t *sel) { for (history_t *h = history_tail; h != NULL; h = h->prev) { if (desktop_matches(&h->loc, ref, sel)) { *dst = h->loc; return true; } } return false; } bool history_find_desktop(history_dir_t hdi, coordinates_t *ref, coordinates_t *dst, desktop_select_t *sel) { if (history_needle == NULL || record_history) { history_needle = history_tail; } history_t *h; for (h = history_needle; h != NULL; h = (hdi == HISTORY_OLDER ? h->prev : h->next)) { if (!h->latest || h->loc.desktop == ref->desktop || !desktop_matches(&h->loc, ref, sel)) { continue; } if (!record_history) { history_needle = h; } *dst = h->loc; return true; } return false; } bool history_find_newest_monitor(coordinates_t *ref, coordinates_t *dst, monitor_select_t *sel) { for (history_t *h = history_tail; h != NULL; h = h->prev) { if (monitor_matches(&h->loc, ref, sel)) { *dst = h->loc; return true; } } return false; } bool history_find_monitor(history_dir_t hdi, coordinates_t *ref, coordinates_t *dst, monitor_select_t *sel) { if (history_needle == NULL || record_history) { history_needle = history_tail; } history_t *h; for (h = history_needle; h != NULL; h = (hdi == HISTORY_OLDER ? h->prev : h->next)) { if (!h->latest || h->loc.monitor == ref->monitor || !monitor_matches(&h->loc, ref, sel)) { continue; } if (!record_history) { history_needle = h; } *dst = h->loc; return true; } return false; } uint32_t history_rank(node_t *n) { uint32_t r = 0; history_t *h = history_tail; while (h != NULL && (!h->latest || h->loc.node != n)) { h = h->prev; r++; } if (h == NULL) { return UINT32_MAX; } else { return r; } } ================================================ FILE: src/history.h ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef BSPWM_HISTORY_H #define BSPWM_HISTORY_H #include "types.h" history_t *make_history(monitor_t *m, desktop_t *d, node_t *n); void history_add(monitor_t *m, desktop_t *d, node_t *n, bool focused); void history_insert_after(history_t *a, history_t *b); void history_insert_before(history_t *a, history_t *b); void history_remove(desktop_t *d, node_t *n, bool deep); void empty_history(void); node_t *history_last_node(desktop_t *d, node_t *n); desktop_t *history_last_desktop(monitor_t *m, desktop_t *d); monitor_t *history_last_monitor(monitor_t *m); bool history_find_newest_node(coordinates_t *ref, coordinates_t *dst, node_select_t *sel); bool history_find_node(history_dir_t hdi, coordinates_t *ref, coordinates_t *dst, node_select_t *sel); bool history_find_newest_desktop(coordinates_t *ref, coordinates_t *dst, desktop_select_t *sel); bool history_find_desktop(history_dir_t hdi, coordinates_t *ref, coordinates_t *dst, desktop_select_t *sel); bool history_find_newest_monitor(coordinates_t *ref, coordinates_t *dst, monitor_select_t *sel); bool history_find_monitor(history_dir_t hdi, coordinates_t *ref, coordinates_t *dst, monitor_select_t *sel); uint32_t history_rank(node_t *n); #endif ================================================ FILE: src/jsmn.c ================================================ #include "jsmn.h" /** * Allocates a fresh unused token from the token pool. */ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, size_t num_tokens) { jsmntok_t *tok; if (parser->toknext >= num_tokens) { return NULL; } tok = &tokens[parser->toknext++]; tok->start = tok->end = -1; tok->size = 0; #ifdef JSMN_PARENT_LINKS tok->parent = -1; #endif return tok; } /** * Fills token type and boundaries. */ static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, int start, int end) { token->type = type; token->start = start; token->end = end; token->size = 0; } /** * Fills next available token with JSON primitive. */ static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, size_t num_tokens) { jsmntok_t *token; int start; start = parser->pos; for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { switch (js[parser->pos]) { #ifndef JSMN_STRICT /* In strict mode primitive must be followed by "," or "}" or "]" */ case ':': #endif case '\t' : case '\r' : case '\n' : case ' ' : case ',' : case ']' : case '}' : goto found; } if (js[parser->pos] < 32 || js[parser->pos] >= 127) { parser->pos = start; return JSMN_ERROR_INVAL; } } #ifdef JSMN_STRICT /* In strict mode primitive must be followed by a comma/object/array */ parser->pos = start; return JSMN_ERROR_PART; #endif found: if (tokens == NULL) { parser->pos--; return 0; } token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) { parser->pos = start; return JSMN_ERROR_NOMEM; } jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; #endif parser->pos--; return 0; } /** * Fills next token with JSON string. */ static int jsmn_parse_string(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, size_t num_tokens) { jsmntok_t *token; int start = parser->pos; parser->pos++; /* Skip starting quote */ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { char c = js[parser->pos]; /* Quote: end of string */ if (c == '\"') { if (tokens == NULL) { return 0; } token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) { parser->pos = start; return JSMN_ERROR_NOMEM; } jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; #endif return 0; } /* Backslash: Quoted symbol expected */ if (c == '\\' && parser->pos + 1 < len) { int i; parser->pos++; switch (js[parser->pos]) { /* Allowed escaped symbols */ case '\"': case '/' : case '\\' : case 'b' : case 'f' : case 'r' : case 'n' : case 't' : break; /* Allows escaped symbol \uXXXX */ case 'u': parser->pos++; for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { /* If it isn't a hex character we have an error */ if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ parser->pos = start; return JSMN_ERROR_INVAL; } parser->pos++; } parser->pos--; break; /* Unexpected symbol */ default: parser->pos = start; return JSMN_ERROR_INVAL; } } } parser->pos = start; return JSMN_ERROR_PART; } /** * Parse JSON string and fill tokens. */ int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, unsigned int num_tokens) { int r; int i; jsmntok_t *token; int count = parser->toknext; for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { char c; jsmntype_t type; c = js[parser->pos]; switch (c) { case '{': case '[': count++; if (tokens == NULL) { break; } token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) return JSMN_ERROR_NOMEM; if (parser->toksuper != -1) { tokens[parser->toksuper].size++; #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; #endif } token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); token->start = parser->pos; parser->toksuper = parser->toknext - 1; break; case '}': case ']': if (tokens == NULL) break; type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); #ifdef JSMN_PARENT_LINKS if (parser->toknext < 1) { return JSMN_ERROR_INVAL; } token = &tokens[parser->toknext - 1]; for (;;) { if (token->start != -1 && token->end == -1) { if (token->type != type) { return JSMN_ERROR_INVAL; } token->end = parser->pos + 1; parser->toksuper = token->parent; break; } if (token->parent == -1) { if(token->type != type || parser->toksuper == -1) { return JSMN_ERROR_INVAL; } break; } token = &tokens[token->parent]; } #else for (i = parser->toknext - 1; i >= 0; i--) { token = &tokens[i]; if (token->start != -1 && token->end == -1) { if (token->type != type) { return JSMN_ERROR_INVAL; } parser->toksuper = -1; token->end = parser->pos + 1; break; } } /* Error if unmatched closing bracket */ if (i == -1) return JSMN_ERROR_INVAL; for (; i >= 0; i--) { token = &tokens[i]; if (token->start != -1 && token->end == -1) { parser->toksuper = i; break; } } #endif break; case '\"': r = jsmn_parse_string(parser, js, len, tokens, num_tokens); if (r < 0) return r; count++; if (parser->toksuper != -1 && tokens != NULL) tokens[parser->toksuper].size++; break; case '\t' : case '\r' : case '\n' : case ' ': break; case ':': parser->toksuper = parser->toknext - 1; break; case ',': if (tokens != NULL && parser->toksuper != -1 && tokens[parser->toksuper].type != JSMN_ARRAY && tokens[parser->toksuper].type != JSMN_OBJECT) { #ifdef JSMN_PARENT_LINKS parser->toksuper = tokens[parser->toksuper].parent; #else for (i = parser->toknext - 1; i >= 0; i--) { if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { if (tokens[i].start != -1 && tokens[i].end == -1) { parser->toksuper = i; break; } } } #endif } break; #ifdef JSMN_STRICT /* In strict mode primitives are: numbers and booleans */ case '-': case '0': case '1' : case '2': case '3' : case '4': case '5': case '6': case '7' : case '8': case '9': case 't': case 'f': case 'n' : /* And they must not be keys of the object */ if (tokens != NULL && parser->toksuper != -1) { jsmntok_t *t = &tokens[parser->toksuper]; if (t->type == JSMN_OBJECT || (t->type == JSMN_STRING && t->size != 0)) { return JSMN_ERROR_INVAL; } } #else /* In non-strict mode every unquoted value is a primitive */ default: #endif r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); if (r < 0) return r; count++; if (parser->toksuper != -1 && tokens != NULL) tokens[parser->toksuper].size++; break; #ifdef JSMN_STRICT /* Unexpected char in strict mode */ default: return JSMN_ERROR_INVAL; #endif } } if (tokens != NULL) { for (i = parser->toknext - 1; i >= 0; i--) { /* Unmatched opened object or array */ if (tokens[i].start != -1 && tokens[i].end == -1) { return JSMN_ERROR_PART; } } } return count; } /** * Creates a new parser based over a given buffer with an array of tokens * available. */ void jsmn_init(jsmn_parser *parser) { parser->pos = 0; parser->toknext = 0; parser->toksuper = -1; } ================================================ FILE: src/jsmn.h ================================================ #ifndef __JSMN_H_ #define __JSMN_H_ #include #ifdef __cplusplus extern "C" { #endif /** * JSON type identifier. Basic types are: * o Object * o Array * o String * o Other primitive: number, boolean (true/false) or null */ typedef enum { JSMN_UNDEFINED = 0, JSMN_OBJECT = 1, JSMN_ARRAY = 2, JSMN_STRING = 3, JSMN_PRIMITIVE = 4 } jsmntype_t; enum jsmnerr { /* Not enough tokens were provided */ JSMN_ERROR_NOMEM = -1, /* Invalid character inside JSON string */ JSMN_ERROR_INVAL = -2, /* The string is not a full JSON packet, more bytes expected */ JSMN_ERROR_PART = -3 }; /** * JSON token description. * type type (object, array, string etc.) * start start position in JSON data string * end end position in JSON data string */ typedef struct { jsmntype_t type; int start; int end; int size; #ifdef JSMN_PARENT_LINKS int parent; #endif } jsmntok_t; /** * JSON parser. Contains an array of token blocks available. Also stores * the string being parsed now and current position in that string */ typedef struct { unsigned int pos; /* offset in the JSON string */ unsigned int toknext; /* next token to allocate */ int toksuper; /* superior token node, e.g parent object or array */ } jsmn_parser; /** * Create JSON parser over an array of tokens */ void jsmn_init(jsmn_parser *parser); /** * Run JSON parser. It parses a JSON data string into and array of tokens, each describing * a single JSON object. */ int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, unsigned int num_tokens); #ifdef __cplusplus } #endif #endif /* __JSMN_H_ */ ================================================ FILE: src/messages.c ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include "bspwm.h" #include "desktop.h" #include "monitor.h" #include "pointer.h" #include "query.h" #include "rule.h" #include "restore.h" #include "settings.h" #include "tree.h" #include "window.h" #include "common.h" #include "parse.h" #include "messages.h" void handle_message(char *msg, int msg_len, FILE *rsp) { int cap = INIT_CAP; int num = 0; char **args = calloc(cap, sizeof(char *)); if (args == NULL) { perror("Handle message: calloc"); return; } for (int i = 0, j = 0; i < msg_len; i++) { if (msg[i] == 0) { args[num++] = msg + j; j = i + 1; } if (num >= cap) { cap *= 2; char **new = realloc(args, cap * sizeof(char *)); if (new == NULL) { free(args); perror("Handle message: realloc"); return; } else { args = new; } } } if (num < 1) { free(args); fail(rsp, "No arguments given.\n"); return; } char **args_orig = args; process_message(args, num, rsp); free(args_orig); } void process_message(char **args, int num, FILE *rsp) { if (streq("node", *args)) { cmd_node(++args, --num, rsp); } else if (streq("desktop", *args)) { cmd_desktop(++args, --num, rsp); } else if (streq("monitor", *args)) { cmd_monitor(++args, --num, rsp); } else if (streq("query", *args)) { cmd_query(++args, --num, rsp); } else if (streq("subscribe", *args)) { cmd_subscribe(++args, --num, rsp); return; } else if (streq("wm", *args)) { cmd_wm(++args, --num, rsp); } else if (streq("rule", *args)) { cmd_rule(++args, --num, rsp); } else if (streq("config", *args)) { cmd_config(++args, --num, rsp); } else if (streq("quit", *args)) { cmd_quit(++args, --num, rsp); } else { fail(rsp, "Unknown domain or command: '%s'.\n", *args); } fflush(rsp); fclose(rsp); } void cmd_node(char **args, int num, FILE *rsp) { if (num < 1) { fail(rsp, "node: Missing arguments.\n"); return; } coordinates_t ref = {mon, mon->desk, mon->desk->focus}; coordinates_t trg = ref; if ((*args)[0] != OPT_CHR) { int ret; if ((ret = node_from_desc(*args, &ref, &trg)) == SELECTOR_OK) { num--, args++; } else { handle_failure(ret, "node", *args, rsp); return; } } if (num < 1) { fail(rsp, "node: Missing commands.\n"); return; } bool changed = false; while (num > 0) { if (streq("-f", *args) || streq("--focus", *args)) { coordinates_t dst = trg; if (num > 1 && *(args + 1)[0] != OPT_CHR) { num--, args++; int ret; if ((ret = node_from_desc(*args, &ref, &dst)) != SELECTOR_OK) { handle_failure(ret, "node -f", *args, rsp); break; } } if (dst.node == NULL || !focus_node(dst.monitor, dst.desktop, dst.node)) { fail(rsp, ""); break; } } else if (streq("-a", *args) || streq("--activate", *args)) { coordinates_t dst = trg; if (num > 1 && *(args + 1)[0] != OPT_CHR) { num--, args++; int ret; if ((ret = node_from_desc(*args, &ref, &dst)) != SELECTOR_OK) { handle_failure(ret, "node -a", *args, rsp); break; } } if (dst.node == NULL || !activate_node(dst.monitor, dst.desktop, dst.node)) { fail(rsp, ""); break; } } else if (streq("-d", *args) || streq("--to-desktop", *args)) { num--, args++; if (num < 1) { fail(rsp, "node %s: Not enough arguments.\n", *(args - 1)); break; } coordinates_t dst; int ret; if ((ret = desktop_from_desc(*args, &ref, &dst)) == SELECTOR_OK) { bool follow = false; if (num > 1 && streq("--follow", *(args+1))) { follow = true; num--, args++; } if (transfer_node(trg.monitor, trg.desktop, trg.node, dst.monitor, dst.desktop, dst.desktop->focus, follow)) { trg.monitor = dst.monitor; trg.desktop = dst.desktop; } else { fail(rsp, ""); break; } } else { handle_failure(ret, "node -d", *args, rsp); break; } } else if (streq("-m", *args) || streq("--to-monitor", *args)) { num--, args++; if (num < 1) { fail(rsp, "node %s: Not enough arguments.\n", *(args - 1)); break; } coordinates_t dst; int ret; if ((ret = monitor_from_desc(*args, &ref, &dst)) == SELECTOR_OK) { bool follow = false; if (num > 1 && streq("--follow", *(args+1))) { follow = true; num--, args++; } if (transfer_node(trg.monitor, trg.desktop, trg.node, dst.monitor, dst.monitor->desk, dst.monitor->desk->focus, follow)) { trg.monitor = dst.monitor; trg.desktop = dst.monitor->desk; } else { fail(rsp, ""); break; } } else { handle_failure(ret, "node -m", *args, rsp); break; } } else if (streq("-n", *args) || streq("--to-node", *args)) { num--, args++; if (num < 1) { fail(rsp, "node %s: Not enough arguments.\n", *(args - 1)); break; } coordinates_t dst; int ret; if ((ret = node_from_desc(*args, &ref, &dst)) == SELECTOR_OK) { bool follow = false; if (num > 1 && streq("--follow", *(args+1))) { follow = true; num--, args++; } if (transfer_node(trg.monitor, trg.desktop, trg.node, dst.monitor, dst.desktop, dst.node, follow)) { trg.monitor = dst.monitor; trg.desktop = dst.desktop; } else { fail(rsp, ""); break; } } else { handle_failure(ret, "node -n", *args, rsp); break; } } else if (streq("-s", *args) || streq("--swap", *args)) { num--, args++; if (num < 1) { fail(rsp, "node %s: Not enough arguments.\n", *(args - 1)); break; } coordinates_t dst; int ret; if ((ret = node_from_desc(*args, &ref, &dst)) == SELECTOR_OK) { bool follow = false; if (num > 1 && streq("--follow", *(args+1))) { follow = true; num--, args++; } if (swap_nodes(trg.monitor, trg.desktop, trg.node, dst.monitor, dst.desktop, dst.node, follow)) { trg.monitor = dst.monitor; trg.desktop = dst.desktop; } else { fail(rsp, ""); break; } } else { handle_failure(ret, "node -s", *args, rsp); break; } } else if (streq("-l", *args) || streq("--layer", *args)) { num--, args++; if (num < 1) { fail(rsp, "node %s: Not enough arguments.\n", *(args - 1)); break; } stack_layer_t lyr; if (parse_stack_layer(*args, &lyr)) { if (!set_layer(trg.monitor, trg.desktop, trg.node, lyr)) { fail(rsp, ""); break; } } else { fail(rsp, "node %s: Invalid argument: '%s'.\n", *(args - 1), *args); break; } } else if (streq("-t", *args) || streq("--state", *args)) { num--, args++; if (num < 1) { fail(rsp, "node %s: Not enough arguments.\n", *(args - 1)); break; } client_state_t cst; bool alternate = false; if ((*args)[0] == '~') { alternate = true; (*args)++; } if (alternate && (*args)[0] == '\0') { if (trg.node != NULL && trg.node->client != NULL) { cst = trg.node->client->last_state; } else { fail(rsp, ""); break; } } else if (parse_client_state(*args, &cst)) { if (alternate && trg.node != NULL && trg.node->client != NULL && trg.node->client->state == cst) { cst = trg.node->client->last_state; } } else { fail(rsp, "node %s: Invalid argument: '%s'.\n", *(args - 1), *args); break; } if (!set_state(trg.monitor, trg.desktop, trg.node, cst)) { fail(rsp, ""); break; } changed = true; } else if (streq("-g", *args) || streq("--flag", *args)) { num--, args++; if (num < 1) { fail(rsp, "node %s: Not enough arguments.\n", *(args - 1)); break; } if (trg.node == NULL) { fail(rsp, ""); break; } char *key = strtok(*args, EQL_TOK); char *val = strtok(NULL, EQL_TOK); alter_state_t a; bool b; if (val == NULL) { a = ALTER_TOGGLE; } else { if (parse_bool(val, &b)) { a = ALTER_SET; } else { fail(rsp, "node %s: Invalid value for %s: '%s'.\n", *(args - 1), key, val); break; } } if (streq("hidden", key)) { set_hidden(trg.monitor, trg.desktop, trg.node, (a == ALTER_SET ? b : !trg.node->hidden)); changed = true; } else if (streq("sticky", key)) { set_sticky(trg.monitor, trg.desktop, trg.node, (a == ALTER_SET ? b : !trg.node->sticky)); } else if (streq("private", key)) { set_private(trg.monitor, trg.desktop, trg.node, (a == ALTER_SET ? b : !trg.node->private)); } else if (streq("locked", key)) { set_locked(trg.monitor, trg.desktop, trg.node, (a == ALTER_SET ? b : !trg.node->locked)); } else if (streq("marked", key)) { set_marked(trg.monitor, trg.desktop, trg.node, (a == ALTER_SET ? b : !trg.node->marked)); } else { fail(rsp, "node %s: Invalid key: '%s'.\n", *(args - 1), key); break; } } else if (streq("-p", *args) || streq("--presel-dir", *args)) { num--, args++; if (num < 1) { fail(rsp, "node %s: Not enough arguments.\n", *(args - 1)); break; } if (trg.node == NULL || trg.node->vacant) { fail(rsp, ""); break; } if (streq("cancel", *args)) { cancel_presel(trg.monitor, trg.desktop, trg.node); } else { bool alternate = false; if ((*args)[0] == '~') { alternate = true; (*args)++; } direction_t dir; if (parse_direction(*args, &dir)) { if (alternate && trg.node->presel != NULL && trg.node->presel->split_dir == dir) { cancel_presel(trg.monitor, trg.desktop, trg.node); } else { presel_dir(trg.monitor, trg.desktop, trg.node, dir); if (!IS_RECEPTACLE(trg.node)) { draw_presel_feedback(trg.monitor, trg.desktop, trg.node); } } } else { fail(rsp, "node %s: Invalid argument: '%s%s'.\n", *(args - 1), alternate?"~":"", *args); break; } } } else if (streq("-o", *args) || streq("--presel-ratio", *args)) { num--, args++; if (num < 1) { fail(rsp, "node %s: Not enough arguments.\n", *(args - 1)); break; } if (trg.node == NULL || trg.node->vacant) { fail(rsp, ""); break; } double rat; if (sscanf(*args, "%lf", &rat) != 1 || rat <= 0 || rat >= 1) { fail(rsp, "node %s: Invalid argument: '%s'.\n", *(args - 1), *args); break; } else { presel_ratio(trg.monitor, trg.desktop, trg.node, rat); draw_presel_feedback(trg.monitor, trg.desktop, trg.node); } } else if (streq("-v", *args) || streq("--move", *args)) { num--, args++; if (num < 2) { fail(rsp, "node %s: Not enough arguments.\n", *(args - 1)); break; } int dx = 0, dy = 0; if (sscanf(*args, "%i", &dx) == 1) { num--, args++; if (sscanf(*args, "%i", &dy) == 1) { if (!move_client(&trg, dx, dy)) { fail(rsp, ""); break; } } else { fail(rsp, "node %s: Invalid dy argument: '%s'.\n", *(args - 3), *args); break; } } else { fail(rsp, "node %s: Invalid dx argument: '%s'.\n", *(args - 2), *args); break; } } else if (streq("-z", *args) || streq("--resize", *args)) { num--, args++; if (num < 3) { fail(rsp, "node %s: Not enough arguments.\n", *(args - 1)); break; } resize_handle_t rh; if (parse_resize_handle(*args, &rh)) { num--, args++; int dx = 0, dy = 0; if (sscanf(*args, "%i", &dx) == 1) { num--, args++; if (sscanf(*args, "%i", &dy) == 1) { if (!resize_client(&trg, rh, dx, dy, true)) { fail(rsp, ""); break; } } else { fail(rsp, "node %s: Invalid dy argument: '%s'.\n", *(args - 3), *args); break; } } else { fail(rsp, "node %s: Invalid dx argument: '%s'.\n", *(args - 2), *args); break; } } else { fail(rsp, "node %s: Invalid resize handle argument: '%s'.\n", *(args - 1), *args); break; } } else if (streq("-y", *args) || streq("--type", *args)) { num--, args++; if (num < 1) { fail(rsp, "node %s: Not enough arguments.\n", *(args - 1)); break; } if (trg.node == NULL) { fail(rsp, ""); break; } cycle_dir_t cyc; split_type_t typ; if (parse_cycle_direction(*args, &cyc)) { set_type(trg.node, (trg.node->split_type + 1) % 2); changed = true; } else if (parse_split_type(*args, &typ)) { changed |= set_type(trg.node, typ); } else { fail(rsp, ""); break; } } else if (streq("-r", *args) || streq("--ratio", *args)) { num--, args++; if (num < 1) { fail(rsp, "node %s: Not enough arguments.\n", *(args - 1)); break; } if (trg.node == NULL) { fail(rsp, ""); break; } if ((*args)[0] == '+' || (*args)[0] == '-') { float delta; if (sscanf(*args, "%f", &delta) == 1) { double rat = trg.node->split_ratio; if (delta > -1 && delta < 1) { rat += delta; } else { int max = (trg.node->split_type == TYPE_HORIZONTAL ? trg.node->rectangle.height : trg.node->rectangle.width); rat = ((max * rat) + delta) / max; } if (rat > 0 && rat < 1) { changed |= set_ratio(trg.node, rat); } else { fail(rsp, ""); break; } } else { fail(rsp, "node %s: Invalid argument: '%s'.\n", *(args - 1), *args); break; } } else { double rat; if (sscanf(*args, "%lf", &rat) == 1 && rat > 0 && rat < 1) { changed |= set_ratio(trg.node, rat); } else { fail(rsp, "node %s: Invalid argument: '%s'.\n", *(args - 1), *args); break; } } } else if (streq("-F", *args) || streq("--flip", *args)) { num--, args++; if (num < 1) { fail(rsp, "node %s: Not enough arguments.\n", *(args - 1)); break; } if (trg.node == NULL) { fail(rsp, ""); break; } flip_t flp; if (parse_flip(*args, &flp)) { flip_tree(trg.node, flp); changed = true; } else { fail(rsp, ""); break; } } else if (streq("-R", *args) || streq("--rotate", *args)) { num--, args++; if (num < 1) { fail(rsp, "node %s: Not enough arguments.\n", *(args - 1)); break; } if (trg.node == NULL) { fail(rsp, ""); break; } int deg; if (parse_degree(*args, °)) { rotate_tree(trg.node, deg); changed = true; } else { fail(rsp, "node %s: Invalid argument: '%s'.\n", *(args - 1), *args); break; } } else if (streq("-E", *args) || streq("--equalize", *args)) { if (trg.node == NULL) { fail(rsp, ""); break; } equalize_tree(trg.node); changed = true; } else if (streq("-B", *args) || streq("--balance", *args)) { if (trg.node == NULL) { fail(rsp, ""); break; } balance_tree(trg.node); changed = true; } else if (streq("-C", *args) || streq("--circulate", *args)) { num--, args++; if (num < 1) { fail(rsp, "node %s: Not enough arguments.\n", *(args - 1)); break; } if (trg.node == NULL) { fail(rsp, ""); break; } circulate_dir_t cir; if (parse_circulate_direction(*args, &cir)) { circulate_leaves(trg.monitor, trg.desktop, trg.node, cir); changed = true; } else { fail(rsp, "node %s: Invalid argument: '%s'.\n", *(args - 1), *args); break; } } else if (streq("-i", *args) || streq("--insert-receptacle", *args)) { insert_receptacle(trg.monitor, trg.desktop, trg.node); changed = true; } else if (streq("-c", *args) || streq("--close", *args)) { if (num > 1) { fail(rsp, "node %s: Trailing commands.\n", *args); break; } if (trg.node == NULL || locked_count(trg.node) > 0) { fail(rsp, ""); break; } close_node(trg.node); break; } else if (streq("-k", *args) || streq("--kill", *args)) { if (num > 1) { fail(rsp, "node %s: Trailing commands.\n", *args); break; } if (trg.node == NULL) { fail(rsp, ""); break; } kill_node(trg.monitor, trg.desktop, trg.node); changed = true; break; } else { fail(rsp, "node: Unknown command: '%s'.\n", *args); break; } num--, args++; } if (changed) { arrange(trg.monitor, trg.desktop); } } void cmd_desktop(char **args, int num, FILE *rsp) { if (num < 1) { fail(rsp, "desktop: Missing arguments.\n"); return; } coordinates_t ref = {mon, mon->desk, NULL}; coordinates_t trg = ref; if ((*args)[0] != OPT_CHR) { int ret; if ((ret = desktop_from_desc(*args, &ref, &trg)) == SELECTOR_OK) { num--, args++; } else { handle_failure(ret, "desktop", *args, rsp); return; } } if (num < 1) { fail(rsp, "desktop: Missing commands.\n"); return; } bool changed = false; while (num > 0) { if (streq("-f", *args) || streq("--focus", *args)) { coordinates_t dst = trg; if (num > 1 && *(args + 1)[0] != OPT_CHR) { num--, args++; int ret; if ((ret = desktop_from_desc(*args, &ref, &dst)) != SELECTOR_OK) { handle_failure(ret, "desktop -f", *args, rsp); break; } } focus_node(dst.monitor, dst.desktop, NULL); } else if (streq("-a", *args) || streq("--activate", *args)) { coordinates_t dst = trg; if (num > 1 && *(args + 1)[0] != OPT_CHR) { num--, args++; int ret; if ((ret = desktop_from_desc(*args, &ref, &dst)) != SELECTOR_OK) { handle_failure(ret, "desktop -a", *args, rsp); break; } } if (activate_desktop(dst.monitor, dst.desktop)) { activate_node(dst.monitor, dst.desktop, NULL); } else { fail(rsp, ""); break; } } else if (streq("-m", *args) || streq("--to-monitor", *args)) { num--, args++; if (num < 1) { fail(rsp, "desktop %s: Not enough arguments.\n", *(args - 1)); break; } if (trg.monitor->desk_head == trg.monitor->desk_tail) { fail(rsp, ""); break; } coordinates_t dst; int ret; if ((ret = monitor_from_desc(*args, &ref, &dst)) == SELECTOR_OK) { bool follow = false; if (num > 1 && streq("--follow", *(args+1))) { follow = true; num--, args++; } if (transfer_desktop(trg.monitor, dst.monitor, trg.desktop, follow)) { trg.monitor = dst.monitor; } else { fail(rsp, ""); break; } } else { handle_failure(ret, "desktop -m", *args, rsp); break; } } else if (streq("-s", *args) || streq("--swap", *args)) { num--, args++; if (num < 1) { fail(rsp, "desktop %s: Not enough arguments.\n", *(args - 1)); break; } coordinates_t dst; int ret; if ((ret = desktop_from_desc(*args, &ref, &dst)) == SELECTOR_OK) { bool follow = false; if (num > 1 && streq("--follow", *(args+1))) { follow = true; num--, args++; } if (swap_desktops(trg.monitor, trg.desktop, dst.monitor, dst.desktop, follow)) { trg.monitor = dst.monitor; } else { fail(rsp, ""); break; } } else { handle_failure(ret, "desktop -s", *args, rsp); break; } } else if (streq("-b", *args) || streq("--bubble", *args)) { num--, args++; if (num < 1) { fail(rsp, "desktop %s: Not enough arguments.\n", *(args - 1)); break; } cycle_dir_t cyc; if (parse_cycle_direction(*args, &cyc)) { desktop_t *d = trg.desktop; if (cyc == CYCLE_PREV) { if (d->prev == NULL) { while (d->next != NULL) { swap_desktops(trg.monitor, d, trg.monitor, d->next, false); } } else { swap_desktops(trg.monitor, d, trg.monitor, d->prev, false); } } else { if (d->next == NULL) { while (d->prev != NULL) { swap_desktops(trg.monitor, d, trg.monitor, d->prev, false); } } else { swap_desktops(trg.monitor, d, trg.monitor, d->next, false); } } } else { fail(rsp, "desktop %s: Invalid argument: '%s'.\n", *(args - 1), *args); break; } } else if (streq("-l", *args) || streq("--layout", *args)) { num--, args++; if (num < 1) { fail(rsp, "desktop %s: Not enough arguments.\n", *(args - 1)); break; } bool ret; layout_t lyt; cycle_dir_t cyc; if (parse_cycle_direction(*args, &cyc)) { ret = set_layout(trg.monitor, trg.desktop, (trg.desktop->user_layout + 1) % 2, true); } else if (parse_layout(*args, &lyt)) { ret = set_layout(trg.monitor, trg.desktop, lyt, true); } else { fail(rsp, "desktop %s: Invalid argument: '%s'.\n", *(args - 1), *args); break; } if (!ret) { fail(rsp, ""); break; } } else if (streq("-n", *args) || streq("--rename", *args)) { num--, args++; if (num < 1) { fail(rsp, "desktop %s: Not enough arguments.\n", *(args - 1)); break; } rename_desktop(trg.monitor, trg.desktop, *args); } else if (streq("-r", *args) || streq("--remove", *args)) { if (num > 1) { fail(rsp, "desktop %s: Trailing commands.\n", *args); break; } if (trg.monitor->desk_head != trg.monitor->desk_tail) { desktop_t *fallback = trg.desktop->prev == NULL ? trg.desktop->next : trg.desktop->prev; merge_desktops(trg.monitor, trg.desktop, trg.monitor, fallback); remove_desktop(trg.monitor, trg.desktop); return; } else { fail(rsp, ""); break; } } else { fail(rsp, "desktop: Unknown command: '%s'.\n", *args); break; } num--, args++; } if (changed) { arrange(trg.monitor, trg.desktop); } } void cmd_monitor(char **args, int num, FILE *rsp) { if (num < 1) { fail(rsp, "monitor: Missing arguments.\n"); return; } coordinates_t ref = {mon, NULL, NULL}; coordinates_t trg = ref; if ((*args)[0] != OPT_CHR) { int ret; if ((ret = monitor_from_desc(*args, &ref, &trg)) == SELECTOR_OK) { num--, args++; } else { handle_failure(ret, "monitor", *args, rsp); return; } } if (num < 1) { fail(rsp, "monitor: Missing commands.\n"); return; } while (num > 0) { if (streq("-f", *args) || streq("--focus", *args)) { coordinates_t dst = trg; if (num > 1 && *(args + 1)[0] != OPT_CHR) { num--, args++; int ret; if ((ret = monitor_from_desc(*args, &ref, &dst)) != SELECTOR_OK) { handle_failure(ret, "monitor -f", *args, rsp); fail(rsp, ""); return; } } focus_node(dst.monitor, NULL, NULL); } else if (streq("-s", *args) || streq("--swap", *args)) { num--, args++; if (num < 1) { fail(rsp, "monitor %s: Not enough arguments.\n", *(args - 1)); return; } coordinates_t dst; int ret; if ((ret = monitor_from_desc(*args, &ref, &dst)) == SELECTOR_OK) { if (!swap_monitors(trg.monitor, dst.monitor)) { fail(rsp, ""); return; } } else { handle_failure(ret, "monitor -s", *args, rsp); return; } } else if (streq("-d", *args) || streq("--reset-desktops", *args)) { num--, args++; if (num < 1) { fail(rsp, "monitor %s: Not enough arguments.\n", *(args - 1)); return; } desktop_t *d = trg.monitor->desk_head; while (num > 0 && d != NULL) { rename_desktop(trg.monitor, d, *args); d = d->next; num--, args++; } put_status(SBSC_MASK_REPORT); while (num > 0) { add_desktop(trg.monitor, make_desktop(*args, XCB_NONE)); num--, args++; } while (d != NULL) { desktop_t *next = d->next; if (d == mon->desk) { focus_node(trg.monitor, d->prev, d->prev->focus); } merge_desktops(trg.monitor, d, mon, mon->desk); remove_desktop(trg.monitor, d); d = next; } } else if (streq("-a", *args) || streq("--add-desktops", *args)) { num--, args++; if (num < 1) { fail(rsp, "monitor %s: Not enough arguments.\n", *(args - 1)); return; } while (num > 0) { add_desktop(trg.monitor, make_desktop(*args, XCB_NONE)); num--, args++; } } else if (streq("-r", *args) || streq("--remove", *args)) { if (num > 1) { fail(rsp, "monitor %s: Trailing commands.\n", *args); return; } if (mon_head == mon_tail) { fail(rsp, ""); return; } remove_monitor(trg.monitor); return; } else if (streq("-o", *args) || streq("--reorder-desktops", *args)) { num--, args++; if (num < 1) { fail(rsp, "monitor %s: Not enough arguments.\n", *(args - 1)); return; } desktop_t *d = trg.monitor->desk_head; while (d != NULL && num > 0) { desktop_t *next = d->next; coordinates_t dst; if (locate_desktop(*args, &dst) && dst.monitor == trg.monitor) { swap_desktops(trg.monitor, d, dst.monitor, dst.desktop, false); if (next == dst.desktop) { next = d; } } d = next; num--, args++; } } else if (streq("-g", *args) || streq("--rectangle", *args)) { num--, args++; if (num < 1) { fail(rsp, "monitor %s: Not enough arguments.\n", *(args - 1)); return; } xcb_rectangle_t r; if (parse_rectangle(*args, &r)) { update_root(trg.monitor, &r); } else { fail(rsp, "monitor %s: Invalid argument: '%s'.\n", *(args - 1), *args); return; } } else if (streq("-n", *args) || streq("--rename", *args)) { num--, args++; if (num < 1) { fail(rsp, "monitor %s: Not enough arguments.\n", *(args - 1)); return; } rename_monitor(trg.monitor, *args); } else { fail(rsp, "monitor: Unknown command: '%s'.\n", *args); return; } num--, args++; } } void cmd_query(char **args, int num, FILE *rsp) { coordinates_t monitor_ref = {mon, NULL, NULL}; coordinates_t desktop_ref = {mon, mon->desk, NULL}; coordinates_t node_ref = {mon, mon->desk, mon->desk->focus}; coordinates_t trg = {NULL, NULL, NULL}; monitor_select_t *monitor_sel = NULL; desktop_select_t *desktop_sel = NULL; node_select_t *node_sel = NULL; domain_t dom = DOMAIN_TREE; bool print_ids = true; uint8_t d = 0; if (num < 1) { fail(rsp, "query: Missing arguments.\n"); return; } while (num > 0) { if (streq("-T", *args) || streq("--tree", *args)) { dom = DOMAIN_TREE, d++; } else if (streq("-M", *args) || streq("--monitors", *args)) { dom = DOMAIN_MONITOR, d++; if (num > 1 && *(args + 1)[0] != OPT_CHR) { num--, args++; int ret; coordinates_t tmp = monitor_ref; monitor_ref.desktop = NULL; monitor_ref.node = NULL; if ((ret = monitor_from_desc(*args, &tmp, &monitor_ref)) != SELECTOR_OK) { handle_failure(ret, "query -M", *args, rsp); goto end; } } } else if (streq("-D", *args) || streq("--desktops", *args)) { dom = DOMAIN_DESKTOP, d++; if (num > 1 && *(args + 1)[0] != OPT_CHR) { num--, args++; int ret; coordinates_t tmp = desktop_ref; desktop_ref.node = NULL; if ((ret = desktop_from_desc(*args, &tmp, &desktop_ref)) != SELECTOR_OK) { handle_failure(ret, "query -D", *args, rsp); goto end; } } } else if (streq("-N", *args) || streq("--nodes", *args)) { dom = DOMAIN_NODE, d++; if (num > 1 && *(args + 1)[0] != OPT_CHR) { num--, args++; int ret; coordinates_t tmp = node_ref; if ((ret = node_from_desc(*args, &tmp, &node_ref)) != SELECTOR_OK) { handle_failure(ret, "query -N", *args, rsp); goto end; } } } else if (streq("-m", *args) || streq("--monitor", *args)) { if (num > 1 && *(args + 1)[0] != OPT_CHR) { num--, args++; int ret; if ((*args)[0] == '.') { free(monitor_sel); monitor_sel = malloc(sizeof(monitor_select_t)); *monitor_sel = make_monitor_select(); char *desc = copy_string(*args, strlen(*args)); if (!parse_monitor_modifiers(desc, monitor_sel)) { handle_failure(SELECTOR_BAD_MODIFIERS, "query -m", *args, rsp); free(desc); goto end; } free(desc); } else if ((ret = monitor_from_desc(*args, &monitor_ref, &trg)) != SELECTOR_OK) { handle_failure(ret, "query -m", *args, rsp); goto end; } } else { trg = monitor_ref; } } else if (streq("-d", *args) || streq("--desktop", *args)) { if (num > 1 && *(args + 1)[0] != OPT_CHR) { num--, args++; int ret; if ((*args)[0] == '.') { free(desktop_sel); desktop_sel = malloc(sizeof(desktop_select_t)); *desktop_sel = make_desktop_select(); char *desc = copy_string(*args, strlen(*args)); if (!parse_desktop_modifiers(desc, desktop_sel)) { handle_failure(SELECTOR_BAD_MODIFIERS, "query -d", *args, rsp); free(desc); goto end; } free(desc); } else if ((ret = desktop_from_desc(*args, &desktop_ref, &trg)) != SELECTOR_OK) { handle_failure(ret, "query -d", *args, rsp); goto end; } } else { trg = desktop_ref; } } else if (streq("-n", *args) || streq("--node", *args)) { if (num > 1 && *(args + 1)[0] != OPT_CHR) { num--, args++; int ret; if ((*args)[0] == '.') { free(node_sel); node_sel = malloc(sizeof(node_select_t)); *node_sel = make_node_select(); char *desc = copy_string(*args, strlen(*args)); if (!parse_node_modifiers(desc, node_sel)) { handle_failure(SELECTOR_BAD_MODIFIERS, "query -n", *args, rsp); free(desc); goto end; } free(desc); } else if ((ret = node_from_desc(*args, &node_ref, &trg)) != SELECTOR_OK) { handle_failure(ret, "query -n", *args, rsp); goto end; } } else { trg = node_ref; if (trg.node == NULL) { fail(rsp, ""); goto end; } } } else if (streq("--names", *args)) { print_ids = false; } else { fail(rsp, "query: Unknown option: '%s'.\n", *args); goto end; } num--, args++; } if (d < 1) { fail(rsp, "query: No commands given.\n"); goto end; } if (d > 1) { fail(rsp, "query: Multiple commands given.\n"); goto end; } if (dom == DOMAIN_TREE && trg.monitor == NULL) { fail(rsp, "query -T: No options given.\n"); goto end; } if (!print_ids && (dom == DOMAIN_NODE || dom == DOMAIN_TREE)) { fail(rsp, "query -%c: --names only applies to -M and -D.\n", dom == DOMAIN_NODE ? 'N' : 'T'); goto end; } if ((dom == DOMAIN_MONITOR && (desktop_sel != NULL || node_sel != NULL)) || (dom == DOMAIN_DESKTOP && node_sel != NULL)) { fail(rsp, "query -%c: Incompatible descriptor-free constraints.\n", dom == DOMAIN_MONITOR ? 'M' : 'D'); goto end; } if (dom == DOMAIN_NODE) { if (query_node_ids(&monitor_ref, &desktop_ref, &node_ref, &trg, monitor_sel, desktop_sel, node_sel, rsp) < 1) { fail(rsp, ""); } } else if (dom == DOMAIN_DESKTOP) { if (query_desktop_ids(&monitor_ref, &desktop_ref, &trg, monitor_sel, desktop_sel, print_ids ? fprint_desktop_id : fprint_desktop_name, rsp) < 1) { fail(rsp, ""); } } else if (dom == DOMAIN_MONITOR) { if (query_monitor_ids(&monitor_ref, &trg, monitor_sel, print_ids ? fprint_monitor_id : fprint_monitor_name, rsp) < 1) { fail(rsp, ""); } } else { if (trg.node != NULL) { query_node(trg.node, rsp); } else if (trg.desktop != NULL) { query_desktop(trg.desktop, rsp); } else { query_monitor(trg.monitor, rsp); } fprintf(rsp, "\n"); } end: free(monitor_sel); free(desktop_sel); free(node_sel); } void cmd_rule(char **args, int num, FILE *rsp) { if (num < 1) { fail(rsp, "rule: Missing commands.\n"); return; } while (num > 0) { if (streq("-a", *args) || streq("--add", *args)) { num--, args++; if (num < 2) { fail(rsp, "rule %s: Not enough arguments.\n", *(args - 1)); return; } rule_t *rule = make_rule(); struct tokenize_state state; char *class_name = tokenize_with_escape(&state, args[0], COL_TOK[0]); char *instance_name = tokenize_with_escape(&state, NULL, COL_TOK[0]); char *name = tokenize_with_escape(&state, NULL, COL_TOK[0]); if (!class_name || !instance_name || !name) { free(class_name); free(instance_name); free(name); return; } snprintf(rule->class_name, sizeof(rule->class_name), "%s", class_name); snprintf(rule->instance_name, sizeof(rule->instance_name), "%s", instance_name[0] == '\0' ? MATCH_ANY : instance_name); snprintf(rule->name, sizeof(rule->name), "%s", name[0] == '\0' ? MATCH_ANY : name); free(class_name); free(instance_name); free(name); num--, args++; size_t i = 0; while (num > 0) { if (streq("-o", *args) || streq("--one-shot", *args)) { rule->one_shot = true; } else { for (size_t j = 0; i < sizeof(rule->effect) && j < strlen(*args); i++, j++) { rule->effect[i] = (*args)[j]; } if (num > 1 && i < sizeof(rule->effect)) { rule->effect[i++] = ' '; } } num--, args++; } rule->effect[MIN(i, sizeof(rule->effect) - 1)] = '\0'; add_rule(rule); } else if (streq("-r", *args) || streq("--remove", *args)) { num--, args++; if (num < 1) { fail(rsp, "rule %s: Not enough arguments.\n", *(args - 1)); return; } uint16_t idx; while (num > 0) { if (parse_index(*args, &idx)) { remove_rule_by_index(idx - 1); } else if (streq("tail", *args)) { remove_rule(rule_tail); } else if (streq("head", *args)) { remove_rule(rule_head); } else { remove_rule_by_cause(*args); } num--, args++; } } else if (streq("-l", *args) || streq("--list", *args)) { list_rules(rsp); } else { fail(rsp, "rule: Unknown command: '%s'.\n", *args); return; } num--, args++; } } void cmd_wm(char **args, int num, FILE *rsp) { if (num < 1) { fail(rsp, "wm: Missing commands.\n"); return; } while (num > 0) { if (streq("-d", *args) || streq("--dump-state", *args)) { query_state(rsp); fprintf(rsp, "\n"); } else if (streq("-l", *args) || streq("--load-state", *args)) { num--, args++; if (num < 1) { fail(rsp, "wm %s: Not enough arguments.\n", *(args - 1)); break; } if (!restore_state(*args)) { fail(rsp, ""); break; } } else if (streq("-a", *args) || streq("--add-monitor", *args)) { num--, args++; if (num < 2) { fail(rsp, "wm %s: Not enough arguments.\n", *(args - 1)); break; } char *name = *args; num--, args++; xcb_rectangle_t r; if (parse_rectangle(*args, &r)) { monitor_t *m = make_monitor(name, &r, XCB_NONE); add_monitor(m); add_desktop(m, make_desktop(NULL, XCB_NONE)); } else { fail(rsp, "wm %s: Invalid argument: '%s'.\n", *(args - 1), *args); break; } } else if (streq("-O", *args) || streq("--reorder-monitors", *args)) { num--, args++; if (num < 1) { fail(rsp, "wm %s: Not enough arguments.\n", *(args - 1)); return; } monitor_t *m = mon_head; while (m != NULL && num > 0) { monitor_t *next = m->next; coordinates_t dst; if (locate_monitor(*args, &dst)) { swap_monitors(m, dst.monitor); if (next == dst.monitor) { next = m; } } m = next; num--, args++; } } else if (streq("-o", *args) || streq("--adopt-orphans", *args)) { adopt_orphans(); } else if (streq("-g", *args) || streq("--get-status", *args)) { print_report(rsp); } else if (streq("-h", *args) || streq("--record-history", *args)) { num--, args++; if (num < 1) { fail(rsp, "wm %s: Not enough arguments.\n", *(args - 1)); break; } bool b; if (parse_bool(*args, &b)) { record_history = b; } else { fail(rsp, "wm %s: Invalid argument: '%s'.\n", *(args - 1), *args); break; } } else if (streq("-r", *args) || streq("--restart", *args)) { running = false; restart = true; break; } else { fail(rsp, "wm: Unknown command: '%s'.\n", *args); break; } num--, args++; } } void cmd_subscribe(char **args, int num, FILE *rsp) { int field = 0; int count = -1; FILE *stream = rsp; char *fifo_path = NULL; subscriber_mask_t mask; while (num > 0) { if (streq("-c", *args) || streq("--count", *args)) { num--, args++; if (num < 1) { fail(rsp, "subscribe %s: Not enough arguments.\n", *(args - 1)); goto failed; } if (sscanf(*args, "%i", &count) != 1 || count < 1) { fail(rsp, "subscribe %s: Invalid argument: '%s'.\n", *(args - 1), *args); goto failed; } } else if (streq("-f", *args) || streq("--fifo", *args)) { fifo_path = mktempfifo(FIFO_TEMPLATE); if (fifo_path == NULL) { fail(rsp, "subscribe %s: Can't create FIFO.\n", *(args - 1)); goto failed; } } else if (parse_subscriber_mask(*args, &mask)) { field |= mask; } else { fail(rsp, "subscribe: Invalid argument: '%s'.\n", *args); goto failed; } num--, args++; } if (field == 0) { field = SBSC_MASK_REPORT; } if (fifo_path) { fprintf(rsp, "%s\n", fifo_path); fflush(rsp); fclose(rsp); stream = fopen(fifo_path, "w"); if (stream == NULL) { perror("subscribe: fopen"); goto free_fifo_path; } } subscriber_list_t *sb = make_subscriber(stream, fifo_path, field, count); add_subscriber(sb); return; failed: fflush(rsp); fclose(rsp); free_fifo_path: if (fifo_path) { unlink(fifo_path); free(fifo_path); } } void cmd_quit(char **args, int num, FILE *rsp) { if (num > 0 && sscanf(*args, "%i", &exit_status) != 1) { fail(rsp, "%s: Invalid argument: '%s'.\n", *(args - 1), *args); return; } running = false; } void cmd_config(char **args, int num, FILE *rsp) { if (num < 1) { fail(rsp, "config: Missing arguments.\n"); return; } coordinates_t ref = {mon, mon->desk, mon->desk->focus}; coordinates_t trg = {NULL, NULL, NULL}; while (num > 0 && (*args)[0] == OPT_CHR) { if (streq("-m", *args) || streq("--monitor", *args)) { num--, args++; if (num < 1) { fail(rsp, "config %s: Not enough arguments.\n", *(args - 1)); return; } int ret; if ((ret = monitor_from_desc(*args, &ref, &trg)) != SELECTOR_OK) { handle_failure(ret, "config -m", *args, rsp); return; } } else if (streq("-d", *args) || streq("--desktop", *args)) { num--, args++; if (num < 1) { fail(rsp, "config %s: Not enough arguments.\n", *(args - 1)); return; } int ret; if ((ret = desktop_from_desc(*args, &ref, &trg)) != SELECTOR_OK) { handle_failure(ret, "config -d", *args, rsp); return; } } else if (streq("-n", *args) || streq("--node", *args)) { num--, args++; if (num < 1) { fail(rsp, "config %s: Not enough arguments.\n", *(args - 1)); return; } int ret; if ((ret = node_from_desc(*args, &ref, &trg)) != SELECTOR_OK) { handle_failure(ret, "config -n", *args, rsp); return; } } else { fail(rsp, "config: Unknown option: '%s'.\n", *args); return; } num--, args++; } if (num == 2) { set_setting(trg, *args, *(args + 1), rsp); } else if (num == 1) { get_setting(trg, *args, rsp); } else { fail(rsp, "config: Was expecting 1 or 2 arguments, received %i.\n", num); } } void set_setting(coordinates_t loc, char *name, char *value, FILE *rsp) { bool colors_changed = false; #define SET_DEF_DEFMON_DEFDESK_WIN(k, v) \ if (loc.node != NULL) { \ for (node_t *n = first_extrema(loc.node); n != NULL; n = next_leaf(n, loc.node)) { \ if (n->client != NULL) { \ n->client->k = v; \ } \ } \ } else if (loc.desktop != NULL) { \ loc.desktop->k = v; \ for (node_t *n = first_extrema(loc.desktop->root); n != NULL; n = next_leaf(n, loc.desktop->root)) { \ if (n->client != NULL) { \ n->client->k = v; \ } \ } \ } else if (loc.monitor != NULL) { \ loc.monitor->k = v; \ for (desktop_t *d = loc.monitor->desk_head; d != NULL; d = d->next) { \ d->k = v; \ for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) { \ if (n->client != NULL) { \ n->client->k = v; \ } \ } \ } \ } else { \ k = v; \ for (monitor_t *m = mon_head; m != NULL; m = m->next) { \ m->k = v; \ for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { \ d->k = v; \ for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) { \ if (n->client != NULL) { \ n->client->k = v; \ } \ } \ } \ } \ } if (streq("border_width", name)) { unsigned int bw; if (sscanf(value, "%u", &bw) != 1) { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); return; } SET_DEF_DEFMON_DEFDESK_WIN(border_width, bw) #undef SET_DEF_DEFMON_DEFDESK_WIN #define SET_DEF_WIN(k, v) \ if (loc.node != NULL) { \ for (node_t *n = first_extrema(loc.node); n != NULL; n = next_leaf(n, loc.node)) { \ if (n->client != NULL) { \ n->client->k = v; \ } \ } \ } else if (loc.desktop != NULL) { \ for (node_t *n = first_extrema(loc.desktop->root); n != NULL; n = next_leaf(n, loc.desktop->root)) { \ if (n->client != NULL) { \ n->client->k = v; \ } \ } \ } else if (loc.monitor != NULL) { \ for (desktop_t *d = loc.monitor->desk_head; d != NULL; d = d->next) { \ for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) { \ if (n->client != NULL) { \ n->client->k = v; \ } \ } \ } \ } else { \ k = v; \ for (monitor_t *m = mon_head; m != NULL; m = m->next) { \ for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { \ for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) { \ if (n->client != NULL) { \ n->client->k = v; \ } \ } \ } \ } \ } } else if (streq("honor_size_hints", name)) { honor_size_hints_mode_t hsh; if (!parse_honor_size_hints_mode(value, &hsh)) { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); return; } SET_DEF_WIN(honor_size_hints, hsh) #undef SET_DEF_WIN #define SET_DEF_DEFMON_DESK(k, v) \ if (loc.desktop != NULL) { \ loc.desktop->k = v; \ } else if (loc.monitor != NULL) { \ loc.monitor->k = v; \ for (desktop_t *d = loc.monitor->desk_head; d != NULL; d = d->next) { \ d->k = v; \ } \ } else { \ k = v; \ for (monitor_t *m = mon_head; m != NULL; m = m->next) { \ m->k = v; \ for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { \ d->k = v; \ } \ } \ } } else if (streq("window_gap", name)) { int wg; if (sscanf(value, "%i", &wg) != 1) { fail(rsp, ""); return; } SET_DEF_DEFMON_DESK(window_gap, wg) #undef SET_DEF_DEFMON_DESK #define SET_DEF_MON_DESK(k, v) \ if (loc.desktop != NULL) { \ loc.desktop->k = v; \ } else if (loc.monitor != NULL) { \ loc.monitor->k = v; \ } else { \ k = v; \ for (monitor_t *m = mon_head; m != NULL; m = m->next) { \ m->k = v; \ } \ } } else if (streq("top_padding", name)) { int tp; if (sscanf(value, "%i", &tp) != 1) { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); return; } SET_DEF_MON_DESK(padding.top, tp) } else if (streq("right_padding", name)) { int rp; if (sscanf(value, "%i", &rp) != 1) { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); return; } SET_DEF_MON_DESK(padding.right, rp) } else if (streq("bottom_padding", name)) { int bp; if (sscanf(value, "%i", &bp) != 1) { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); return; } SET_DEF_MON_DESK(padding.bottom, bp) } else if (streq("left_padding", name)) { int lp; if (sscanf(value, "%i", &lp) != 1) { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); return; } SET_DEF_MON_DESK(padding.left, lp) #undef SET_DEF_MON_DESK } else if (streq("top_monocle_padding", name)) { if (sscanf(value, "%i", &monocle_padding.top) != 1) { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); } } else if (streq("right_monocle_padding", name)) { if (sscanf(value, "%i", &monocle_padding.right) != 1) { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); } } else if (streq("bottom_monocle_padding", name)) { if (sscanf(value, "%i", &monocle_padding.bottom) != 1) { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); } } else if (streq("left_monocle_padding", name)) { if (sscanf(value, "%i", &monocle_padding.left) != 1) { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); } #define SET_STR(s) \ } else if (streq(#s, name)) { \ if (snprintf(s, sizeof(s), "%s", value) < 0) { \ fail(rsp, ""); \ return; \ } SET_STR(external_rules_command) SET_STR(status_prefix) #undef SET_STR } else if (streq("split_ratio", name)) { double r; if (sscanf(value, "%lf", &r) == 1 && r > 0 && r < 1) { split_ratio = r; } else { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); \ return; } return; #define SET_COLOR(s) \ } else if (streq(#s, name)) { \ if (!is_hex_color(value)) { \ fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); \ return; \ } else { \ snprintf(s, sizeof(s), "%s", value); \ colors_changed = true; \ } SET_COLOR(normal_border_color) SET_COLOR(active_border_color) SET_COLOR(focused_border_color) SET_COLOR(presel_feedback_color) #undef SET_COLOR } else if (streq("initial_polarity", name)) { child_polarity_t p; if (parse_child_polarity(value, &p)) { initial_polarity = p; } else { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); return; } } else if (streq("automatic_scheme", name)) { automatic_scheme_t a; if (parse_automatic_scheme(value, &a)) { automatic_scheme = a; } else { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); return; } } else if (streq("mapping_events_count", name)) { if (sscanf(value, "%" SCNi8, &mapping_events_count) != 1) { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); return; } } else if (streq("directional_focus_tightness", name)) { tightness_t p; if (parse_tightness(value, &p)) { directional_focus_tightness = p; } else { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); return; } } else if (streq("ignore_ewmh_fullscreen", name)) { state_transition_t m; if (parse_state_transition(value, &m)) { ignore_ewmh_fullscreen = m; } else { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); return; } } else if (streq("pointer_modifier", name)) { if (parse_modifier_mask(value, &pointer_modifier)) { ungrab_buttons(); grab_buttons(); } else { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); return; } } else if (streq("pointer_motion_interval", name)) { if (sscanf(value, "%u", &pointer_motion_interval) != 1) { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); return; } } else if (streq("pointer_action1", name) || streq("pointer_action2", name) || streq("pointer_action3", name)) { int index = name[14] - '1'; if (parse_pointer_action(value, &pointer_actions[index])) { ungrab_buttons(); grab_buttons(); } else { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); return; } } else if (streq("click_to_focus", name)) { if (parse_button_index(value, &click_to_focus)) { ungrab_buttons(); grab_buttons(); } else { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); return; } } else if (streq("single_monocle", name)) { bool b; if (parse_bool(value, &b)) { if (b == single_monocle) { fail(rsp, ""); return; } single_monocle = b; for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { layout_t l = (single_monocle && tiled_count(d->root, true) <= 1) ? LAYOUT_MONOCLE : d->user_layout; set_layout(m, d, l, false); } } } else { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); return; } } else if (streq("focus_follows_pointer", name)) { bool b; if (parse_bool(value, &b)) { if (b == focus_follows_pointer) { fail(rsp, ""); return; } focus_follows_pointer = b; for (monitor_t *m = mon_head; m != NULL; m = m->next) { if (focus_follows_pointer) { window_show(m->root); } else { window_hide(m->root); } for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { listen_enter_notify(d->root, focus_follows_pointer); } } if (focus_follows_pointer) { update_motion_recorder(); } else { disable_motion_recorder(); } return; } else { fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); return; } #define SET_BOOL(s) \ } else if (streq(#s, name)) { \ if (!parse_bool(value, &s)) { \ fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); \ return; \ } SET_BOOL(presel_feedback) SET_BOOL(borderless_monocle) SET_BOOL(gapless_monocle) SET_BOOL(borderless_singleton) SET_BOOL(swallow_first_click) SET_BOOL(pointer_follows_focus) SET_BOOL(pointer_follows_monitor) SET_BOOL(ignore_ewmh_focus) SET_BOOL(ignore_ewmh_struts) SET_BOOL(center_pseudo_tiled) SET_BOOL(removal_adjustment) #undef SET_BOOL #define SET_MON_BOOL(s) \ } else if (streq(#s, name)) { \ if (!parse_bool(value, &s)) { \ fail(rsp, "config: %s: Invalid value: '%s'.\n", name, value); \ return; \ } \ if (s) { \ update_monitors(); \ } SET_MON_BOOL(remove_disabled_monitors) SET_MON_BOOL(remove_unplugged_monitors) SET_MON_BOOL(merge_overlapping_monitors) #undef SET_MON_BOOL } else { fail(rsp, "config: Unknown setting: '%s'.\n", name); return; } for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { arrange(m, d); if (colors_changed) { update_colors_in(d->root, d, m); } } } } void get_setting(coordinates_t loc, char *name, FILE* rsp) { if (streq("split_ratio", name)) { fprintf(rsp, "%lf", split_ratio); } else if (streq("border_width", name)) { if (loc.node != NULL) { for (node_t *n = first_extrema(loc.node); n != NULL; n = next_leaf(n, loc.node)) { if (n->client != NULL) { fprintf(rsp, "%u", n->client->border_width); break; } } } else if (loc.desktop != NULL) { fprintf(rsp, "%u", loc.desktop->border_width); } else if (loc.monitor != NULL) { fprintf(rsp, "%u", loc.monitor->border_width); } else { fprintf(rsp, "%u", border_width); } } else if (streq("window_gap", name)) { if (loc.desktop != NULL) { fprintf(rsp, "%i", loc.desktop->window_gap); } else if (loc.monitor != NULL) { fprintf(rsp, "%i", loc.monitor->window_gap); } else { fprintf(rsp, "%i", window_gap); } #define GET_DEF_MON_DESK(k) \ if (loc.desktop != NULL) { \ fprintf(rsp, "%i", loc.desktop->k); \ } else if (loc.monitor != NULL) { \ fprintf(rsp, "%i", loc.monitor->k); \ } else { \ fprintf(rsp, "%i", k); \ } } else if (streq("top_padding", name)) { GET_DEF_MON_DESK(padding.top) } else if (streq("right_padding", name)) { GET_DEF_MON_DESK(padding.right) } else if (streq("bottom_padding", name)) { GET_DEF_MON_DESK(padding.bottom) } else if (streq("left_padding", name)) { GET_DEF_MON_DESK(padding.left) #undef GET_DEF_MON_DESK } else if (streq("top_monocle_padding", name)) { fprintf(rsp, "%i", monocle_padding.top); } else if (streq("right_monocle_padding", name)) { fprintf(rsp, "%i", monocle_padding.right); } else if (streq("bottom_monocle_padding", name)) { fprintf(rsp, "%i", monocle_padding.bottom); } else if (streq("left_monocle_padding", name)) { fprintf(rsp, "%i", monocle_padding.left); } else if (streq("external_rules_command", name)) { fprintf(rsp, "%s", external_rules_command); } else if (streq("status_prefix", name)) { fprintf(rsp, "%s", status_prefix); } else if (streq("initial_polarity", name)) { fprintf(rsp, "%s", CHILD_POL_STR(initial_polarity)); } else if (streq("automatic_scheme", name)) { fprintf(rsp, "%s", AUTO_SCM_STR(automatic_scheme)); } else if (streq("honor_size_hints", name)) { fprintf(rsp, "%s", HSH_MODE_STR(honor_size_hints)); } else if (streq("mapping_events_count", name)) { fprintf(rsp, "%" PRIi8, mapping_events_count); } else if (streq("directional_focus_tightness", name)) { fprintf(rsp, "%s", TIGHTNESS_STR(directional_focus_tightness)); } else if (streq("ignore_ewmh_fullscreen", name)) { print_ignore_request(ignore_ewmh_fullscreen, rsp); } else if (streq("pointer_modifier", name)) { print_modifier_mask(pointer_modifier, rsp); } else if (streq("click_to_focus", name)) { print_button_index(click_to_focus, rsp); } else if (streq("pointer_motion_interval", name)) { fprintf(rsp, "%u", pointer_motion_interval); } else if (streq("pointer_action1", name) || streq("pointer_action2", name) || streq("pointer_action3", name)) { int index = name[14] - '1'; print_pointer_action(pointer_actions[index], rsp); #define GET_COLOR(s) \ } else if (streq(#s, name)) { \ fprintf(rsp, "%s", s); GET_COLOR(normal_border_color) GET_COLOR(active_border_color) GET_COLOR(focused_border_color) GET_COLOR(presel_feedback_color) #undef GET_COLOR #define GET_BOOL(s) \ } else if (streq(#s, name)) { \ fprintf(rsp, "%s", BOOL_STR(s)); GET_BOOL(presel_feedback) GET_BOOL(borderless_monocle) GET_BOOL(gapless_monocle) GET_BOOL(single_monocle) GET_BOOL(borderless_singleton) GET_BOOL(swallow_first_click) GET_BOOL(focus_follows_pointer) GET_BOOL(pointer_follows_focus) GET_BOOL(pointer_follows_monitor) GET_BOOL(ignore_ewmh_focus) GET_BOOL(ignore_ewmh_struts) GET_BOOL(center_pseudo_tiled) GET_BOOL(removal_adjustment) GET_BOOL(remove_disabled_monitors) GET_BOOL(remove_unplugged_monitors) GET_BOOL(merge_overlapping_monitors) #undef GET_BOOL } else { fail(rsp, "config: Unknown setting: '%s'.\n", name); return; } fprintf(rsp, "\n"); } void handle_failure(int code, char *src, char *val, FILE *rsp) { switch (code) { case SELECTOR_BAD_DESCRIPTOR: fail(rsp, "%s: Invalid descriptor found in '%s'.\n", src, val); break; case SELECTOR_BAD_MODIFIERS: fail(rsp, "%s: Invalid modifier found in '%s'.\n", src, val); break; case SELECTOR_INVALID: fail(rsp, ""); break; } } void fail(FILE *rsp, char *fmt, ...) { fprintf(rsp, FAILURE_MESSAGE); va_list ap; va_start(ap, fmt); vfprintf(rsp, fmt, ap); va_end(ap); } ================================================ FILE: src/messages.h ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef BSPWM_MESSAGES_H #define BSPWM_MESSAGES_H #include "types.h" #include "subscribe.h" void handle_message(char *msg, int msg_len, FILE *rsp); void process_message(char **args, int num, FILE *rsp); void cmd_node(char **args, int num, FILE *rsp); void cmd_desktop(char **args, int num, FILE *rsp); void cmd_monitor(char **args, int num, FILE *rsp); void cmd_query(char **args, int num, FILE *rsp); void cmd_rule(char **args, int num, FILE *rsp); void cmd_wm(char **args, int num, FILE *rsp); void cmd_subscribe(char **args, int num, FILE *rsp); void cmd_quit(char **args, int num, FILE *rsp); void cmd_config(char **args, int num, FILE *rsp); void set_setting(coordinates_t loc, char *name, char *value, FILE *rsp); void get_setting(coordinates_t loc, char *name, FILE* rsp); void handle_failure(int code, char *src, char *val, FILE *rsp); void fail(FILE *rsp, char *fmt, ...); #endif ================================================ FILE: src/monitor.c ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include "bspwm.h" #include "desktop.h" #include "ewmh.h" #include "query.h" #include "pointer.h" #include "settings.h" #include "geometry.h" #include "tree.h" #include "subscribe.h" #include "window.h" #include "monitor.h" monitor_t *make_monitor(const char *name, xcb_rectangle_t *rect, uint32_t id) { monitor_t *m = calloc(1, sizeof(monitor_t)); if (id == XCB_NONE) { m->id = xcb_generate_id(dpy); } m->randr_id = XCB_NONE; snprintf(m->name, sizeof(m->name), "%s", name == NULL ? DEFAULT_MON_NAME : name); m->padding = padding; m->border_width = border_width; m->window_gap = window_gap; m->root = XCB_NONE; m->prev = m->next = NULL; m->desk = m->desk_head = m->desk_tail = NULL; m->wired = true; m->sticky_count = 0; if (rect != NULL) { update_root(m, rect); } else { m->rectangle = (xcb_rectangle_t) {0, 0, screen_width, screen_height}; } return m; } void update_root(monitor_t *m, xcb_rectangle_t *rect) { xcb_rectangle_t last_rect = m->rectangle; m->rectangle = *rect; if (m->root == XCB_NONE) { uint32_t values[] = {XCB_EVENT_MASK_ENTER_WINDOW}; m->root = xcb_generate_id(dpy); xcb_create_window(dpy, XCB_COPY_FROM_PARENT, m->root, root, rect->x, rect->y, rect->width, rect->height, 0, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values); xcb_icccm_set_wm_class(dpy, m->root, sizeof(ROOT_WINDOW_IC), ROOT_WINDOW_IC); xcb_icccm_set_wm_name(dpy, m->root, XCB_ATOM_STRING, 8, strlen(m->name), m->name); xcb_ewmh_set_wm_window_type(ewmh, m->root, 1, &ewmh->_NET_WM_WINDOW_TYPE_DESKTOP); window_lower(m->root); if (focus_follows_pointer) { window_show(m->root); } } else { window_move_resize(m->root, rect->x, rect->y, rect->width, rect->height); put_status(SBSC_MASK_MONITOR_GEOMETRY, "monitor_geometry 0x%08X %ux%u+%i+%i\n", m->id, rect->width, rect->height, rect->x, rect->y); } for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) { if (n->client == NULL) { continue; } adapt_geometry(&last_rect, rect, n); } arrange(m, d); } reorder_monitor(m); } void reorder_monitor(monitor_t *m) { if (m == NULL) { return; } monitor_t *prev = m->prev; while (prev != NULL && rect_cmp(m->rectangle, prev->rectangle) < 0) { swap_monitors(m, prev); prev = m->prev; } monitor_t *next = m->next; while (next != NULL && rect_cmp(m->rectangle, next->rectangle) > 0) { swap_monitors(m, next); next = m->next; } } void rename_monitor(monitor_t *m, const char *name) { put_status(SBSC_MASK_MONITOR_RENAME, "monitor_rename 0x%08X %s %s\n", m->id, m->name, name); snprintf(m->name, sizeof(m->name), "%s", name); xcb_icccm_set_wm_name(dpy, m->root, XCB_ATOM_STRING, 8, strlen(m->name), m->name); put_status(SBSC_MASK_REPORT); } monitor_t *find_monitor(uint32_t id) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { if (m->id == id) { return m; } } return NULL; } monitor_t *get_monitor_by_randr_id(xcb_randr_output_t id) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { if (m->randr_id == id) { return m; } } return NULL; } void embrace_client(monitor_t *m, client_t *c) { if ((c->floating_rectangle.x + c->floating_rectangle.width) <= m->rectangle.x) { c->floating_rectangle.x = m->rectangle.x; } else if (c->floating_rectangle.x >= (m->rectangle.x + m->rectangle.width)) { c->floating_rectangle.x = (m->rectangle.x + m->rectangle.width) - c->floating_rectangle.width; } if ((c->floating_rectangle.y + c->floating_rectangle.height) <= m->rectangle.y) { c->floating_rectangle.y = m->rectangle.y; } else if (c->floating_rectangle.y >= (m->rectangle.y + m->rectangle.height)) { c->floating_rectangle.y = (m->rectangle.y + m->rectangle.height) - c->floating_rectangle.height; } } void adapt_geometry(xcb_rectangle_t *rs, xcb_rectangle_t *rd, node_t *n) { for (node_t *f = first_extrema(n); f != NULL; f = next_leaf(f, n)) { if (f->client == NULL) { continue; } client_t *c = f->client; /* Clip the rectangle to fit into the monitor. Without this, the fitting * algorithm doesn't work as expected. This also conserves the * out-of-bounds regions */ int left_adjust = MAX((rs->x - c->floating_rectangle.x), 0); int top_adjust = MAX((rs->y - c->floating_rectangle.y), 0); int right_adjust = MAX((c->floating_rectangle.x + c->floating_rectangle.width) - (rs->x + rs->width), 0); int bottom_adjust = MAX((c->floating_rectangle.y + c->floating_rectangle.height) - (rs->y + rs->height), 0); c->floating_rectangle.x += left_adjust; c->floating_rectangle.y += top_adjust; c->floating_rectangle.width -= (left_adjust + right_adjust); c->floating_rectangle.height -= (top_adjust + bottom_adjust); int dx_s = c->floating_rectangle.x - rs->x; int dy_s = c->floating_rectangle.y - rs->y; int nume_x = dx_s * (rd->width - c->floating_rectangle.width); int nume_y = dy_s * (rd->height - c->floating_rectangle.height); int deno_x = rs->width - c->floating_rectangle.width; int deno_y = rs->height - c->floating_rectangle.height; int dx_d = (deno_x == 0 ? 0 : nume_x / deno_x); int dy_d = (deno_y == 0 ? 0 : nume_y / deno_y); /* Translate and undo clipping */ c->floating_rectangle.width += left_adjust + right_adjust; c->floating_rectangle.height += top_adjust + bottom_adjust; c->floating_rectangle.x = rd->x + dx_d - left_adjust; c->floating_rectangle.y = rd->y + dy_d - top_adjust; } } void add_monitor(monitor_t *m) { xcb_rectangle_t r = m->rectangle; if (mon == NULL) { mon = m; mon_head = m; mon_tail = m; } else { monitor_t *a = mon_head; while (a != NULL && rect_cmp(m->rectangle, a->rectangle) > 0) { a = a->next; } if (a != NULL) { monitor_t *b = a->prev; if (b != NULL) { b->next = m; } else { mon_head = m; } m->prev = b; m->next = a; a->prev = m; } else { mon_tail->next = m; m->prev = mon_tail; mon_tail = m; } } put_status(SBSC_MASK_MONITOR_ADD, "monitor_add 0x%08X %s %ux%u+%i+%i\n", m->id, m->name, r.width, r.height, r.x, r.y); put_status(SBSC_MASK_REPORT); } void unlink_monitor(monitor_t *m) { monitor_t *prev = m->prev; monitor_t *next = m->next; if (prev != NULL) { prev->next = next; } if (next != NULL) { next->prev = prev; } if (mon_head == m) { mon_head = next; } if (mon_tail == m) { mon_tail = prev; } if (pri_mon == m) { pri_mon = NULL; } if (mon == m) { mon = NULL; } } void remove_monitor(monitor_t *m) { put_status(SBSC_MASK_MONITOR_REMOVE, "monitor_remove 0x%08X\n", m->id); while (m->desk_head != NULL) { remove_desktop(m, m->desk_head); } monitor_t *last_mon = mon; unlink_monitor(m); xcb_destroy_window(dpy, m->root); free(m); if (mon != last_mon) { focus_node(NULL, NULL, NULL); } put_status(SBSC_MASK_REPORT); } void merge_monitors(monitor_t *ms, monitor_t *md) { if (ms == NULL || md == NULL || ms == md) { return; } desktop_t *d = ms->desk_head; while (d != NULL) { desktop_t *next = d->next; transfer_desktop(ms, md, d, false); d = next; } } bool swap_monitors(monitor_t *m1, monitor_t *m2) { if (m1 == NULL || m2 == NULL || m1 == m2) { return false; } put_status(SBSC_MASK_MONITOR_SWAP, "monitor_swap 0x%08X 0x%08X\n", m1->id, m2->id); if (mon_head == m1) { mon_head = m2; } else if (mon_head == m2) { mon_head = m1; } if (mon_tail == m1) { mon_tail = m2; } else if (mon_tail == m2) { mon_tail = m1; } monitor_t *p1 = m1->prev; monitor_t *n1 = m1->next; monitor_t *p2 = m2->prev; monitor_t *n2 = m2->next; if (p1 != NULL && p1 != m2) { p1->next = m2; } if (n1 != NULL && n1 != m2) { n1->prev = m2; } if (p2 != NULL && p2 != m1) { p2->next = m1; } if (n2 != NULL && n2 != m1) { n2->prev = m1; } m1->prev = p2 == m1 ? m2 : p2; m1->next = n2 == m1 ? m2 : n2; m2->prev = p1 == m2 ? m1 : p1; m2->next = n1 == m2 ? m1 : n1; ewmh_update_wm_desktops(); ewmh_update_desktop_names(); ewmh_update_desktop_viewport(); ewmh_update_current_desktop(); put_status(SBSC_MASK_REPORT); return true; } monitor_t *closest_monitor(monitor_t *m, cycle_dir_t dir, monitor_select_t *sel) { monitor_t *f = (dir == CYCLE_PREV ? m->prev : m->next); if (f == NULL) { f = (dir == CYCLE_PREV ? mon_tail : mon_head); } while (f != m) { coordinates_t loc = {f, NULL, NULL}; if (monitor_matches(&loc, &loc, sel)) { return f; } f = (dir == CYCLE_PREV ? f->prev : f->next); if (f == NULL) { f = (dir == CYCLE_PREV ? mon_tail : mon_head); } } return NULL; } bool is_inside_monitor(monitor_t *m, xcb_point_t pt) { return is_inside(pt, m->rectangle); } monitor_t *monitor_from_point(xcb_point_t pt) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { if (is_inside_monitor(m, pt)) { return m; } } return NULL; } monitor_t *monitor_from_client(client_t *c) { int16_t xc = c->floating_rectangle.x + c->floating_rectangle.width/2; int16_t yc = c->floating_rectangle.y + c->floating_rectangle.height/2; xcb_point_t pt = {xc, yc}; monitor_t *nearest = monitor_from_point(pt); if (nearest == NULL) { int dmin = INT_MAX; for (monitor_t *m = mon_head; m != NULL; m = m->next) { xcb_rectangle_t r = m->rectangle; int d = abs((r.x + r.width / 2) - xc) + abs((r.y + r.height / 2) - yc); if (d < dmin) { dmin = d; nearest = m; } } } return nearest; } monitor_t *nearest_monitor(monitor_t *m, direction_t dir, monitor_select_t *sel) { uint32_t dmin = UINT32_MAX; monitor_t *nearest = NULL; xcb_rectangle_t rect = m->rectangle; for (monitor_t *f = mon_head; f != NULL; f = f->next) { coordinates_t loc = {f, NULL, NULL}; xcb_rectangle_t r = f->rectangle; if (f == m || !monitor_matches(&loc, &loc, sel) || !on_dir_side(rect, r, dir)) { continue; } uint32_t d = boundary_distance(rect, r, dir); if (d < dmin) { dmin = d; nearest = f; } } return nearest; } bool find_any_monitor(coordinates_t *ref, coordinates_t *dst, monitor_select_t *sel) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { coordinates_t loc = {m, NULL, NULL}; if (monitor_matches(&loc, ref, sel)) { *dst = loc; return true; } } return false; } bool update_monitors(void) { xcb_randr_get_screen_resources_reply_t *sres = xcb_randr_get_screen_resources_reply(dpy, xcb_randr_get_screen_resources(dpy, root), NULL); if (sres == NULL) { return false; } monitor_t *last_wired = NULL; int len = xcb_randr_get_screen_resources_outputs_length(sres); xcb_randr_output_t *outputs = xcb_randr_get_screen_resources_outputs(sres); xcb_randr_get_output_info_cookie_t cookies[len]; for (int i = 0; i < len; i++) { cookies[i] = xcb_randr_get_output_info(dpy, outputs[i], XCB_CURRENT_TIME); } for (monitor_t *m = mon_head; m != NULL; m = m->next) { m->wired = false; } for (int i = 0; i < len; i++) { xcb_randr_get_output_info_reply_t *info = xcb_randr_get_output_info_reply(dpy, cookies[i], NULL); if (info != NULL) { if (info->crtc != XCB_NONE) { xcb_randr_get_crtc_info_reply_t *cir = xcb_randr_get_crtc_info_reply(dpy, xcb_randr_get_crtc_info(dpy, info->crtc, XCB_CURRENT_TIME), NULL); if (cir != NULL) { xcb_rectangle_t rect = (xcb_rectangle_t) {cir->x, cir->y, cir->width, cir->height}; last_wired = get_monitor_by_randr_id(outputs[i]); if (last_wired != NULL) { update_root(last_wired, &rect); last_wired->wired = true; } else { char *name = (char *) xcb_randr_get_output_info_name(info); size_t len = (size_t) xcb_randr_get_output_info_name_length(info); char *name_copy = copy_string(name, len); last_wired = make_monitor(name_copy, &rect, XCB_NONE); free(name_copy); last_wired->randr_id = outputs[i]; add_monitor(last_wired); } } free(cir); } else if (!remove_disabled_monitors && info->connection != XCB_RANDR_CONNECTION_DISCONNECTED) { monitor_t *m = get_monitor_by_randr_id(outputs[i]); if (m != NULL) { m->wired = true; } } } free(info); } xcb_randr_get_output_primary_reply_t *gpo = xcb_randr_get_output_primary_reply(dpy, xcb_randr_get_output_primary(dpy, root), NULL); if (gpo != NULL) { pri_mon = get_monitor_by_randr_id(gpo->output); } free(gpo); /* handle overlapping monitors */ if (merge_overlapping_monitors) { monitor_t *m = mon_head; while (m != NULL) { monitor_t *next = m->next; if (m->wired) { monitor_t *mb = mon_head; while (mb != NULL) { monitor_t *mb_next = mb->next; if (m != mb && mb->wired && contains(m->rectangle, mb->rectangle)) { if (last_wired == mb) { last_wired = m; } if (next == mb) { next = mb_next; } merge_monitors(mb, m); remove_monitor(mb); } mb = mb_next; } } m = next; } } /* merge and remove disconnected monitors */ if (remove_unplugged_monitors) { monitor_t *m = mon_head; while (m != NULL) { monitor_t *next = m->next; if (!m->wired) { merge_monitors(m, last_wired); remove_monitor(m); } m = next; } } /* add one desktop to each new monitor */ for (monitor_t *m = mon_head; m != NULL; m = m->next) { if (m->desk == NULL) { add_desktop(m, make_desktop(NULL, XCB_NONE)); } } if (!running && mon != NULL) { if (pri_mon != NULL) { mon = pri_mon; } center_pointer(mon->rectangle); ewmh_update_current_desktop(); } free(sres); return (mon != NULL); } ================================================ FILE: src/monitor.h ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef BSPWM_MONITOR_H #define BSPWM_MONITOR_H #define DEFAULT_MON_NAME "MONITOR" monitor_t *make_monitor(const char *name, xcb_rectangle_t *rect, uint32_t id); void update_root(monitor_t *m, xcb_rectangle_t *rect); void reorder_monitor(monitor_t *m); void rename_monitor(monitor_t *m, const char *name); monitor_t *find_monitor(uint32_t id); monitor_t *get_monitor_by_randr_id(xcb_randr_output_t id); void embrace_client(monitor_t *m, client_t *c); void adapt_geometry(xcb_rectangle_t *rs, xcb_rectangle_t *rd, node_t *n); void add_monitor(monitor_t *m); void unlink_monitor(monitor_t *m); void remove_monitor(monitor_t *m); void merge_monitors(monitor_t *ms, monitor_t *md); bool swap_monitors(monitor_t *m1, monitor_t *m2); monitor_t *closest_monitor(monitor_t *m, cycle_dir_t dir, monitor_select_t *sel); bool is_inside_monitor(monitor_t *m, xcb_point_t pt); monitor_t *monitor_from_point(xcb_point_t pt); monitor_t *monitor_from_client(client_t *c); monitor_t *nearest_monitor(monitor_t *m, direction_t dir, monitor_select_t *sel); bool find_any_monitor(coordinates_t *ref, coordinates_t *dst, monitor_select_t *sel); bool update_monitors(void); #endif ================================================ FILE: src/parse.c ================================================ #include #include #include #include #include #include "parse.h" bool parse_bool(char *value, bool *b) { if (streq("true", value) || streq("on", value)) { *b = true; return true; } else if (streq("false", value) || streq("off", value)) { *b = false; return true; } return false; } bool parse_split_type(char *s, split_type_t *t) { if (streq("horizontal", s)) { *t = TYPE_HORIZONTAL; return true; } else if (streq("vertical", s)) { *t = TYPE_VERTICAL; return true; } return false; } bool parse_split_mode(char *s, split_mode_t *m) { if (streq("automatic", s)) { *m = MODE_AUTOMATIC; return true; } else if (streq("vertical", s)) { *m = MODE_MANUAL; return true; } return false; } bool parse_layout(char *s, layout_t *l) { if (streq("monocle", s)) { *l = LAYOUT_MONOCLE; return true; } else if (streq("tiled", s)) { *l = LAYOUT_TILED; return true; } return false; } bool parse_client_state(char *s, client_state_t *t) { if (streq("tiled", s)) { *t = STATE_TILED; return true; } else if (streq("pseudo_tiled", s)) { *t = STATE_PSEUDO_TILED; return true; } else if (streq("floating", s)) { *t = STATE_FLOATING; return true; } else if (streq("fullscreen", s)) { *t = STATE_FULLSCREEN; return true; } return false; } bool parse_stack_layer(char *s, stack_layer_t *l) { if (streq("below", s)) { *l = LAYER_BELOW; return true; } else if (streq("normal", s)) { *l = LAYER_NORMAL; return true; } else if (streq("above", s)) { *l = LAYER_ABOVE; return true; } return false; } bool parse_direction(char *s, direction_t *d) { if (streq("north", s)) { *d = DIR_NORTH; return true; } else if (streq("west", s)) { *d = DIR_WEST; return true; } else if (streq("south", s)) { *d = DIR_SOUTH; return true; } else if (streq("east", s)) { *d = DIR_EAST; return true; } return false; } bool parse_cycle_direction(char *s, cycle_dir_t *d) { if (streq("next", s)) { *d = CYCLE_NEXT; return true; } else if (streq("prev", s)) { *d = CYCLE_PREV; return true; } return false; } bool parse_circulate_direction(char *s, circulate_dir_t *d) { if (streq("forward", s)) { *d = CIRCULATE_FORWARD; return true; } else if (streq("backward", s)) { *d = CIRCULATE_BACKWARD; return true; } return false; } bool parse_history_direction(char *s, history_dir_t *d) { if (streq("older", s)) { *d = HISTORY_OLDER; return true; } else if (streq("newer", s)) { *d = HISTORY_NEWER; return true; } return false; } bool parse_flip(char *s, flip_t *f) { if (streq("horizontal", s)) { *f = FLIP_HORIZONTAL; return true; } else if (streq("vertical", s)) { *f = FLIP_VERTICAL; return true; } return false; } bool parse_resize_handle(char *s, resize_handle_t *h) { if (streq("left", s)) { *h = HANDLE_LEFT; return true; } else if (streq("top", s)) { *h = HANDLE_TOP; return true; } else if (streq("right", s)) { *h = HANDLE_RIGHT; return true; } else if (streq("bottom", s)) { *h = HANDLE_BOTTOM; return true; } else if (streq("top_left", s)) { *h = HANDLE_TOP_LEFT; return true; } else if (streq("top_right", s)) { *h = HANDLE_TOP_RIGHT; return true; } else if (streq("bottom_right", s)) { *h = HANDLE_BOTTOM_RIGHT; return true; } else if (streq("bottom_left", s)) { *h = HANDLE_BOTTOM_LEFT; return true; } return false; } bool parse_modifier_mask(char *s, uint16_t *m) { if (strcmp(s, "shift") == 0) { *m = XCB_MOD_MASK_SHIFT; return true; } else if (strcmp(s, "control") == 0) { *m = XCB_MOD_MASK_CONTROL; return true; } else if (strcmp(s, "lock") == 0) { *m = XCB_MOD_MASK_LOCK; return true; } else if (strcmp(s, "mod1") == 0) { *m = XCB_MOD_MASK_1; return true; } else if (strcmp(s, "mod2") == 0) { *m = XCB_MOD_MASK_2; return true; } else if (strcmp(s, "mod3") == 0) { *m = XCB_MOD_MASK_3; return true; } else if (strcmp(s, "mod4") == 0) { *m = XCB_MOD_MASK_4; return true; } else if (strcmp(s, "mod5") == 0) { *m = XCB_MOD_MASK_5; return true; } return false; } bool parse_button_index(char *s, int8_t *b) { if (strcmp(s, "any") == 0) { *b = XCB_BUTTON_INDEX_ANY; return true; } else if (strcmp(s, "button1") == 0) { *b = XCB_BUTTON_INDEX_1; return true; } else if (strcmp(s, "button2") == 0) { *b = XCB_BUTTON_INDEX_2; return true; } else if (strcmp(s, "button3") == 0) { *b = XCB_BUTTON_INDEX_3; return true; } else if (strcmp(s, "none") == 0) { *b = -1; return true; } return false; } bool parse_pointer_action(char *s, pointer_action_t *a) { if (streq("move", s)) { *a = ACTION_MOVE; return true; } else if (streq("resize_corner", s)) { *a = ACTION_RESIZE_CORNER; return true; } else if (streq("resize_side", s)) { *a = ACTION_RESIZE_SIDE; return true; } else if (streq("focus", s)) { *a = ACTION_FOCUS; return true; } else if (streq("none", s)) { *a = ACTION_NONE; return true; } return false; } bool parse_child_polarity(char *s, child_polarity_t *p) { if (streq("first_child", s)) { *p = FIRST_CHILD; return true; } else if (streq("second_child", s)) { *p = SECOND_CHILD; return true; } return false; } bool parse_automatic_scheme(char *s, automatic_scheme_t *a) { if (streq("longest_side", s)) { *a = SCHEME_LONGEST_SIDE; return true; } else if (streq("alternate", s)) { *a = SCHEME_ALTERNATE; return true; } else if (streq("spiral", s)) { *a = SCHEME_SPIRAL; return true; } return false; } bool parse_honor_size_hints_mode(char *s, honor_size_hints_mode_t *a) { bool b; if (parse_bool(s, &b)) { *a = b ? HONOR_SIZE_HINTS_YES : HONOR_SIZE_HINTS_NO; return true; } else if (streq("floating", s)) { *a = HONOR_SIZE_HINTS_FLOATING; return true; } else if (streq("tiled", s)) { *a = HONOR_SIZE_HINTS_TILED; return true; } return false; } bool parse_state_transition(char *s, state_transition_t *m) { if (streq("none", s)) { *m = 0; return true; } else if (streq("all", s)) { *m = STATE_TRANSITION_ENTER | STATE_TRANSITION_EXIT; return true; } else { state_transition_t w = 0; char *x = copy_string(s, strlen(s)); char *key = strtok(x, ","); while (key != NULL) { if (streq("enter", key)) { w |= STATE_TRANSITION_ENTER; } else if (streq("exit", key)) { w |= STATE_TRANSITION_EXIT; } else { free(x); return false; } key = strtok(NULL, ","); } free(x); if (w != 0) { *m = w; return true; } else { return false; } } return false; } bool parse_tightness(char *s, tightness_t *t) { if (streq("high", s)) { *t = TIGHTNESS_HIGH; return true; } else if (streq("low", s)) { *t = TIGHTNESS_LOW; return true; } return false; } bool parse_degree(char *s, int *d) { int i = atoi(s); while (i < 0) i += 360; while (i > 359) i -= 360; if ((i % 90) != 0) { return false; } else { *d = i; return true; } } bool parse_id(char *s, uint32_t *id) { char *end; errno = 0; uint32_t v = strtol(s, &end, 0); if (errno != 0 || *end != '\0') { return false; } *id = v; return true; } bool parse_bool_declaration(char *s, char **key, bool *value, alter_state_t *state) { *key = strtok(s, EQL_TOK); char *v = strtok(NULL, EQL_TOK); if (v == NULL) { *state = ALTER_TOGGLE; return true; } else { if (parse_bool(v, value)) { *state = ALTER_SET; return true; } else { return false; } } return false; } bool parse_index(char *s, uint16_t *idx) { return (sscanf(s, "^%hu", idx) == 1); } bool parse_rectangle(char *s, xcb_rectangle_t *r) { uint16_t w, h; int16_t x, y; if (sscanf(s, "%hux%hu+%hi+%hi", &w, &h, &x, &y) != 4) { return false; } r->width = w; r->height = h; r->x = x; r->y = y; return true; } bool parse_subscriber_mask(char *s, subscriber_mask_t *mask) { if (streq("all", s)) { *mask = SBSC_MASK_ALL; } else if (streq("node", s)) { *mask = SBSC_MASK_NODE; } else if (streq("desktop", s)) { *mask = SBSC_MASK_DESKTOP; } else if (streq("monitor", s)) { *mask = SBSC_MASK_MONITOR; } else if (streq("pointer_action", s)) { *mask = SBSC_MASK_POINTER_ACTION; } else if (streq("node_add", s)) { *mask = SBSC_MASK_NODE_ADD; } else if (streq("node_remove", s)) { *mask = SBSC_MASK_NODE_REMOVE; } else if (streq("node_swap", s)) { *mask = SBSC_MASK_NODE_SWAP; } else if (streq("node_transfer", s)) { *mask = SBSC_MASK_NODE_TRANSFER; } else if (streq("node_focus", s)) { *mask = SBSC_MASK_NODE_FOCUS; } else if (streq("node_presel", s)) { *mask = SBSC_MASK_NODE_PRESEL; } else if (streq("node_stack", s)) { *mask = SBSC_MASK_NODE_STACK; } else if (streq("node_activate", s)) { *mask = SBSC_MASK_NODE_ACTIVATE; } else if (streq("node_geometry", s)) { *mask = SBSC_MASK_NODE_GEOMETRY; } else if (streq("node_state", s)) { *mask = SBSC_MASK_NODE_STATE; } else if (streq("node_flag", s)) { *mask = SBSC_MASK_NODE_FLAG; } else if (streq("node_layer", s)) { *mask = SBSC_MASK_NODE_LAYER; } else if (streq("desktop_add", s)) { *mask = SBSC_MASK_DESKTOP_ADD; } else if (streq("desktop_rename", s)) { *mask = SBSC_MASK_DESKTOP_RENAME; } else if (streq("desktop_remove", s)) { *mask = SBSC_MASK_DESKTOP_REMOVE; } else if (streq("desktop_swap", s)) { *mask = SBSC_MASK_DESKTOP_SWAP; } else if (streq("desktop_transfer", s)) { *mask = SBSC_MASK_DESKTOP_TRANSFER; } else if (streq("desktop_focus", s)) { *mask = SBSC_MASK_DESKTOP_FOCUS; } else if (streq("desktop_activate", s)) { *mask = SBSC_MASK_DESKTOP_ACTIVATE; } else if (streq("desktop_layout", s)) { *mask = SBSC_MASK_DESKTOP_LAYOUT; } else if (streq("monitor_add", s)) { *mask = SBSC_MASK_MONITOR_ADD; } else if (streq("monitor_rename", s)) { *mask = SBSC_MASK_MONITOR_RENAME; } else if (streq("monitor_remove", s)) { *mask = SBSC_MASK_MONITOR_REMOVE; } else if (streq("monitor_swap", s)) { *mask = SBSC_MASK_MONITOR_SWAP; } else if (streq("monitor_focus", s)) { *mask = SBSC_MASK_MONITOR_FOCUS; } else if (streq("monitor_geometry", s)) { *mask = SBSC_MASK_MONITOR_GEOMETRY; } else if (streq("report", s)) { *mask = SBSC_MASK_REPORT; } else { return false; } return true; } #define GET_MOD(k) \ } else if (streq(#k, tok)) { \ sel->k = OPTION_TRUE; \ } else if (streq("!" #k, tok)) { \ sel->k = OPTION_FALSE; bool parse_monitor_modifiers(char *desc, monitor_select_t *sel) { char *tok; while ((tok = strrchr(desc, CAT_CHR)) != NULL) { tok[0] = '\0'; tok++; if (streq("occupied", tok)) { sel->occupied = OPTION_TRUE; } else if (streq("!occupied", tok)) { sel->occupied = OPTION_FALSE; GET_MOD(focused) } else { return false; } } return true; } bool parse_desktop_modifiers(char *desc, desktop_select_t *sel) { char *tok; while ((tok = strrchr(desc, CAT_CHR)) != NULL) { tok[0] = '\0'; tok++; if (streq("occupied", tok)) { sel->occupied = OPTION_TRUE; } else if (streq("!occupied", tok)) { sel->occupied = OPTION_FALSE; GET_MOD(focused) GET_MOD(active) GET_MOD(urgent) GET_MOD(local) GET_MOD(tiled) GET_MOD(monocle) GET_MOD(user_tiled) GET_MOD(user_monocle) } else { return false; } } return true; } bool parse_node_modifiers(char *desc, node_select_t *sel) { char *tok; while ((tok = strrchr(desc, CAT_CHR)) != NULL) { tok[0] = '\0'; tok++; if (streq("tiled", tok)) { sel->tiled = OPTION_TRUE; } else if (streq("!tiled", tok)) { sel->tiled = OPTION_FALSE; GET_MOD(automatic) GET_MOD(focused) GET_MOD(active) GET_MOD(local) GET_MOD(leaf) GET_MOD(window) GET_MOD(pseudo_tiled) GET_MOD(floating) GET_MOD(fullscreen) GET_MOD(hidden) GET_MOD(sticky) GET_MOD(private) GET_MOD(locked) GET_MOD(marked) GET_MOD(urgent) GET_MOD(same_class) GET_MOD(descendant_of) GET_MOD(ancestor_of) GET_MOD(below) GET_MOD(normal) GET_MOD(above) GET_MOD(horizontal) GET_MOD(vertical) } else { return false; } } return true; } #undef GET_MOD ================================================ FILE: src/parse.h ================================================ #ifndef BSPWM_PARSE_H #define BSPWM_PARSE_H #include "types.h" #include "subscribe.h" #define OPT_CHR '-' #define CAT_CHR '.' #define EQL_TOK "=" #define COL_TOK ":" bool parse_bool(char *value, bool *b); bool parse_split_type(char *s, split_type_t *t); bool parse_split_mode(char *s, split_mode_t *m); bool parse_layout(char *s, layout_t *l); bool parse_client_state(char *s, client_state_t *t); bool parse_stack_layer(char *s, stack_layer_t *l); bool parse_direction(char *s, direction_t *d); bool parse_cycle_direction(char *s, cycle_dir_t *d); bool parse_circulate_direction(char *s, circulate_dir_t *d); bool parse_history_direction(char *s, history_dir_t *d); bool parse_flip(char *s, flip_t *f); bool parse_resize_handle(char *s, resize_handle_t *h); bool parse_modifier_mask(char *s, uint16_t *m); bool parse_button_index(char *s, int8_t *b); bool parse_pointer_action(char *s, pointer_action_t *a); bool parse_child_polarity(char *s, child_polarity_t *p); bool parse_automatic_scheme(char *s, automatic_scheme_t *a); bool parse_honor_size_hints_mode(char *s, honor_size_hints_mode_t *a); bool parse_state_transition(char *s, state_transition_t *m); bool parse_tightness(char *s, tightness_t *t); bool parse_degree(char *s, int *d); bool parse_id(char *s, uint32_t *id); bool parse_bool_declaration(char *s, char **key, bool *value, alter_state_t *state); bool parse_index(char *s, uint16_t *idx); bool parse_rectangle(char *s, xcb_rectangle_t *r); bool parse_subscriber_mask(char *s, subscriber_mask_t *mask); bool parse_monitor_modifiers(char *desc, monitor_select_t *sel); bool parse_desktop_modifiers(char *desc, desktop_select_t *sel); bool parse_node_modifiers(char *desc, node_select_t *sel); #endif ================================================ FILE: src/pointer.c ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include "bspwm.h" #include "query.h" #include "settings.h" #include "stack.h" #include "tree.h" #include "monitor.h" #include "subscribe.h" #include "events.h" #include "window.h" #include "pointer.h" uint16_t num_lock; uint16_t caps_lock; uint16_t scroll_lock; bool grabbing; node_t *grabbed_node; void pointer_init(void) { num_lock = modfield_from_keysym(XK_Num_Lock); caps_lock = modfield_from_keysym(XK_Caps_Lock); scroll_lock = modfield_from_keysym(XK_Scroll_Lock); if (caps_lock == XCB_NO_SYMBOL) { caps_lock = XCB_MOD_MASK_LOCK; } grabbing = false; grabbed_node = NULL; } void window_grab_buttons(xcb_window_t win) { for (unsigned int i = 0; i < LENGTH(BUTTONS); i++) { if (click_to_focus == (int8_t) XCB_BUTTON_INDEX_ANY || click_to_focus == (int8_t) BUTTONS[i]) { window_grab_button(win, BUTTONS[i], XCB_NONE); } if (pointer_actions[i] != ACTION_NONE) { window_grab_button(win, BUTTONS[i], pointer_modifier); } } } void window_grab_button(xcb_window_t win, uint8_t button, uint16_t modifier) { #define GRAB(b, m) \ xcb_grab_button(dpy, false, win, XCB_EVENT_MASK_BUTTON_PRESS, \ XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, XCB_NONE, XCB_NONE, b, m) GRAB(button, modifier); if (num_lock != XCB_NO_SYMBOL && caps_lock != XCB_NO_SYMBOL && scroll_lock != XCB_NO_SYMBOL) { GRAB(button, modifier | num_lock | caps_lock | scroll_lock); } if (num_lock != XCB_NO_SYMBOL && caps_lock != XCB_NO_SYMBOL) { GRAB(button, modifier | num_lock | caps_lock); } if (caps_lock != XCB_NO_SYMBOL && scroll_lock != XCB_NO_SYMBOL) { GRAB(button, modifier | caps_lock | scroll_lock); } if (num_lock != XCB_NO_SYMBOL && scroll_lock != XCB_NO_SYMBOL) { GRAB(button, modifier | num_lock | scroll_lock); } if (num_lock != XCB_NO_SYMBOL) { GRAB(button, modifier | num_lock); } if (caps_lock != XCB_NO_SYMBOL) { GRAB(button, modifier | caps_lock); } if (scroll_lock != XCB_NO_SYMBOL) { GRAB(button, modifier | scroll_lock); } #undef GRAB } void grab_buttons(void) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) { window_grab_buttons(n->id); if (n->presel != NULL) { window_grab_buttons(n->presel->feedback); } } } } } void ungrab_buttons(void) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) { xcb_ungrab_button(dpy, XCB_BUTTON_INDEX_ANY, n->id, XCB_MOD_MASK_ANY); } } } } int16_t modfield_from_keysym(xcb_keysym_t keysym) { uint16_t modfield = 0; xcb_keycode_t *keycodes = NULL, *mod_keycodes = NULL; xcb_get_modifier_mapping_reply_t *reply = NULL; xcb_key_symbols_t *symbols = xcb_key_symbols_alloc(dpy); if ((keycodes = xcb_key_symbols_get_keycode(symbols, keysym)) == NULL || (reply = xcb_get_modifier_mapping_reply(dpy, xcb_get_modifier_mapping(dpy), NULL)) == NULL || reply->keycodes_per_modifier < 1 || (mod_keycodes = xcb_get_modifier_mapping_keycodes(reply)) == NULL) { goto end; } unsigned int num_mod = xcb_get_modifier_mapping_keycodes_length(reply) / reply->keycodes_per_modifier; for (unsigned int i = 0; i < num_mod; i++) { for (unsigned int j = 0; j < reply->keycodes_per_modifier; j++) { xcb_keycode_t mk = mod_keycodes[i * reply->keycodes_per_modifier + j]; if (mk == XCB_NO_SYMBOL) { continue; } for (xcb_keycode_t *k = keycodes; *k != XCB_NO_SYMBOL; k++) { if (*k == mk) { modfield |= (1 << i); } } } } end: xcb_key_symbols_free(symbols); free(keycodes); free(reply); return modfield; } resize_handle_t get_handle(node_t *n, xcb_point_t pos, pointer_action_t pac) { resize_handle_t rh = HANDLE_BOTTOM_RIGHT; xcb_rectangle_t rect = get_rectangle(NULL, NULL, n); if (pac == ACTION_RESIZE_SIDE) { float W = rect.width; float H = rect.height; float ratio = W / H; float x = pos.x - rect.x; float y = pos.y - rect.y; float diag_a = ratio * y; float diag_b = W - diag_a; if (x < diag_a) { if (x < diag_b) { rh = HANDLE_LEFT; } else { rh = HANDLE_BOTTOM; } } else { if (x < diag_b) { rh = HANDLE_TOP; } else { rh = HANDLE_RIGHT; } } } else if (pac == ACTION_RESIZE_CORNER) { int16_t mid_x = rect.x + (rect.width / 2); int16_t mid_y = rect.y + (rect.height / 2); if (pos.x > mid_x) { if (pos.y > mid_y) { rh = HANDLE_BOTTOM_RIGHT; } else { rh = HANDLE_TOP_RIGHT; } } else { if (pos.y > mid_y) { rh = HANDLE_BOTTOM_LEFT; } else { rh = HANDLE_TOP_LEFT; } } } return rh; } bool grab_pointer(pointer_action_t pac) { xcb_window_t win = XCB_NONE; xcb_point_t pos; query_pointer(&win, &pos); coordinates_t loc; if (!locate_window(win, &loc)) { if (pac == ACTION_FOCUS) { monitor_t *m = monitor_from_point(pos); if (m != NULL && m != mon && (win == XCB_NONE || win == m->root)) { focus_node(m, m->desk, m->desk->focus); return true; } } return false; } if (pac == ACTION_FOCUS) { if (loc.node != mon->desk->focus) { focus_node(loc.monitor, loc.desktop, loc.node); return true; } else if (focus_follows_pointer) { stack(loc.desktop, loc.node, true); } return false; } if (loc.node->client->state == STATE_FULLSCREEN) { return true; } xcb_grab_pointer_reply_t *reply = xcb_grab_pointer_reply(dpy, xcb_grab_pointer(dpy, 0, root, XCB_EVENT_MASK_BUTTON_RELEASE|XCB_EVENT_MASK_BUTTON_MOTION, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_NONE, XCB_NONE, XCB_CURRENT_TIME), NULL); if (reply == NULL || reply->status != XCB_GRAB_STATUS_SUCCESS) { free(reply); return true; } free(reply); if (pac == ACTION_MOVE) { put_status(SBSC_MASK_POINTER_ACTION, "pointer_action 0x%08X 0x%08X 0x%08X move begin\n", loc.monitor->id, loc.desktop->id, loc.node->id); } else if (pac == ACTION_RESIZE_CORNER) { put_status(SBSC_MASK_POINTER_ACTION, "pointer_action 0x%08X 0x%08X 0x%08X resize_corner begin\n", loc.monitor->id, loc.desktop->id, loc.node->id); } else if (pac == ACTION_RESIZE_SIDE) { put_status(SBSC_MASK_POINTER_ACTION, "pointer_action 0x%08X 0x%08X 0x%08X resize_side begin\n", loc.monitor->id, loc.desktop->id, loc.node->id); } track_pointer(loc, pac, pos); return true; } void track_pointer(coordinates_t loc, pointer_action_t pac, xcb_point_t pos) { node_t *n = loc.node; resize_handle_t rh = get_handle(loc.node, pos, pac); uint16_t last_motion_x = pos.x, last_motion_y = pos.y; xcb_timestamp_t last_motion_time = 0; xcb_generic_event_t *evt = NULL; grabbing = true; grabbed_node = n; do { free(evt); while ((evt = xcb_wait_for_event(dpy)) == NULL) { xcb_flush(dpy); } uint8_t resp_type = XCB_EVENT_RESPONSE_TYPE(evt); if (resp_type == XCB_MOTION_NOTIFY) { xcb_motion_notify_event_t *e = (xcb_motion_notify_event_t*) evt; uint32_t dtime = e->time - last_motion_time; if (dtime < pointer_motion_interval) { continue; } last_motion_time = e->time; int16_t dx = e->root_x - last_motion_x; int16_t dy = e->root_y - last_motion_y; if (pac == ACTION_MOVE) { move_client(&loc, dx, dy); } else if (n) { client_t *c = n->client; if (c && SHOULD_HONOR_SIZE_HINTS(c->honor_size_hints, c->state)) { resize_client(&loc, rh, e->root_x, e->root_y, false); } else { resize_client(&loc, rh, dx, dy, true); } } last_motion_x = e->root_x; last_motion_y = e->root_y; xcb_flush(dpy); } else if (resp_type == XCB_BUTTON_RELEASE) { grabbing = false; } else { handle_event(evt); } } while (grabbing && grabbed_node != NULL); free(evt); xcb_ungrab_pointer(dpy, XCB_CURRENT_TIME); if (grabbed_node == NULL) { grabbing = false; return; } if (pac == ACTION_MOVE) { put_status(SBSC_MASK_POINTER_ACTION, "pointer_action 0x%08X 0x%08X 0x%08X move end\n", loc.monitor->id, loc.desktop->id, n->id); } else if (pac == ACTION_RESIZE_CORNER) { put_status(SBSC_MASK_POINTER_ACTION, "pointer_action 0x%08X 0x%08X 0x%08X resize_corner end\n", loc.monitor->id, loc.desktop->id, n->id); } else if (pac == ACTION_RESIZE_SIDE) { put_status(SBSC_MASK_POINTER_ACTION, "pointer_action 0x%08X 0x%08X 0x%08X resize_side end\n", loc.monitor->id, loc.desktop->id, n->id); } xcb_rectangle_t r = get_rectangle(NULL, NULL, n); put_status(SBSC_MASK_NODE_GEOMETRY, "node_geometry 0x%08X 0x%08X 0x%08X %ux%u+%i+%i\n", loc.monitor->id, loc.desktop->id, loc.node->id, r.width, r.height, r.x, r.y); if ((pac == ACTION_MOVE && IS_TILED(n->client)) || ((pac == ACTION_RESIZE_CORNER || pac == ACTION_RESIZE_SIDE) && n->client->state == STATE_TILED)) { for (node_t *f = first_extrema(loc.desktop->root); f != NULL; f = next_leaf(f, loc.desktop->root)) { if (f == n || f->client == NULL || !IS_TILED(f->client)) { continue; } xcb_rectangle_t r = f->client->tiled_rectangle; put_status(SBSC_MASK_NODE_GEOMETRY, "node_geometry 0x%08X 0x%08X 0x%08X %ux%u+%i+%i\n", loc.monitor->id, loc.desktop->id, f->id, r.width, r.height, r.x, r.y); } } } ================================================ FILE: src/pointer.h ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef BSPWM_POINTER_H #define BSPWM_POINTER_H #define XK_Num_Lock 0xff7f #define XK_Caps_Lock 0xffe5 #define XK_Scroll_Lock 0xff14 extern uint16_t num_lock; extern uint16_t caps_lock; extern uint16_t scroll_lock; extern bool grabbing; extern node_t *grabbed_node; void pointer_init(void); void window_grab_buttons(xcb_window_t win); void window_grab_button(xcb_window_t win, uint8_t button, uint16_t modifier); void grab_buttons(void); void ungrab_buttons(void); int16_t modfield_from_keysym(xcb_keysym_t keysym); resize_handle_t get_handle(node_t *n, xcb_point_t pos, pointer_action_t pac); bool grab_pointer(pointer_action_t pac); void track_pointer(coordinates_t loc, pointer_action_t pac, xcb_point_t pos); #endif ================================================ FILE: src/query.c ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include "bspwm.h" #include "desktop.h" #include "history.h" #include "parse.h" #include "monitor.h" #include "window.h" #include "tree.h" #include "query.h" #include "geometry.h" void query_state(FILE *rsp) { fprintf(rsp, "{"); fprintf(rsp, "\"focusedMonitorId\":%u,", mon->id); if (pri_mon != NULL) { fprintf(rsp, "\"primaryMonitorId\":%u,", pri_mon->id); } fprintf(rsp, "\"clientsCount\":%i,", clients_count); fprintf(rsp, "\"monitors\":"); fprintf(rsp, "["); for (monitor_t *m = mon_head; m != NULL; m = m->next) { query_monitor(m, rsp); if (m->next != NULL) { fprintf(rsp, ","); } } fprintf(rsp, "]"); fprintf(rsp,","); fprintf(rsp, "\"focusHistory\":"); query_history(rsp); fprintf(rsp,","); fprintf(rsp, "\"stackingList\":"); query_stack(rsp); if (restart) { fprintf(rsp,","); fprintf(rsp, "\"eventSubscribers\":"); query_subscribers(rsp); } fprintf(rsp, "}"); } void query_monitor(monitor_t *m, FILE *rsp) { fprintf(rsp, "{"); fprintf(rsp, "\"name\":\"%s\",", m->name); fprintf(rsp, "\"id\":%u,", m->id); fprintf(rsp, "\"randrId\":%u,", m->randr_id); fprintf(rsp, "\"wired\":%s,", BOOL_STR(m->wired)); fprintf(rsp, "\"stickyCount\":%i,", m->sticky_count); fprintf(rsp, "\"windowGap\":%i,", m->window_gap); fprintf(rsp, "\"borderWidth\":%u,", m->border_width); fprintf(rsp, "\"focusedDesktopId\":%u,", m->desk->id); fprintf(rsp, "\"padding\":"); query_padding(m->padding, rsp); fprintf(rsp,","); fprintf(rsp, "\"rectangle\":"); query_rectangle(m->rectangle, rsp); fprintf(rsp,","); fprintf(rsp, "\"desktops\":"); fprintf(rsp, "["); for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { query_desktop(d, rsp); if (d->next != NULL) { fprintf(rsp,","); } } fprintf(rsp, "]"); fprintf(rsp, "}"); } void query_desktop(desktop_t *d, FILE *rsp) { fprintf(rsp, "{"); fprintf(rsp, "\"name\":\"%s\",", d->name); fprintf(rsp, "\"id\":%u,", d->id); fprintf(rsp, "\"layout\":\"%s\",", LAYOUT_STR(d->layout)); fprintf(rsp, "\"userLayout\":\"%s\",", LAYOUT_STR(d->user_layout)); fprintf(rsp, "\"windowGap\":%i,", d->window_gap); fprintf(rsp, "\"borderWidth\":%u,", d->border_width); fprintf(rsp, "\"focusedNodeId\":%u,", d->focus != NULL ? d->focus->id : 0); fprintf(rsp, "\"padding\":"); query_padding(d->padding, rsp); fprintf(rsp,","); fprintf(rsp, "\"root\":"); query_node(d->root, rsp); fprintf(rsp, "}"); } void query_node(node_t *n, FILE *rsp) { if (n == NULL) { fprintf(rsp, "null"); } else { fprintf(rsp, "{"); fprintf(rsp, "\"id\":%u,", n->id); fprintf(rsp, "\"splitType\":\"%s\",", SPLIT_TYPE_STR(n->split_type)); fprintf(rsp, "\"splitRatio\":%lf,", n->split_ratio); fprintf(rsp, "\"vacant\":%s,", BOOL_STR(n->vacant)); fprintf(rsp, "\"hidden\":%s,", BOOL_STR(n->hidden)); fprintf(rsp, "\"sticky\":%s,", BOOL_STR(n->sticky)); fprintf(rsp, "\"private\":%s,", BOOL_STR(n->private)); fprintf(rsp, "\"locked\":%s,", BOOL_STR(n->locked)); fprintf(rsp, "\"marked\":%s,", BOOL_STR(n->marked)); fprintf(rsp, "\"presel\":"); query_presel(n->presel, rsp); fprintf(rsp,","); fprintf(rsp, "\"rectangle\":"); query_rectangle(n->rectangle, rsp); fprintf(rsp,","); fprintf(rsp, "\"constraints\":"); query_constraints(n->constraints, rsp); fprintf(rsp,","); fprintf(rsp, "\"firstChild\":"); query_node(n->first_child, rsp); fprintf(rsp,","); fprintf(rsp, "\"secondChild\":"); query_node(n->second_child, rsp); fprintf(rsp,","); fprintf(rsp, "\"client\":"); query_client(n->client, rsp); fprintf(rsp, "}"); } } void query_presel(presel_t *p, FILE *rsp) { if (p == NULL) { fprintf(rsp, "null"); } else { fprintf(rsp, "{\"splitDir\":\"%s\",\"splitRatio\":%lf}", SPLIT_DIR_STR(p->split_dir), p->split_ratio); } } void query_client(client_t *c, FILE *rsp) { if (c == NULL) { fprintf(rsp, "null"); } else { fprintf(rsp, "{"); fprintf(rsp, "\"className\":\"%s\",", c->class_name); fprintf(rsp, "\"instanceName\":\"%s\",", c->instance_name); fprintf(rsp, "\"borderWidth\":%u,", c->border_width); fprintf(rsp, "\"state\":\"%s\",", STATE_STR(c->state)); fprintf(rsp, "\"lastState\":\"%s\",", STATE_STR(c->last_state)); fprintf(rsp, "\"layer\":\"%s\",", LAYER_STR(c->layer)); fprintf(rsp, "\"lastLayer\":\"%s\",", LAYER_STR(c->last_layer)); fprintf(rsp, "\"urgent\":%s,", BOOL_STR(c->urgent)); fprintf(rsp, "\"shown\":%s,", BOOL_STR(c->shown)); fprintf(rsp, "\"tiledRectangle\":"); query_rectangle(c->tiled_rectangle, rsp); fprintf(rsp,","); fprintf(rsp, "\"floatingRectangle\":"); query_rectangle(c->floating_rectangle, rsp); fprintf(rsp, "}"); } } void query_rectangle(xcb_rectangle_t r, FILE *rsp) { fprintf(rsp, "{\"x\":%i,\"y\":%i,\"width\":%u,\"height\":%u}", r.x, r.y, r.width, r.height); } void query_constraints(constraints_t c, FILE *rsp) { fprintf(rsp, "{\"min_width\":%u,\"min_height\":%u}", c.min_width, c.min_height); } void query_padding(padding_t p, FILE *rsp) { fprintf(rsp, "{\"top\":%i,\"right\":%i,\"bottom\":%i,\"left\":%i}", p.top, p.right, p.bottom, p.left); } void query_history(FILE *rsp) { fprintf(rsp, "["); for (history_t *h = history_head; h != NULL; h = h->next) { query_coordinates(&h->loc, rsp); if (h->next != NULL) { fprintf(rsp, ","); } } fprintf(rsp, "]"); } void query_coordinates(coordinates_t *loc, FILE *rsp) { fprintf(rsp, "{\"monitorId\":%u,\"desktopId\":%u,\"nodeId\":%u}", loc->monitor->id, loc->desktop->id, loc->node!=NULL?loc->node->id:0); } void query_stack(FILE *rsp) { fprintf(rsp, "["); for (stacking_list_t *s = stack_head; s != NULL; s = s->next) { fprintf(rsp, "%u", s->node->id); if (s->next != NULL) { fprintf(rsp, ","); } } fprintf(rsp, "]"); } void query_subscribers(FILE *rsp) { fprintf(rsp, "["); for (subscriber_list_t *s = subscribe_head; s != NULL; s = s->next) { fprintf(rsp, "{\"fileDescriptor\": %i", fileno(s->stream)); if (s->fifo_path != NULL) { fprintf(rsp, ",\"fifoPath\":\"%s\"", s->fifo_path); } fprintf(rsp, ",\"field\":%i,\"count\":%i}", s->field, s->count); if (s->next != NULL) { fprintf(rsp, ","); } } fprintf(rsp, "]"); } int query_node_ids(coordinates_t *mon_ref, coordinates_t *desk_ref, coordinates_t* ref, coordinates_t *trg, monitor_select_t *mon_sel, desktop_select_t *desk_sel, node_select_t *sel, FILE *rsp) { int count = 0; for (monitor_t *m = mon_head; m != NULL; m = m->next) { coordinates_t loc = {m, NULL, NULL}; if ((trg->monitor != NULL && m != trg->monitor) || (mon_sel != NULL && !monitor_matches(&loc, mon_ref, mon_sel))) { continue; } for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { coordinates_t loc = {m, d, NULL}; if ((trg->desktop != NULL && d != trg->desktop) || (desk_sel != NULL && !desktop_matches(&loc, desk_ref, desk_sel))) { continue; } count += query_node_ids_in(d->root, d, m, ref, trg, sel, rsp); } } return count; } int query_node_ids_in(node_t *n, desktop_t *d, monitor_t *m, coordinates_t *ref, coordinates_t *trg, node_select_t *sel, FILE *rsp) { int count = 0; if (n == NULL) { return 0; } else { coordinates_t loc = {m, d, n}; if ((trg->node == NULL || n == trg->node) && (sel == NULL || node_matches(&loc, ref, sel))) { fprintf(rsp, "0x%08X\n", n->id); count++; } count += query_node_ids_in(n->first_child, d, m, ref, trg, sel, rsp); count += query_node_ids_in(n->second_child, d, m, ref, trg, sel, rsp); } return count; } int query_desktop_ids(coordinates_t* mon_ref, coordinates_t *ref, coordinates_t *trg, monitor_select_t *mon_sel, desktop_select_t *sel, desktop_printer_t printer, FILE *rsp) { int count = 0; for (monitor_t *m = mon_head; m != NULL; m = m->next) { coordinates_t loc = {m, NULL, NULL}; if ((trg->monitor != NULL && m != trg->monitor) || (mon_sel != NULL && !monitor_matches(&loc, mon_ref, mon_sel))) { continue; } for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { coordinates_t loc = {m, d, NULL}; if ((trg->desktop != NULL && d != trg->desktop) || (sel != NULL && !desktop_matches(&loc, ref, sel))) { continue; } printer(d, rsp); count++; } } return count; } int query_monitor_ids(coordinates_t *ref, coordinates_t *trg, monitor_select_t *sel, monitor_printer_t printer, FILE *rsp) { int count = 0; for (monitor_t *m = mon_head; m != NULL; m = m->next) { coordinates_t loc = {m, NULL, NULL}; if ((trg->monitor != NULL && m != trg->monitor) || (sel != NULL && !monitor_matches(&loc, ref, sel))) { continue; } printer(m, rsp); count++; } return count; } void fprint_monitor_id(monitor_t *m, FILE *rsp) { fprintf(rsp, "0x%08X\n", m->id); } void fprint_monitor_name(monitor_t *m, FILE *rsp) { fprintf(rsp, "%s\n", m->name); } void fprint_desktop_id(desktop_t *d, FILE *rsp) { fprintf(rsp, "0x%08X\n", d->id); } void fprint_desktop_name(desktop_t *d, FILE *rsp) { fprintf(rsp, "%s\n", d->name); } void print_ignore_request(state_transition_t st, FILE *rsp) { if (st == 0) { fprintf(rsp, "none"); } else { unsigned int cnt = 0; if (st & STATE_TRANSITION_ENTER) { fprintf(rsp, "enter"); cnt++; } if (st & STATE_TRANSITION_EXIT) { fprintf(rsp, "%sexit", cnt > 0 ? "," : ""); } } } void print_modifier_mask(uint16_t m, FILE *rsp) { switch (m) { case XCB_MOD_MASK_SHIFT: fprintf(rsp, "shift"); break; case XCB_MOD_MASK_CONTROL: fprintf(rsp, "control"); break; case XCB_MOD_MASK_LOCK: fprintf(rsp, "lock"); break; case XCB_MOD_MASK_1: fprintf(rsp, "mod1"); break; case XCB_MOD_MASK_2: fprintf(rsp, "mod2"); break; case XCB_MOD_MASK_3: fprintf(rsp, "mod3"); break; case XCB_MOD_MASK_4: fprintf(rsp, "mod4"); break; case XCB_MOD_MASK_5: fprintf(rsp, "mod5"); break; } } void print_button_index(int8_t b, FILE *rsp) { switch (b) { case XCB_BUTTON_INDEX_ANY: fprintf(rsp, "any"); break; case XCB_BUTTON_INDEX_1: fprintf(rsp, "button1"); break; case XCB_BUTTON_INDEX_2: fprintf(rsp, "button2"); break; case XCB_BUTTON_INDEX_3: fprintf(rsp, "button3"); break; case -1: fprintf(rsp, "none"); break; } } void print_pointer_action(pointer_action_t a, FILE *rsp) { switch (a) { case ACTION_MOVE: fprintf(rsp, "move"); break; case ACTION_RESIZE_SIDE: fprintf(rsp, "resize_side"); break; case ACTION_RESIZE_CORNER: fprintf(rsp, "resize_corner"); break; case ACTION_FOCUS: fprintf(rsp, "focus"); break; case ACTION_NONE: fprintf(rsp, "none"); break; } } void resolve_rule_consequence(rule_consequence_t *csq) { coordinates_t ref = {mon, mon->desk, mon->desk->focus}; coordinates_t dst = {NULL, NULL, NULL}; monitor_t *monitor = monitor_from_desc(csq->monitor_desc, &ref, &dst) != SELECTOR_OK ? NULL : dst.monitor; desktop_t *desktop = desktop_from_desc(csq->desktop_desc, &ref, &dst) != SELECTOR_OK ? NULL : dst.desktop; node_t *node = node_from_desc(csq->node_desc, &ref, &dst) != SELECTOR_OK ? NULL : dst.node; #define PRINT_OBJECT_ID(name) \ if (name == NULL) { \ csq->name##_desc[0] = '\0'; \ } else { \ snprintf(csq->name##_desc, 11, "0x%08X", name->id); \ } PRINT_OBJECT_ID(monitor) PRINT_OBJECT_ID(desktop) PRINT_OBJECT_ID(node) #undef PRINT_OBJECT_ID } void print_rule_consequence(char **buf, rule_consequence_t *csq) { char *rect_buf = NULL; print_rectangle(&rect_buf, csq->rect); if (rect_buf == NULL) { rect_buf = malloc(1); *rect_buf = '\0'; } asprintf(buf, "monitor=%s desktop=%s node=%s state=%s layer=%s honor_size_hints=%s split_dir=%s split_ratio=%lf hidden=%s sticky=%s private=%s locked=%s marked=%s center=%s follow=%s manage=%s focus=%s border=%s rectangle=%s", csq->monitor_desc, csq->desktop_desc, csq->node_desc, csq->state == NULL ? "" : STATE_STR(*csq->state), csq->layer == NULL ? "" : LAYER_STR(*csq->layer), csq->honor_size_hints == HONOR_SIZE_HINTS_DEFAULT ? "" : HSH_MODE_STR(csq->honor_size_hints), csq->split_dir == NULL ? "" : SPLIT_DIR_STR(*csq->split_dir), csq->split_ratio, ON_OFF_STR(csq->hidden), ON_OFF_STR(csq->sticky), ON_OFF_STR(csq->private), ON_OFF_STR(csq->locked), ON_OFF_STR(csq->marked), ON_OFF_STR(csq->center), ON_OFF_STR(csq->follow), ON_OFF_STR(csq->manage), ON_OFF_STR(csq->focus), ON_OFF_STR(csq->border), rect_buf); free(rect_buf); } void print_rectangle(char **buf, xcb_rectangle_t *rect) { if (rect != NULL) { asprintf(buf, "%hux%hu+%hi+%hi", rect->width, rect->height, rect->x, rect->y); } } node_select_t make_node_select(void) { node_select_t sel = { .automatic = OPTION_NONE, .focused = OPTION_NONE, .active = OPTION_NONE, .local = OPTION_NONE, .leaf = OPTION_NONE, .window = OPTION_NONE, .tiled = OPTION_NONE, .pseudo_tiled = OPTION_NONE, .floating = OPTION_NONE, .fullscreen = OPTION_NONE, .hidden = OPTION_NONE, .sticky = OPTION_NONE, .private = OPTION_NONE, .locked = OPTION_NONE, .marked = OPTION_NONE, .urgent = OPTION_NONE, .same_class = OPTION_NONE, .descendant_of = OPTION_NONE, .ancestor_of = OPTION_NONE, .below = OPTION_NONE, .normal = OPTION_NONE, .above = OPTION_NONE, .horizontal = OPTION_NONE, .vertical = OPTION_NONE }; return sel; } desktop_select_t make_desktop_select(void) { desktop_select_t sel = { .occupied = OPTION_NONE, .focused = OPTION_NONE, .active = OPTION_NONE, .urgent = OPTION_NONE, .local = OPTION_NONE, .tiled = OPTION_NONE, .monocle = OPTION_NONE, .user_tiled = OPTION_NONE, .user_monocle = OPTION_NONE }; return sel; } monitor_select_t make_monitor_select(void) { monitor_select_t sel = { .occupied = OPTION_NONE, .focused = OPTION_NONE }; return sel; } int node_from_desc(char *desc, coordinates_t *ref, coordinates_t *dst) { dst->node = NULL; coordinates_t ref_copy = *ref; ref = &ref_copy; char *desc_copy = copy_string(desc, strlen(desc)); desc = desc_copy; char *hash = strrchr(desc, '#'); char *path = strrchr(desc, '@'); char *colon = strrchr(desc, ':'); /* Adjust or discard hashes inside a DESKTOP_SEL, e.g. `newest#@prev#older:/1/2` */ if (hash != NULL && colon != NULL && path != NULL && path < hash && hash < colon) { if (path > desc && *(path - 1) == '#') { hash = path - 1; } else { hash = NULL; } } if (hash != NULL) { *hash = '\0'; int ret; coordinates_t tmp = {mon, mon->desk, mon->desk->focus}; if ((ret = node_from_desc(desc, &tmp, ref)) == SELECTOR_OK) { desc = hash + 1; } else { free(desc_copy); return ret; } } /* Discard colons within references, e.g. `@next.occupied:/#any.descendant_of.window` */ if (colon != NULL && hash != NULL && colon < hash) { colon = NULL; } node_select_t sel = make_node_select(); if (!parse_node_modifiers(colon != NULL ? colon : desc, &sel)) { free(desc_copy); return SELECTOR_BAD_MODIFIERS; } direction_t dir; cycle_dir_t cyc; history_dir_t hdi; if (parse_direction(desc, &dir)) { find_nearest_neighbor(ref, dst, dir, &sel); } else if (parse_cycle_direction(desc, &cyc)) { find_closest_node(ref, dst, cyc, &sel); } else if (parse_history_direction(desc, &hdi)) { history_find_node(hdi, ref, dst, &sel); } else if (streq("any", desc)) { find_any_node(ref, dst, &sel); } else if (streq("first_ancestor", desc)) { find_first_ancestor(ref, dst, &sel); } else if (streq("last", desc)) { history_find_node(HISTORY_OLDER, ref, dst, &sel); } else if (streq("newest", desc)) { history_find_newest_node(ref, dst, &sel); } else if (streq("biggest", desc)) { find_by_area(AREA_BIGGEST, ref, dst, &sel); } else if (streq("smallest", desc)) { find_by_area(AREA_SMALLEST, ref, dst, &sel); } else if (streq("pointed", desc)) { xcb_window_t win = XCB_NONE; query_pointer(&win, NULL); if (locate_leaf(win, dst) && node_matches(dst, ref, &sel)) { return SELECTOR_OK; } else { return SELECTOR_INVALID; } } else if (streq("focused", desc)) { coordinates_t loc = {mon, mon->desk, mon->desk->focus}; if (node_matches(&loc, ref, &sel)) { *dst = loc; } } else if (*desc == '@') { desc++; *dst = *ref; if (colon != NULL) { *colon = '\0'; int ret; if ((ret = desktop_from_desc(desc, ref, dst)) == SELECTOR_OK) { dst->node = dst->desktop->focus; desc = colon + 1; } else { free(desc_copy); return ret; } } if (*desc == '/') { dst->node = dst->desktop->root; } char *move = strtok(desc, PTH_TOK); while (move != NULL && dst->node != NULL) { if (streq("first", move) || streq("1", move)) { dst->node = dst->node->first_child; } else if (streq("second", move) || streq("2", move)) { dst->node = dst->node->second_child; } else if (streq("parent", move)) { dst->node = dst->node->parent; } else if (streq("brother", move)) { dst->node = brother_tree(dst->node); } else { direction_t dir; if (parse_direction(move, &dir)) { dst->node = find_fence(dst->node, dir); } else { free(desc_copy); return SELECTOR_BAD_DESCRIPTOR; } } move = strtok(NULL, PTH_TOK); } free(desc_copy); if (dst->node != NULL) { if (node_matches(dst, ref, &sel)) { return SELECTOR_OK; } else { return SELECTOR_INVALID; } } else if (dst->desktop->root != NULL) { return SELECTOR_INVALID; } return SELECTOR_OK; } else { uint32_t id; if (parse_id(desc, &id)) { free(desc_copy); if (find_by_id(id, dst) && node_matches(dst, ref, &sel)) { return SELECTOR_OK; } else { return SELECTOR_INVALID; } } else { free(desc_copy); return SELECTOR_BAD_DESCRIPTOR; } } free(desc_copy); if (dst->node == NULL) { return SELECTOR_INVALID; } return SELECTOR_OK; } int desktop_from_desc(char *desc, coordinates_t *ref, coordinates_t *dst) { dst->desktop = NULL; if (*desc == '%') { locate_desktop(desc + 1, dst); goto end; } coordinates_t ref_copy = *ref; ref = &ref_copy; char *desc_copy = copy_string(desc, strlen(desc)); desc = desc_copy; char *hash = strrchr(desc, '#'); char *colon = strrchr(desc, ':'); /* Discard hashes inside a MONITOR_SEL, e.g. `primary#next:focused` */ if (hash != NULL && colon != NULL && hash < colon) { hash = NULL; } if (hash != NULL) { *hash = '\0'; int ret; coordinates_t tmp = {mon, mon->desk, NULL}; if ((ret = desktop_from_desc(desc, &tmp, ref)) == SELECTOR_OK) { desc = hash + 1; } else { free(desc_copy); return ret; } } /* Discard colons within references, e.g. `DisplayPort-1:focused#next.local` */ if (colon != NULL && hash != NULL && colon < hash) { colon = NULL; } desktop_select_t sel = make_desktop_select(); if (!parse_desktop_modifiers(colon != NULL ? colon : desc, &sel)) { free(desc_copy); return SELECTOR_BAD_MODIFIERS; } cycle_dir_t cyc; history_dir_t hdi; uint16_t idx; uint32_t id; if (parse_cycle_direction(desc, &cyc)) { find_closest_desktop(ref, dst, cyc, &sel); } else if (parse_history_direction(desc, &hdi)) { history_find_desktop(hdi, ref, dst, &sel); } else if (streq("any", desc)) { find_any_desktop(ref, dst, &sel); } else if (streq("last", desc)) { history_find_desktop(HISTORY_OLDER, ref, dst, &sel); } else if (streq("newest", desc)) { history_find_newest_desktop(ref, dst, &sel); } else if (streq("focused", desc)) { coordinates_t loc = {mon, mon->desk, NULL}; if (desktop_matches(&loc, ref, &sel)) { *dst = loc; } } else if (colon != NULL) { *colon = '\0'; int ret; if ((ret = monitor_from_desc(desc, ref, dst)) == SELECTOR_OK) { if (streq("focused", colon + 1)) { coordinates_t loc = {dst->monitor, dst->monitor->desk, NULL}; if (desktop_matches(&loc, ref, &sel)) { *dst = loc; } } else if (parse_index(colon + 1, &idx)) { free(desc_copy); if (desktop_from_index(idx, dst, dst->monitor) && desktop_matches(dst, ref, &sel)) { return SELECTOR_OK; } else { return SELECTOR_INVALID; } } else { free(desc_copy); return SELECTOR_BAD_DESCRIPTOR; } } else { free(desc_copy); return ret; } } else if (parse_index(desc, &idx) && desktop_from_index(idx, dst, NULL)) { free(desc_copy); if (desktop_matches(dst, ref, &sel)) { return SELECTOR_OK; } else { return SELECTOR_INVALID; } } else if (parse_id(desc, &id) && desktop_from_id(id, dst, NULL)) { free(desc_copy); if (desktop_matches(dst, ref, &sel)) { return SELECTOR_OK; } else { return SELECTOR_INVALID; } } else { int hits = 0; if (desktop_from_name(desc, ref, dst, &sel, &hits)) { free(desc_copy); return SELECTOR_OK; } else { free(desc_copy); if (hits > 0) { return SELECTOR_INVALID; } else { return SELECTOR_BAD_DESCRIPTOR; } } } free(desc_copy); end: if (dst->desktop == NULL) { return SELECTOR_INVALID; } return SELECTOR_OK; } int monitor_from_desc(char *desc, coordinates_t *ref, coordinates_t *dst) { dst->monitor = NULL; if (*desc == '%') { locate_monitor(desc + 1, dst); goto end; } coordinates_t ref_copy = *ref; ref = &ref_copy; char *desc_copy = copy_string(desc, strlen(desc)); desc = desc_copy; char *hash = strrchr(desc, '#'); if (hash != NULL) { *hash = '\0'; int ret; coordinates_t tmp = {mon, NULL, NULL}; if ((ret = monitor_from_desc(desc, &tmp, ref)) == SELECTOR_OK) { desc = hash + 1; } else { free(desc_copy); return ret; } } monitor_select_t sel = make_monitor_select(); if (!parse_monitor_modifiers(desc, &sel)) { free(desc_copy); return SELECTOR_BAD_MODIFIERS; } direction_t dir; cycle_dir_t cyc; history_dir_t hdi; uint16_t idx; uint32_t id; if (parse_direction(desc, &dir)) { dst->monitor = nearest_monitor(ref->monitor, dir, &sel); } else if (parse_cycle_direction(desc, &cyc)) { dst->monitor = closest_monitor(ref->monitor, cyc, &sel); } else if (parse_history_direction(desc, &hdi)) { history_find_monitor(hdi, ref, dst, &sel); } else if (streq("any", desc)) { find_any_monitor(ref, dst, &sel); } else if (streq("last", desc)) { history_find_monitor(HISTORY_OLDER, ref, dst, &sel); } else if (streq("newest", desc)) { history_find_newest_monitor(ref, dst, &sel); } else if (streq("primary", desc)) { if (pri_mon != NULL) { coordinates_t loc = {pri_mon, NULL, NULL}; if (monitor_matches(&loc, ref, &sel)) { dst->monitor = pri_mon; } } } else if (streq("focused", desc)) { coordinates_t loc = {mon, NULL, NULL}; if (monitor_matches(&loc, ref, &sel)) { dst->monitor = mon; } } else if (streq("pointed", desc)) { xcb_point_t pointer; query_pointer(NULL, &pointer); for (monitor_t *m = mon_head; m != NULL; m = m->next) { if (is_inside(pointer, m->rectangle)) { dst->monitor = m; break; } } } else if (parse_index(desc, &idx) && monitor_from_index(idx, dst)) { free(desc_copy); if (monitor_matches(dst, ref, &sel)) { return SELECTOR_OK; } else { return SELECTOR_INVALID; } } else if (parse_id(desc, &id) && monitor_from_id(id, dst)) { free(desc_copy); if (monitor_matches(dst, ref, &sel)) { return SELECTOR_OK; } else { return SELECTOR_INVALID; } } else { if (locate_monitor(desc, dst)) { free(desc_copy); if (monitor_matches(dst, ref, &sel)) { return SELECTOR_OK; } else { return SELECTOR_INVALID; } } else { free(desc_copy); return SELECTOR_BAD_DESCRIPTOR; } } free(desc_copy); end: if (dst->monitor == NULL) { return SELECTOR_INVALID; } return SELECTOR_OK; } bool locate_leaf(xcb_window_t win, coordinates_t *loc) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) { if (n->id == win) { loc->monitor = m; loc->desktop = d; loc->node = n; return true; } } } } return false; } bool locate_window(xcb_window_t win, coordinates_t *loc) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) { if (n->client == NULL) { continue; } if (n->id == win) { loc->monitor = m; loc->desktop = d; loc->node = n; return true; } } } } return false; } bool locate_desktop(char *name, coordinates_t *loc) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { if (streq(d->name, name)) { loc->monitor = m; loc->desktop = d; return true; } } } return false; } bool locate_monitor(char *name, coordinates_t *loc) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { if (streq(m->name, name)) { loc->monitor = m; return true; } } return false; } bool desktop_from_id(uint32_t id, coordinates_t *loc, monitor_t *mm) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { if (mm != NULL && m != mm) { continue; } for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { if (d->id == id) { loc->monitor = m; loc->desktop = d; loc->node = NULL; return true; } } } return false; } bool desktop_from_name(char *name, coordinates_t *ref, coordinates_t *dst, desktop_select_t *sel, int *hits) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { if (streq(d->name, name)) { if (hits != NULL) { (*hits)++; } coordinates_t loc = {m, d, NULL}; if (desktop_matches(&loc, ref, sel)) { dst->monitor = m; dst->desktop = d; return true; } } } } return false; } bool desktop_from_index(uint16_t idx, coordinates_t *loc, monitor_t *mm) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { if (mm != NULL && m != mm) { continue; } for (desktop_t *d = m->desk_head; d != NULL; d = d->next, idx--) { if (idx == 1) { loc->monitor = m; loc->desktop = d; loc->node = NULL; return true; } } } return false; } bool monitor_from_id(uint32_t id, coordinates_t *loc) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { if (m->id == id) { loc->monitor = m; loc->desktop = NULL; loc->node = NULL; return true; } } return false; } bool monitor_from_index(int idx, coordinates_t *loc) { for (monitor_t *m = mon_head; m != NULL; m = m->next, idx--) { if (idx == 1) { loc->monitor = m; loc->desktop = NULL; loc->node = NULL; return true; } } return false; } bool node_matches(coordinates_t *loc, coordinates_t *ref, node_select_t *sel) { if (loc->node == NULL) { return false; } if (sel->focused != OPTION_NONE && loc->node != mon->desk->focus ? sel->focused == OPTION_TRUE : sel->focused == OPTION_FALSE) { return false; } if (sel->active != OPTION_NONE && loc->node != loc->desktop->focus ? sel->active == OPTION_TRUE : sel->active == OPTION_FALSE) { return false; } if (sel->automatic != OPTION_NONE && loc->node->presel != NULL ? sel->automatic == OPTION_TRUE : sel->automatic == OPTION_FALSE) { return false; } if (sel->local != OPTION_NONE && loc->desktop != ref->desktop ? sel->local == OPTION_TRUE : sel->local == OPTION_FALSE) { return false; } if (sel->active != OPTION_NONE && loc->desktop != loc->monitor->desk ? sel->active == OPTION_TRUE : sel->active == OPTION_FALSE) { return false; } if (sel->leaf != OPTION_NONE && !is_leaf(loc->node) ? sel->leaf == OPTION_TRUE : sel->leaf == OPTION_FALSE) { return false; } if (sel->window != OPTION_NONE && loc->node->client == NULL ? sel->window == OPTION_TRUE : sel->window == OPTION_FALSE) { return false; } #define NFLAG(p) \ if (sel->p != OPTION_NONE && \ !loc->node->p \ ? sel->p == OPTION_TRUE \ : sel->p == OPTION_FALSE) { \ return false; \ } NFLAG(hidden) NFLAG(sticky) NFLAG(private) NFLAG(locked) NFLAG(marked) #undef NFLAG #define NSPLIT(p, e) \ if (sel->p != OPTION_NONE && \ loc->node->split_type != e \ ? sel->p == OPTION_TRUE \ : sel->p == OPTION_FALSE) { \ return false; \ } NSPLIT(horizontal, TYPE_HORIZONTAL) NSPLIT(vertical, TYPE_VERTICAL) #undef NSPLIT if (sel->descendant_of != OPTION_NONE && !is_descendant(loc->node, ref->node) ? sel->descendant_of == OPTION_TRUE : sel->descendant_of == OPTION_FALSE) { return false; } if (sel->ancestor_of != OPTION_NONE && !is_descendant(ref->node, loc->node) ? sel->ancestor_of == OPTION_TRUE : sel->ancestor_of == OPTION_FALSE) { return false; } if (loc->node->client == NULL) { if (sel->same_class == OPTION_TRUE || sel->tiled == OPTION_TRUE || sel->pseudo_tiled == OPTION_TRUE || sel->floating == OPTION_TRUE || sel->fullscreen == OPTION_TRUE || sel->below == OPTION_TRUE || sel->normal == OPTION_TRUE || sel->above == OPTION_TRUE || sel->urgent == OPTION_TRUE) { return false; } return true; } if (ref->node != NULL && ref->node->client != NULL && sel->same_class != OPTION_NONE && streq(loc->node->client->class_name, ref->node->client->class_name) ? sel->same_class == OPTION_FALSE : sel->same_class == OPTION_TRUE) { return false; } #define WSTATE(p, e) \ if (sel->p != OPTION_NONE && \ loc->node->client->state != e \ ? sel->p == OPTION_TRUE \ : sel->p == OPTION_FALSE) { \ return false; \ } WSTATE(tiled, STATE_TILED) WSTATE(pseudo_tiled, STATE_PSEUDO_TILED) WSTATE(floating, STATE_FLOATING) WSTATE(fullscreen, STATE_FULLSCREEN) #undef WSTATE #define WLAYER(p, e) \ if (sel->p != OPTION_NONE && \ loc->node->client->layer != e \ ? sel->p == OPTION_TRUE \ : sel->p == OPTION_FALSE) { \ return false; \ } WLAYER(below, LAYER_BELOW) WLAYER(normal, LAYER_NORMAL) WLAYER(above, LAYER_ABOVE) #undef WLAYER #define WFLAG(p) \ if (sel->p != OPTION_NONE && \ !loc->node->client->p \ ? sel->p == OPTION_TRUE \ : sel->p == OPTION_FALSE) { \ return false; \ } WFLAG(urgent) #undef WFLAG return true; } bool desktop_matches(coordinates_t *loc, coordinates_t *ref, desktop_select_t *sel) { if (sel->occupied != OPTION_NONE && loc->desktop->root == NULL ? sel->occupied == OPTION_TRUE : sel->occupied == OPTION_FALSE) { return false; } if (sel->focused != OPTION_NONE && loc->desktop != mon->desk ? sel->focused == OPTION_TRUE : sel->focused == OPTION_FALSE) { return false; } if (sel->active != OPTION_NONE && loc->desktop != loc->monitor->desk ? sel->active == OPTION_TRUE : sel->active == OPTION_FALSE) { return false; } if (sel->urgent != OPTION_NONE && !is_urgent(loc->desktop) ? sel->urgent == OPTION_TRUE : sel->urgent == OPTION_FALSE) { return false; } if (sel->local != OPTION_NONE && ref->monitor != loc->monitor ? sel->local == OPTION_TRUE : sel->local == OPTION_FALSE) { return false; } #define DLAYOUT(p, e) \ if (sel->p != OPTION_NONE && \ loc->desktop->layout != e \ ? sel->p == OPTION_TRUE \ : sel->p == OPTION_FALSE) { \ return false; \ } DLAYOUT(tiled, LAYOUT_TILED) DLAYOUT(monocle, LAYOUT_MONOCLE) #undef DLAYOUT #define DUSERLAYOUT(p, e) \ if (sel->p != OPTION_NONE && \ loc->desktop->user_layout != e \ ? sel->p == OPTION_TRUE \ : sel->p == OPTION_FALSE) { \ return false; \ } DUSERLAYOUT(user_tiled, LAYOUT_TILED) DUSERLAYOUT(user_monocle, LAYOUT_MONOCLE) #undef DUSERLAYOUT return true; } bool monitor_matches(coordinates_t *loc, __attribute__((unused)) coordinates_t *ref, monitor_select_t *sel) { if (sel->occupied != OPTION_NONE && loc->monitor->desk->root == NULL ? sel->occupied == OPTION_TRUE : sel->occupied == OPTION_FALSE) { return false; } if (sel->focused != OPTION_NONE && loc->monitor != mon ? sel->focused == OPTION_TRUE : sel->focused == OPTION_FALSE) { return false; } return true; } ================================================ FILE: src/query.h ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef BSPWM_QUERY_H #define BSPWM_QUERY_H #define PTH_TOK "/" typedef enum { DOMAIN_TREE, DOMAIN_MONITOR, DOMAIN_DESKTOP, DOMAIN_NODE } domain_t; enum { SELECTOR_OK, SELECTOR_INVALID, SELECTOR_BAD_MODIFIERS, SELECTOR_BAD_DESCRIPTOR }; typedef void (*monitor_printer_t)(monitor_t *m, FILE *rsp); typedef void (*desktop_printer_t)(desktop_t *m, FILE *rsp); void query_state(FILE *rsp); void query_monitor(monitor_t *m, FILE *rsp); void query_desktop(desktop_t *d, FILE *rsp); void query_node(node_t *n, FILE *rsp); void query_presel(presel_t *p, FILE *rsp); void query_client(client_t *c, FILE *rsp); void query_rectangle(xcb_rectangle_t r, FILE *rsp); void query_constraints(constraints_t c, FILE *rsp); void query_padding(padding_t p, FILE *rsp); void query_history(FILE *rsp); void query_coordinates(coordinates_t *loc, FILE *rsp); void query_stack(FILE *rsp); void query_subscribers(FILE *rsp); int query_node_ids(coordinates_t *mon_ref, coordinates_t *desk_ref, coordinates_t* ref, coordinates_t *trg, monitor_select_t *mon_sel, desktop_select_t *desk_sel, node_select_t *sel, FILE *rsp); int query_node_ids_in(node_t *n, desktop_t *d, monitor_t *m, coordinates_t *ref, coordinates_t *trg, node_select_t *sel, FILE *rsp); int query_desktop_ids(coordinates_t* mon_ref, coordinates_t *ref, coordinates_t *trg, monitor_select_t *mon_sel, desktop_select_t *sel, desktop_printer_t printer, FILE *rsp); int query_monitor_ids(coordinates_t *ref, coordinates_t *trg, monitor_select_t *sel, monitor_printer_t printer, FILE *rsp); void fprint_monitor_id(monitor_t *m, FILE *rsp); void fprint_monitor_name(monitor_t *m, FILE *rsp); void fprint_desktop_id(desktop_t *d, FILE *rsp); void fprint_desktop_name(desktop_t *d, FILE *rsp); void print_ignore_request(state_transition_t st, FILE *rsp); void print_modifier_mask(uint16_t m, FILE *rsp); void print_button_index(int8_t b, FILE *rsp); void print_pointer_action(pointer_action_t a, FILE *rsp); void resolve_rule_consequence(rule_consequence_t *csq); void print_rule_consequence(char **buf, rule_consequence_t *csq); void print_rectangle(char **buf, xcb_rectangle_t *rect); node_select_t make_node_select(void); desktop_select_t make_desktop_select(void); monitor_select_t make_monitor_select(void); int node_from_desc(char *desc, coordinates_t *ref, coordinates_t *dst); int desktop_from_desc(char *desc, coordinates_t *ref, coordinates_t *dst); int monitor_from_desc(char *desc, coordinates_t *ref, coordinates_t *dst); bool locate_leaf(xcb_window_t win, coordinates_t *loc); bool locate_window(xcb_window_t win, coordinates_t *loc); bool locate_desktop(char *name, coordinates_t *loc); bool locate_monitor(char *name, coordinates_t *loc); bool desktop_from_id(uint32_t id, coordinates_t *loc, monitor_t *mm); bool desktop_from_name(char *name, coordinates_t *ref, coordinates_t *dst, desktop_select_t *sel, int *hits); bool desktop_from_index(uint16_t idx, coordinates_t *loc, monitor_t *mm); bool monitor_from_id(uint32_t id, coordinates_t *loc); bool monitor_from_index(int idx, coordinates_t *loc); bool node_matches(coordinates_t *loc, coordinates_t *ref, node_select_t *sel); bool desktop_matches(coordinates_t *loc, coordinates_t *ref, desktop_select_t *sel); bool monitor_matches(coordinates_t *loc, __attribute__((unused)) coordinates_t *ref, monitor_select_t *sel); #endif ================================================ FILE: src/restore.c ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include "bspwm.h" #include "desktop.h" #include "ewmh.h" #include "history.h" #include "pointer.h" #include "monitor.h" #include "query.h" #include "stack.h" #include "tree.h" #include "settings.h" #include "subscribe.h" #include "restore.h" #include "window.h" #include "parse.h" bool restore_state(const char *file_path) { size_t jslen; char *json = read_string(file_path, &jslen); if (json == NULL) { return false; } int nbtok = 256; jsmn_parser parser; jsmntok_t *tokens = malloc(nbtok * sizeof(jsmntok_t)); if (tokens == NULL) { perror("Restore tree: malloc"); free(json); return false; } jsmn_init(&parser); int ret; while ((ret = jsmn_parse(&parser, json, jslen, tokens, nbtok)) == JSMN_ERROR_NOMEM) { nbtok *= 2; jsmntok_t *rtokens = realloc(tokens, nbtok * sizeof(jsmntok_t)); if (rtokens == NULL) { perror("Restore tree: realloc"); free(tokens); free(json); return false; } else { tokens = rtokens; } } if (ret < 0) { warn("Restore tree: jsmn_parse: "); switch (ret) { case JSMN_ERROR_NOMEM: warn("not enough memory.\n"); break; case JSMN_ERROR_INVAL: warn("found invalid character inside JSON string.\n"); break; case JSMN_ERROR_PART: warn("not a full JSON packet.\n"); break; default: warn("unknown error.\n"); break; } free(tokens); free(json); return false; } int num = tokens[0].size; if (num < 1) { free(tokens); free(json); return false; } mon = NULL; while (mon_head != NULL) { remove_monitor(mon_head); } jsmntok_t *t = tokens + 1; uint32_t focused_monitor_id = 0, primary_monitor_id = 0; jsmntok_t *focus_history_token = NULL, *stacking_list_token = NULL; for (int i = 0; i < num; i++) { if (keyeq("focusedMonitorId", t, json)) { t++; sscanf(json + t->start, "%u", &focused_monitor_id); } else if (keyeq("primaryMonitorId", t, json)) { t++; sscanf(json + t->start, "%u", &primary_monitor_id); } else if (keyeq("clientsCount", t, json)) { t++; sscanf(json + t->start, "%u", &clients_count); } else if (keyeq("monitors", t, json)) { t++; int s = t->size; t++; for (int j = 0; j < s; j++) { monitor_t *m = restore_monitor(&t, json); if (m->desk == NULL) { add_desktop(m, make_desktop(NULL, XCB_NONE)); } add_monitor(m); } continue; } else if (keyeq("focusHistory", t, json)) { t++; if (mon == NULL) { focus_history_token = t; } restore_history(&t, json); continue; } else if (keyeq("stackingList", t, json)) { t++; if (mon == NULL) { stacking_list_token = t; } restore_stack(&t, json); continue; } else if (keyeq("eventSubscribers", t, json)) { t++; restore_subscribers(&t, json); continue; } t++; } if (focused_monitor_id != 0) { coordinates_t loc; if (monitor_from_id(focused_monitor_id, &loc)) { mon = loc.monitor; } } if (primary_monitor_id != 0) { coordinates_t loc; if (monitor_from_id(primary_monitor_id, &loc)) { pri_mon = loc.monitor; } } if (focus_history_token != NULL) { restore_history(&focus_history_token, json); } if (stacking_list_token != NULL) { restore_stack(&stacking_list_token, json); } for (monitor_t *m = mon_head; m != NULL; m = m->next) { m->id = xcb_generate_id(dpy); for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { d->id = xcb_generate_id(dpy); regenerate_ids_in(d->root); refresh_presel_feedbacks(m, d, d->root); restack_presel_feedbacks(d); for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) { if (n->client == NULL) { continue; } initialize_client(n); uint32_t values[] = {CLIENT_EVENT_MASK | (focus_follows_pointer ? XCB_EVENT_MASK_ENTER_WINDOW : 0)}; xcb_change_window_attributes(dpy, n->id, XCB_CW_EVENT_MASK, values); window_grab_buttons(n->id); } } } ewmh_update_number_of_desktops(); ewmh_update_desktop_names(); ewmh_update_desktop_viewport(); ewmh_update_current_desktop(); ewmh_update_client_list(false); ewmh_update_client_list(true); ewmh_update_active_window(); free(tokens); free(json); return true; } #define RESTORE_INT(k, p) \ } else if (keyeq(#k, *t, json)) { \ (*t)++; \ sscanf(json + (*t)->start, "%i", p); #define RESTORE_UINT(k, p) \ } else if (keyeq(#k, *t, json)) { \ (*t)++; \ sscanf(json + (*t)->start, "%u", p); #define RESTORE_USINT(k, p) \ } else if (keyeq(#k, *t, json)) { \ (*t)++; \ sscanf(json + (*t)->start, "%hu", p); #define RESTORE_DOUBLE(k, p) \ } else if (keyeq(#k, *t, json)) { \ (*t)++; \ sscanf(json + (*t)->start, "%lf", p); #define RESTORE_ANY(k, p, f) \ } else if (keyeq(#k, *t, json)) { \ (*t)++; \ char *val = copy_string(json + (*t)->start, (*t)->end - (*t)->start); \ f(val, p); \ free(val); #define RESTORE_BOOL(k, p) RESTORE_ANY(k, p, parse_bool) monitor_t *restore_monitor(jsmntok_t **t, char *json) { int num = (*t)->size; (*t)++; monitor_t *m = make_monitor(NULL, NULL, UINT32_MAX); uint32_t focused_desktop_id = 0; for (int i = 0; i < num; i++) { if (keyeq("name", *t, json)) { (*t)++; snprintf(m->name, (*t)->end - (*t)->start + 1, "%s", json + (*t)->start); RESTORE_UINT(id, &m->id) RESTORE_UINT(randrId, &m->randr_id) RESTORE_BOOL(wired, &m->wired) RESTORE_UINT(stickyCount, &m->sticky_count) RESTORE_INT(windowGap, &m->window_gap) RESTORE_UINT(borderWidth, &m->border_width) RESTORE_UINT(focusedDesktopId, &focused_desktop_id) } else if (keyeq("padding", *t, json)) { (*t)++; restore_padding(&m->padding, t, json); continue; } else if (keyeq("rectangle", *t, json)) { (*t)++; restore_rectangle(&m->rectangle, t, json); update_root(m, &m->rectangle); continue; } else if (keyeq("desktops", *t, json)) { (*t)++; int s = (*t)->size; (*t)++; for (int j = 0; j < s; j++) { desktop_t *d = restore_desktop(t, json); add_desktop(m, d); } continue; } else { warn("Restore monitor: unknown key: '%.*s'.\n", (*t)->end - (*t)->start, json + (*t)->start); (*t)++; } (*t)++; } if (focused_desktop_id != 0) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { if (d->id == focused_desktop_id) { m->desk = d; break; } } } return m; } desktop_t *restore_desktop(jsmntok_t **t, char *json) { int s = (*t)->size; (*t)++; desktop_t *d = make_desktop(NULL, UINT32_MAX); xcb_window_t focusedNodeId = XCB_NONE; for (int i = 0; i < s; i++) { if (keyeq("name", *t, json)) { (*t)++; snprintf(d->name, (*t)->end - (*t)->start + 1, "%s", json + (*t)->start); RESTORE_UINT(id, &d->id) RESTORE_ANY(layout, &d->layout, parse_layout) RESTORE_ANY(userLayout, &d->user_layout, parse_layout) RESTORE_INT(windowGap, &d->window_gap) RESTORE_UINT(borderWidth, &d->border_width) } else if (keyeq("focusedNodeId", *t, json)) { (*t)++; sscanf(json + (*t)->start, "%u", &focusedNodeId); } else if (keyeq("padding", *t, json)) { (*t)++; restore_padding(&d->padding, t, json); continue; } else if (keyeq("root", *t, json)) { (*t)++; d->root = restore_node(t, json); continue; } else { warn("Restore desktop: unknown key: '%.*s'.\n", (*t)->end - (*t)->start, json + (*t)->start); (*t)++; } (*t)++; } if (focusedNodeId != XCB_NONE) { d->focus = find_by_id_in(d->root, focusedNodeId); } return d; } node_t *restore_node(jsmntok_t **t, char *json) { if ((*t)->type == JSMN_PRIMITIVE) { (*t)++; return NULL; } else { int s = (*t)->size; (*t)++; /* hack to prevent a new ID from being generated */ node_t *n = make_node(UINT32_MAX); for (int i = 0; i < s; i++) { if (keyeq("id", *t, json)) { (*t)++; sscanf(json + (*t)->start, "%u", &n->id); RESTORE_ANY(splitType, &n->split_type, parse_split_type) RESTORE_DOUBLE(splitRatio, &n->split_ratio) RESTORE_BOOL(vacant, &n->vacant) RESTORE_BOOL(hidden, &n->hidden) RESTORE_BOOL(sticky, &n->sticky) RESTORE_BOOL(private, &n->private) RESTORE_BOOL(locked, &n->locked) RESTORE_BOOL(marked, &n->marked) } else if (keyeq("presel", *t, json)) { (*t)++; n->presel = restore_presel(t, json); continue; } else if (keyeq("rectangle", *t, json)) { (*t)++; restore_rectangle(&n->rectangle, t, json); continue; } else if (keyeq("constraints", *t, json)) { (*t)++; restore_constraints(&n->constraints, t, json); continue; } else if (keyeq("firstChild", *t, json)) { (*t)++; node_t *fc = restore_node(t, json); n->first_child = fc; if (fc != NULL) { fc->parent = n; } continue; } else if (keyeq("secondChild", *t, json)) { (*t)++; node_t *sc = restore_node(t, json); n->second_child = sc; if (sc != NULL) { sc->parent = n; } continue; } else if (keyeq("client", *t, json)) { (*t)++; n->client = restore_client(t, json); continue; } else { warn("Restore node: unknown key: '%.*s'.\n", (*t)->end - (*t)->start, json + (*t)->start); (*t)++; } (*t)++; } return n; } } presel_t *restore_presel(jsmntok_t **t, char *json) { if ((*t)->type == JSMN_PRIMITIVE) { (*t)++; return NULL; } else { int s = (*t)->size; (*t)++; presel_t *p = make_presel(); for (int i = 0; i < s; i++) { if (keyeq("splitRatio", *t, json)) { (*t)++; sscanf(json + (*t)->start, "%lf", &p->split_ratio); RESTORE_ANY(splitDir, &p->split_dir, parse_direction) } (*t)++; } return p; } } client_t *restore_client(jsmntok_t **t, char *json) { if ((*t)->type == JSMN_PRIMITIVE) { (*t)++; return NULL; } else { int s = (*t)->size; (*t)++; client_t *c = make_client(); for (int i = 0; i < s; i++) { if (keyeq("className", *t, json)) { (*t)++; snprintf(c->class_name, (*t)->end - (*t)->start + 1, "%s", json + (*t)->start); } else if (keyeq("instanceName", *t, json)) { (*t)++; snprintf(c->instance_name, (*t)->end - (*t)->start + 1, "%s", json + (*t)->start); RESTORE_ANY(state, &c->state, parse_client_state) RESTORE_ANY(lastState, &c->last_state, parse_client_state) RESTORE_ANY(layer, &c->layer, parse_stack_layer) RESTORE_ANY(lastLayer, &c->last_layer, parse_stack_layer) RESTORE_UINT(borderWidth, &c->border_width) RESTORE_BOOL(urgent, &c->urgent) RESTORE_BOOL(shown, &c->shown) } else if (keyeq("tiledRectangle", *t, json)) { (*t)++; restore_rectangle(&c->tiled_rectangle, t, json); continue; } else if (keyeq("floatingRectangle", *t, json)) { (*t)++; restore_rectangle(&c->floating_rectangle, t, json); continue; } else { warn("Restore client: unknown key: '%.*s'.\n", (*t)->end - (*t)->start, json + (*t)->start); (*t)++; } (*t)++; } return c; } } void restore_rectangle(xcb_rectangle_t *r, jsmntok_t **t, char *json) { int s = (*t)->size; (*t)++; for (int i = 0; i < s; i++) { if (keyeq("x", *t, json)) { (*t)++; sscanf(json + (*t)->start, "%hi", &r->x); } else if (keyeq("y", *t, json)) { (*t)++; sscanf(json + (*t)->start, "%hi", &r->y); } else if (keyeq("width", *t, json)) { (*t)++; sscanf(json + (*t)->start, "%hu", &r->width); } else if (keyeq("height", *t, json)) { (*t)++; sscanf(json + (*t)->start, "%hu", &r->height); } (*t)++; } } void restore_constraints(constraints_t *c, jsmntok_t **t, char *json) { int s = (*t)->size; (*t)++; for (int i = 0; i < s; i++) { if (keyeq("min_width", *t, json)) { (*t)++; sscanf(json + (*t)->start, "%hu", &c->min_width); } else if (keyeq("min_height", *t, json)) { (*t)++; sscanf(json + (*t)->start, "%hu", &c->min_height); } (*t)++; } } void restore_padding(padding_t *p, jsmntok_t **t, char *json) { int s = (*t)->size; (*t)++; for (int i = 0; i < s; i++) { if (keyeq("top", *t, json)) { (*t)++; sscanf(json + (*t)->start, "%i", &p->top); } else if (keyeq("right", *t, json)) { (*t)++; sscanf(json + (*t)->start, "%i", &p->right); } else if (keyeq("bottom", *t, json)) { (*t)++; sscanf(json + (*t)->start, "%i", &p->bottom); } else if (keyeq("left", *t, json)) { (*t)++; sscanf(json + (*t)->start, "%i", &p->left); } (*t)++; } } void restore_history(jsmntok_t **t, char *json) { int s = (*t)->size; (*t)++; for (int i = 0; i < s; i++) { coordinates_t loc = {NULL, NULL, NULL}; restore_coordinates(&loc, t, json); if (loc.monitor != NULL && loc.desktop != NULL) { history_add(loc.monitor, loc.desktop, loc.node, true); } } } void restore_subscribers(jsmntok_t **t, char *json) { int s = (*t)->size; (*t)++; for (int i = 0; i < s; i++) { subscriber_list_t *s = make_subscriber(NULL, NULL, 0, 0); restore_subscriber(s, t, json); add_subscriber(s); } } void restore_subscriber(subscriber_list_t *s, jsmntok_t **t, char *json) { int n = (*t)->size; (*t)++; for (int i = 0; i < n; i++) { if (keyeq("fileDescriptor", *t, json)) { (*t)++; int fd; sscanf(json + (*t)->start, "%i", &fd); s->stream = fdopen(fd, "w"); } else if (keyeq("fifoPath", *t, json)) { (*t)++; free(s->fifo_path); s->fifo_path = copy_string(json + (*t)->start, (*t)->end - (*t)->start); RESTORE_INT(field, &s->field) RESTORE_INT(count, &s->count) } (*t)++; } } void restore_coordinates(coordinates_t *loc, jsmntok_t **t, char *json) { int s = (*t)->size; (*t)++; uint32_t id = 0; for (int i = 0; i < s; i++) { if (keyeq("monitorId", *t, json)) { (*t)++; sscanf(json + (*t)->start, "%u", &id); loc->monitor = find_monitor(id); } else if (keyeq("desktopId", *t, json)) { (*t)++; sscanf(json + (*t)->start, "%u", &id); loc->desktop = find_desktop_in(id, loc->monitor); } else if (keyeq("nodeId", *t, json)) { (*t)++; sscanf(json + (*t)->start, "%u", &id); loc->node = find_by_id_in(loc->desktop != NULL ? loc->desktop->root : NULL, id); } (*t)++; } } void restore_stack(jsmntok_t **t, char *json) { int s = (*t)->size; (*t)++; for (int i = 0; i < s; i++) { uint32_t id; sscanf(json + (*t)->start, "%u", &id); coordinates_t loc; if (locate_window(id, &loc)) { stack_insert_after(stack_tail, loc.node); } (*t)++; } } #undef RESTORE_INT #undef RESTORE_UINT #undef RESTORE_USINT #undef RESTORE_DOUBLE #undef RESTORE_ANY #undef RESTORE_BOOL bool keyeq(char *s, jsmntok_t *key, char *json) { size_t n = key->end - key->start; return (strlen(s) == n && strncmp(s, json + key->start, n) == 0); } ================================================ FILE: src/restore.h ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef BSPWM_RESTORE_H #define BSPWM_RESTORE_H #include "jsmn.h" bool restore_state(const char *file_path); monitor_t *restore_monitor(jsmntok_t **t, char *json); desktop_t *restore_desktop(jsmntok_t **t, char *json); node_t *restore_node(jsmntok_t **t, char *json); presel_t *restore_presel(jsmntok_t **t, char *json); client_t *restore_client(jsmntok_t **t, char *json); void restore_rectangle(xcb_rectangle_t *r, jsmntok_t **t, char *json); void restore_constraints(constraints_t *c, jsmntok_t **t, char *json); void restore_padding(padding_t *p, jsmntok_t **t, char *json); void restore_history(jsmntok_t **t, char *json); void restore_subscribers(jsmntok_t **t, char *json); void restore_subscriber(subscriber_list_t *s, jsmntok_t **t, char *json); void restore_coordinates(coordinates_t *loc, jsmntok_t **t, char *json); void restore_stack(jsmntok_t **t, char *json); bool keyeq(char *s, jsmntok_t *key, char *json); #endif ================================================ FILE: src/rule.c ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include "bspwm.h" #include "ewmh.h" #include "window.h" #include "query.h" #include "parse.h" #include "settings.h" #include "rule.h" rule_t *make_rule(void) { rule_t *r = calloc(1, sizeof(rule_t)); r->class_name[0] = r->instance_name[0] = r->name[0] = r->effect[0] = '\0'; r->next = r->prev = NULL; r->one_shot = false; return r; } void add_rule(rule_t *r) { if (rule_head == NULL) { rule_head = rule_tail = r; } else { rule_tail->next = r; r->prev = rule_tail; rule_tail = r; } } void remove_rule(rule_t *r) { if (r == NULL) { return; } rule_t *prev = r->prev; rule_t *next = r->next; if (prev != NULL) { prev->next = next; } if (next != NULL) { next->prev = prev; } if (r == rule_head) { rule_head = next; } if (r == rule_tail) { rule_tail = prev; } free(r); } void remove_rule_by_cause(char *cause) { rule_t *r = rule_head; struct tokenize_state state; char *class_name = tokenize_with_escape(&state, cause, COL_TOK[0]); char *instance_name = tokenize_with_escape(&state, NULL, COL_TOK[0]); char *name = tokenize_with_escape(&state, NULL, COL_TOK[0]); if (!class_name || !instance_name || !name) { free(class_name); free(instance_name); free(name); return; } while (r != NULL) { rule_t *next = r->next; if ((class_name != NULL && (streq(class_name, MATCH_ANY) || streq(r->class_name, class_name))) && (instance_name == NULL || streq(instance_name, MATCH_ANY) || streq(r->instance_name, instance_name)) && (name == NULL || streq(name, MATCH_ANY) || streq(r->name, name))) { remove_rule(r); } r = next; } free(class_name); free(instance_name); free(name); } bool remove_rule_by_index(int idx) { for (rule_t *r = rule_head; r != NULL; r = r->next, idx--) { if (idx == 0) { remove_rule(r); return true; } } return false; } rule_consequence_t *make_rule_consequence(void) { rule_consequence_t *rc = calloc(1, sizeof(rule_consequence_t)); rc->manage = rc->focus = rc->border = true; rc->layer = NULL; rc->state = NULL; rc->rect = NULL; rc->honor_size_hints = HONOR_SIZE_HINTS_DEFAULT; return rc; } pending_rule_t *make_pending_rule(int fd, xcb_window_t win, rule_consequence_t *csq) { pending_rule_t *pr = calloc(1, sizeof(pending_rule_t)); pr->prev = pr->next = NULL; pr->event_head = pr->event_tail = NULL; pr->fd = fd; pr->win = win; pr->csq = csq; return pr; } void add_pending_rule(pending_rule_t *pr) { if (pr == NULL) { return; } if (pending_rule_head == NULL) { pending_rule_head = pending_rule_tail = pr; } else { pending_rule_tail->next = pr; pr->prev = pending_rule_tail; pending_rule_tail = pr; } } void remove_pending_rule(pending_rule_t *pr) { if (pr == NULL) { return; } pending_rule_t *a = pr->prev; pending_rule_t *b = pr->next; if (a != NULL) { a->next = b; } if (b != NULL) { b->prev = a; } if (pr == pending_rule_head) { pending_rule_head = b; } if (pr == pending_rule_tail) { pending_rule_tail = a; } close(pr->fd); free(pr->csq); event_queue_t *eq = pr->event_head; while (eq != NULL) { event_queue_t *next = eq->next; free(eq); eq = next; } free(pr); } void postpone_event(pending_rule_t *pr, xcb_generic_event_t *evt) { event_queue_t *eq = make_event_queue(evt); if (pr->event_tail == NULL) { pr->event_head = pr->event_tail = eq; } else { pr->event_tail->next = eq; eq->prev = pr->event_tail; pr->event_tail = eq; } } event_queue_t *make_event_queue(xcb_generic_event_t *evt) { event_queue_t *eq = calloc(1, sizeof(event_queue_t)); eq->prev = eq->next = NULL; eq->event = *evt; return eq; } #define SET_CSQ_SPLIT_DIR(val) \ do { \ if (csq->split_dir == NULL) { \ csq->split_dir = calloc(1, sizeof(direction_t)); \ } \ *(csq->split_dir) = (val); \ } while (0) #define SET_CSQ_STATE(val) \ do { \ if (csq->state == NULL) { \ csq->state = calloc(1, sizeof(client_state_t)); \ } \ *(csq->state) = (val); \ } while (0) #define SET_CSQ_LAYER(val) \ do { \ if (csq->layer == NULL) { \ csq->layer = calloc(1, sizeof(stack_layer_t)); \ } \ *(csq->layer) = (val); \ } while (0) void _apply_window_type(xcb_window_t win, rule_consequence_t *csq) { xcb_ewmh_get_atoms_reply_t win_type; if (xcb_ewmh_get_wm_window_type_reply(ewmh, xcb_ewmh_get_wm_window_type(ewmh, win), &win_type, NULL) == 1) { for (unsigned int i = 0; i < win_type.atoms_len; i++) { xcb_atom_t a = win_type.atoms[i]; if (a == ewmh->_NET_WM_WINDOW_TYPE_TOOLBAR || a == ewmh->_NET_WM_WINDOW_TYPE_UTILITY) { csq->focus = false; } else if (a == ewmh->_NET_WM_WINDOW_TYPE_DIALOG) { SET_CSQ_STATE(STATE_FLOATING); csq->center = true; } else if (a == ewmh->_NET_WM_WINDOW_TYPE_DOCK || a == ewmh->_NET_WM_WINDOW_TYPE_DESKTOP || a == ewmh->_NET_WM_WINDOW_TYPE_NOTIFICATION) { csq->manage = false; if (a == ewmh->_NET_WM_WINDOW_TYPE_DESKTOP) { window_lower(win); } } } xcb_ewmh_get_atoms_reply_wipe(&win_type); } } void _apply_window_state(xcb_window_t win, rule_consequence_t *csq) { xcb_ewmh_get_atoms_reply_t win_state; if (xcb_ewmh_get_wm_state_reply(ewmh, xcb_ewmh_get_wm_state(ewmh, win), &win_state, NULL) == 1) { for (unsigned int i = 0; i < win_state.atoms_len; i++) { xcb_atom_t a = win_state.atoms[i]; if (a == ewmh->_NET_WM_STATE_FULLSCREEN) { SET_CSQ_STATE(STATE_FULLSCREEN); } else if (a == ewmh->_NET_WM_STATE_BELOW) { SET_CSQ_LAYER(LAYER_BELOW); } else if (a == ewmh->_NET_WM_STATE_ABOVE) { SET_CSQ_LAYER(LAYER_ABOVE); } else if (a == ewmh->_NET_WM_STATE_STICKY) { csq->sticky = true; } } xcb_ewmh_get_atoms_reply_wipe(&win_state); } } void _apply_transient(xcb_window_t win, rule_consequence_t *csq) { xcb_window_t transient_for = XCB_NONE; xcb_icccm_get_wm_transient_for_reply(dpy, xcb_icccm_get_wm_transient_for(dpy, win), &transient_for, NULL); if (transient_for != XCB_NONE) { SET_CSQ_STATE(STATE_FLOATING); } } void _apply_hints(xcb_window_t win, rule_consequence_t *csq) { xcb_size_hints_t size_hints; if (xcb_icccm_get_wm_normal_hints_reply(dpy, xcb_icccm_get_wm_normal_hints(dpy, win), &size_hints, NULL) == 1) { if ((size_hints.flags & (XCB_ICCCM_SIZE_HINT_P_MIN_SIZE | XCB_ICCCM_SIZE_HINT_P_MAX_SIZE)) && size_hints.min_width == size_hints.max_width && size_hints.min_height == size_hints.max_height) { SET_CSQ_STATE(STATE_FLOATING); } } } void _apply_class(xcb_window_t win, rule_consequence_t *csq) { xcb_icccm_get_wm_class_reply_t reply; if (xcb_icccm_get_wm_class_reply(dpy, xcb_icccm_get_wm_class(dpy, win), &reply, NULL) == 1) { snprintf(csq->class_name, sizeof(csq->class_name), "%s", reply.class_name); snprintf(csq->instance_name, sizeof(csq->instance_name), "%s", reply.instance_name); xcb_icccm_get_wm_class_reply_wipe(&reply); } } void _apply_name(xcb_window_t win, rule_consequence_t *csq) { xcb_icccm_get_text_property_reply_t reply; if (xcb_icccm_get_wm_name_reply(dpy, xcb_icccm_get_wm_name(dpy, win), &reply, NULL) == 1) { snprintf(csq->name, sizeof(csq->name), "%.*s", reply.name_len, reply.name); xcb_icccm_get_text_property_reply_wipe(&reply); } } void parse_keys_values(char *buf, rule_consequence_t *csq) { char *key = strtok(buf, CSQ_BLK); char *value = strtok(NULL, CSQ_BLK); while (key != NULL && value != NULL) { parse_key_value(key, value, csq); key = strtok(NULL, CSQ_BLK); value = strtok(NULL, CSQ_BLK); } } void apply_rules(xcb_window_t win, rule_consequence_t *csq) { _apply_window_type(win, csq); _apply_window_state(win, csq); _apply_transient(win, csq); _apply_hints(win, csq); _apply_class(win, csq); _apply_name(win, csq); rule_t *rule = rule_head; while (rule != NULL) { rule_t *next = rule->next; if ((streq(rule->class_name, MATCH_ANY) || streq(rule->class_name, csq->class_name)) && (streq(rule->instance_name, MATCH_ANY) || streq(rule->instance_name, csq->instance_name)) && (streq(rule->name, MATCH_ANY) || streq(rule->name, csq->name))) { char effect[MAXLEN]; snprintf(effect, sizeof(effect), "%s", rule->effect); parse_keys_values(effect, csq); if (rule->one_shot) { remove_rule(rule); break; } } rule = next; } } bool schedule_rules(xcb_window_t win, rule_consequence_t *csq) { if (external_rules_command[0] == '\0') { return false; } resolve_rule_consequence(csq); int fds[2]; if (pipe(fds) == -1) { return false; } pid_t pid = fork(); if (pid == 0) { if (dpy != NULL) { close(xcb_get_file_descriptor(dpy)); } dup2(fds[1], 1); close(fds[0]); char wid[SMALEN]; char *csq_buf; print_rule_consequence(&csq_buf, csq); snprintf(wid, sizeof(wid), "%i", win); setsid(); execl(external_rules_command, external_rules_command, wid, csq->class_name, csq->instance_name, csq_buf, NULL); free(csq_buf); err("Couldn't spawn rule command.\n"); } else if (pid > 0) { close(fds[1]); pending_rule_t *pr = make_pending_rule(fds[0], win, csq); add_pending_rule(pr); } return (pid != -1); } void parse_rule_consequence(int fd, rule_consequence_t *csq) { if (fd == -1) { return; } char data[BUFSIZ]; int nb; while ((nb = read(fd, data, sizeof(data))) > 0) { int end = MIN(nb, (int) sizeof(data) - 1); data[end] = '\0'; parse_keys_values(data, csq); } } void parse_key_value(char *key, char *value, rule_consequence_t *csq) { bool v; if (streq("monitor", key)) { snprintf(csq->monitor_desc, sizeof(csq->monitor_desc), "%s", value); } else if (streq("desktop", key)) { snprintf(csq->desktop_desc, sizeof(csq->desktop_desc), "%s", value); } else if (streq("node", key)) { snprintf(csq->node_desc, sizeof(csq->node_desc), "%s", value); } else if (streq("split_dir", key)) { direction_t dir; if (parse_direction(value, &dir)) { SET_CSQ_SPLIT_DIR(dir); } } else if (streq("state", key)) { client_state_t cst; if (parse_client_state(value, &cst)) { SET_CSQ_STATE(cst); } } else if (streq("layer", key)) { stack_layer_t lyr; if (parse_stack_layer(value, &lyr)) { SET_CSQ_LAYER(lyr); } } else if (streq("split_ratio", key)) { double rat; if (sscanf(value, "%lf", &rat) == 1 && rat > 0 && rat < 1) { csq->split_ratio = rat; } } else if (streq("rectangle", key)) { if (csq->rect == NULL) { csq->rect = calloc(1, sizeof(xcb_rectangle_t)); } if (!parse_rectangle(value, csq->rect)) { free(csq->rect); csq->rect = NULL; } } else if (streq("honor_size_hints", key)) { if (!parse_honor_size_hints_mode(value, &csq->honor_size_hints)) { csq->honor_size_hints = HONOR_SIZE_HINTS_DEFAULT; } } else if (parse_bool(value, &v)) { if (streq("hidden", key)) { csq->hidden = v; } #define SETCSQ(name) \ else if (streq(#name, key)) { \ csq->name = v; \ } SETCSQ(sticky) SETCSQ(private) SETCSQ(locked) SETCSQ(marked) SETCSQ(center) SETCSQ(follow) SETCSQ(manage) SETCSQ(focus) SETCSQ(border) #undef SETCSQ } } #undef SET_CSQ_LAYER #undef SET_CSQ_STATE void list_rules(FILE *rsp) { for (rule_t *r = rule_head; r != NULL; r = r->next) { fprintf(rsp, "%s:%s:%s %c> %s\n", r->class_name, r->instance_name, r->name, r->one_shot?'-':'=', r->effect); } } ================================================ FILE: src/rule.h ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef BSPWM_RULE_H #define BSPWM_RULE_H #define MATCH_ANY "*" #define CSQ_BLK " =,\n" rule_t *make_rule(void); void add_rule(rule_t *r); void remove_rule(rule_t *r); void remove_rule_by_cause(char *cause); bool remove_rule_by_index(int idx); rule_consequence_t *make_rule_consequence(void); pending_rule_t *make_pending_rule(int fd, xcb_window_t win, rule_consequence_t *csq); void add_pending_rule(pending_rule_t *pr); void remove_pending_rule(pending_rule_t *pr); void postpone_event(pending_rule_t *pr, xcb_generic_event_t *evt); event_queue_t *make_event_queue(xcb_generic_event_t *evt); void _apply_window_type(xcb_window_t win, rule_consequence_t *csq); void _apply_window_state(xcb_window_t win, rule_consequence_t *csq); void _apply_transient(xcb_window_t win, rule_consequence_t *csq); void _apply_hints(xcb_window_t win, rule_consequence_t *csq); void _apply_class(xcb_window_t win, rule_consequence_t *csq); void _apply_name(xcb_window_t win, rule_consequence_t *csq); void parse_keys_values(char *buf, rule_consequence_t *csq); void apply_rules(xcb_window_t win, rule_consequence_t *csq); bool schedule_rules(xcb_window_t win, rule_consequence_t *csq); void parse_rule_consequence(int fd, rule_consequence_t *csq); void parse_key_value(char *key, char *value, rule_consequence_t *csq); void list_rules(FILE *rsp); #endif ================================================ FILE: src/settings.c ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include "bspwm.h" #include "settings.h" char external_rules_command[MAXLEN]; char status_prefix[MAXLEN]; char normal_border_color[MAXLEN]; char active_border_color[MAXLEN]; char focused_border_color[MAXLEN]; char presel_feedback_color[MAXLEN]; padding_t padding; padding_t monocle_padding; int window_gap; unsigned int border_width; double split_ratio; child_polarity_t initial_polarity; automatic_scheme_t automatic_scheme; bool removal_adjustment; tightness_t directional_focus_tightness; uint16_t pointer_modifier; uint32_t pointer_motion_interval; pointer_action_t pointer_actions[3]; int8_t mapping_events_count; bool presel_feedback; bool borderless_monocle; bool gapless_monocle; bool single_monocle; bool borderless_singleton; bool focus_follows_pointer; bool pointer_follows_focus; bool pointer_follows_monitor; int8_t click_to_focus; bool swallow_first_click; bool ignore_ewmh_focus; bool ignore_ewmh_struts; state_transition_t ignore_ewmh_fullscreen; bool center_pseudo_tiled; honor_size_hints_mode_t honor_size_hints; bool remove_disabled_monitors; bool remove_unplugged_monitors; bool merge_overlapping_monitors; void run_config(int run_level) { if (fork() == 0) { if (dpy != NULL) { close(xcb_get_file_descriptor(dpy)); } setsid(); char arg1[2]; snprintf(arg1, 2, "%i", run_level); execl(config_path, config_path, arg1, (char *) NULL); err("Couldn't execute the configuration file.\n"); } } void load_settings(void) { snprintf(external_rules_command, sizeof(external_rules_command), "%s", EXTERNAL_RULES_COMMAND); snprintf(status_prefix, sizeof(status_prefix), "%s", STATUS_PREFIX); snprintf(normal_border_color, sizeof(normal_border_color), "%s", NORMAL_BORDER_COLOR); snprintf(active_border_color, sizeof(active_border_color), "%s", ACTIVE_BORDER_COLOR); snprintf(focused_border_color, sizeof(focused_border_color), "%s", FOCUSED_BORDER_COLOR); snprintf(presel_feedback_color, sizeof(presel_feedback_color), "%s", PRESEL_FEEDBACK_COLOR); padding = (padding_t) PADDING; monocle_padding = (padding_t) MONOCLE_PADDING; window_gap = WINDOW_GAP; border_width = BORDER_WIDTH; split_ratio = SPLIT_RATIO; initial_polarity = SECOND_CHILD; automatic_scheme = AUTOMATIC_SCHEME; removal_adjustment = REMOVAL_ADJUSTMENT; directional_focus_tightness = TIGHTNESS_HIGH; pointer_modifier = POINTER_MODIFIER; pointer_motion_interval = POINTER_MOTION_INTERVAL; pointer_actions[0] = ACTION_MOVE; pointer_actions[1] = ACTION_RESIZE_SIDE; pointer_actions[2] = ACTION_RESIZE_CORNER; mapping_events_count = MAPPING_EVENTS_COUNT; presel_feedback = PRESEL_FEEDBACK; borderless_monocle = BORDERLESS_MONOCLE; gapless_monocle = GAPLESS_MONOCLE; single_monocle = SINGLE_MONOCLE; borderless_singleton = BORDERLESS_SINGLETON; focus_follows_pointer = FOCUS_FOLLOWS_POINTER; pointer_follows_focus = POINTER_FOLLOWS_FOCUS; pointer_follows_monitor = POINTER_FOLLOWS_MONITOR; click_to_focus = CLICK_TO_FOCUS; swallow_first_click = SWALLOW_FIRST_CLICK; ignore_ewmh_focus = IGNORE_EWMH_FOCUS; ignore_ewmh_fullscreen = IGNORE_EWMH_FULLSCREEN; ignore_ewmh_struts = IGNORE_EWMH_STRUTS; center_pseudo_tiled = CENTER_PSEUDO_TILED; honor_size_hints = HONOR_SIZE_HINTS; remove_disabled_monitors = REMOVE_DISABLED_MONITORS; remove_unplugged_monitors = REMOVE_UNPLUGGED_MONITORS; merge_overlapping_monitors = MERGE_OVERLAPPING_MONITORS; } ================================================ FILE: src/settings.h ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef BSPWM_SETTINGS_H #define BSPWM_SETTINGS_H #include "types.h" #define POINTER_MODIFIER XCB_MOD_MASK_4 #define POINTER_MOTION_INTERVAL 17 #define EXTERNAL_RULES_COMMAND "" #define STATUS_PREFIX "W" #define NORMAL_BORDER_COLOR "#30302f" #define ACTIVE_BORDER_COLOR "#474645" #define FOCUSED_BORDER_COLOR "#817f7f" #define PRESEL_FEEDBACK_COLOR "#f4d775" #define PADDING {0, 0, 0, 0} #define MONOCLE_PADDING {0, 0, 0, 0} #define WINDOW_GAP 6 #define BORDER_WIDTH 1 #define SPLIT_RATIO 0.5 #define AUTOMATIC_SCHEME SCHEME_LONGEST_SIDE #define REMOVAL_ADJUSTMENT true #define PRESEL_FEEDBACK true #define BORDERLESS_MONOCLE false #define GAPLESS_MONOCLE false #define SINGLE_MONOCLE false #define BORDERLESS_SINGLETON false #define FOCUS_FOLLOWS_POINTER false #define POINTER_FOLLOWS_FOCUS false #define POINTER_FOLLOWS_MONITOR false #define CLICK_TO_FOCUS XCB_BUTTON_INDEX_1 #define SWALLOW_FIRST_CLICK false #define IGNORE_EWMH_FOCUS false #define IGNORE_EWMH_FULLSCREEN 0 #define IGNORE_EWMH_STRUTS false #define CENTER_PSEUDO_TILED true #define HONOR_SIZE_HINTS HONOR_SIZE_HINTS_NO #define MAPPING_EVENTS_COUNT 1 #define REMOVE_DISABLED_MONITORS false #define REMOVE_UNPLUGGED_MONITORS false #define MERGE_OVERLAPPING_MONITORS false extern char external_rules_command[MAXLEN]; extern char status_prefix[MAXLEN]; extern char normal_border_color[MAXLEN]; extern char active_border_color[MAXLEN]; extern char focused_border_color[MAXLEN]; extern char presel_feedback_color[MAXLEN]; extern padding_t padding; extern padding_t monocle_padding; extern int window_gap; extern unsigned int border_width; extern double split_ratio; extern child_polarity_t initial_polarity; extern automatic_scheme_t automatic_scheme; extern bool removal_adjustment; extern tightness_t directional_focus_tightness; extern uint16_t pointer_modifier; extern uint32_t pointer_motion_interval; extern pointer_action_t pointer_actions[3]; extern int8_t mapping_events_count; extern bool presel_feedback; extern bool borderless_monocle; extern bool gapless_monocle; extern bool single_monocle; extern bool borderless_singleton; extern bool focus_follows_pointer; extern bool pointer_follows_focus; extern bool pointer_follows_monitor; extern int8_t click_to_focus; extern bool swallow_first_click; extern bool ignore_ewmh_focus; extern bool ignore_ewmh_struts; extern state_transition_t ignore_ewmh_fullscreen; extern bool center_pseudo_tiled; extern honor_size_hints_mode_t honor_size_hints; extern bool remove_disabled_monitors; extern bool remove_unplugged_monitors; extern bool merge_overlapping_monitors; void run_config(int run_level); void load_settings(void); #endif ================================================ FILE: src/stack.c ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "bspwm.h" #include "window.h" #include "subscribe.h" #include "ewmh.h" #include "tree.h" #include "stack.h" stacking_list_t *make_stack(node_t *n) { stacking_list_t *s = calloc(1, sizeof(stacking_list_t)); s->node = n; s->prev = s->next = NULL; return s; } void stack_insert_after(stacking_list_t *a, node_t *n) { stacking_list_t *s = make_stack(n); if (a == NULL) { stack_head = stack_tail = s; } else { if (a->node == n) { free(s); return; } remove_stack_node(n); stacking_list_t *b = a->next; if (b != NULL) { b->prev = s; } s->next = b; s->prev = a; a->next = s; if (stack_tail == a) { stack_tail = s; } } } void stack_insert_before(stacking_list_t *a, node_t *n) { stacking_list_t *s = make_stack(n); if (a == NULL) { stack_head = stack_tail = s; } else { if (a->node == n) { free(s); return; } remove_stack_node(n); stacking_list_t *b = a->prev; if (b != NULL) { b->next = s; } s->prev = b; s->next = a; a->prev = s; if (stack_head == a) { stack_head = s; } } } void remove_stack(stacking_list_t *s) { if (s == NULL) { return; } stacking_list_t *a = s->prev; stacking_list_t *b = s->next; if (a != NULL) { a->next = b; } if (b != NULL) { b->prev = a; } if (s == stack_head) { stack_head = b; } if (s == stack_tail) { stack_tail = a; } free(s); } void remove_stack_node(node_t *n) { for (node_t *f = first_extrema(n); f != NULL; f = next_leaf(f, n)) { for (stacking_list_t *s = stack_head; s != NULL; s = s->next) { if (s->node == f) { remove_stack(s); break; } } } } int stack_level(client_t *c) { int layer_level = (c->layer == LAYER_NORMAL ? 1 : (c->layer == LAYER_BELOW ? 0 : 2)); int state_level = (IS_TILED(c) ? 0 : (IS_FLOATING(c) ? 1 : 2)); return 3 * layer_level + state_level; } int stack_cmp(client_t *c1, client_t *c2) { return stack_level(c1) - stack_level(c2); } stacking_list_t *limit_above(node_t *n) { stacking_list_t *s = stack_head; while (s != NULL && stack_cmp(n->client, s->node->client) >= 0) { s = s->next; } if (s == NULL) { s = stack_tail; } if (s->node == n) { s = s->prev; } return s; } stacking_list_t *limit_below(node_t *n) { stacking_list_t *s = stack_tail; while (s != NULL && stack_cmp(n->client, s->node->client) <= 0) { s = s->prev; } if (s == NULL) { s = stack_head; } if (s->node == n) { s = s->next; } return s; } void stack(desktop_t *d, node_t *n, bool focused) { for (node_t *f = first_extrema(n); f != NULL; f = next_leaf(f, n)) { if (f->client == NULL || (IS_FLOATING(f->client) && !auto_raise)) { continue; } if (stack_head == NULL) { stack_insert_after(NULL, f); } else { stacking_list_t *s = (focused ? limit_above(f) : limit_below(f)); if (s == NULL) { continue; } int i = stack_cmp(f->client, s->node->client); if (i < 0 || (i == 0 && !focused)) { stack_insert_before(s, f); window_below(f->id, s->node->id); put_status(SBSC_MASK_NODE_STACK, "node_stack 0x%08X below 0x%08X\n", f->id, s->node->id); } else { stack_insert_after(s, f); window_above(f->id, s->node->id); put_status(SBSC_MASK_NODE_STACK, "node_stack 0x%08X above 0x%08X\n", f->id, s->node->id); } } } ewmh_update_client_list(true); restack_presel_feedbacks(d); } void restack_presel_feedbacks(desktop_t *d) { stacking_list_t *s = stack_tail; while (s != NULL && !IS_TILED(s->node->client)) { s = s->prev; } if (s != NULL) { restack_presel_feedbacks_in(d->root, s->node); } } void restack_presel_feedbacks_in(node_t *r, node_t *n) { if (r == NULL) { return; } else { if (r->presel != NULL) { window_above(r->presel->feedback, n->id); } restack_presel_feedbacks_in(r->first_child, n); restack_presel_feedbacks_in(r->second_child, n); } } ================================================ FILE: src/stack.h ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef BSPWM_STACK_H #define BSPWM_STACK_H stacking_list_t *make_stack(node_t *n); void stack_insert_after(stacking_list_t *a, node_t *n); void stack_insert_before(stacking_list_t *a, node_t *n); void remove_stack(stacking_list_t *s); void remove_stack_node(node_t *n); int stack_level(client_t *c); int stack_cmp(client_t *c1, client_t *c2); stacking_list_t *limit_above(node_t *n); stacking_list_t *limit_below(node_t *n); void stack(desktop_t *d, node_t *n, bool focused); void restack_presel_feedbacks(desktop_t *d); void restack_presel_feedbacks_in(node_t *r, node_t *n); #endif ================================================ FILE: src/subscribe.c ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include "bspwm.h" #include "desktop.h" #include "settings.h" #include "subscribe.h" #include "tree.h" subscriber_list_t *make_subscriber(FILE *stream, char *fifo_path, int field, int count) { subscriber_list_t *sb = calloc(1, sizeof(subscriber_list_t)); sb->prev = sb->next = NULL; sb->stream = stream; sb->fifo_path = fifo_path; sb->field = field; sb->count = count; return sb; } void remove_subscriber(subscriber_list_t *sb) { if (sb == NULL) { return; } subscriber_list_t *a = sb->prev; subscriber_list_t *b = sb->next; if (a != NULL) { a->next = b; } if (b != NULL) { b->prev = a; } if (sb == subscribe_head) { subscribe_head = b; } if (sb == subscribe_tail) { subscribe_tail = a; } if (restart) { int cli_fd = fileno(sb->stream); fcntl(cli_fd, F_SETFD, ~FD_CLOEXEC & fcntl(cli_fd, F_GETFD)); } else { fclose(sb->stream); unlink(sb->fifo_path); } free(sb->fifo_path); free(sb); } void add_subscriber(subscriber_list_t *sb) { if (subscribe_head == NULL) { subscribe_head = subscribe_tail = sb; } else { subscribe_tail->next = sb; sb->prev = subscribe_tail; subscribe_tail = sb; } int cli_fd = fileno(sb->stream); fcntl(cli_fd, F_SETFD, FD_CLOEXEC | fcntl(cli_fd, F_GETFD)); if (sb->field & SBSC_MASK_REPORT) { print_report(sb->stream); if (sb->count-- == 1) { remove_subscriber(sb); } } } int print_report(FILE *stream) { fprintf(stream, "%s", status_prefix); for (monitor_t *m = mon_head; m != NULL; m = m->next) { fprintf(stream, "%c%s", (mon == m ? 'M' : 'm'), m->name); for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { char c = (is_urgent(d) ? 'u' : (d->root == NULL ? 'f' : 'o')); if (m->desk == d) { c = toupper(c); } fprintf(stream, ":%c%s", c, d->name); } if (m->desk != NULL) { fprintf(stream, ":L%c", LAYOUT_CHR(m->desk->layout)); if (m->desk->focus != NULL) { node_t *n = m->desk->focus; if (n->client != NULL) { fprintf(stream, ":T%c", STATE_CHR(n->client->state)); } else { fprintf(stream, ":T@"); } int i = 0; char flags[5]; if (n->sticky) { flags[i++] = 'S'; } if (n->private) { flags[i++] = 'P'; } if (n->locked) { flags[i++] = 'L'; } if (n->marked) { flags[i++] = 'M'; } flags[i] = '\0'; fprintf(stream, ":G%s", flags); } } if (m != mon_tail) { fprintf(stream, "%s", ":"); } } fprintf(stream, "%s", "\n"); return fflush(stream); } void put_status(subscriber_mask_t mask, ...) { subscriber_list_t *sb = subscribe_head; int ret; while (sb != NULL) { subscriber_list_t *next = sb->next; if (sb->field & mask) { if (sb->count > 0) { sb->count--; } if (mask == SBSC_MASK_REPORT) { ret = print_report(sb->stream); } else { char *fmt; va_list args; va_start(args, mask); fmt = va_arg(args, char *); vfprintf(sb->stream, fmt, args); va_end(args); ret = fflush(sb->stream); } if (ret != 0 || sb->count == 0) { remove_subscriber(sb); } } sb = next; } } void prune_dead_subscribers(void) { subscriber_list_t *sb = subscribe_head; while (sb != NULL) { subscriber_list_t *next = sb->next; // Is the subscriber's stream closed? if (write(fileno(sb->stream), NULL, 0) == -1) { remove_subscriber(sb); } sb = next; } } ================================================ FILE: src/subscribe.h ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef BSPWM_SUBSCRIBE_H #define BSPWM_SUBSCRIBE_H #define FIFO_TEMPLATE "bspwm_fifo.XXXXXX" typedef enum { SBSC_MASK_REPORT = 1 << 0, SBSC_MASK_MONITOR_ADD = 1 << 1, SBSC_MASK_MONITOR_RENAME = 1 << 2, SBSC_MASK_MONITOR_REMOVE = 1 << 3, SBSC_MASK_MONITOR_SWAP = 1 << 4, SBSC_MASK_MONITOR_FOCUS = 1 << 5, SBSC_MASK_MONITOR_GEOMETRY = 1 << 6, SBSC_MASK_DESKTOP_ADD = 1 << 7, SBSC_MASK_DESKTOP_RENAME = 1 << 8, SBSC_MASK_DESKTOP_REMOVE = 1 << 9, SBSC_MASK_DESKTOP_SWAP = 1 << 10, SBSC_MASK_DESKTOP_TRANSFER = 1 << 11, SBSC_MASK_DESKTOP_FOCUS = 1 << 12, SBSC_MASK_DESKTOP_ACTIVATE = 1 << 13, SBSC_MASK_DESKTOP_LAYOUT = 1 << 14, SBSC_MASK_NODE_ADD = 1 << 15, SBSC_MASK_NODE_REMOVE = 1 << 16, SBSC_MASK_NODE_SWAP = 1 << 17, SBSC_MASK_NODE_TRANSFER = 1 << 18, SBSC_MASK_NODE_FOCUS = 1 << 19, SBSC_MASK_NODE_PRESEL = 1 << 20, SBSC_MASK_NODE_STACK = 1 << 21, SBSC_MASK_NODE_ACTIVATE = 1 << 22, SBSC_MASK_NODE_GEOMETRY = 1 << 23, SBSC_MASK_NODE_STATE = 1 << 24, SBSC_MASK_NODE_FLAG = 1 << 25, SBSC_MASK_NODE_LAYER = 1 << 26, SBSC_MASK_POINTER_ACTION = 1 << 27, SBSC_MASK_MONITOR = (1 << 7) - (1 << 1), SBSC_MASK_DESKTOP = (1 << 15) - (1 << 7), SBSC_MASK_NODE = (1 << 27) - (1 << 15), SBSC_MASK_ALL = (1 << 28) - 1 } subscriber_mask_t; subscriber_list_t *make_subscriber(FILE *stream, char *fifo_path, int field, int count); void remove_subscriber(subscriber_list_t *sb); void add_subscriber(subscriber_list_t *sb); int print_report(FILE *stream); void put_status(subscriber_mask_t mask, ...); /* Remove any subscriber for which the stream has been closed and is no longer * writable. */ void prune_dead_subscribers(void); #endif ================================================ FILE: src/tree.c ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include "bspwm.h" #include "desktop.h" #include "ewmh.h" #include "history.h" #include "monitor.h" #include "query.h" #include "geometry.h" #include "subscribe.h" #include "settings.h" #include "pointer.h" #include "stack.h" #include "window.h" #include "tree.h" void arrange(monitor_t *m, desktop_t *d) { if (d->root == NULL) { return; } xcb_rectangle_t rect = m->rectangle; rect.x += m->padding.left + d->padding.left; rect.y += m->padding.top + d->padding.top; rect.width -= m->padding.left + d->padding.left + d->padding.right + m->padding.right; rect.height -= m->padding.top + d->padding.top + d->padding.bottom + m->padding.bottom; if (d->layout == LAYOUT_MONOCLE) { rect.x += monocle_padding.left; rect.y += monocle_padding.top; rect.width -= monocle_padding.left + monocle_padding.right; rect.height -= monocle_padding.top + monocle_padding.bottom; } if (!gapless_monocle || d->layout != LAYOUT_MONOCLE) { rect.x += d->window_gap; rect.y += d->window_gap; rect.width -= d->window_gap; rect.height -= d->window_gap; } apply_layout(m, d, d->root, rect, rect); } void apply_layout(monitor_t *m, desktop_t *d, node_t *n, xcb_rectangle_t rect, xcb_rectangle_t root_rect) { if (n == NULL) { return; } n->rectangle = rect; if (n->presel != NULL) { draw_presel_feedback(m, d, n); } if (is_leaf(n)) { if (n->client == NULL) { return; } unsigned int bw; bool the_only_window = !m->prev && !m->next && d->root->client; if ((borderless_monocle && d->layout == LAYOUT_MONOCLE && IS_TILED(n->client)) || (borderless_singleton && the_only_window) || n->client->state == STATE_FULLSCREEN) { bw = 0; } else { bw = n->client->border_width; } xcb_rectangle_t r; xcb_rectangle_t cr = get_window_rectangle(n); client_state_t s = n->client->state; /* tiled and pseudo-tiled clients */ if (s == STATE_TILED || s == STATE_PSEUDO_TILED) { int wg = (gapless_monocle && d->layout == LAYOUT_MONOCLE ? 0 : d->window_gap); r = rect; int bleed = wg + 2 * bw; r.width = (bleed < r.width ? r.width - bleed : 1); r.height = (bleed < r.height ? r.height - bleed : 1); /* pseudo-tiled clients */ if (s == STATE_PSEUDO_TILED) { xcb_rectangle_t f = n->client->floating_rectangle; r.width = MIN(r.width, f.width); r.height = MIN(r.height, f.height); if (center_pseudo_tiled) { r.x = rect.x - bw + (rect.width - wg - r.width) / 2; r.y = rect.y - bw + (rect.height - wg - r.height) / 2; } } n->client->tiled_rectangle = r; /* floating clients */ } else if (s == STATE_FLOATING) { r = n->client->floating_rectangle; /* fullscreen clients */ } else { r = m->rectangle; n->client->tiled_rectangle = r; } apply_size_hints(n->client, &r.width, &r.height); if (!rect_eq(r, cr)) { window_move_resize(n->id, r.x, r.y, r.width, r.height); if (!grabbing) { put_status(SBSC_MASK_NODE_GEOMETRY, "node_geometry 0x%08X 0x%08X 0x%08X %ux%u+%i+%i\n", m->id, d->id, n->id, r.width, r.height, r.x, r.y); } } window_border_width(n->id, bw); } else { xcb_rectangle_t first_rect; xcb_rectangle_t second_rect; if (d->layout == LAYOUT_MONOCLE || n->first_child->vacant || n->second_child->vacant) { first_rect = second_rect = rect; } else { unsigned int fence; if (n->split_type == TYPE_VERTICAL) { fence = rect.width * n->split_ratio; if ((n->first_child->constraints.min_width + n->second_child->constraints.min_width) <= rect.width) { if (fence < n->first_child->constraints.min_width) { fence = n->first_child->constraints.min_width; n->split_ratio = (double) fence / (double) rect.width; } else if (fence > (uint16_t) (rect.width - n->second_child->constraints.min_width)) { fence = (rect.width - n->second_child->constraints.min_width); n->split_ratio = (double) fence / (double) rect.width; } } first_rect = (xcb_rectangle_t) {rect.x, rect.y, fence, rect.height}; second_rect = (xcb_rectangle_t) {rect.x + fence, rect.y, rect.width - fence, rect.height}; } else { fence = rect.height * n->split_ratio; if ((n->first_child->constraints.min_height + n->second_child->constraints.min_height) <= rect.height) { if (fence < n->first_child->constraints.min_height) { fence = n->first_child->constraints.min_height; n->split_ratio = (double) fence / (double) rect.height; } else if (fence > (uint16_t) (rect.height - n->second_child->constraints.min_height)) { fence = (rect.height - n->second_child->constraints.min_height); n->split_ratio = (double) fence / (double) rect.height; } } first_rect = (xcb_rectangle_t) {rect.x, rect.y, rect.width, fence}; second_rect = (xcb_rectangle_t) {rect.x, rect.y + fence, rect.width, rect.height - fence}; } } apply_layout(m, d, n->first_child, first_rect, root_rect); apply_layout(m, d, n->second_child, second_rect, root_rect); } } presel_t *make_presel(void) { presel_t *p = calloc(1, sizeof(presel_t)); p->split_dir = DIR_EAST; p->split_ratio = split_ratio; p->feedback = XCB_NONE; return p; } bool set_type(node_t *n, split_type_t typ) { if (n == NULL || n->split_type == typ) { return false; } n->split_type = typ; update_constraints(n); rebuild_constraints_towards_root(n); return true; } bool set_ratio(node_t *n, double rat) { if (n == NULL || n->split_ratio == rat) { return false; } n->split_ratio = rat; return true; } void presel_dir(monitor_t *m, desktop_t *d, node_t *n, direction_t dir) { if (n->presel == NULL) { n->presel = make_presel(); } n->presel->split_dir = dir; put_status(SBSC_MASK_NODE_PRESEL, "node_presel 0x%08X 0x%08X 0x%08X dir %s\n", m->id, d->id, n->id, SPLIT_DIR_STR(dir)); } void presel_ratio(monitor_t *m, desktop_t *d, node_t *n, double ratio) { if (n->presel == NULL) { n->presel = make_presel(); } n->presel->split_ratio = ratio; put_status(SBSC_MASK_NODE_PRESEL, "node_presel 0x%08X 0x%08X 0x%08X ratio %lf\n", m->id, d->id, n->id, ratio); } void cancel_presel(monitor_t *m, desktop_t *d, node_t *n) { if (n->presel == NULL) { return; } if (n->presel->feedback != XCB_NONE) { xcb_destroy_window(dpy, n->presel->feedback); } free(n->presel); n->presel = NULL; put_status(SBSC_MASK_NODE_PRESEL, "node_presel 0x%08X 0x%08X 0x%08X cancel\n", m->id, d->id, n->id); } void cancel_presel_in(monitor_t *m, desktop_t *d, node_t *n) { if (n == NULL) { return; } cancel_presel(m, d, n); cancel_presel_in(m, d, n->first_child); cancel_presel_in(m, d, n->second_child); } node_t *find_public(desktop_t *d) { unsigned int b_manual_area = 0; unsigned int b_automatic_area = 0; node_t *b_manual = NULL; node_t *b_automatic = NULL; for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) { if (n->vacant) { continue; } unsigned int n_area = node_area(d, n); if (n_area > b_manual_area && (n->presel != NULL || !n->private)) { b_manual = n; b_manual_area = n_area; } if (n_area > b_automatic_area && n->presel == NULL && !n->private && private_count(n->parent) == 0) { b_automatic = n; b_automatic_area = n_area; } } if (b_automatic != NULL) { return b_automatic; } else { return b_manual; } } node_t *insert_node(monitor_t *m, desktop_t *d, node_t *n, node_t *f) { if (d == NULL || n == NULL) { return NULL; } bool d_was_not_occupied = d->root == NULL; /* n: inserted node */ /* c: new internal node */ /* f: focus or insertion anchor */ /* p: parent of focus */ /* g: grand parent of focus */ if (f == NULL) { f = d->root; } if (f == NULL) { d->root = n; } else if (IS_RECEPTACLE(f) && f->presel == NULL) { node_t *p = f->parent; if (p != NULL) { if (is_first_child(f)) { p->first_child = n; } else { p->second_child = n; } } else { d->root = n; } n->parent = p; free(f); f = NULL; } else { node_t *c = make_node(XCB_NONE); node_t *p = f->parent; if (f->presel == NULL && (f->private || private_count(f->parent) > 0)) { node_t *k = find_public(d); if (k != NULL) { f = k; p = f->parent; } if (f->presel == NULL && (f->private || private_count(f->parent) > 0)) { xcb_rectangle_t rect = get_rectangle(m, d, f); presel_dir(m, d, f, (rect.width >= rect.height ? DIR_EAST : DIR_SOUTH)); } } n->parent = c; if (f->presel == NULL) { bool single_tiled = f->client != NULL && IS_TILED(f->client) && tiled_count(d->root, true) == 1; if (p == NULL || automatic_scheme != SCHEME_SPIRAL || single_tiled) { if (p != NULL) { if (is_first_child(f)) { p->first_child = c; } else { p->second_child = c; } } else { d->root = c; } c->parent = p; f->parent = c; if (initial_polarity == FIRST_CHILD) { c->first_child = n; c->second_child = f; } else { c->first_child = f; c->second_child = n; } if (p == NULL || automatic_scheme == SCHEME_LONGEST_SIDE || single_tiled) { if (f->rectangle.width > f->rectangle.height) { c->split_type = TYPE_VERTICAL; } else { c->split_type = TYPE_HORIZONTAL; } } else { node_t *q = p; while (q != NULL && (q->first_child->vacant || q->second_child->vacant)) { q = q->parent; } if (q == NULL) { q = p; } if (q->split_type == TYPE_HORIZONTAL) { c->split_type = TYPE_VERTICAL; } else { c->split_type = TYPE_HORIZONTAL; } } } else { node_t *g = p->parent; c->parent = g; if (g != NULL) { if (is_first_child(p)) { g->first_child = c; } else { g->second_child = c; } } else { d->root = c; } c->split_type = p->split_type; c->split_ratio = p->split_ratio; p->parent = c; int rot; if (is_first_child(f)) { c->first_child = n; c->second_child = p; rot = 90; } else { c->first_child = p; c->second_child = n; rot = 270; } if (!n->vacant) { rotate_tree(p, rot); } } } else { if (p != NULL) { if (is_first_child(f)) { p->first_child = c; } else { p->second_child = c; } } c->split_ratio = f->presel->split_ratio; c->parent = p; f->parent = c; switch (f->presel->split_dir) { case DIR_WEST: c->split_type = TYPE_VERTICAL; c->first_child = n; c->second_child = f; break; case DIR_EAST: c->split_type = TYPE_VERTICAL; c->first_child = f; c->second_child = n; break; case DIR_NORTH: c->split_type = TYPE_HORIZONTAL; c->first_child = n; c->second_child = f; break; case DIR_SOUTH: c->split_type = TYPE_HORIZONTAL; c->first_child = f; c->second_child = n; break; } if (d->root == f) { d->root = c; } cancel_presel(m, d, f); set_marked(m, d, n, false); } } propagate_flags_upward(m, d, n); if (d->focus == NULL && is_focusable(n)) { d->focus = n; } if (d_was_not_occupied) { put_status(SBSC_MASK_REPORT); } return f; } void insert_receptacle(monitor_t *m, desktop_t *d, node_t *n) { node_t *r = make_node(XCB_NONE); insert_node(m, d, r, n); put_status(SBSC_MASK_NODE_ADD, "node_add 0x%08X 0x%08X 0x%08X 0x%08X\n", m->id, d->id, n != NULL ? n->id : 0, r->id); if (single_monocle && d->layout == LAYOUT_MONOCLE && tiled_count(d->root, true) > 1) { set_layout(m, d, d->user_layout, false); } } bool activate_node(monitor_t *m, desktop_t *d, node_t *n) { if (n == NULL && d->root != NULL) { n = d->focus; if (n == NULL) { n = history_last_node(d, NULL); } if (n == NULL) { n = first_focusable_leaf(d->root); } } if (d == mon->desk || (n != NULL && !is_focusable(n))) { return false; } if (n != NULL) { if (d->focus != NULL && n != d->focus) { neutralize_occluding_windows(m, d, n); } stack(d, n, true); if (d->focus != n) { for (node_t *f = first_extrema(d->focus); f != NULL; f = next_leaf(f, d->focus)) { if (f->client != NULL && !is_descendant(f, n)) { window_draw_border(f->id, get_border_color(false, (m == mon))); } } } draw_border(n, true, (m == mon)); } d->focus = n; history_add(m, d, n, false); put_status(SBSC_MASK_REPORT); if (n == NULL) { return true; } put_status(SBSC_MASK_NODE_ACTIVATE, "node_activate 0x%08X 0x%08X 0x%08X\n", m->id, d->id, n->id); return true; } void transfer_sticky_nodes(monitor_t *ms, desktop_t *ds, monitor_t *md, desktop_t *dd, node_t *n) { if (n == NULL) { return; } else if (n->sticky) { sticky_still = false; transfer_node(ms, ds, n, md, dd, dd->focus, false); sticky_still = true; } else { /* we need references to the children because n might be freed after * the first recursive call */ node_t *first_child = n->first_child; node_t *second_child = n->second_child; transfer_sticky_nodes(ms, ds, md, dd, first_child); transfer_sticky_nodes(ms, ds, md, dd, second_child); } } bool focus_node(monitor_t *m, desktop_t *d, node_t *n) { if (m == NULL) { m = mon; if (m == NULL) { m = history_last_monitor(NULL); } if (m == NULL) { m = mon_head; } } if (m == NULL) { return false; } if (d == NULL) { d = m->desk; if (d == NULL) { d = history_last_desktop(m, NULL); } if (d == NULL) { d = m->desk_head; } } if (d == NULL) { return false; } bool guess = (n == NULL); if (n == NULL && d->root != NULL) { n = d->focus; if (n == NULL) { n = history_last_node(d, NULL); } if (n == NULL) { n = first_focusable_leaf(d->root); } } if (n != NULL && !is_focusable(n)) { return false; } if ((mon != NULL && mon->desk != d) || n == NULL || n->client == NULL) { clear_input_focus(); } if (m->sticky_count > 0 && m->desk != NULL && d != m->desk) { if (guess && m->desk->focus != NULL && m->desk->focus->sticky) { n = m->desk->focus; } transfer_sticky_nodes(m, m->desk, m, d, m->desk->root); if (n == NULL && d->focus != NULL) { n = d->focus; } } if (d->focus != NULL && n != d->focus) { neutralize_occluding_windows(m, d, n); } if (n != NULL && n->client != NULL && n->client->urgent) { set_urgent(m, d, n, false); } if (mon != m) { if (mon != NULL) { for (desktop_t *e = mon->desk_head; e != NULL; e = e->next) { draw_border(e->focus, true, false); } } for (desktop_t *e = m->desk_head; e != NULL; e = e->next) { if (e == d) { continue; } draw_border(e->focus, true, true); } } if (d->focus != n) { for (node_t *f = first_extrema(d->focus); f != NULL; f = next_leaf(f, d->focus)) { if (f->client != NULL && !is_descendant(f, n)) { window_draw_border(f->id, get_border_color(false, true)); } } } draw_border(n, true, true); bool desk_changed = (m != mon || m->desk != d); bool has_input_focus = false; if (mon != m) { mon = m; if (pointer_follows_monitor) { center_pointer(m->rectangle); } put_status(SBSC_MASK_MONITOR_FOCUS, "monitor_focus 0x%08X\n", m->id); } if (m->desk != d) { show_desktop(d); set_input_focus(n); has_input_focus = true; hide_desktop(m->desk); m->desk = d; } if (desk_changed) { ewmh_update_current_desktop(); put_status(SBSC_MASK_DESKTOP_FOCUS, "desktop_focus 0x%08X 0x%08X\n", m->id, d->id); } d->focus = n; if (!has_input_focus) { set_input_focus(n); } ewmh_update_active_window(); history_add(m, d, n, true); put_status(SBSC_MASK_REPORT); if (n == NULL) { if (focus_follows_pointer) { update_motion_recorder(); } return true; } put_status(SBSC_MASK_NODE_FOCUS, "node_focus 0x%08X 0x%08X 0x%08X\n", m->id, d->id, n->id); stack(d, n, true); if (pointer_follows_focus) { center_pointer(get_rectangle(m, d, n)); } else if (focus_follows_pointer) { update_motion_recorder(); } return true; } void hide_node(desktop_t *d, node_t *n) { if (n == NULL || (!hide_sticky && n->sticky)) { return; } else { if (!n->hidden) { if (n->presel != NULL && d->layout != LAYOUT_MONOCLE) { window_hide(n->presel->feedback); } if (n->client != NULL) { window_hide(n->id); } } if (n->client != NULL) { n->client->shown = false; } hide_node(d, n->first_child); hide_node(d, n->second_child); } } void show_node(desktop_t *d, node_t *n) { if (n == NULL) { return; } else { if (!n->hidden) { if (n->client != NULL) { window_show(n->id); } if (n->presel != NULL && d->layout != LAYOUT_MONOCLE) { window_show(n->presel->feedback); } } if (n->client != NULL) { n->client->shown = true; } show_node(d, n->first_child); show_node(d, n->second_child); } } node_t *make_node(uint32_t id) { if (id == XCB_NONE) { id = xcb_generate_id(dpy); } node_t *n = calloc(1, sizeof(node_t)); n->id = id; n->parent = n->first_child = n->second_child = NULL; n->vacant = n->hidden = n->sticky = n->private = n->locked = n->marked = false; n->split_ratio = split_ratio; n->split_type = TYPE_VERTICAL; n->constraints = (constraints_t) {MIN_WIDTH, MIN_HEIGHT}; n->presel = NULL; n->client = NULL; return n; } client_t *make_client(void) { client_t *c = calloc(1, sizeof(client_t)); c->state = c->last_state = STATE_TILED; c->layer = c->last_layer = LAYER_NORMAL; snprintf(c->class_name, sizeof(c->class_name), "%s", MISSING_VALUE); snprintf(c->instance_name, sizeof(c->instance_name), "%s", MISSING_VALUE); c->border_width = border_width; c->urgent = false; c->shown = false; c->wm_flags = 0; c->icccm_props.input_hint = true; c->icccm_props.take_focus = false; c->icccm_props.delete_window = false; c->size_hints.flags = 0; c->honor_size_hints = honor_size_hints; return c; } void initialize_client(node_t *n) { xcb_window_t win = n->id; client_t *c = n->client; xcb_icccm_get_wm_protocols_reply_t protos; if (xcb_icccm_get_wm_protocols_reply(dpy, xcb_icccm_get_wm_protocols(dpy, win, ewmh->WM_PROTOCOLS), &protos, NULL) == 1) { for (uint32_t i = 0; i < protos.atoms_len; i++) { if (protos.atoms[i] == WM_TAKE_FOCUS) { c->icccm_props.take_focus = true; } else if (protos.atoms[i] == WM_DELETE_WINDOW) { c->icccm_props.delete_window = true; } } xcb_icccm_get_wm_protocols_reply_wipe(&protos); } xcb_ewmh_get_atoms_reply_t wm_state; if (xcb_ewmh_get_wm_state_reply(ewmh, xcb_ewmh_get_wm_state(ewmh, win), &wm_state, NULL) == 1) { for (unsigned int i = 0; i < wm_state.atoms_len && i < MAX_WM_STATES; i++) { #define HANDLE_WM_STATE(s) \ if (wm_state.atoms[i] == ewmh->_NET_WM_STATE_##s) { \ c->wm_flags |= WM_FLAG_##s; continue; \ } HANDLE_WM_STATE(MODAL) HANDLE_WM_STATE(STICKY) HANDLE_WM_STATE(MAXIMIZED_VERT) HANDLE_WM_STATE(MAXIMIZED_HORZ) HANDLE_WM_STATE(SHADED) HANDLE_WM_STATE(SKIP_TASKBAR) HANDLE_WM_STATE(SKIP_PAGER) HANDLE_WM_STATE(HIDDEN) HANDLE_WM_STATE(FULLSCREEN) HANDLE_WM_STATE(ABOVE) HANDLE_WM_STATE(BELOW) HANDLE_WM_STATE(DEMANDS_ATTENTION) #undef HANDLE_WM_STATE } xcb_ewmh_get_atoms_reply_wipe(&wm_state); } xcb_icccm_wm_hints_t hints; if (xcb_icccm_get_wm_hints_reply(dpy, xcb_icccm_get_wm_hints(dpy, win), &hints, NULL) == 1 && (hints.flags & XCB_ICCCM_WM_HINT_INPUT)) { c->icccm_props.input_hint = hints.input; } xcb_icccm_get_wm_normal_hints_reply(dpy, xcb_icccm_get_wm_normal_hints(dpy, win), &c->size_hints, NULL); } bool is_focusable(node_t *n) { for (node_t *f = first_extrema(n); f != NULL; f = next_leaf(f, n)) { if (f->client != NULL && !f->hidden) { return true; } } return false; } bool is_leaf(node_t *n) { return (n != NULL && n->first_child == NULL && n->second_child == NULL); } bool is_first_child(node_t *n) { return (n != NULL && n->parent != NULL && n->parent->first_child == n); } bool is_second_child(node_t *n) { return (n != NULL && n->parent != NULL && n->parent->second_child == n); } unsigned int clients_count_in(node_t *n) { if (n == NULL) { return 0; } else { return (n->client != NULL ? 1 : 0) + clients_count_in(n->first_child) + clients_count_in(n->second_child); } } node_t *brother_tree(node_t *n) { if (n == NULL || n->parent == NULL) { return NULL; } if (is_first_child(n)) { return n->parent->second_child; } else { return n->parent->first_child; } } node_t *first_extrema(node_t *n) { if (n == NULL) { return NULL; } else if (n->first_child == NULL) { return n; } else { return first_extrema(n->first_child); } } node_t *second_extrema(node_t *n) { if (n == NULL) { return NULL; } else if (n->second_child == NULL) { return n; } else { return second_extrema(n->second_child); } } node_t *first_focusable_leaf(node_t *n) { for (node_t *f = first_extrema(n); f != NULL; f = next_leaf(f, n)) { if (f->client != NULL && !f->hidden) { return f; } } return NULL; } node_t *next_node(node_t *n) { if (n == NULL) { return NULL; } if (n->second_child != NULL) { return first_extrema(n->second_child); } else { node_t *p = n; while (is_second_child(p)) { p = p->parent; } if (is_first_child(p)) { return p->parent; } else { return NULL; } } } node_t *prev_node(node_t *n) { if (n == NULL) { return NULL; } if (n->first_child != NULL) { return second_extrema(n->first_child); } else { node_t *p = n; while (is_first_child(p)) { p = p->parent; } if (is_second_child(p)) { return p->parent; } else { return NULL; } } } node_t *next_leaf(node_t *n, node_t *r) { if (n == NULL) { return NULL; } node_t *p = n; while (is_second_child(p) && p != r) { p = p->parent; } if (p == r) { return NULL; } return first_extrema(p->parent->second_child); } node_t *prev_leaf(node_t *n, node_t *r) { if (n == NULL) { return NULL; } node_t *p = n; while (is_first_child(p) && p != r) { p = p->parent; } if (p == r) { return NULL; } return second_extrema(p->parent->first_child); } node_t *next_tiled_leaf(node_t *n, node_t *r) { node_t *next = next_leaf(n, r); if (next == NULL || (next->client != NULL && !next->vacant)) { return next; } else { return next_tiled_leaf(next, r); } } node_t *prev_tiled_leaf(node_t *n, node_t *r) { node_t *prev = prev_leaf(n, r); if (prev == NULL || (prev->client != NULL && !prev->vacant)) { return prev; } else { return prev_tiled_leaf(prev, r); } } /* Returns true if *b* is adjacent to *a* in the direction *dir* */ bool is_adjacent(node_t *a, node_t *b, direction_t dir) { switch (dir) { case DIR_EAST: return (a->rectangle.x + a->rectangle.width) == b->rectangle.x; break; case DIR_SOUTH: return (a->rectangle.y + a->rectangle.height) == b->rectangle.y; break; case DIR_WEST: return (b->rectangle.x + b->rectangle.width) == a->rectangle.x; break; case DIR_NORTH: return (b->rectangle.y + b->rectangle.height) == a->rectangle.y; break; } return false; } node_t *find_fence(node_t *n, direction_t dir) { node_t *p; if (n == NULL) { return NULL; } p = n->parent; while (p != NULL) { if ((dir == DIR_NORTH && p->split_type == TYPE_HORIZONTAL && p->rectangle.y < n->rectangle.y) || (dir == DIR_WEST && p->split_type == TYPE_VERTICAL && p->rectangle.x < n->rectangle.x) || (dir == DIR_SOUTH && p->split_type == TYPE_HORIZONTAL && (p->rectangle.y + p->rectangle.height) > (n->rectangle.y + n->rectangle.height)) || (dir == DIR_EAST && p->split_type == TYPE_VERTICAL && (p->rectangle.x + p->rectangle.width) > (n->rectangle.x + n->rectangle.width))) return p; p = p->parent; } return NULL; } /* returns *true* if *a* is a child of *b* */ bool is_child(node_t *a, node_t *b) { if (a == NULL || b == NULL) { return false; } return (a->parent != NULL && a->parent == b); } /* returns *true* if *a* is a descendant of *b* */ bool is_descendant(node_t *a, node_t *b) { if (a == NULL || b == NULL) { return false; } while (a != b && a != NULL) { a = a->parent; } return a == b; } bool find_by_id(uint32_t id, coordinates_t *loc) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { node_t *n = find_by_id_in(d->root, id); if (n != NULL) { loc->monitor = m; loc->desktop = d; loc->node = n; return true; } } } return false; } node_t *find_by_id_in(node_t *r, uint32_t id) { if (r == NULL) { return NULL; } else if (r->id == id) { return r; } else { node_t *f = find_by_id_in(r->first_child, id); if (f != NULL) { return f; } else { return find_by_id_in(r->second_child, id); } } } void find_any_node(coordinates_t *ref, coordinates_t *dst, node_select_t *sel) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { if (find_any_node_in(m, d, d->root, ref, dst, sel)) { return; } } } } bool find_any_node_in(monitor_t *m, desktop_t *d, node_t *n, coordinates_t *ref, coordinates_t *dst, node_select_t *sel) { if (n == NULL) { return false; } else { coordinates_t loc = {m, d, n}; if (node_matches(&loc, ref, sel)) { *dst = loc; return true; } else { if (find_any_node_in(m, d, n->first_child, ref, dst, sel)) { return true; } else { return find_any_node_in(m, d, n->second_child, ref, dst, sel); } } } } void find_first_ancestor(coordinates_t *ref, coordinates_t *dst, node_select_t *sel) { if (ref->node == NULL) { return; } coordinates_t loc = {ref->monitor, ref->desktop, ref->node}; while ((loc.node = loc.node->parent) != NULL) { if (node_matches(&loc, ref, sel)) { *dst = loc; return; } } } /* Based on https://github.com/ntrrgc/right-window */ void find_nearest_neighbor(coordinates_t *ref, coordinates_t *dst, direction_t dir, node_select_t *sel) { xcb_rectangle_t rect = get_rectangle(ref->monitor, ref->desktop, ref->node); uint32_t md = UINT32_MAX, mr = UINT32_MAX; for (monitor_t *m = mon_head; m != NULL; m = m->next) { desktop_t *d = m->desk; for (node_t *f = first_extrema(d->root); f != NULL; f = next_leaf(f, d->root)) { coordinates_t loc = {m, d, f}; xcb_rectangle_t r = get_rectangle(m, d, f); if (f == ref->node || f->client == NULL || f->hidden || is_descendant(f, ref->node) || !node_matches(&loc, ref, sel) || !on_dir_side(rect, r, dir)) { continue; } uint32_t fd = boundary_distance(rect, r, dir); uint32_t fr = history_rank(f); if (fd < md || (fd == md && fr < mr)) { md = fd; mr = fr; *dst = loc; } } } } unsigned int node_area(desktop_t *d, node_t *n) { if (n == NULL) { return 0; } return area(get_rectangle(NULL, d, n)); } int tiled_count(node_t *n, bool include_receptacles) { if (n == NULL) { return 0; } int cnt = 0; for (node_t *f = first_extrema(n); f != NULL; f = next_leaf(f, n)) { if (!f->hidden && ((include_receptacles && f->client == NULL) || (f->client != NULL && IS_TILED(f->client)))) { cnt++; } } return cnt; } void find_by_area(area_peak_t ap, coordinates_t *ref, coordinates_t *dst, node_select_t *sel) { unsigned int p_area; if (ap == AREA_BIGGEST) { p_area = 0; } else { p_area = UINT_MAX; } for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { for (node_t *f = first_extrema(d->root); f != NULL; f = next_leaf(f, d->root)) { coordinates_t loc = {m, d, f}; if (f->vacant || !node_matches(&loc, ref, sel)) { continue; } unsigned int f_area = node_area(d, f); if ((ap == AREA_BIGGEST && f_area > p_area) || (ap == AREA_SMALLEST && f_area < p_area)) { *dst = loc; p_area = f_area; } } } } } void rotate_tree(node_t *n, int deg) { rotate_tree_rec(n, deg); rebuild_constraints_from_leaves(n); rebuild_constraints_towards_root(n); } void rotate_tree_rec(node_t *n, int deg) { if (n == NULL || is_leaf(n) || deg == 0) { return; } node_t *tmp; if ((deg == 90 && n->split_type == TYPE_HORIZONTAL) || (deg == 270 && n->split_type == TYPE_VERTICAL) || deg == 180) { tmp = n->first_child; n->first_child = n->second_child; n->second_child = tmp; n->split_ratio = 1.0 - n->split_ratio; } if (deg != 180) { if (n->split_type == TYPE_HORIZONTAL) { n->split_type = TYPE_VERTICAL; } else if (n->split_type == TYPE_VERTICAL) { n->split_type = TYPE_HORIZONTAL; } } rotate_tree_rec(n->first_child, deg); rotate_tree_rec(n->second_child, deg); } void flip_tree(node_t *n, flip_t flp) { if (n == NULL || is_leaf(n)) { return; } node_t *tmp; if ((flp == FLIP_HORIZONTAL && n->split_type == TYPE_HORIZONTAL) || (flp == FLIP_VERTICAL && n->split_type == TYPE_VERTICAL)) { tmp = n->first_child; n->first_child = n->second_child; n->second_child = tmp; n->split_ratio = 1.0 - n->split_ratio; } flip_tree(n->first_child, flp); flip_tree(n->second_child, flp); } void equalize_tree(node_t *n) { if (n == NULL || n->vacant) { return; } else { n->split_ratio = split_ratio; equalize_tree(n->first_child); equalize_tree(n->second_child); } } int balance_tree(node_t *n) { if (n == NULL || n->vacant) { return 0; } else if (is_leaf(n)) { return 1; } else { int b1 = balance_tree(n->first_child); int b2 = balance_tree(n->second_child); int b = b1 + b2; if (b1 > 0 && b2 > 0) { n->split_ratio = (double) b1 / b; } return b; } } /* Adjust the split ratios so that they keep their position * despite the potential alteration of their rectangle. */ void adjust_ratios(node_t *n, xcb_rectangle_t rect) { #define NULL_OR_VACANT(n) ((n) == NULL || (n)->vacant) if (NULL_OR_VACANT(n)) { return; } double ratio; if (n->split_type == TYPE_VERTICAL) { double position = (double) n->rectangle.x + n->split_ratio * (double) n->rectangle.width; ratio = (position - (double) rect.x) / (double) rect.width; } else { double position = (double) n->rectangle.y + n->split_ratio * (double) n->rectangle.height; ratio = (position - (double) rect.y) / (double) rect.height; } ratio = MAX(0.0, ratio); ratio = MIN(1.0, ratio); n->split_ratio = ratio; xcb_rectangle_t first_rect; xcb_rectangle_t second_rect; unsigned int fence; if (NULL_OR_VACANT(n->first_child)) { adjust_ratios(n->second_child, rect); return; } if (NULL_OR_VACANT(n->second_child)) { adjust_ratios(n->first_child, rect); return; } #undef NULL_OR_VACANT if (n->split_type == TYPE_VERTICAL) { fence = rect.width * n->split_ratio; first_rect = (xcb_rectangle_t) {rect.x, rect.y, fence, rect.height}; second_rect = (xcb_rectangle_t) {rect.x + fence, rect.y, rect.width - fence, rect.height}; } else { fence = rect.height * n->split_ratio; first_rect = (xcb_rectangle_t) {rect.x, rect.y, rect.width, fence}; second_rect = (xcb_rectangle_t) {rect.x, rect.y + fence, rect.width, rect.height - fence}; } adjust_ratios(n->first_child, first_rect); adjust_ratios(n->second_child, second_rect); } void unlink_node(monitor_t *m, desktop_t *d, node_t *n) { if (d == NULL || n == NULL) { return; } node_t *p = n->parent; if (p == NULL) { d->root = NULL; d->focus = NULL; put_status(SBSC_MASK_REPORT); } else { if (d->focus == p || is_descendant(d->focus, n)) { d->focus = NULL; } history_remove(d, p, false); cancel_presel(m, d, p); if (p->sticky) { m->sticky_count--; } node_t *b = brother_tree(n); node_t *g = p->parent; b->parent = g; if (g != NULL) { if (is_first_child(p)) { g->first_child = b; } else { g->second_child = b; } } else { d->root = b; } if (!n->vacant && removal_adjustment) { if (automatic_scheme == SCHEME_SPIRAL) { if (is_first_child(n)) { rotate_tree(b, 270); } else { rotate_tree(b, 90); } } else if (automatic_scheme == SCHEME_LONGEST_SIDE || g == NULL) { if (p != NULL) { if (p->rectangle.width > p->rectangle.height) { b->split_type = TYPE_VERTICAL; } else { b->split_type = TYPE_HORIZONTAL; } } } else if (automatic_scheme == SCHEME_ALTERNATE) { if (g->split_type == TYPE_HORIZONTAL) { b->split_type = TYPE_VERTICAL; } else { b->split_type = TYPE_HORIZONTAL; } } } free(p); n->parent = NULL; propagate_flags_upward(m, d, b); } } void close_node(node_t *n) { if (n == NULL) { return; } else if (n->client != NULL) { if (n->client->icccm_props.delete_window) { send_client_message(n->id, ewmh->WM_PROTOCOLS, WM_DELETE_WINDOW); } else { xcb_kill_client(dpy, n->id); } } else { close_node(n->first_child); close_node(n->second_child); } } void kill_node(monitor_t *m, desktop_t *d, node_t *n) { if (n == NULL) { return; } if (IS_RECEPTACLE(n)) { put_status(SBSC_MASK_NODE_REMOVE, "node_remove 0x%08X 0x%08X 0x%08X\n", m->id, d->id, n->id); remove_node(m, d, n); } else { for (node_t *f = first_extrema(n); f != NULL; f = next_leaf(f, n)) { if (f->client != NULL) { xcb_kill_client(dpy, f->id); } } } } void remove_node(monitor_t *m, desktop_t *d, node_t *n) { if (n == NULL) { return; } unlink_node(m, d, n); history_remove(d, n, true); remove_stack_node(n); cancel_presel_in(m, d, n); if (m->sticky_count > 0 && d == m->desk) { m->sticky_count -= sticky_count(n); } clients_count -= clients_count_in(n); if (is_descendant(grabbed_node, n)) { grabbed_node = NULL; } free_node(n); if (single_monocle && d->layout != LAYOUT_MONOCLE && tiled_count(d->root, true) <= 1) { set_layout(m, d, LAYOUT_MONOCLE, false); } ewmh_update_client_list(false); ewmh_update_client_list(true); if (mon != NULL && d->focus == NULL) { if (d == mon->desk) { focus_node(m, d, NULL); } else { activate_node(m, d, NULL); } } } void free_node(node_t *n) { if (n == NULL) { return; } node_t *first_child = n->first_child; node_t *second_child = n->second_child; free(n->client); free(n); free_node(first_child); free_node(second_child); } bool swap_nodes(monitor_t *m1, desktop_t *d1, node_t *n1, monitor_t *m2, desktop_t *d2, node_t *n2, bool follow) { if (n1 == NULL || n2 == NULL || n1 == n2 || is_descendant(n1, n2) || is_descendant(n2, n1) || (d1 != d2 && ((m1->sticky_count > 0 && sticky_count(n1) > 0) || (m2->sticky_count > 0 && sticky_count(n2) > 0)))) { return false; } put_status(SBSC_MASK_NODE_SWAP, "node_swap 0x%08X 0x%08X 0x%08X 0x%08X 0x%08X 0x%08X\n", m1->id, d1->id, n1->id, m2->id, d2->id, n2->id); node_t *pn1 = n1->parent; node_t *pn2 = n2->parent; bool n1_first_child = is_first_child(n1); bool n2_first_child = is_first_child(n2); bool n1_held_focus = is_descendant(d1->focus, n1); bool n2_held_focus = is_descendant(d2->focus, n2); node_t *last_d1_focus = d1->focus; node_t *last_d2_focus = d2->focus; if (pn1 != NULL) { if (n1_first_child) { pn1->first_child = n2; } else { pn1->second_child = n2; } } if (pn2 != NULL) { if (n2_first_child) { pn2->first_child = n1; } else { pn2->second_child = n1; } } n1->parent = pn2; n2->parent = pn1; propagate_flags_upward(m2, d2, n1); propagate_flags_upward(m1, d1, n2); if (d1 != d2) { if (d1->root == n1) { d1->root = n2; } if (d2->root == n2) { d2->root = n1; } if (n1_held_focus) { d1->focus = n2_held_focus ? last_d2_focus : n2; } if (n2_held_focus) { d2->focus = n1_held_focus ? last_d1_focus : n1; } if (m1 != m2) { adapt_geometry(&m2->rectangle, &m1->rectangle, n2); adapt_geometry(&m1->rectangle, &m2->rectangle, n1); } ewmh_set_wm_desktop(n1, d2); ewmh_set_wm_desktop(n2, d1); history_remove(d1, n1, true); history_remove(d2, n2, true); bool d1_was_focused = (d1 == mon->desk); bool d2_was_focused = (d2 == mon->desk); if (m1->desk != d1 && m2->desk == d2) { show_node(d2, n1); if (!follow || !d2_was_focused || !n2_held_focus) { hide_node(d2, n2); } } else if (m1->desk == d1 && m2->desk != d2) { if (!follow || !d1_was_focused || !n1_held_focus) { hide_node(d1, n1); } show_node(d1, n2); } if (single_monocle) { layout_t l1 = tiled_count(d1->root, true) <= 1 ? LAYOUT_MONOCLE : d1->user_layout; layout_t l2 = tiled_count(d2->root, true) <= 1 ? LAYOUT_MONOCLE : d2->user_layout; set_layout(m1, d1, l1, false); set_layout(m2, d2, l2, false); } if (n1_held_focus) { if (d1_was_focused) { if (follow) { focus_node(m2, d2, last_d1_focus); } else { focus_node(m1, d1, d1->focus); } } else { activate_node(m1, d1, d1->focus); } } else { draw_border(n2, is_descendant(n2, d1->focus), (m1 == mon)); } if (n2_held_focus) { if (d2_was_focused) { if (follow) { focus_node(m1, d1, last_d2_focus); } else { focus_node(m2, d2, d2->focus); } } else { activate_node(m2, d2, d2->focus); } } else { draw_border(n1, is_descendant(n1, d2->focus), (m2 == mon)); } } else { if (!n1_held_focus) { draw_border(n1, is_descendant(n1, d2->focus), (m2 == mon)); } if (!n2_held_focus) { draw_border(n2, is_descendant(n2, d1->focus), (m1 == mon)); } } arrange(m1, d1); if (d1 != d2) { arrange(m2, d2); } else { if (pointer_follows_focus && (n1_held_focus || n2_held_focus)) { center_pointer(get_rectangle(m1, d1, d1->focus)); } } return true; } bool transfer_node(monitor_t *ms, desktop_t *ds, node_t *ns, monitor_t *md, desktop_t *dd, node_t *nd, bool follow) { if (ns == NULL || ns == nd || is_child(ns, nd) || is_descendant(nd, ns)) { return false; } unsigned int sc = (ms->sticky_count > 0 && ds == ms->desk) ? sticky_count(ns) : 0; if (sticky_still && sc > 0 && dd != md->desk) { return false; } put_status(SBSC_MASK_NODE_TRANSFER, "node_transfer 0x%08X 0x%08X 0x%08X 0x%08X 0x%08X 0x%08X\n", ms->id, ds->id, ns->id, md->id, dd->id, nd!=NULL?nd->id:0); bool held_focus = is_descendant(ds->focus, ns); /* avoid ending up with a dangling pointer (because of unlink_node) */ node_t *last_ds_focus = is_child(ns, ds->focus) ? NULL : ds->focus; bool ds_was_focused = (ds == mon->desk); if (held_focus && ds_was_focused) { clear_input_focus(); } unlink_node(ms, ds, ns); insert_node(md, dd, ns, nd); if (md != ms) { if (ns->client == NULL || monitor_from_client(ns->client) != md) { adapt_geometry(&ms->rectangle, &md->rectangle, ns); } ms->sticky_count -= sc; md->sticky_count += sc; } if (ds != dd) { ewmh_set_wm_desktop(ns, dd); if (sticky_still) { if (ds == ms->desk && dd != md->desk) { hide_node(ds, ns); } else if (ds != ms->desk && dd == md->desk) { show_node(dd, ns); } } } history_remove(ds, ns, true); stack(dd, ns, false); if (ds == dd) { if (held_focus) { if (ds_was_focused) { focus_node(ms, ds, last_ds_focus); } else { activate_node(ms, ds, last_ds_focus); } } else { draw_border(ns, is_descendant(ns, ds->focus), (ms == mon)); } } else { if (single_monocle) { if (ds->layout != LAYOUT_MONOCLE && tiled_count(ds->root, true) <= 1) { set_layout(ms, ds, LAYOUT_MONOCLE, false); } if (dd->layout == LAYOUT_MONOCLE && tiled_count(dd->root, true) > 1) { set_layout(md, dd, dd->user_layout, false); } } if (held_focus) { if (follow) { if (ds_was_focused) { focus_node(md, dd, last_ds_focus); } activate_node(ms, ds, ds->focus); } else { if (ds_was_focused) { focus_node(ms, ds, ds->focus); } else { activate_node(ms, ds, ds->focus); } } } if (!held_focus || !follow || !ds_was_focused) { if (dd->focus == ns) { if (dd == mon->desk) { focus_node(md, dd, held_focus ? last_ds_focus : ns); } else { activate_node(md, dd, held_focus ? last_ds_focus : ns); } } else { draw_border(ns, is_descendant(ns, dd->focus), (md == mon)); } } } arrange(ms, ds); if (ds != dd) { arrange(md, dd); } return true; } bool find_closest_node(coordinates_t *ref, coordinates_t *dst, cycle_dir_t dir, node_select_t *sel) { monitor_t *m = ref->monitor; desktop_t *d = ref->desktop; node_t *n = ref->node; n = (dir == CYCLE_PREV ? prev_node(n) : next_node(n)); #define HANDLE_BOUNDARIES(m, d, n) \ while (n == NULL) { \ d = (dir == CYCLE_PREV ? d->prev : d->next); \ if (d == NULL) { \ m = (dir == CYCLE_PREV ? m->prev : m->next); \ if (m == NULL) { \ m = (dir == CYCLE_PREV ? mon_tail : mon_head); \ } \ d = (dir == CYCLE_PREV ? m->desk_tail : m->desk_head); \ } \ n = (dir == CYCLE_PREV ? second_extrema(d->root) : first_extrema(d->root)); \ if (ref->node == NULL && d == ref->desktop) { \ break; \ } \ } HANDLE_BOUNDARIES(m, d, n); while (n != ref->node) { coordinates_t loc = {m, d, n}; if (node_matches(&loc, ref, sel)) { *dst = loc; return true; } n = (dir == CYCLE_PREV ? prev_node(n) : next_node(n)); HANDLE_BOUNDARIES(m, d, n); if (ref->node == NULL && d == ref->desktop) { break; } } #undef HANDLE_BOUNDARIES return false; } void circulate_leaves(monitor_t *m, desktop_t *d, node_t *n, circulate_dir_t dir) { if (tiled_count(n, false) < 2) { return; } node_t *p = d->focus->parent; bool focus_first_child = is_first_child(d->focus); if (dir == CIRCULATE_FORWARD) { node_t *e = second_extrema(n); while (e != NULL && (e->client == NULL || !IS_TILED(e->client))) { e = prev_leaf(e, n); } for (node_t *s = e, *f = prev_tiled_leaf(s, n); f != NULL; s = prev_tiled_leaf(f, n), f = prev_tiled_leaf(s, n)) { swap_nodes(m, d, f, m, d, s, false); } } else { node_t *e = first_extrema(n); while (e != NULL && (e->client == NULL || !IS_TILED(e->client))) { e = next_leaf(e, n); } for (node_t *f = e, *s = next_tiled_leaf(f, n); s != NULL; f = next_tiled_leaf(s, n), s = next_tiled_leaf(f, n)) { swap_nodes(m, d, f, m, d, s, false); } } if (p != NULL) { node_t *f = focus_first_child ? p->first_child : p->second_child; if (is_leaf(f)) { if (d == mon->desk) { focus_node(m, d, f); } else { activate_node(m, d, f); } } } } void set_vacant(monitor_t *m, desktop_t *d, node_t *n, bool value) { if (n->vacant == value) { return; } propagate_vacant_downward(m, d, n, value); propagate_vacant_upward(m, d, n); } void set_vacant_local(monitor_t *m, desktop_t *d, node_t *n, bool value) { if (n->vacant == value) { return; } n->vacant = value; if (value) { cancel_presel(m, d, n); } } void propagate_vacant_downward(monitor_t *m, desktop_t *d, node_t *n, bool value) { if (n == NULL) { return; } set_vacant_local(m, d, n, value); propagate_vacant_downward(m, d, n->first_child, value); propagate_vacant_downward(m, d, n->second_child, value); } void propagate_vacant_upward(monitor_t *m, desktop_t *d, node_t *n) { if (n == NULL) { return; } node_t *p = n->parent; if (p != NULL) { set_vacant_local(m, d, p, (p->first_child->vacant && p->second_child->vacant)); } propagate_vacant_upward(m, d, p); } bool set_layer(monitor_t *m, desktop_t *d, node_t *n, stack_layer_t l) { if (n == NULL || n->client == NULL || n->client->layer == l) { return false; } n->client->last_layer = n->client->layer; n->client->layer = l; if (l == LAYER_ABOVE) { n->client->wm_flags |= WM_FLAG_ABOVE; n->client->wm_flags &= ~WM_FLAG_BELOW; } else if (l == LAYER_BELOW) { n->client->wm_flags |= WM_FLAG_BELOW; n->client->wm_flags &= ~WM_FLAG_ABOVE; } else { n->client->wm_flags &= ~(WM_FLAG_ABOVE | WM_FLAG_BELOW); } ewmh_wm_state_update(n); put_status(SBSC_MASK_NODE_LAYER, "node_layer 0x%08X 0x%08X 0x%08X %s\n", m->id, d->id, n->id, LAYER_STR(l)); if (d->focus == n) { neutralize_occluding_windows(m, d, n); } stack(d, n, (d->focus == n)); return true; } bool set_state(monitor_t *m, desktop_t *d, node_t *n, client_state_t s) { if (n == NULL || n->client == NULL || n->client->state == s) { return false; } client_t *c = n->client; bool was_tiled = IS_TILED(c); c->last_state = c->state; c->state = s; switch (c->last_state) { case STATE_TILED: case STATE_PSEUDO_TILED: break; case STATE_FLOATING: set_floating(m, d, n, false); break; case STATE_FULLSCREEN: set_fullscreen(m, d, n, false); break; } put_status(SBSC_MASK_NODE_STATE, "node_state 0x%08X 0x%08X 0x%08X %s off\n", m->id, d->id, n->id, STATE_STR(c->last_state)); switch (c->state) { case STATE_TILED: case STATE_PSEUDO_TILED: break; case STATE_FLOATING: set_floating(m, d, n, true); break; case STATE_FULLSCREEN: set_fullscreen(m, d, n, true); break; } put_status(SBSC_MASK_NODE_STATE, "node_state 0x%08X 0x%08X 0x%08X %s on\n", m->id, d->id, n->id, STATE_STR(c->state)); if (n == m->desk->focus) { put_status(SBSC_MASK_REPORT); } if (single_monocle && was_tiled != IS_TILED(c)) { if (was_tiled && d->layout != LAYOUT_MONOCLE && tiled_count(d->root, true) <= 1) { set_layout(m, d, LAYOUT_MONOCLE, false); } else if (!was_tiled && d->layout == LAYOUT_MONOCLE && tiled_count(d->root, true) > 1) { set_layout(m, d, d->user_layout, false); } } return true; } void set_floating(monitor_t *m, desktop_t *d, node_t *n, bool value) { if (n == NULL) { return; } cancel_presel(m, d, n); if (!n->hidden) { set_vacant(m, d, n, value); } if (!value && d->focus == n) { neutralize_occluding_windows(m, d, n); } stack(d, n, (d->focus == n)); } void set_fullscreen(monitor_t *m, desktop_t *d, node_t *n, bool value) { if (n == NULL) { return; } client_t *c = n->client; cancel_presel(m, d, n); if (!n->hidden) { set_vacant(m, d, n, value); } if (value) { c->wm_flags |= WM_FLAG_FULLSCREEN; } else { c->wm_flags &= ~WM_FLAG_FULLSCREEN; if (d->focus == n) { neutralize_occluding_windows(m, d, n); } } ewmh_wm_state_update(n); stack(d, n, (d->focus == n)); } void neutralize_occluding_windows(monitor_t *m, desktop_t *d, node_t *n) { bool changed = false; for (node_t *f = first_extrema(n); f != NULL; f = next_leaf(f, n)) { for (node_t *a = first_extrema(d->root); a != NULL; a = next_leaf(a, d->root)) { if (a != f && a->client != NULL && f->client != NULL && IS_FULLSCREEN(a->client) && stack_cmp(f->client, a->client) < 0) { set_state(m, d, a, a->client->last_state); changed = true; } } } if (changed) { arrange(m, d); } } void rebuild_constraints_from_leaves(node_t *n) { if (n == NULL || is_leaf(n)) { return; } else { rebuild_constraints_from_leaves(n->first_child); rebuild_constraints_from_leaves(n->second_child); update_constraints(n); } } void rebuild_constraints_towards_root(node_t *n) { if (n == NULL) { return; } node_t *p = n->parent; if (p != NULL) { update_constraints(p); } rebuild_constraints_towards_root(p); } void update_constraints(node_t *n) { if (n == NULL || is_leaf(n)) { return; } if (n->split_type == TYPE_VERTICAL) { n->constraints.min_width = n->first_child->constraints.min_width + n->second_child->constraints.min_width; n->constraints.min_height = MAX(n->first_child->constraints.min_height, n->second_child->constraints.min_height); } else { n->constraints.min_width = MAX(n->first_child->constraints.min_width, n->second_child->constraints.min_width); n->constraints.min_height = n->first_child->constraints.min_height + n->second_child->constraints.min_height; } } void propagate_flags_upward(monitor_t *m, desktop_t *d, node_t *n) { if (n == NULL) { return; } node_t *p = n->parent; if (p != NULL) { set_vacant_local(m, d, p, (p->first_child->vacant && p->second_child->vacant)); set_hidden_local(m, d, p, (p->first_child->hidden && p->second_child->hidden)); update_constraints(p); } propagate_flags_upward(m, d, p); } void set_hidden(monitor_t *m, desktop_t *d, node_t *n, bool value) { if (n == NULL || n->hidden == value) { return; } bool held_focus = is_descendant(d->focus, n); propagate_hidden_downward(m, d, n, value); propagate_hidden_upward(m, d, n); put_status(SBSC_MASK_NODE_FLAG, "node_flag 0x%08X 0x%08X 0x%08X hidden %s\n", m->id, d->id, n->id, ON_OFF_STR(value)); if (held_focus || d->focus == NULL) { if (d->focus != NULL) { d->focus = NULL; draw_border(n, false, (mon == m)); } if (d == mon->desk) { focus_node(m, d, d->focus); } else { activate_node(m, d, d->focus); } } if (single_monocle) { if (value && d->layout != LAYOUT_MONOCLE && tiled_count(d->root, true) <= 1) { set_layout(m, d, LAYOUT_MONOCLE, false); } else if (!value && d->layout == LAYOUT_MONOCLE && tiled_count(d->root, true) > 1) { set_layout(m, d, d->user_layout, false); } } } void set_hidden_local(monitor_t *m, desktop_t *d, node_t *n, bool value) { if (n->hidden == value) { return; } n->hidden = value; if (n->client != NULL) { if (n->client->shown) { window_set_visibility(n->id, !value); } if (IS_TILED(n->client)) { set_vacant(m, d, n, value); } if (value) { n->client->wm_flags |= WM_FLAG_HIDDEN; } else { n->client->wm_flags &= ~WM_FLAG_HIDDEN; } ewmh_wm_state_update(n); } } void propagate_hidden_downward(monitor_t *m, desktop_t *d, node_t *n, bool value) { if (n == NULL) { return; } set_hidden_local(m, d, n, value); propagate_hidden_downward(m, d, n->first_child, value); propagate_hidden_downward(m, d, n->second_child, value); } void propagate_hidden_upward(monitor_t *m, desktop_t *d, node_t *n) { if (n == NULL) { return; } node_t *p = n->parent; if (p != NULL) { set_hidden_local(m, d, p, p->first_child->hidden && p->second_child->hidden); } propagate_hidden_upward(m, d, p); } void set_sticky(monitor_t *m, desktop_t *d, node_t *n, bool value) { if (n == NULL || n->sticky == value) { return; } if (d != m->desk) { transfer_node(m, d, n, m, m->desk, m->desk->focus, false); } n->sticky = value; if (value) { m->sticky_count++; } else { m->sticky_count--; } if (n->client != NULL) { if (value) { n->client->wm_flags |= WM_FLAG_STICKY; } else { n->client->wm_flags &= ~WM_FLAG_STICKY; } ewmh_wm_state_update(n); } put_status(SBSC_MASK_NODE_FLAG, "node_flag 0x%08X 0x%08X 0x%08X sticky %s\n", m->id, d->id, n->id, ON_OFF_STR(value)); if (n == m->desk->focus) { put_status(SBSC_MASK_REPORT); } } void set_private(monitor_t *m, desktop_t *d, node_t *n, bool value) { if (n == NULL || n->private == value) { return; } n->private = value; put_status(SBSC_MASK_NODE_FLAG, "node_flag 0x%08X 0x%08X 0x%08X private %s\n", m->id, d->id, n->id, ON_OFF_STR(value)); if (n == m->desk->focus) { put_status(SBSC_MASK_REPORT); } } void set_locked(monitor_t *m, desktop_t *d, node_t *n, bool value) { if (n == NULL || n->locked == value) { return; } n->locked = value; put_status(SBSC_MASK_NODE_FLAG, "node_flag 0x%08X 0x%08X 0x%08X locked %s\n", m->id, d->id, n->id, ON_OFF_STR(value)); if (n == m->desk->focus) { put_status(SBSC_MASK_REPORT); } } void set_marked(monitor_t *m, desktop_t *d, node_t *n, bool value) { if (n == NULL || n->marked == value) { return; } n->marked = value; put_status(SBSC_MASK_NODE_FLAG, "node_flag 0x%08X 0x%08X 0x%08X marked %s\n", m->id, d->id, n->id, ON_OFF_STR(value)); if (n == m->desk->focus) { put_status(SBSC_MASK_REPORT); } } void set_urgent(monitor_t *m, desktop_t *d, node_t *n, bool value) { if (value && mon->desk->focus == n) { return; } n->client->urgent = value; if (value) { n->client->wm_flags |= WM_FLAG_DEMANDS_ATTENTION; } else { n->client->wm_flags &= ~WM_FLAG_DEMANDS_ATTENTION; } ewmh_wm_state_update(n); put_status(SBSC_MASK_NODE_FLAG, "node_flag 0x%08X 0x%08X 0x%08X urgent %s\n", m->id, d->id, n->id, ON_OFF_STR(value)); put_status(SBSC_MASK_REPORT); } xcb_rectangle_t get_rectangle(monitor_t *m, desktop_t *d, node_t *n) { if (n == NULL) { return m->rectangle; } client_t *c = n->client; if (c != NULL) { if (IS_FLOATING(c)) { return c->floating_rectangle; } else { return c->tiled_rectangle; } } else { int wg = (d == NULL ? 0 : (gapless_monocle && d->layout == LAYOUT_MONOCLE ? 0 : d->window_gap)); xcb_rectangle_t rect = n->rectangle; rect.width -= wg; rect.height -= wg; return rect; } } void listen_enter_notify(node_t *n, bool enable) { uint32_t mask = CLIENT_EVENT_MASK | (enable ? XCB_EVENT_MASK_ENTER_WINDOW : 0); for (node_t *f = first_extrema(n); f != NULL; f = next_leaf(f, n)) { if (f->client == NULL) { continue; } xcb_change_window_attributes(dpy, f->id, XCB_CW_EVENT_MASK, &mask); if (f->presel != NULL) { xcb_change_window_attributes(dpy, f->presel->feedback, XCB_CW_EVENT_MASK, &mask); } } } void regenerate_ids_in(node_t *n) { if (n == NULL || n->client != NULL) { return; } n->id = xcb_generate_id(dpy); regenerate_ids_in(n->first_child); regenerate_ids_in(n->second_child); } #define DEF_FLAG_COUNT(flag) \ unsigned int flag##_count(node_t *n) \ { \ if (n == NULL) { \ return 0; \ } else { \ return ((n->flag ? 1 : 0) + \ flag##_count(n->first_child) + \ flag##_count(n->second_child)); \ } \ } DEF_FLAG_COUNT(sticky) DEF_FLAG_COUNT(private) DEF_FLAG_COUNT(locked) #undef DEF_FLAG_COUNT ================================================ FILE: src/tree.h ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef BSPWM_TREE_H #define BSPWM_TREE_H #define MIN_WIDTH 32 #define MIN_HEIGHT 32 void arrange(monitor_t *m, desktop_t *d); void apply_layout(monitor_t *m, desktop_t *d, node_t *n, xcb_rectangle_t rect, xcb_rectangle_t root_rect); presel_t *make_presel(void); bool set_type(node_t *n, split_type_t typ); bool set_ratio(node_t *n, double rat); void presel_dir(monitor_t *m, desktop_t *d, node_t *n, direction_t dir); void presel_ratio(monitor_t *m, desktop_t *d, node_t *n, double ratio); void cancel_presel(monitor_t *m, desktop_t *d, node_t *n); void cancel_presel_in(monitor_t *m, desktop_t *d, node_t *n); node_t *find_public(desktop_t *d); node_t *insert_node(monitor_t *m, desktop_t *d, node_t *n, node_t *f); void insert_receptacle(monitor_t *m, desktop_t *d, node_t *n); bool activate_node(monitor_t *m, desktop_t *d, node_t *n); void transfer_sticky_nodes(monitor_t *ms, desktop_t *ds, monitor_t *md, desktop_t *dd, node_t *n); bool focus_node(monitor_t *m, desktop_t *d, node_t *n); void hide_node(desktop_t *d, node_t *n); void show_node(desktop_t *d, node_t *n); node_t *make_node(uint32_t id); client_t *make_client(void); void initialize_client(node_t *n); bool is_focusable(node_t *n); bool is_leaf(node_t *n); bool is_first_child(node_t *n); bool is_second_child(node_t *n); unsigned int clients_count_in(node_t *n); node_t *brother_tree(node_t *n); node_t *first_extrema(node_t *n); node_t *second_extrema(node_t *n); node_t *first_focusable_leaf(node_t *n); node_t *next_node(node_t *n); node_t *prev_node(node_t *n); node_t *next_leaf(node_t *n, node_t *r); node_t *prev_leaf(node_t *n, node_t *r); node_t *next_tiled_leaf(node_t *n, node_t *r); node_t *prev_tiled_leaf(node_t *n, node_t *r); bool is_adjacent(node_t *a, node_t *b, direction_t dir); node_t *find_fence(node_t *n, direction_t dir); bool is_child(node_t *a, node_t *b); bool is_descendant(node_t *a, node_t *b); bool find_by_id(uint32_t id, coordinates_t *loc); node_t *find_by_id_in(node_t *r, uint32_t id); void find_any_node(coordinates_t *ref, coordinates_t *dst, node_select_t *sel); bool find_any_node_in(monitor_t *m, desktop_t *d, node_t *n, coordinates_t *ref, coordinates_t *dst, node_select_t *sel); void find_first_ancestor(coordinates_t *ref, coordinates_t *dst, node_select_t *sel); void find_nearest_neighbor(coordinates_t *ref, coordinates_t *dst, direction_t dir, node_select_t *sel); unsigned int node_area(desktop_t *d, node_t *n); int tiled_count(node_t *n, bool include_receptacles); void find_by_area(area_peak_t ap, coordinates_t *ref, coordinates_t *dst, node_select_t *sel); void rotate_tree(node_t *n, int deg); void rotate_tree_rec(node_t *n, int deg); void flip_tree(node_t *n, flip_t flp); void equalize_tree(node_t *n); int balance_tree(node_t *n); void adjust_ratios(node_t *n, xcb_rectangle_t rect); void unlink_node(monitor_t *m, desktop_t *d, node_t *n); void close_node(node_t *n); void kill_node(monitor_t *m, desktop_t *d, node_t *n); void remove_node(monitor_t *m, desktop_t *d, node_t *n); void free_node(node_t *n); bool swap_nodes(monitor_t *m1, desktop_t *d1, node_t *n1, monitor_t *m2, desktop_t *d2, node_t *n2, bool follow); bool transfer_node(monitor_t *ms, desktop_t *ds, node_t *ns, monitor_t *md, desktop_t *dd, node_t *nd, bool follow); bool find_closest_node(coordinates_t *ref, coordinates_t *dst, cycle_dir_t dir, node_select_t *sel); void circulate_leaves(monitor_t *m, desktop_t *d, node_t *n, circulate_dir_t dir); void set_vacant(monitor_t *m, desktop_t *d, node_t *n, bool value); void set_vacant_local(monitor_t *m, desktop_t *d, node_t *n, bool value); void propagate_vacant_downward(monitor_t *m, desktop_t *d, node_t *n, bool value); void propagate_vacant_upward(monitor_t *m, desktop_t *d, node_t *n); bool set_layer(monitor_t *m, desktop_t *d, node_t *n, stack_layer_t l); bool set_state(monitor_t *m, desktop_t *d, node_t *n, client_state_t s); void set_floating(monitor_t *m, desktop_t *d, node_t *n, bool value); void set_fullscreen(monitor_t *m, desktop_t *d, node_t *n, bool value); void neutralize_occluding_windows(monitor_t *m, desktop_t *d, node_t *n); void rebuild_constraints_from_leaves(node_t *n); void rebuild_constraints_towards_root(node_t *n); void update_constraints(node_t *n); void propagate_flags_upward(monitor_t *m, desktop_t *d, node_t *n); void set_hidden(monitor_t *m, desktop_t *d, node_t *n, bool value); void set_hidden_local(monitor_t *m, desktop_t *d, node_t *n, bool value); void propagate_hidden_downward(monitor_t *m, desktop_t *d, node_t *n, bool value); void propagate_hidden_upward(monitor_t *m, desktop_t *d, node_t *n); void set_sticky(monitor_t *m, desktop_t *d, node_t *n, bool value); void set_private(monitor_t *m, desktop_t *d, node_t *n, bool value); void set_locked(monitor_t *m, desktop_t *d, node_t *n, bool value); void set_marked(monitor_t *m, desktop_t *d, node_t *n, bool value); void set_urgent(monitor_t *m, desktop_t *d, node_t *n, bool value); xcb_rectangle_t get_rectangle(monitor_t *m, desktop_t *d, node_t *n); void listen_enter_notify(node_t *n, bool enable); void regenerate_ids_in(node_t *n); unsigned int sticky_count(node_t *n); unsigned int private_count(node_t *n); unsigned int locked_count(node_t *n); #endif ================================================ FILE: src/types.h ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef BSPWM_TYPES_H #define BSPWM_TYPES_H #include #include #include #include #include #include "helpers.h" #define MISSING_VALUE "N/A" #define MAX_WM_STATES 4 typedef enum { TYPE_HORIZONTAL, TYPE_VERTICAL } split_type_t; typedef enum { MODE_AUTOMATIC, MODE_MANUAL } split_mode_t; typedef enum { SCHEME_LONGEST_SIDE, SCHEME_ALTERNATE, SCHEME_SPIRAL } automatic_scheme_t; typedef enum { HONOR_SIZE_HINTS_NO = 0, HONOR_SIZE_HINTS_YES, HONOR_SIZE_HINTS_FLOATING, HONOR_SIZE_HINTS_TILED, HONOR_SIZE_HINTS_DEFAULT } honor_size_hints_mode_t; typedef enum { STATE_TILED, STATE_PSEUDO_TILED, STATE_FLOATING, STATE_FULLSCREEN } client_state_t; typedef enum { WM_FLAG_MODAL = 1 << 0, WM_FLAG_STICKY = 1 << 1, WM_FLAG_MAXIMIZED_VERT = 1 << 2, WM_FLAG_MAXIMIZED_HORZ = 1 << 3, WM_FLAG_SHADED = 1 << 4, WM_FLAG_SKIP_TASKBAR = 1 << 5, WM_FLAG_SKIP_PAGER = 1 << 6, WM_FLAG_HIDDEN = 1 << 7, WM_FLAG_FULLSCREEN = 1 << 8, WM_FLAG_ABOVE = 1 << 9, WM_FLAG_BELOW = 1 << 10, WM_FLAG_DEMANDS_ATTENTION = 1 << 11, } wm_flags_t; typedef enum { LAYER_BELOW, LAYER_NORMAL, LAYER_ABOVE } stack_layer_t; typedef enum { OPTION_NONE, OPTION_TRUE, OPTION_FALSE } option_bool_t; typedef enum { ALTER_TOGGLE, ALTER_SET } alter_state_t; typedef enum { CYCLE_NEXT, CYCLE_PREV } cycle_dir_t; typedef enum { CIRCULATE_FORWARD, CIRCULATE_BACKWARD } circulate_dir_t; typedef enum { HISTORY_OLDER, HISTORY_NEWER } history_dir_t; typedef enum { DIR_NORTH, DIR_WEST, DIR_SOUTH, DIR_EAST } direction_t; typedef enum { HANDLE_LEFT = 1 << 0, HANDLE_TOP = 1 << 1, HANDLE_RIGHT = 1 << 2, HANDLE_BOTTOM = 1 << 3, HANDLE_TOP_LEFT = HANDLE_TOP | HANDLE_LEFT, HANDLE_TOP_RIGHT = HANDLE_TOP | HANDLE_RIGHT, HANDLE_BOTTOM_RIGHT = HANDLE_BOTTOM | HANDLE_RIGHT, HANDLE_BOTTOM_LEFT = HANDLE_BOTTOM | HANDLE_LEFT } resize_handle_t; typedef enum { ACTION_NONE, ACTION_FOCUS, ACTION_MOVE, ACTION_RESIZE_SIDE, ACTION_RESIZE_CORNER } pointer_action_t; typedef enum { LAYOUT_TILED, LAYOUT_MONOCLE } layout_t; typedef enum { FLIP_HORIZONTAL, FLIP_VERTICAL } flip_t; typedef enum { FIRST_CHILD, SECOND_CHILD } child_polarity_t; typedef enum { TIGHTNESS_LOW, TIGHTNESS_HIGH, } tightness_t; typedef enum { AREA_BIGGEST, AREA_SMALLEST, } area_peak_t; typedef enum { STATE_TRANSITION_ENTER = 1 << 0, STATE_TRANSITION_EXIT = 1 << 1, } state_transition_t; typedef struct { option_bool_t automatic; option_bool_t focused; option_bool_t active; option_bool_t local; option_bool_t leaf; option_bool_t window; option_bool_t tiled; option_bool_t pseudo_tiled; option_bool_t floating; option_bool_t fullscreen; option_bool_t hidden; option_bool_t sticky; option_bool_t private; option_bool_t locked; option_bool_t marked; option_bool_t urgent; option_bool_t same_class; option_bool_t descendant_of; option_bool_t ancestor_of; option_bool_t below; option_bool_t normal; option_bool_t above; option_bool_t horizontal; option_bool_t vertical; } node_select_t; typedef struct { option_bool_t occupied; option_bool_t focused; option_bool_t active; option_bool_t urgent; option_bool_t local; option_bool_t tiled; option_bool_t monocle; option_bool_t user_tiled; option_bool_t user_monocle; } desktop_select_t; typedef struct { option_bool_t occupied; option_bool_t focused; } monitor_select_t; typedef struct icccm_props_t icccm_props_t; struct icccm_props_t { bool take_focus; bool input_hint; bool delete_window; }; typedef struct { char class_name[MAXLEN]; char instance_name[MAXLEN]; char name[MAXLEN]; unsigned int border_width; bool urgent; bool shown; client_state_t state; client_state_t last_state; stack_layer_t layer; stack_layer_t last_layer; xcb_rectangle_t floating_rectangle; xcb_rectangle_t tiled_rectangle; honor_size_hints_mode_t honor_size_hints; xcb_size_hints_t size_hints; icccm_props_t icccm_props; wm_flags_t wm_flags; } client_t; typedef struct presel_t presel_t; struct presel_t { double split_ratio; direction_t split_dir; xcb_window_t feedback; }; typedef struct constraints_t constraints_t; struct constraints_t { uint16_t min_width; uint16_t min_height; }; typedef struct node_t node_t; struct node_t { uint32_t id; split_type_t split_type; double split_ratio; presel_t *presel; xcb_rectangle_t rectangle; constraints_t constraints; bool vacant; bool hidden; bool sticky; bool private; bool locked; bool marked; node_t *first_child; node_t *second_child; node_t *parent; client_t *client; }; typedef struct padding_t padding_t; struct padding_t { int top; int right; int bottom; int left; }; typedef struct desktop_t desktop_t; struct desktop_t { char name[SMALEN]; uint32_t id; layout_t layout; layout_t user_layout; node_t *root; node_t *focus; desktop_t *prev; desktop_t *next; padding_t padding; int window_gap; unsigned int border_width; }; typedef struct monitor_t monitor_t; struct monitor_t { char name[SMALEN]; uint32_t id; xcb_randr_output_t randr_id; xcb_window_t root; bool wired; padding_t padding; unsigned int sticky_count; int window_gap; unsigned int border_width; xcb_rectangle_t rectangle; desktop_t *desk; desktop_t *desk_head; desktop_t *desk_tail; monitor_t *prev; monitor_t *next; }; typedef struct { monitor_t *monitor; desktop_t *desktop; node_t *node; } coordinates_t; typedef struct history_t history_t; struct history_t { coordinates_t loc; bool latest; history_t *prev; history_t *next; }; typedef struct stacking_list_t stacking_list_t; struct stacking_list_t { node_t *node; stacking_list_t *prev; stacking_list_t *next; }; typedef struct event_queue_t event_queue_t; struct event_queue_t { xcb_generic_event_t event; event_queue_t *prev; event_queue_t *next; }; typedef struct subscriber_list_t subscriber_list_t; struct subscriber_list_t { FILE *stream; char* fifo_path; int field; int count; subscriber_list_t *prev; subscriber_list_t *next; }; typedef struct rule_t rule_t; struct rule_t { char class_name[MAXLEN]; char instance_name[MAXLEN]; char name[MAXLEN]; char effect[MAXLEN]; bool one_shot; rule_t *prev; rule_t *next; }; typedef struct { char class_name[MAXLEN]; char instance_name[MAXLEN]; char name[MAXLEN]; char monitor_desc[MAXLEN]; char desktop_desc[MAXLEN]; char node_desc[MAXLEN]; direction_t *split_dir; double split_ratio; stack_layer_t *layer; client_state_t *state; honor_size_hints_mode_t honor_size_hints; bool hidden; bool sticky; bool private; bool locked; bool marked; bool center; bool follow; bool manage; bool focus; bool border; xcb_rectangle_t *rect; } rule_consequence_t; typedef struct pending_rule_t pending_rule_t; struct pending_rule_t { int fd; xcb_window_t win; rule_consequence_t *csq; event_queue_t *event_head; event_queue_t *event_tail; pending_rule_t *prev; pending_rule_t *next; }; #endif ================================================ FILE: src/window.c ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include "bspwm.h" #include "ewmh.h" #include "monitor.h" #include "desktop.h" #include "query.h" #include "rule.h" #include "settings.h" #include "geometry.h" #include "pointer.h" #include "stack.h" #include "tree.h" #include "parse.h" #include "window.h" void schedule_window(xcb_window_t win) { coordinates_t loc; uint8_t override_redirect = 0; xcb_get_window_attributes_reply_t *wa = xcb_get_window_attributes_reply(dpy, xcb_get_window_attributes(dpy, win), NULL); if (wa != NULL) { override_redirect = wa->override_redirect; free(wa); } if (override_redirect || locate_window(win, &loc)) { return; } /* ignore pending windows */ for (pending_rule_t *pr = pending_rule_head; pr != NULL; pr = pr->next) { if (pr->win == win) { return; } } rule_consequence_t *csq = make_rule_consequence(); apply_rules(win, csq); if (!schedule_rules(win, csq)) { manage_window(win, csq, -1); free(csq); } } bool manage_window(xcb_window_t win, rule_consequence_t *csq, int fd) { monitor_t *m = mon; desktop_t *d = mon->desk; node_t *f = mon->desk->focus; parse_rule_consequence(fd, csq); if (!ignore_ewmh_struts && ewmh_handle_struts(win)) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { arrange(m, d); } } } if (!csq->manage) { free(csq->layer); free(csq->state); window_show(win); return false; } if (csq->node_desc[0] != '\0') { coordinates_t ref = {m, d, f}; coordinates_t trg = {NULL, NULL, NULL}; if (node_from_desc(csq->node_desc, &ref, &trg) == SELECTOR_OK) { m = trg.monitor; d = trg.desktop; f = trg.node; } } else if (csq->desktop_desc[0] != '\0') { coordinates_t ref = {m, d, NULL}; coordinates_t trg = {NULL, NULL, NULL}; if (desktop_from_desc(csq->desktop_desc, &ref, &trg) == SELECTOR_OK) { m = trg.monitor; d = trg.desktop; f = trg.desktop->focus; } } else if (csq->monitor_desc[0] != '\0') { coordinates_t ref = {m, NULL, NULL}; coordinates_t trg = {NULL, NULL, NULL}; if (monitor_from_desc(csq->monitor_desc, &ref, &trg) == SELECTOR_OK) { m = trg.monitor; d = trg.monitor->desk; f = trg.monitor->desk->focus; } } if (csq->sticky) { m = mon; d = mon->desk; f = mon->desk->focus; } if (csq->split_dir != NULL && f != NULL) { presel_dir(m, d, f, *csq->split_dir); } if (csq->split_ratio != 0 && f != NULL) { presel_ratio(m, d, f, csq->split_ratio); } node_t *n = make_node(win); client_t *c = make_client(); c->border_width = csq->border ? d->border_width : 0; n->client = c; initialize_client(n); initialize_floating_rectangle(n); if (csq->rect != NULL) { c->floating_rectangle = *csq->rect; free(csq->rect); } else if (c->floating_rectangle.x == 0 && c->floating_rectangle.y == 0) { csq->center = true; } monitor_t *mm = monitor_from_client(c); embrace_client(mm, c); adapt_geometry(&mm->rectangle, &m->rectangle, n); if (csq->center) { window_center(m, c); } snprintf(c->class_name, sizeof(c->class_name), "%s", csq->class_name); snprintf(c->instance_name, sizeof(c->instance_name), "%s", csq->instance_name); if ((csq->state != NULL && (*(csq->state) == STATE_FLOATING || *(csq->state) == STATE_FULLSCREEN)) || csq->hidden) { n->vacant = true; } f = insert_node(m, d, n, f); clients_count++; if (single_monocle && d->layout == LAYOUT_MONOCLE && tiled_count(d->root, true) > 1) { set_layout(m, d, d->user_layout, false); } n->vacant = false; put_status(SBSC_MASK_NODE_ADD, "node_add 0x%08X 0x%08X 0x%08X 0x%08X\n", m->id, d->id, f!=NULL?f->id:0, win); if (f != NULL && f->client != NULL && csq->state != NULL && *(csq->state) == STATE_FLOATING) { c->layer = f->client->layer; } if (csq->layer != NULL) { c->layer = *(csq->layer); } if (csq->state != NULL) { set_state(m, d, n, *(csq->state)); } if (csq->honor_size_hints != HONOR_SIZE_HINTS_DEFAULT) { c->honor_size_hints = csq->honor_size_hints; } set_hidden(m, d, n, csq->hidden); set_sticky(m, d, n, csq->sticky); set_private(m, d, n, csq->private); set_locked(m, d, n, csq->locked); set_marked(m, d, n, csq->marked); arrange(m, d); uint32_t values[] = {CLIENT_EVENT_MASK | (focus_follows_pointer ? XCB_EVENT_MASK_ENTER_WINDOW : 0)}; xcb_change_window_attributes(dpy, win, XCB_CW_EVENT_MASK, values); set_window_state(win, XCB_ICCCM_WM_STATE_NORMAL); window_grab_buttons(win); if (d == m->desk) { show_node(d, n); } else { hide_node(d, n); } ewmh_update_client_list(false); ewmh_set_wm_desktop(n, d); if (!csq->hidden && csq->focus) { if (d == mon->desk || csq->follow) { focus_node(m, d, n); } else { activate_node(m, d, n); } } else { stack(d, n, false); draw_border(n, false, (m == mon)); } free(csq->layer); free(csq->state); return true; } void set_window_state(xcb_window_t win, xcb_icccm_wm_state_t state) { long data[] = {state, XCB_NONE}; xcb_change_property(dpy, XCB_PROP_MODE_REPLACE, win, WM_STATE, WM_STATE, 32, 2, data); } void unmanage_window(xcb_window_t win) { coordinates_t loc; if (locate_window(win, &loc)) { put_status(SBSC_MASK_NODE_REMOVE, "node_remove 0x%08X 0x%08X 0x%08X\n", loc.monitor->id, loc.desktop->id, win); remove_node(loc.monitor, loc.desktop, loc.node); arrange(loc.monitor, loc.desktop); } else { for (pending_rule_t *pr = pending_rule_head; pr != NULL; pr = pr->next) { if (pr->win == win) { remove_pending_rule(pr); return; } } } } bool is_presel_window(xcb_window_t win) { xcb_icccm_get_wm_class_reply_t reply; bool ret = false; if (xcb_icccm_get_wm_class_reply(dpy, xcb_icccm_get_wm_class(dpy, win), &reply, NULL) == 1) { if (streq(BSPWM_CLASS_NAME, reply.class_name) && streq(PRESEL_FEEDBACK_I, reply.instance_name)) { ret = true; } xcb_icccm_get_wm_class_reply_wipe(&reply); } return ret; } void initialize_presel_feedback(node_t *n) { if (n == NULL || n->presel == NULL || n->presel->feedback != XCB_NONE) { return; } xcb_window_t win = xcb_generate_id(dpy); uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_SAVE_UNDER; uint32_t values[] = {get_color_pixel(presel_feedback_color), 1}; xcb_create_window(dpy, XCB_COPY_FROM_PARENT, win, root, 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, mask, values); xcb_icccm_set_wm_class(dpy, win, sizeof(PRESEL_FEEDBACK_IC), PRESEL_FEEDBACK_IC); /* Make presel window's input shape NULL to pass any input to window below */ xcb_shape_rectangles(dpy, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, win, 0, 0, 0, NULL); stacking_list_t *s = stack_tail; while (s != NULL && !IS_TILED(s->node->client)) { s = s->prev; } if (s != NULL) { window_above(win, s->node->id); } n->presel->feedback = win; } void draw_presel_feedback(monitor_t *m, desktop_t *d, node_t *n) { if (n == NULL || n->presel == NULL || d->user_layout == LAYOUT_MONOCLE || !presel_feedback) { return; } bool exists = (n->presel->feedback != XCB_NONE); if (!exists) { initialize_presel_feedback(n); } int gap = gapless_monocle && d->layout == LAYOUT_MONOCLE ? 0 : d->window_gap; presel_t *p = n->presel; xcb_rectangle_t rect = n->rectangle; rect.x = rect.y = 0; rect.width -= gap; rect.height -= gap; xcb_rectangle_t presel_rect = rect; switch (p->split_dir) { case DIR_NORTH: presel_rect.height = p->split_ratio * rect.height; break; case DIR_EAST: presel_rect.width = (1 - p->split_ratio) * rect.width; presel_rect.x = rect.width - presel_rect.width; break; case DIR_SOUTH: presel_rect.height = (1 - p->split_ratio) * rect.height; presel_rect.y = rect.height - presel_rect.height; break; case DIR_WEST: presel_rect.width = p->split_ratio * rect.width; break; } window_move_resize(p->feedback, n->rectangle.x + presel_rect.x, n->rectangle.y + presel_rect.y, presel_rect.width, presel_rect.height); if (!exists && m->desk == d) { window_show(p->feedback); } } void refresh_presel_feedbacks(monitor_t *m, desktop_t *d, node_t *n) { if (n == NULL) { return; } else { if (n->presel != NULL) { draw_presel_feedback(m, d, n); } refresh_presel_feedbacks(m, d, n->first_child); refresh_presel_feedbacks(m, d, n->second_child); } } void show_presel_feedbacks(monitor_t *m, desktop_t *d, node_t *n) { if (n == NULL) { return; } else { if (n->presel != NULL) { window_show(n->presel->feedback); } show_presel_feedbacks(m, d, n->first_child); show_presel_feedbacks(m, d, n->second_child); } } void hide_presel_feedbacks(monitor_t *m, desktop_t *d, node_t *n) { if (n == NULL) { return; } else { if (n->presel != NULL) { window_hide(n->presel->feedback); } hide_presel_feedbacks(m, d, n->first_child); hide_presel_feedbacks(m, d, n->second_child); } } void update_colors(void) { for (monitor_t *m = mon_head; m != NULL; m = m->next) { for (desktop_t *d = m->desk_head; d != NULL; d = d->next) { update_colors_in(d->root, d, m); } } } void update_colors_in(node_t *n, desktop_t *d, monitor_t *m) { if (n == NULL) { return; } else { if (n->presel != NULL) { uint32_t pxl = get_color_pixel(presel_feedback_color); xcb_change_window_attributes(dpy, n->presel->feedback, XCB_CW_BACK_PIXEL, &pxl); if (d == m->desk) { /* hack to induce back pixel refresh */ window_hide(n->presel->feedback); window_show(n->presel->feedback); } } if (n == d->focus) { draw_border(n, true, (m == mon)); } else if (n->client != NULL) { draw_border(n, false, (m == mon)); } else { update_colors_in(n->first_child, d, m); update_colors_in(n->second_child, d, m); } } } void draw_border(node_t *n, bool focused_node, bool focused_monitor) { if (n == NULL) { return; } uint32_t border_color_pxl = get_border_color(focused_node, focused_monitor); for (node_t *f = first_extrema(n); f != NULL; f = next_leaf(f, n)) { if (f->client != NULL) { window_draw_border(f->id, border_color_pxl); } } } void window_draw_border(xcb_window_t win, uint32_t border_color_pxl) { xcb_change_window_attributes(dpy, win, XCB_CW_BORDER_PIXEL, &border_color_pxl); } void adopt_orphans(void) { xcb_query_tree_reply_t *qtr = xcb_query_tree_reply(dpy, xcb_query_tree(dpy, root), NULL); if (qtr == NULL) { return; } int len = xcb_query_tree_children_length(qtr); xcb_window_t *wins = xcb_query_tree_children(qtr); for (int i = 0; i < len; i++) { uint32_t idx; xcb_window_t win = wins[i]; if (xcb_ewmh_get_wm_desktop_reply(ewmh, xcb_ewmh_get_wm_desktop(ewmh, win), &idx, NULL) == 1) { schedule_window(win); } } free(qtr); } uint32_t get_border_color(bool focused_node, bool focused_monitor) { if (focused_monitor && focused_node) { return get_color_pixel(focused_border_color); } else if (focused_node) { return get_color_pixel(active_border_color); } else { return get_color_pixel(normal_border_color); } } void initialize_floating_rectangle(node_t *n) { client_t *c = n->client; xcb_get_geometry_reply_t *geo = xcb_get_geometry_reply(dpy, xcb_get_geometry(dpy, n->id), NULL); if (geo != NULL) { c->floating_rectangle = (xcb_rectangle_t) {geo->x, geo->y, geo->width, geo->height}; } free(geo); } xcb_rectangle_t get_window_rectangle(node_t *n) { client_t *c = n->client; if (c != NULL) { xcb_get_geometry_reply_t *g = xcb_get_geometry_reply(dpy, xcb_get_geometry(dpy, n->id), NULL); if (g != NULL) { xcb_rectangle_t rect = (xcb_rectangle_t) {g->x, g->y, g->width, g->height}; free(g); return rect; } } return (xcb_rectangle_t) {0, 0, screen_width, screen_height}; } bool move_client(coordinates_t *loc, int dx, int dy) { node_t *n = loc->node; if (n == NULL || n->client == NULL) { return false; } monitor_t *pm = NULL; if (IS_TILED(n->client)) { if (!grabbing) { return false; } xcb_window_t pwin = XCB_NONE; query_pointer(&pwin, NULL); if (pwin == n->id) { return false; } coordinates_t dst; bool is_managed = (pwin != XCB_NONE && locate_window(pwin, &dst)); if (is_managed && dst.monitor == loc->monitor && IS_TILED(dst.node->client)) { swap_nodes(loc->monitor, loc->desktop, n, loc->monitor, loc->desktop, dst.node, false); return true; } else { if (is_managed && dst.monitor == loc->monitor) { return false; } else { xcb_point_t pt = {0, 0}; query_pointer(NULL, &pt); pm = monitor_from_point(pt); } } } else { client_t *c = n->client; xcb_rectangle_t rect = c->floating_rectangle; int16_t x = rect.x + dx; int16_t y = rect.y + dy; window_move(n->id, x, y); c->floating_rectangle.x = x; c->floating_rectangle.y = y; if (!grabbing) { put_status(SBSC_MASK_NODE_GEOMETRY, "node_geometry 0x%08X 0x%08X 0x%08X %ux%u+%i+%i\n", loc->monitor->id, loc->desktop->id, loc->node->id, rect.width, rect.height, x, y); } pm = monitor_from_client(c); } if (pm == NULL || pm == loc->monitor) { return true; } transfer_node(loc->monitor, loc->desktop, n, pm, pm->desk, pm->desk->focus, true); loc->monitor = pm; loc->desktop = pm->desk; return true; } bool resize_client(coordinates_t *loc, resize_handle_t rh, int dx, int dy, bool relative) { node_t *n = loc->node; if (n == NULL || n->client == NULL || n->client->state == STATE_FULLSCREEN) { return false; } node_t *horizontal_fence = NULL, *vertical_fence = NULL; xcb_rectangle_t rect = get_rectangle(NULL, NULL, n); uint16_t width = rect.width, height = rect.height; int16_t x = rect.x, y = rect.y; if (n->client->state == STATE_TILED) { if (rh & HANDLE_LEFT) { vertical_fence = find_fence(n, DIR_WEST); } else if (rh & HANDLE_RIGHT) { vertical_fence = find_fence(n, DIR_EAST); } if (rh & HANDLE_TOP) { horizontal_fence = find_fence(n, DIR_NORTH); } else if (rh & HANDLE_BOTTOM) { horizontal_fence = find_fence(n, DIR_SOUTH); } if (vertical_fence == NULL && horizontal_fence == NULL) { return false; } if (vertical_fence != NULL) { double sr = 0.0; if (relative) { sr = vertical_fence->split_ratio + (double) dx / (double) vertical_fence->rectangle.width; } else { sr = (double) (dx - vertical_fence->rectangle.x) / (double) vertical_fence->rectangle.width; } sr = MAX(0, sr); sr = MIN(1, sr); vertical_fence->split_ratio = sr; adjust_ratios(vertical_fence, vertical_fence->rectangle); } if (horizontal_fence != NULL) { double sr = 0.0; if (relative) { sr = horizontal_fence->split_ratio + (double) dy / (double) horizontal_fence->rectangle.height; } else { sr = (double) (dy - horizontal_fence->rectangle.y) / (double) horizontal_fence->rectangle.height; } sr = MAX(0, sr); sr = MIN(1, sr); horizontal_fence->split_ratio = sr; adjust_ratios(horizontal_fence, horizontal_fence->rectangle); } arrange(loc->monitor, loc->desktop); } else { int w = width, h = height; if (relative) { w += dx * (rh & HANDLE_LEFT ? -1 : (rh & HANDLE_RIGHT ? 1 : 0)); h += dy * (rh & HANDLE_TOP ? -1 : (rh & HANDLE_BOTTOM ? 1 : 0)); } else { if (rh & HANDLE_LEFT) { w = x + width - dx; } else if (rh & HANDLE_RIGHT) { w = dx - x; } if (rh & HANDLE_TOP) { h = y + height - dy; } else if (rh & HANDLE_BOTTOM) { h = dy - y; } } width = MAX(1, w); height = MAX(1, h); apply_size_hints(n->client, &width, &height); if (rh & HANDLE_LEFT) { x += rect.width - width; } if (rh & HANDLE_TOP) { y += rect.height - height; } n->client->floating_rectangle = (xcb_rectangle_t) {x, y, width, height}; if (n->client->state == STATE_FLOATING) { window_move_resize(n->id, x, y, width, height); if (!grabbing) { put_status(SBSC_MASK_NODE_GEOMETRY, "node_geometry 0x%08X 0x%08X 0x%08X %ux%u+%i+%i\n", loc->monitor->id, loc->desktop->id, loc->node->id, width, height, x, y); } } else { arrange(loc->monitor, loc->desktop); } } return true; } /* taken from awesomeWM */ void apply_size_hints(client_t *c, uint16_t *width, uint16_t *height) { if (!SHOULD_HONOR_SIZE_HINTS(c->honor_size_hints, c->state)) { return; } int32_t minw = 0, minh = 0; int32_t basew = 0, baseh = 0, real_basew = 0, real_baseh = 0; if (c->size_hints.flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE) { basew = c->size_hints.base_width; baseh = c->size_hints.base_height; real_basew = basew; real_baseh = baseh; } else if (c->size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) { /* base size is substituted with min size if not specified */ basew = c->size_hints.min_width; baseh = c->size_hints.min_height; } if (c->size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) { minw = c->size_hints.min_width; minh = c->size_hints.min_height; } else if (c->size_hints.flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE) { /* min size is substituted with base size if not specified */ minw = c->size_hints.base_width; minh = c->size_hints.base_height; } /* Handle the size aspect ratio */ if (c->size_hints.flags & XCB_ICCCM_SIZE_HINT_P_ASPECT && c->size_hints.min_aspect_den > 0 && c->size_hints.max_aspect_den > 0 && *height > real_baseh && *width > real_basew) { /* ICCCM mandates: * If a base size is provided along with the aspect ratio fields, the base size should be subtracted from the * window size prior to checking that the aspect ratio falls in range. If a base size is not provided, nothing * should be subtracted from the window size. (The minimum size is not to be used in place of the base size for * this purpose.) */ double dx = *width - real_basew; double dy = *height - real_baseh; double ratio = dx / dy; double min = c->size_hints.min_aspect_num / (double) c->size_hints.min_aspect_den; double max = c->size_hints.max_aspect_num / (double) c->size_hints.max_aspect_den; if (max > 0 && min > 0 && ratio > 0) { if (ratio < min) { /* dx is lower than allowed, make dy lower to compensate this (+ 0.5 to force proper rounding). */ dy = dx / min + 0.5; *width = dx + real_basew; *height = dy + real_baseh; } else if (ratio > max) { /* dx is too high, lower it (+0.5 for proper rounding) */ dx = dy * max + 0.5; *width = dx + real_basew; *height = dy + real_baseh; } } } /* Handle the minimum size */ *width = MAX(*width, minw); *height = MAX(*height, minh); /* Handle the maximum size */ if (c->size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE) { if (c->size_hints.max_width > 0) { *width = MIN(*width, c->size_hints.max_width); } if (c->size_hints.max_height > 0) { *height = MIN(*height, c->size_hints.max_height); } } /* Handle the size increment */ if (c->size_hints.flags & (XCB_ICCCM_SIZE_HINT_P_RESIZE_INC | XCB_ICCCM_SIZE_HINT_BASE_SIZE) && c->size_hints.width_inc > 0 && c->size_hints.height_inc > 0) { uint16_t t1 = *width, t2 = *height; unsigned_subtract(t1, basew); unsigned_subtract(t2, baseh); *width -= t1 % c->size_hints.width_inc; *height -= t2 % c->size_hints.height_inc; } } void query_pointer(xcb_window_t *win, xcb_point_t *pt) { if (motion_recorder.enabled) { window_hide(motion_recorder.id); } xcb_query_pointer_reply_t *qpr = xcb_query_pointer_reply(dpy, xcb_query_pointer(dpy, root), NULL); if (qpr != NULL) { if (win != NULL) { if (qpr->child == XCB_NONE) { xcb_point_t mpt = (xcb_point_t) {qpr->root_x, qpr->root_y}; monitor_t *m = monitor_from_point(mpt); if (m != NULL) { desktop_t *d = m->desk; for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) { if (n->client == NULL && is_inside(mpt, get_rectangle(m, d, n))) { *win = n->id; break; } } } } else { *win = qpr->child; xcb_point_t pt = {qpr->root_x, qpr->root_y}; for (stacking_list_t *s = stack_tail; s != NULL; s = s->prev) { if (!s->node->client->shown || s->node->hidden) { continue; } xcb_rectangle_t rect = get_rectangle(NULL, NULL, s->node); if (is_inside(pt, rect)) { if (s->node->id == qpr->child || is_presel_window(qpr->child)) { *win = s->node->id; } break; } } } } if (pt != NULL) { *pt = (xcb_point_t) {qpr->root_x, qpr->root_y}; } } free(qpr); if (motion_recorder.enabled) { window_show(motion_recorder.id); } } void update_motion_recorder(void) { xcb_point_t pt; xcb_window_t win = XCB_NONE; query_pointer(&win, &pt); if (win == XCB_NONE) { return; } monitor_t *m = monitor_from_point(pt); if (m == NULL) { return; } desktop_t *d = m->desk; node_t *n = NULL; for (n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) { if (n->id == win || (n->presel != NULL && n->presel->feedback == win)) { break; } } if ((n != NULL && n != mon->desk->focus) || (n == NULL && m != mon)) { enable_motion_recorder(win); } else { disable_motion_recorder(); } } void enable_motion_recorder(xcb_window_t win) { xcb_get_geometry_reply_t *geo = xcb_get_geometry_reply(dpy, xcb_get_geometry(dpy, win), NULL); if (geo != NULL) { uint16_t width = geo->width + 2 * geo->border_width; uint16_t height = geo->height + 2 * geo->border_width; window_move_resize(motion_recorder.id, geo->x, geo->y, width, height); window_above(motion_recorder.id, win); window_show(motion_recorder.id); motion_recorder.enabled = true; } free(geo); } void disable_motion_recorder(void) { if (!motion_recorder.enabled) { return; } window_hide(motion_recorder.id); motion_recorder.enabled = false; } void window_border_width(xcb_window_t win, uint32_t bw) { uint32_t values[] = {bw}; xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_BORDER_WIDTH, values); } void window_move(xcb_window_t win, int16_t x, int16_t y) { uint32_t values[] = {x, y}; xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_X_Y, values); } void window_resize(xcb_window_t win, uint16_t w, uint16_t h) { uint32_t values[] = {w, h}; xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_WIDTH_HEIGHT, values); } void window_move_resize(xcb_window_t win, int16_t x, int16_t y, uint16_t w, uint16_t h) { uint32_t values[] = {x, y, w, h}; xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_X_Y_WIDTH_HEIGHT, values); } void window_center(monitor_t *m, client_t *c) { xcb_rectangle_t *r = &c->floating_rectangle; xcb_rectangle_t a = m->rectangle; if (r->width >= a.width) { r->x = a.x; } else { r->x = a.x + (a.width - r->width) / 2; } if (r->height >= a.height) { r->y = a.y; } else { r->y = a.y + (a.height - r->height) / 2; } r->x -= c->border_width; r->y -= c->border_width; } void window_stack(xcb_window_t w1, xcb_window_t w2, uint32_t mode) { if (w2 == XCB_NONE) { return; } uint16_t mask = XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE; uint32_t values[] = {w2, mode}; xcb_configure_window(dpy, w1, mask, values); } /* Stack w1 above w2 */ void window_above(xcb_window_t w1, xcb_window_t w2) { window_stack(w1, w2, XCB_STACK_MODE_ABOVE); } /* Stack w1 below w2 */ void window_below(xcb_window_t w1, xcb_window_t w2) { window_stack(w1, w2, XCB_STACK_MODE_BELOW); } void window_lower(xcb_window_t win) { uint32_t values[] = {XCB_STACK_MODE_BELOW}; xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_STACK_MODE, values); } void window_set_visibility(xcb_window_t win, bool visible) { uint32_t values_off[] = {ROOT_EVENT_MASK & ~XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY}; uint32_t values_on[] = {ROOT_EVENT_MASK}; xcb_change_window_attributes(dpy, root, XCB_CW_EVENT_MASK, values_off); if (visible) { set_window_state(win, XCB_ICCCM_WM_STATE_NORMAL); xcb_map_window(dpy, win); } else { xcb_unmap_window(dpy, win); set_window_state(win, XCB_ICCCM_WM_STATE_ICONIC); } xcb_change_window_attributes(dpy, root, XCB_CW_EVENT_MASK, values_on); } void window_hide(xcb_window_t win) { window_set_visibility(win, false); } void window_show(xcb_window_t win) { window_set_visibility(win, true); } void update_input_focus(void) { set_input_focus(mon->desk->focus); } void set_input_focus(node_t *n) { if (n == NULL || n->client == NULL) { clear_input_focus(); } else { if (n->client->icccm_props.input_hint) { xcb_set_input_focus(dpy, XCB_INPUT_FOCUS_PARENT, n->id, XCB_CURRENT_TIME); } else if (n->client->icccm_props.take_focus) { send_client_message(n->id, ewmh->WM_PROTOCOLS, WM_TAKE_FOCUS); } } } void clear_input_focus(void) { xcb_set_input_focus(dpy, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME); } void center_pointer(xcb_rectangle_t r) { if (grabbing) { return; } int16_t cx = r.x + r.width / 2; int16_t cy = r.y + r.height / 2; xcb_warp_pointer(dpy, XCB_NONE, root, 0, 0, 0, 0, cx, cy); } void get_atom(char *name, xcb_atom_t *atom) { xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(dpy, xcb_intern_atom(dpy, 0, strlen(name), name), NULL); if (reply != NULL) { *atom = reply->atom; } else { *atom = XCB_NONE; } free(reply); } void set_atom(xcb_window_t win, xcb_atom_t atom, uint32_t value) { xcb_change_property(dpy, XCB_PROP_MODE_REPLACE, win, atom, XCB_ATOM_CARDINAL, 32, 1, &value); } void send_client_message(xcb_window_t win, xcb_atom_t property, xcb_atom_t value) { xcb_client_message_event_t *e = calloc(32, 1); e->response_type = XCB_CLIENT_MESSAGE; e->window = win; e->type = property; e->format = 32; e->data.data32[0] = value; e->data.data32[1] = XCB_CURRENT_TIME; xcb_send_event(dpy, false, win, XCB_EVENT_MASK_NO_EVENT, (char *) e); xcb_flush(dpy); free(e); } bool window_exists(xcb_window_t win) { xcb_generic_error_t *err; free(xcb_query_tree_reply(dpy, xcb_query_tree(dpy, win), &err)); if (err != NULL) { free(err); return false; } return true; } ================================================ FILE: src/window.h ================================================ /* Copyright (c) 2012, Bastien Dejean * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef BSPWM_WINDOW_H #define BSPWM_WINDOW_H #include #include #include #include #include "types.h" void schedule_window(xcb_window_t win); bool manage_window(xcb_window_t win, rule_consequence_t *csq, int fd); void set_window_state(xcb_window_t win, xcb_icccm_wm_state_t state); void unmanage_window(xcb_window_t win); bool is_presel_window(xcb_window_t win); void initialize_presel_feedback(node_t *n); void draw_presel_feedback(monitor_t *m, desktop_t *d, node_t *n); void refresh_presel_feedbacks(monitor_t *m, desktop_t *d, node_t *n); void show_presel_feedbacks(monitor_t *m, desktop_t *d, node_t *n); void hide_presel_feedbacks(monitor_t *m, desktop_t *d, node_t *n); void update_colors(void); void update_colors_in(node_t *n, desktop_t *d, monitor_t *m); void draw_border(node_t *n, bool focused_node, bool focused_monitor); void window_draw_border(xcb_window_t win, uint32_t border_color_pxl); void adopt_orphans(void); uint32_t get_border_color(bool focused_node, bool focused_monitor); void initialize_floating_rectangle(node_t *n); xcb_rectangle_t get_window_rectangle(node_t *n); bool move_client(coordinates_t *loc, int dx, int dy); bool resize_client(coordinates_t *loc, resize_handle_t rh, int dx, int dy, bool relative); void apply_size_hints(client_t *c, uint16_t *width, uint16_t *height); void query_pointer(xcb_window_t *win, xcb_point_t *pt); void update_motion_recorder(void); void enable_motion_recorder(xcb_window_t win); void disable_motion_recorder(void); void window_border_width(xcb_window_t win, uint32_t bw); void window_move(xcb_window_t win, int16_t x, int16_t y); void window_resize(xcb_window_t win, uint16_t w, uint16_t h); void window_move_resize(xcb_window_t win, int16_t x, int16_t y, uint16_t w, uint16_t h); void window_center(monitor_t *m, client_t *c); void window_stack(xcb_window_t w1, xcb_window_t w2, uint32_t mode); void window_above(xcb_window_t w1, xcb_window_t w2); void window_below(xcb_window_t w1, xcb_window_t w2); void window_lower(xcb_window_t win); void window_set_visibility(xcb_window_t win, bool visible); void window_hide(xcb_window_t win); void window_show(xcb_window_t win); void update_input_focus(void); void set_input_focus(node_t *n); void clear_input_focus(void); void center_pointer(xcb_rectangle_t r); void get_atom(char *name, xcb_atom_t *atom); void set_atom(xcb_window_t win, xcb_atom_t atom, uint32_t value); void send_client_message(xcb_window_t win, xcb_atom_t property, xcb_atom_t value); bool window_exists(xcb_window_t win); #endif ================================================ FILE: tests/Makefile ================================================ OUT = test_window CFLAGS += -std=c99 -pedantic -Wall -Wextra LDLIBS = -lxcb -lxcb-icccm SRC = $(wildcard *.c) OBJ = $(SRC:.c=.o) all: $(OUT) clean: $(RM) $(OUT) $(OBJ) .PHONY: all clean ================================================ FILE: tests/README.md ================================================ - Install *jshon*. - Run `make` once. - Run `./run`. ================================================ FILE: tests/desktop/swap ================================================ #! /bin/sh . ./prelude bspc wm -a "TEST-SWAP-A" 1024x512+0+0 bspc wm -a "TEST-SWAP-B" 1024x512+0+512 bspc monitor -f "TEST-SWAP-A" window add 3 bspc monitor -f "TEST-SWAP-B" window add 2 nodes_a=$(bspc query -N -m "TEST-SWAP-A") nodes_b=$(bspc query -N -m "TEST-SWAP-B") bspc desktop "TEST-SWAP-A:^1" -s "TEST-SWAP-B:^1" [ "$(bspc query -N -m 'TEST-SWAP-A')" = "$nodes_b" ] || fail "Wrong nodes in first monitor" [ "$(bspc query -N -m 'TEST-SWAP-B')" = "$nodes_a" ] || fail "Wrong nodes in second monitor" window remove 3 bspc monitor -f "TEST-SWAP-A" window remove 2 bspc monitor "TEST-SWAP-A" -r bspc monitor "TEST-SWAP-B" -r ================================================ FILE: tests/desktop/transfer ================================================ #! /bin/sh . ./prelude bspc wm -a "TEST-TRANSFER-A" 1024x512+0+0 bspc wm -a "TEST-TRANSFER-B" 1024x512+0+512 bspc monitor "TEST-TRANSFER-A" -a source bspc monitor -f "TEST-TRANSFER-A" window add 3 root_rectangle_y=$(bspc query -T -n @/ | jshon -e rectangle -e y) bspc desktop "TEST-TRANSFER-A:focused" -m "TEST-TRANSFER-B" [ "$(bspc query -D -m "TEST-TRANSFER-A" | wc -l)" -eq 1 ] || fail "Invalid number of desktop in source after transfer." bspc desktop "TEST-TRANSFER-B:^2" -f [ "$(bspc query -T -n @/ | jshon -e rectangle -e y)" -ne "$root_rectangle_y" ] || fail "Wrong tiled rectangle for root in destination." window remove 3 bspc monitor "TEST-TRANSFER-A" -r bspc monitor "TEST-TRANSFER-B" -r ================================================ FILE: tests/node/flags ================================================ #! /bin/sh . ./prelude bspc monitor -a "test-sticky-a" bspc monitor -a "test-sticky-b" bspc desktop -f "test-sticky-a" window add 3 bspc node -g sticky sticky_node_id=$(bspc query -N -n) bspc rule -a 'Blah\:\:2:test' -o desktop="test-sticky-b" CLASS_NAME=Blah::2 INSTANCE_NAME=test window add bspc desktop -f "test-sticky-b" bspc query -N -d | grep "$sticky_node_id" > /dev/null || fail "Sticky node is missing in destination." window remove 2 bspc desktop -f "test-sticky-a" window remove 2 bspc desktop "test-sticky-a" -r bspc desktop "test-sticky-b" -r ================================================ FILE: tests/node/insertion ================================================ #! /bin/sh . ./prelude bspc monitor -a "test-insertion" bspc desktop -f "test-insertion" # Automatic mode window add 2 split_type_a=$(bspc query -T -n @/ | jshon -e splitType -u) window add split_type_b=$(bspc query -T -n @/2 | jshon -e splitType -u) [ "$split_type_a" = "$split_type_b" ] && fail "Non-vacant node insertion should rotate brother." split_type_a=$(bspc query -T -n @/ | jshon -e splitType -u) bspc rule -a Test:test -o state=floating window add split_type_b=$(bspc query -T -n @/2 | jshon -e splitType -u) [ "$split_type_a" = "$split_type_b" ] || fail "Vacant node insertion shouldn't rotate brother." window remove # Manual mode for dir in north west south east ; do child=1 split_type=vertical [ "$dir" = "south" -o "$dir" = "east" ] && child=2 [ "$dir" = "north" -o "$dir" = "south" ] && split_type=horizontal bspc node -p $dir window add [ "$(bspc query -N -n)" = "$(bspc query -N -n @parent/${child})" ] || fail "Wrong child polarity for ${dir} preselection." [ "$(bspc query -T -n @parent | jshon -e splitType -u)" = "$split_type" ] || fail "Wrong split type for ${dir} preselection." done window remove 7 bspc desktop "test-insertion" -r ================================================ FILE: tests/node/receptacle ================================================ #! /bin/sh . ./prelude bspc monitor -a "test-receptacle" bspc desktop -f "test-receptacle" bspc node -i bspc node @/ -p east -i bspc node @/2 -p north -i bspc rule -a Test:test -o node=@/1 bspc rule -a Test:test -o node=@/2/1 bspc rule -a Test:test -o node=@/2/2 window add 3 bspc query -N -n '.leaf.!window.local' > /dev/null && fail "At least one remaining receptacle." window remove 3 bspc desktop "test-receptacle" -r ================================================ FILE: tests/node/removal ================================================ #! /bin/sh . ./prelude bspc monitor -a "test-removal" bspc desktop -f "test-removal" window add 3 next_focus=$(bspc query -N -n); bspc node -f @/2/1 bspc node @/2 -k [ "$(bspc query -N -n)" = "$next_focus" ] || fail "Invalid focus after removal." window remove bspc desktop "test-removal" -r ================================================ FILE: tests/node/swap ================================================ #! /bin/sh . ./prelude bspc monitor -a "test-swap-a" "test-swap-b" bspc desktop -f "test-swap-a" window add 5 next_focus_b=$(bspc query -N -n @/2/2/1) bspc desktop -f "test-swap-b" window add 3 bspc node -f @test-swap-a:/2/2/1 bspc node -a @test-swap-b:/1 bspc node @/2 -s @test-swap-b:/1 [ "$(bspc query -N -n @test-swap-b:)" = "$next_focus_b" ] || fail "Invalid focus after swap." window remove 2 bspc desktop -f "test-swap-b" window remove 1 2 window remove 4 bspc desktop "test-swap-a" -r bspc desktop "test-swap-b" -r ================================================ FILE: tests/node/transfer ================================================ #! /bin/sh . ./prelude bspc monitor -a "test-transfer-a" "test-transfer-b" bspc desktop -f "test-transfer-a" window add 5 next_focus_a=$(bspc query -N -n @/1) next_focus_b=$(bspc query -N -n @/2/2/1) bspc node -f $next_focus_b bspc node @/2 -d "test-transfer-b" [ "$next_focus_a" = "$(bspc query -N -n @test-transfer-a:)" ] || fail "Invalid focus after transfer from source." [ "$next_focus_b" = "$(bspc query -N -n @test-transfer-b:)" ] || fail "Invalid focus after transfer in destination." window remove bspc desktop -f "test-transfer-b" window remove 1 2 window remove 2 bspc desktop "test-transfer-a" -r bspc desktop "test-transfer-b" -r ================================================ FILE: tests/prelude ================================================ #! /bin/sh fail() { echo "$@" 1>&2 exit 1 } window() { local action=${1:-add} local iter=${2:-1} local delta=${3:-1} local event=node_${action} local instance_name=${INSTANCE_NAME:-test} local class_name=${CLASS_NAME:-Test} local cmd case "$action" in add) cmd="./test_window $instance_name $class_name" ;; remove) cmd="bspc node -c" ;; esac while [ $iter -gt 0 ] ; do local rsp_chan=$(bspc subscribe -f -c "$delta" "$event") $cmd & cat "$rsp_chan" > /dev/null iter=$((iter - 1)) done } ================================================ FILE: tests/run ================================================ #! /bin/sh focus_follows_pointer=$(bspc config focus_follows_pointer) initial_polarity=$(bspc config initial_polarity) automatic_scheme=$(bspc config automatic_scheme) bspc config automatic_scheme spiral bspc config initial_polarity first_child bspc config focus_follows_pointer false cleanup () { bspc config automatic_scheme "$automatic_scheme" bspc config initial_polarity "$initial_polarity" bspc config focus_follows_pointer "$focus_follows_pointer" } abort() { cleanup echo "One test failed." 1>&2 exit 1 } echo "Node" echo "-> Insertion" ./node/insertion || abort echo "-> Removal" ./node/removal || abort echo "-> Transfer" ./node/transfer || abort echo "-> Swap" ./node/swap || abort echo "-> Flags" ./node/flags || abort echo "-> Receptacle" ./node/receptacle || abort echo "Desktop" echo "-> Transfer" ./desktop/transfer || abort echo "-> Swap" ./desktop/swap || abort cleanup echo "All tests passed." ================================================ FILE: tests/test_window.c ================================================ #include #include #include #include #include #include #define TEST_WINDOW_IC "test\0Test" bool get_atom(xcb_connection_t *dpy, char *name, xcb_atom_t *atom) { bool ret = true; xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(dpy, xcb_intern_atom(dpy, 0, strlen(name), name), NULL); if (reply != NULL) { *atom = reply->atom; } else { ret = false; } free(reply); return ret; } void check_request(xcb_connection_t *dpy, xcb_void_cookie_t cookie, char *msg) { xcb_generic_error_t *err = xcb_request_check(dpy, cookie); if (err != NULL) { fprintf(stderr, "%s: error code: %u.\n", msg, err->error_code); xcb_disconnect(dpy); exit(-1); } } xcb_gc_t get_font_gc(xcb_connection_t *dpy, xcb_window_t win, const char *font_name) { xcb_void_cookie_t ck; xcb_font_t font = xcb_generate_id(dpy); ck = xcb_open_font_checked(dpy, font, strlen(font_name), font_name); check_request(dpy, ck, "Can't open font"); xcb_gcontext_t gc = xcb_generate_id(dpy); uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT; uint32_t values[] = {0xffcccccc, 0xff111111, font}; xcb_create_gc(dpy, gc, win, mask, values); xcb_close_font(dpy, font); return gc; } void render_text(xcb_connection_t *dpy, xcb_window_t win, int16_t x, int16_t y) { char id[11]; xcb_void_cookie_t ck; snprintf(id, sizeof(id), "0x%08X", win); xcb_gcontext_t gc = get_font_gc(dpy, win, "-*-fixed-medium-*-*-*-18-*-*-*-*-*-*-*"); /* Doesn't work without _checked */ ck = xcb_image_text_8_checked(dpy, strlen(id), win, gc, x, y, id); check_request(dpy, ck, "Can't draw text"); xcb_free_gc(dpy, gc); } int main(int argc, char **argv) { char *wm_class = TEST_WINDOW_IC; size_t wm_class_len = sizeof (TEST_WINDOW_IC); bool will_free_wm_class = false; // test instance-name class-name if (argc > 2) { will_free_wm_class = true; size_t len1 = strlen(argv[1]); size_t len2 = strlen(argv[2]); // 2 null terminators wm_class_len = len1 + len2 + 2; wm_class = malloc(wm_class_len); if (!wm_class) return 1; memcpy(wm_class, argv[1], len1 + 1); memcpy(wm_class + len1 + 1, argv[2], len2 + 1); } xcb_connection_t *dpy = xcb_connect(NULL, NULL); if (dpy == NULL) { fprintf(stderr, "Can't connect to X.\n"); return EXIT_FAILURE; } xcb_atom_t WM_PROTOCOLS, WM_DELETE_WINDOW; if (!get_atom(dpy, "WM_PROTOCOLS", &WM_PROTOCOLS) || !get_atom(dpy, "WM_DELETE_WINDOW", &WM_DELETE_WINDOW)) { fprintf(stderr, "Can't get required atoms.\n"); xcb_disconnect(dpy); return EXIT_FAILURE; } xcb_screen_t *screen = xcb_setup_roots_iterator(xcb_get_setup(dpy)).data; if (screen == NULL) { fprintf(stderr, "Can't get current screen.\n"); xcb_disconnect(dpy); return EXIT_FAILURE; } xcb_window_t win = xcb_generate_id(dpy); uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; uint32_t values[] = {0xff111111, XCB_EVENT_MASK_EXPOSURE}; xcb_create_window(dpy, XCB_COPY_FROM_PARENT, win, screen->root, 0, 0, 320, 240, 2, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, mask, values); xcb_icccm_set_wm_class(dpy, win, wm_class_len, wm_class); xcb_map_window(dpy, win); xcb_flush(dpy); xcb_generic_event_t *evt; bool running = true; while (running && (evt = xcb_wait_for_event(dpy)) != NULL) { uint8_t rt = XCB_EVENT_RESPONSE_TYPE(evt); if (rt == XCB_CLIENT_MESSAGE) { xcb_client_message_event_t *cme = (xcb_client_message_event_t *) evt; if (cme->type == WM_PROTOCOLS && cme->data.data32[0] == WM_DELETE_WINDOW) { running = false; } } else if (rt == XCB_EXPOSE) { render_text(dpy, win, 12, 24); } free(evt); } xcb_destroy_window(dpy, win); xcb_disconnect(dpy); if (will_free_wm_class) { free(wm_class); } return EXIT_SUCCESS; }