zwm is a minimalistic and opinionated tiling window manager for X11. It uses XCB instead of Xlib to communicate with the X server. The underlying data structure for managing windows is a customized BSP tree.
- Compliance with a subset of ewmh and icccm
- Multiple layouts: default, master, stack, grid, monocle, three-column, and deck.
- Multiple virtual desktops.
- Multi-monitor support.
- Independent workspaces for each monitor by default.
- Low memory footprint, runs within ~2MB of memory
- Resize, flip, and swap windows or partitions.
- Layouts apply to individual desktops.
- Keyboard-driven, fully controlled via keyboard shortcuts.
- Mouse for convenience when move/resize for floating/tiled windows and partitions.
- Drag-and-drop windows between partitions.
- Customizable settings.
- Customizable window rules.
- Can be integrated with any status bar.
- Can handle multiple bars per monitor/display, in different locations (top, bottom, left, right).
- Config reload on the fly.
- And many more.. look at they keybinds section to see what zwm offers.
ZWM uses binary space partitioning tree (BSP-tree) to store and manage windows.
- Each desktop has its own pointer to a bsp-tree.
- The tree is a partition of a monitor's rectangle into smaller rectangular regions.
- Each leaf node holds exactly one window.
- Each node in a bsp-tree either has zero or two children.
- Each internal node is responsible for splitting a rectangle in half.
Window and Partition Structure in a BSP-tree:
The layout of windows in BSPWM follows a binary tree structure:
I ROOT (root is an INTERNAL NODE, unless it is a leaf by definition)
/ \
I I INTERNAL NODES (screen sections/partitions)
/ \ / \
E E E E EXTERNAL NODES (windows/leaves)
- Internal Nodes (I) represent screen sections where windows can be displayed.
- External Nodes (E) represent the actual windows within those sections.
- Windows (E nodes) share the width, height, and coordinates (x, y) of their parent sections (I nodes).
Example Structure:
I
/ \
I I
/ \ / \
E E E I
/ \
E E
Partition Behavior:
- Internal nodes (I) can contain other partitions or be contained within other partitions.
- External nodes (E) are the leaves (actual windows).
Example of tree:
- 1, 2, 3 are leaves/windows (EXTERNAL_NODE).
- a, b are internal nodes (INTERNAL_NODE), or screen sections/partitions.
1 a a
/ \ / \
---> 1 2 ---> 1 b
/ \
2 3
Visualization of Screen Layout:
+-----------------------+ +-----------------------+ +-----------------------+
| | | | | | | |
| | | | | | | 2 |
| | | | | | | |
| 1 | | 1 | 2 | | 1 |-----------|
| | | | | | | |
| | | | | | | 3 |
| | | | | | | |
+-----------------------+ +-----------------------+ +-----------------------+
Another Example:
- Numbers are the are leaves/windows (EXTERNAL_NODE).
- Letters are internal nodes (INTERNAL_NODE), or screen sections/partitions.
a a a
/ \ / \ / \
1 b ---> c b ---> c b
/ \ / \ / \ / \ / \
2 3 1 4 2 3 1 d 2 3
/ \
4 5
+-----------------------+ +-----------------------+ +-----------------------+
| | | | | | | | |
| | 2 | | 1 | 2 | | 1 | 2 |
| | | | | | | | |
| 1 |-----------| |-----------|-----------| |-----------|-----------|
| | | | | | | | | |
| | 3 | | 4 | 3 | | 4 | 5 | 3 |
| | | | | | | | | |
+-----------------------+ +-----------------------+ +-----------------------+
yay -S zwmxbps-install -S zwm- gcc
- libxcb
- xcb-util
- xcb-util-keysyms
- xcb-util-wm (ewmh,icccm)
- lxcb-randr
- lxcb-xinerama
- lxcb-cursor
git clone https://github.com/Yazeed1s/zwm.git
cd zwm && sudo make installWhen you first start zwm, a config file will be generated in the following location with the default config.
- ~/.config/zwm/zwm.conf
border_width = 2
active_border_color = 0x4a4a48
normal_border_color = 0x30302f
window_gap = 10
virtual_desktops = 7
focus_follow_pointer = true
focus_follow_spawn = false
restore_last_focus = false- border_width: Defines the width of the window borders in pixels.
- active_border_color: Specifies the color of the border for the active (focused) window.
- normal_border_color: Specifies the color of the border for inactive (unfocused) windows.
- window_gap: Sets the gap between windows in pixels.
- virtual_desktops: sets the number of virtual desktops.
- focus_follow_pointer: If false, the window is focused on click; if true, the window is focused when the cursor enters it.
- focus_follow_spawn: If false, new windows require manual focus (e.g., via click); if true, newly spawned windows will automatically receive focus.
- restore_last_focus: If true, ZWM will restore the previously focused window when switching to a desktop, only if that desktop’s layout is not set to stack.
- default: Manual BSP tiling. New windows split the focused partition.
- master: One selected master window with the remaining windows arranged in the stack area.
- stack: All tiled windows occupy the same rectangle; use stack traversal to cycle focus.
- grid: Tiled windows are distributed into an automatic grid.
- monocle: One tiled window is visible at a time. Other tiled windows are unmapped; floating windows remain visible.
- three_col: The window under the cursor becomes the center column when the layout is selected. Other tiled windows are split between the left and right columns. The center column can be resized with
resize:grow/resize:shrinkor mouse resize. - deck: The window under the cursor becomes the left master when the layout is selected. Other tiled windows share the right deck area; stack traversal cycles only through the deck windows. The master/deck split can be resized with
resize:grow/resize:shrinkor mouse resize.
- For a single command:
exec = "polybar"- To specify additional arguments as a list:
exec = ["polybar", "-c", ".config/polybar/config.ini"]rule = wm_class("window class name"), state(tiled|floated), desktop(1..N)- wm_class: The window class name used to identify the window.
- Use the
xproptool to find the wm_class of a window.
- Use the
- state: Specifies whether the window should be tiled or floated.
- tiled: The window will be tiled... clearly.
- floated: The window will be floated... clearly.
- desktop: The virtual desktop number where the window should be placed.
- Use -1 if you do not want to set it to a specific desktop.
; Example:
rule = wm_class("firefox"), state(tiled), desktop(-1)
; This rule sets "firefox" window to be tiled and does not change its virtual desktop.- The format for defining key bindings is:
bind = modifier + key -> action - If two modifiers are used, combine them with a pipe
(|). For example, alt + shift is written asalt|shift. - Note: Some functions require additional arguments to specify details of the action.
- These arguments are provided using a colon syntax, where the function and its argument are separated by a colon.
- Example:
func(switch_desktop:1)means "switch to desktop 1". - Example:
func(resize:grow)means "grow the size of the window". - Example:
func(layout:master)means "toggle master layout".
- super (meta), alt, shift, ctrl
-
run(...): Executes a specified process.
- Example:
bind = super + return -> run("alacritty") - To run a process with arguments, use a list:
Example:
bind = super + p -> run(["rofi", "-show", "drun"])
- Example:
-
func(...): Calls a predefined function. The following functions are available:
- kill: Kills the focused window.
- switch_desktop: Switches to a specified virtual desktop.
- fullscreen: Toggles fullscreen mode for the focused window.
- swap: Swaps the focused window with its sibling.
- transfer_node: Moves the focused window to another virtual desktop.
- layout: Toggles the specified layout (master, default, stack, grid, monocle, three_col, deck).
- traverse: In stack/monocle/deck layouts, moves focus to the previous or next stacked window. In deck, traversal skips the master and cycles through the deck area.
- flip: Changes the window's orientation; if the window is primarily vertical, it becomes horizontal, and vice versa.
- cycle_window: Moves focus to the window in the specified direction (up, down, left, right).
- cycle_desktop: Cycles through the virtual desktops (left, right).
- resize: Adjusts the size of the focused window (grow, shrink).
- reload_config: Reloads the configuration file without restarting ZWM.
- shift_window: Shift the floating window's position to the specified direction by 10px (up, down, left, right).
- gap_handler: Increase or decrease window gaps (GROW, SHRINK).
- change_state: Set window state (FLOATING, TILED).
- grow_floating_window: Grow floating window (horizontally or vertically).
- shrink_floating_window: Shrink floating window (horizontally or vertically).
- cycle_monitors: Cycle between monitors (left or right, relative to the linked-list order not the physical positioning).
- start_keyboard_drag: Enter keyboard-driven drag mode to move tiled windows between partitions.
-
Default keys
bind = super + return -> run("alacritty")
bind = super + space -> run("dmenu_run")
bind = super + p -> run(["rofi", "-show", "drun"])
bind = super + w -> func(kill)
bind = super + 1 -> func(switch_desktop:1)
bind = super + 2 -> func(switch_desktop:2)
bind = super + 3 -> func(switch_desktop:3)
bind = super + 4 -> func(switch_desktop:4)
bind = super + 5 -> func(switch_desktop:5)
bind = super + 6 -> func(switch_desktop:6)
bind = super + 7 -> func(switch_desktop:7)
bind = super + l -> func(resize:grow)
bind = super + h -> func(resize:shrink)
bind = super + i -> func(gap_handler:grow)
bind = super + d -> func(gap_handler:shrink)
bind = super + f -> func(fullscreen)
bind = super + s -> func(swap)
bind = super + up -> func(cycle_window:up)
bind = super + right -> func(cycle_window:right)
bind = super + left -> func(cycle_window:left)
bind = super + down -> func(cycle_window:down)
bind = super|shift + up -> func(shift_window:up)
bind = super|shift + right -> func(shift_window:right)
bind = super|shift + left -> func(shift_window:left)
bind = super|shift + down -> func(shift_window:down)
bind = super|alt + f -> func(change_state:float)
bind = super|alt + t -> func(change_state:tile)
bind = super|shift + t -> func(shrink_floating_window:horizontal)
bind = super|shift + g -> func(shrink_floating_window:vertical)
bind = super|shift + y -> func(grow_floating_window:horizontal)
bind = super|shift + h -> func(grow_floating_window:vertical)
bind = super|alt + left -> func(cycle_desktop:left)
bind = super|alt + right -> func(cycle_desktop:right)
bind = super|ctrl + right -> func(cycle_monitors:next)
bind = super|ctrl + left -> func(cycle_monitors:prev)
bind = super|shift + 1 -> func(transfer_node:1)
bind = super|shift + 2 -> func(transfer_node:2)
bind = super|shift + 3 -> func(transfer_node:3)
bind = super|shift + 4 -> func(transfer_node:4)
bind = super|shift + 5 -> func(transfer_node:5)
bind = super|shift + 6 -> func(transfer_node:6)
bind = super|shift + 7 -> func(transfer_node:7)
bind = super|shift + m -> func(layout:master)
bind = super|shift + s -> func(layout:stack)
bind = super|shift + d -> func(layout:default)
bind = super|shift + c -> func(layout:grid)
bind = super|shift + o -> func(layout:monocle)
bind = super|shift + x -> func(layout:three_col)
bind = super|shift + v -> func(layout:deck)
bind = super|shift + k -> func(traverse:up)
bind = super|shift + j -> func(traverse:down)
bind = super|shift + f -> func(flip)
bind = super + m -> func(start_keyboard_drag)
bind = super|shift + r -> func(reload_config)More options will be added in the future as development progresses.
| Key | Description |
|---|---|
super + w |
kill/close window |
super + return |
launch a terminal (alacritty) |
super + space |
launch dmenu |
super + p |
launch rofi |
super + [1..N] |
switch to desktop |
super + l |
resize window (grow/expand) |
super + h |
resize window (shrink) |
super + f |
toggle fullscreen |
super + shift + [1..N] |
transfer window to a diff desktop |
super + shift + m |
toggle master layout |
super + shift + s |
toggle stack layout |
super + shift + d |
toggle default layout |
super + shift + c |
toggle grid layout |
super + shift + o |
toggle monocle layout |
super + shift + x |
toggle three-column layout |
super + shift + v |
toggle deck layout |
super + shift + j/k |
traverse the stack |
super + shift + f |
flip the window/partition |
super + shift + r |
hot-reload |
super + shift + y |
grow floating windows horizontally |
super + shift + h |
grow floating windows vertically |
super + shift + t |
shrink floating windows horizontally |
super + shift + g |
shrink floating windows vertically |
super + button1 |
move window (drag tiled / move floating) |
super + button3 |
resize window (drag resize) |
super + ctrl + → |
focus/change monitor right |
super + ctrl + ← |
focus/change monitor left |
super + s |
swap window's orientation |
super + ← |
focus window on the left |
super + ↑ |
focus window above |
super + → |
focus window on the right |
super + ↓ |
focus window below |
super + alt + → |
cycle desktop right |
super + alt + ← |
cycle desktop left |
super + shift + ← |
shift floating window to the left by 10px |
super + shift + ↑ |
shift floating window up by 10px |
super + shift + → |
shift floating window to the right by 10px |
super + shift + ↓ |
shift floating window down by 10px |
super + i |
increase window gaps by 5px |
super + d |
decrease window gaps by 5px |
super + alt + t |
tile window |
super + alt + f |
float window |
super + m |
start keyboard-driven window drag |
ZWM is written in a way to be fully keyboard-driven but it also offers mouse support for convenience.
Explained below are the operations that can be performed via both mouse and keyboard.
- Mouse:
super + button1(Left Click) while dragging the window.- Works on Tiled Windows: Drags the window to a new partition (swaps or moves).
- Works on Floating Windows: Moves the window freely.
- Keyboard:
- Tiled:
super + minitiates keyboard drag mode. Use arrow keys to move the window. - Floating:
super + shift + [arrow keys]shifts the window to the arrow direction by 10px.
- Tiled:
- Mouse:
super + button3(Right Click) while dragging near edges/corners or layout split lines.- Works on Tiled Windows in the default layout: Resizes the shared edge between windows.
- Works on Deck and Three-column layouts: Resizes the main split.
- Works on Floating Windows: Resizes the window dimensions.
- Keyboard:
- Tiled:
super + l(Grow) /super + h(Shrink). - Floating:
super + shift + y/h(Grow Horiz/Vert) andsuper + shift + t/g(Shrink Horiz/Vert).
- Tiled:
[module/xwindow]
type = internal/xwindow
format = <label>
; Available tokens:
; %title%
; %instance% (first part of the WM_CLASS atom, new in version 3.7.0)
; %class% (second part of the WM_CLASS atom, new in version 3.7.0)
; Default: %title%
label = %title%
label-maxlen = 50
label-empty = "[null]"[module/ewmh]
type = internal/xworkspaces
label-active = %index%
label-active-background = ${colors.bg}
label-active-underline = ${colors.blue}
label-active-padding = 1
label-occupied = %index%
label-occupied-padding = 1
label-urgent = %index%!
label-urgent-background = ${colors.red}
label-urgent-padding = 1
label-empty = %index%
label-empty-foreground = ${colors.gray}
label-empty-padding = 1
label-separator = " "For further customization please refer to polybar's wiki.
If you would like to add a feature or to fix a bug please feel free to submit a PR.. after we discuss in an Issue.


