-
Notifications
You must be signed in to change notification settings - Fork 0
Home
shod - hybrid window manager
shod
shod is a hybrid (tiling and floating) reparenting X11 window manager which supports tabs, multiple monitors, themes, and window rules. shod sets no keybindings; reads no configuration other than X resources; and works only via mouse with a given key modifier (Alt by default) and by responding to client messages with EWMH hints (so it is needed wmctrl(1) to control shod).
shod maintains one monitor for each physical monitor found by Xinerama(1). One of the monitors is the focused one, where new windows go to when they are created. Each monitor contains a set of desktops. One of the desktops of a monitor is the focused desktop for that monitor.
Windows are displayed in frames. A frame contains borders (four edges and four corners), tabs (one for each client in the window), two buttons (a minimize and a close button), the content (which is the window of the selected tab), and an optional dialog window centered on the content. The buttons and the tabs of a frame together are called the title bar of that frame. Windows can be grouped into the same frame with tabs. One of the tabs is the selected tab. Only the contents of the selected tab of a frame is shown in that frame.
Each frame has one of the following four states:
-
Minimized
Minimized frames belong to no monitor or desktop. Minimized frames are not shown on the screen. When a minimized frame is unminimized, it becomes a normal frame on the focused desktop of the focused monitor.
-
Sticky
Sticky frames belong only to a monitor; but they do not belong to any desktop. Instead, they are “sticked” to the monitor, and they appear on the screen no matter which desktop is focused on that monitor.
-
Tiled
Tiled frames belong to a desktop and its monitor. The monitor is divided into columns, each frame occupies a row in a column. (This tiling style is the same used by the old wmii(1) window manager and the acme(1) editor).
-
Normal
Normal frames belong to a desktop and its monitor. Normal frames, along with sticky frames, are called floating frames. Normal frames appear only when its desktop is the focused one for its monitor.
A non-minimized frame can also be made fullscreen or shaded, but not both. A floating (sticky or normal) frame can also be raised above other floating windows or lowered below other floating frames.
When a new frame spawns, it is set as Normal, and it is placed in an unoccupied region of the monitor. The first frame in a monitor is centered on the monitor.
The frame stacking order is the following (from bottom-to-top):
- Tiled frames appear below any other frames.
- Lowered floating frames appear on top of tiled frames.
- Regular floating frames appear on top of lowered floating frames.
- Raised floating frames appear on top of regular floating frames.
- Fullscreen frames appear on top of raised floating frames.
shod sets the property _SHOD_TAB_GROUP on each window to the ID of the window of the focused tab of its frame. All the windows in the focused frame have the _NET_WM_STATE_FOCUSED atom set in the _NET_WM_STATE property.
The initial state of a window and whether it will be displayed in a new frame or in a new tab of an existing frame is specified by the values of the *desktop, *state, and *autotab X resources.
A window of type _NET_WM_WINDOW_TYPE_PROMPT is mapped on the top of the focused monitor. This window will stay focused and mapped until be closed or a mouse button is pressed outside that window. This is an EWMH extension, only used by xprompt(1).
Transient/dialog windows are not mapped into a new frame or a new tab of an existing frame; instead, they are centered on the content of the window it is a transient for. This behavior is the same as sheets in macOS window system.
Desktop windows, such as conky(1) and windows that manage desktop icons, are mapped bellow all other windows. Those windows are used to draw something on the desktop, such as widgets and icons.
There are several ways shod can manage windows. See the section EXAMPLES for examples on how to use wmctrl(1) for controlling shod.
The main method for managing windows is the mouse.
The buttons set with the shod.focusButtons X resource are used to focus a frame when clicking on it. If no button is specified, the focus follows the mouse pointer.
The buttons set with the shod.raiseButtons X resource are used to raise a frame when clicking on it.
The modifier set with the shod.modifier X resource is the modifier key. Pressing the modifier key and dragging a frame with the Button 1 (the left mouse button) will move that frame. Pressing the modifier key and dragging a frame with the button 3 (the right mouse button) will resize that frame.
Resizing a frame can also be performed by dragging the frame border with the Button 1, without pressing any modifier. Moving a frame can also be performed by dragging the frame border with the button 3, without pressing any modifier. Moving a frame can also be performed by dragging a tab of the frame with the Button 1, without pressing any modifier.
A tab can be detached from its frame by dragging it with the Button 3. A detached tab, while being dragged with the Button 3, can be attached into another frame by dropping it into another frame's title bar. Double clicking on a tab shades or unshades the frame.
Each frame has two buttons on its title bar. Clicking with the mouse Button 1 on the left button (called the minimize button) minimizes the frame. Clicking with the mouse Button 1 on the right button (called the close button) closes the window on the focused tab, and, if it was the last tab on the frame, deletes the frame.
When a normal frame is moved from one monitor to another, that frame moves from the desktop it is in to the focused desktop of the monitor it is moved to.
The wmctrl(1) utility can be used to send client messages to the window manager, and shod respects those messages in different ways. For example, running the following command toggles the sticky option on the active frame.
$ wmctrl -r :ACTIVE: -b toggle,sticky
- Setting the sticky state on a window, sticks the window's frame on the monitor.
- Setting both the maximize_vert and maximize_horz states on a window, tiles the window's frame. For more information on manipulating tiled windows, see the section Managing tiled windows below.
- Setting the hidden state on a window, minimizes the window's frame (it won't be displayed on any desktop or on any monitor).
- Setting the fullscreen state on a window, makes the content of the window's frame be maximized to fit the entire screen.
- Setting the above state on a floating window, raises the window's frame above all other floating frames.
- Setting the below state on a floating window, lowers the window's frame below all other floating frames.
- Setting the shaded state on a window, collapses the window's frame into its title bar.
All other client messages are ignored.
shod acts upon other EWMH client messages sent to windows and to the root window. Most client messages can be sent via wmctrl(1) with a specific option. The options and the messages they send are specified below.
- A message sent with the -s NUMBER option of wmctrl(1) makes shod changes the desktop. That is, hide the windows on the current desktop and show the windows on a new desktop. If the desktop is on another monitor, shod instead moves the pointer to that monitor and focus a window on it.
- A message sent with the -k on or -k off options of wmctrl(1) makes shod show or hide the desktop, respectively.
- A message sent with the -a WINDOW option of wmctrl(1) makes shod change the active frame. That is, focus and raise the frame of the specified window.
- A message sent with the -c WINDOW option of wmctrl(1) makes shod close gently the specified window.
- A message sent with the -e POSITION option of wmctrl(1) makes shod change the position and geometry of the frame of the specified window.
- A message sent with the -t NUMBER option of wmctrl(1) makes shod move the frame of the specified window to the specified desktop.
shod acts upon configure request events sent to windows by resizing and moving their frames just as if the user have resized or moved them with the mouse.
When a window is maximized, its frame is tiled by shod. A tiled frame behaves differently of regular frames. Tiled frames are organized in columns. Each tiled frame ocupies a row in a column.
In order to move a tiled frame from one column to another just move the frame left or right with wmctrl(1) or with the mouse. This will move the frame from its current column to the column to its left or right, or it will create a new column, if needed.
In order to move a tiled frame up or down a column, just move the frame up or down with wmctrl(1) or with the mouse.
Resizing a tiled frame with wmctrl(1) or with the mouse will change the size of the frame, the size of the column it is in, and the size of the neighboring frames.
The following environment variables affect the execution of shod
-
DISPLAY
The display to start shod on.
shod understands the following X resources.
These options control how WM requests are handled.
-
shod.ignoreIndirect
Window management requests (such as to send a window to a given desktop) can be originated from one of two different sources: by the application (indirect request) or by the user (direct request). Applications requesting themselves to maximize their windows or send their windows to a given desktop can be an annoyance. If this resource is set to true, indirect requests are ignored.
These resources specify the mouse buttons that control windows and the keyboard modifier that can be used with mouse buttons.
-
shod.focusButtons
Which mouse buttons can be used to focus a window when clicking on it. buttons is a string of numbers 1 to 5 (corresponding to mouse buttons 1 to 5). For example, setting this resource to 13 makes windows be focused when clicking on them with the mouse buttons 1 and 3 (the left and right mouse buttons, respectively). If this is set to a blank string, no mouse button is used to focus window, and shod uses the focus-follow-cursor focusing style. By default, focus follows mouse click on button 1.
-
shod.modifier
Which modifier, from Mod1 to Mod5 is used to move and resize windows with the mouse pointer.
-
shod.raiseButtons
Which mouse buttons can be used to raise a window when clicking on it. buttons is a string of numbers 1 to 5 (corresponding to mouse buttons 1 to 5). For example, setting this resource to 13 makes windows be raised when clicking on them with the mouse buttons 1 and 3 (the left and right mouse buttons, respectively). By default, raise occurs on mouse click on button 1.
These resources control the appearance of frames and whether the titlebar is visible.
-
shod.font
The font in the X Logical Font Description of the text in the title bar.
-
shod.theme
Path to a .xpm file containing the border decorations. The x_hotspot is interpreted as the width of the border for that decoration. The y_hotspot is interpreted as the width of the buttons for that decoration. The size of the corner is calculated as the sum of the width of the border and the width of the buttons. The height of the title bar (and its tabs) is equal to the width of the buttons. The .xpm file contains in it nine squares representing all the possible decoration states for a frame. A sample .xpm file is distributed with shod.
-
shod.hideTitle
If set to “true”, the title bars of frames with a single tab are hidden.
These resources control the appearance and spacement between tiled frames.
-
shod.gapOuter
The gap in pixels between the sides of the monitor and the frames.
-
shod.gapInner
The gap in pixels between the tiled frames.
-
shod.ignoreGaps
If set to “true”, a single tiled frame ingores the gaps.
-
shod.ignoreTitle
If set to “true”, a single tiled frame ingores the title bar.
-
shod.ignoreBorders
If set to “true”, a single tiled frame does not have borders.
-
shod.mergeBorders
If set to “true”, the borders of adjacent tiled frames are merged into a single border.
These resources control how a new window is managed. They are described according to a group (role, class, instance, or title) and to the description of the group. User-placed windows ignore rules. Groups and descriptions are case sensitive. See the examples for more information.
-
shod.GROUP.DESCRIPTION.autotab
Controls whether a new window should be tabbed with the focused window if they have the same class. If set to floating, auto tabbing occurs if the focused window is floating. If set to tilingAlways, auto tabbing occurs if the focused window is tiled. If set to tilingMulti, auto tabbing occurs if the focused window is tiled and there is at least two tiled windows. If set to always, auto tabbing occurs if the focused window has a visible title bar. If set to off, auto tabbing does not occur.
-
shod.GROUP.DESCRIPTION.desktop
Controls in which desktop a new window should be mapped on.
-
shod.GROUP.DESCRIPTION.state
Controls the initial state of a new window. If set to normal, the window begins in normal state (the default). If set to sticky, the window begins sticked on the screen. If set to tiled, the window begins tiled. If set to minimized, the window begins minimized.
-
shod.GROUP.DESCRIPTION.position
TODO
The following is a sample configuration for X resources. It must be placed in $HOME/.Xresources or $HOME/.Xdefaults or other file called by xrdb(1). This example makes shod uses a 7 pixels wide gap around and between tiled windows. It also sets three window rules: windows with the browser role should be mapped on the second desktop; windows of class Zathura should be mapped tiled; and windows of class XTerm should be tabbed with other windows of the same class.
shod.gapOuter: 7
shod.gapInner: 7
shod.role.browser.desktop: 2
shod.class.Zathura.state: tiled
shod.class.XTerm.autotab: always
The following is a sample configuration for sxhkd(1), a program that binds keypresses (or key releases) to commands. This example uses wmctrl(1) for sending EWMH hints to shod. It uses wmr(1) (a script shown below) for moving and resizing windows, respectively.
# Start terminal
mod1 + Return
xterm
# Killing windows
mod1 + shift + q
wmctrl -c :ACTIVE:
# Workspace
mod1 + {1,2,3,4,5,6,7,8,9,0}
wmctrl -s {0,1,2,3,4,5,6,7,8,9}
mod1 + shift + {1,2,3,4,5,6,7,8,9}
wmctrl -r :ACITVE: -t {0,1,2,3,4,5,6,7,8,9}
# Resize/move windows with wmr
mod1 + {c, v, shift + c, shift + v}
wmr 0 0 {-25 0, 0 -25, +25 0, 0 +25}
mod1 + shift + {h, j, k, l}
wmr {-10 0, 0 10, 0 -10, 10 0} 0 0
# Change window status to sticky/above/below/minimized/fullscreen/maximized
mod1 + shift + {s, a, b, z, f}
wmctrl -r :ACTIVE: -b toggle,{sticky,above,below,hidden,fullscreen}
mod1 + shift + t
wmctrl -r :ACTIVE: -b toggle,maximized_vert,maximized_horz
# Call the unminimize.sh script
mod1 + shift + u
unminimize.sh
The previous example binds the following keys to the following commands:
-
Mod4 + Enter
Spawns a terminal emulator window.
-
Mod4 + Shift + Q
Gently closes the active windows.
-
Mod4 + <N>
Go to the N-th desktop.
-
Mod4 + Shift + <N>
Send active window to the N-th desktop.
-
Mod4 + C
Shrink the active window horizontally by 25 pixels.
-
Mod4 + Shift + C
Expand the active window horizontally by 25 pixels.
-
Mod4 + V
Shrink the active window vertically by 25 pixels.
-
Mod4 + Shift + V
Expand the active window vertically by 25 pixels.
-
Mod4 + Shift + H
Move the active window 10 pixels to the left.
-
Mod4 + Shift + J
Move the active window 10 pixels down.
-
Mod4 + Shift + K
Move the active window 10 pixels up.
-
Mod4 + Shift + L
Move the active window 10 pixels to the right.
-
Mod4 + Shift + S
Make the active window sticky; or make it normal if it was sticky.
-
Mod4 + Shift + A
Raise the active window above the others; or move it to its normal place if it was already above others.
-
Mod4 + Shift + B
Lower the active window below the others; or move it to its normal place if it was already below others.
-
Mod4 + Shift + Z
Hide the active window.
-
Mod4 + Shift + F
Make the active window fullscreen; or make it normal if it was already fullscreen.
-
Mod4 + Shift + T
Tile the active window; or make it floating if it was already tiled.
-
Mod4 + Shift + U
Call the unminimize.sh script (see below).
The following is a sample script for dmenu(1). This script lists the minimized windows and unminimizes the selected one. This script uses xprop(1) to obtain the X properties set by shod.
#!/bin/sh
lsw() {
xprop -notype -f "_NET_CLIENT_LIST" 0x \(aq $0+\n\(aq -root "_NET_CLIENT_LIST" |\
cut -d\(aq \(aq -f2- |\
sed \(aqs/, */\
/g\(aq
}
ishidden() {
xprop -notype -f "_NET_WM_STATE" 32a \(aq $0+\n\(aq -id "$1" "_NET_WM_STATE" |\
cut -d\(aq \(aq -f2- |\
sed \(aqs/, */\
/g\(aq | grep -q "_NET_WM_STATE_HIDDEN"
}
printname() {
name="$(xprop -notype -f "_NET_WM_NAME" 8s \(aq $0+\n\(aq -id "$1" "_NET_WM_NAME" 2>/dev/null)"
[ "$(echo $name)" = "_NET_WM_NAME: not found." ] && \
name="$(xprop -notype -f "WM_NAME" 8s \(aq $0+\n\(aq -id "$1" "WM_NAME" 2>/dev/null)"
echo $name |\
cut -d\(aq \(aq -f2- |\
sed \(aqs/, */\
/g\(aq
}
for win in $(lsw)
do
ishidden $win && printf "%s: " $win && printname $win
done |\
dmenu -i -l 8 -p "unminimize window:" |\
cut -d: -f1 |\
xargs wmctrl -b toggle,hidden -ir
The following script moves and resize the active window by a relative amount of pixels.
#!/bin/sh
# wmr: move and resize window relative to its current position and size
set -e
usage() {
echo "usage: wmr x y w h" >&2
exit 1
}
[ $# -ne 4 ] && usage
eval $(xdotool getactivewindow getwindowgeometry --shell)
xadd=$1
yadd=$2
wadd=$3
hadd=$4
X=$(( X + xadd ))
Y=$(( Y + yadd ))
WIDTH=$(( WIDTH + wadd ))
HEIGHT=$(( HEIGHT + hadd ))
wmctrl -r :ACTIVE: -e 0,$X,$Y,$WIDTH,$HEIGHT
dmenu(1), sxhkd(1), wmctrl(1), xprompt(1)
XSizeHints_(3)_ are ignored. Size hints make no sense in a tiled and tabbed window manager. They only make sense when the size of a single frame depends only on a single window, and a single window dictates the size of a single frame. When the size of a frame depends on the size of other frames (as in the tiled situation), or when a set of windows must have the same size (as in a tabbed situation), it makes no sense to constrain the size of a frame based on the size hints of a single window, because the relation from windows to frames is no more one-to-one.
Naming things is hard. In the context of X Window System, a "window" can mean the UI object the user sees on the screen, or the Xlib object the programmer manipulates on the code. Usually, there is one window (Xlib object) to one window (UI object), but since shod is a tabbed window manager, there can exist more than one window (Xlib object) in a single window (UI object). To help on that, this manual uses the term "frame" to call windows (UI object), and the term "window" to the other sense. But, for historical reasons, the code uses the term "client" to refer to the UI object (frames).
EWMH hints (and other properties) may not be updated when they should. This is a bug and should be reported.