A Waybar module for displaying and managing traditional window buttons in the Niri compositor.

yay -S niri_window_buttons # stable release
yay -S niri_window_buttons-git # latest git version
The compiled module will be at /usr/lib/waybar/libniri_window_buttons.so.
cargo build --release
The compiled module will be at target/release/libniri_window_buttons.so.
{
"modules-left": ["cffi/niri_window_buttons"],
"cffi/niri_window_buttons": {
"module_path": "/path/to/libniri_window_buttons.so",
// Display
"only_current_workspace": true,
"show_window_titles": true,
"truncate_titles": true,
"allow_title_linebreaks": false,
"button_alignment": "left",
"show_tooltip": true,
"tooltip_delay": 200,
// Sizing
"icon_size": 18,
"icon_spacing": 6,
"min_button_width": 10,
"max_button_width": 190,
"max_taskbar_width": 1210,
"scroll_arrow_left": "",
"scroll_arrow_right": "",
// Per-output overrides
"max_taskbar_width_per_output": {
"DP-1": 1400,
"eDP-1": 800
},
"dimensions_per_output": {
"eDP-1": {
"min_button_width": 10,
"max_button_width": 150,
"max_taskbar_width": 600
}
},
// Drag reorder
"drag_style": "browser",
"left_click_focus_on_press": true,
"drag_hover_focus": true,
"drag_hover_focus_delay": 500,
// Click actions
"click_actions": {
"left_click_unfocused": "focus-window",
"left_click_focused": "maximize-column",
"double_click": "maximize-window-to-edges",
"right_click_unfocused": "menu",
"right_click_focused": "menu",
"middle_click_unfocused": "close-window",
"middle_click_focused": "close-window",
"scroll_up": "move-column-left",
"scroll_down": "move-column-right"
},
// Context menu
"context_menu": [
{"label": " Maximize Column", "action": "maximize-column"},
{"label": " Maximize to Edges", "action": "maximize-window-to-edges"},
{"label": " Center Column", "action": "center-window"},
{"label": " Toggle Floating", "action": "toggle-window-floating"},
{"label": " Move WS Up", "action": "move-window-to-workspace-up"},
{"label": " Move WS Down", "action": "move-window-to-workspace-down"},
{"label": " Run Script", "command": "notify-send '{app_id}'"},
{"label": " Close Window", "action": "close-window"}
],
// Multi-select
"multi_select_modifier": "shift",
"multi_select_menu": [
{"label": " Move All Up", "action": "move-to-workspace-up"},
{"label": " Move All Down", "action": "move-to-workspace-down"},
{"label": " Maximize All", "action": "maximize-columns"},
{"label": " Close All", "action": "close-windows"}
],
// Filtering
"ignore_rules": [
{"app_id": "xpad"},
{"app_id": "launcher"},
{"title_contains": "Picture-in-Picture"},
{"app_id": "firefox", "title_regex": "^Extension:"}
],
// Notifications
"notifications": {
"enabled": true,
"use_desktop_entry": true,
"use_fuzzy_matching": true,
"map_app_ids": {
"org.mozilla.firefox": "firefox"
}
},
// Audio indicator
"audio_indicator": {
"enabled": true,
"playing_icon": "",
"muted_icon": "",
"clickable": true
},
// Per-app CSS classes and click overrides
"apps": {
"signal": [
{"match": "\\([0-9]+\\)$", "class": "unread"}
],
"firefox": [
{"match": ".*YouTube.*", "class": "youtube"},
{"match": ".*", "click_actions": {
"middle_click_unfocused": "none",
"middle_click_focused": "none"
}}
]
}
}
}
| Option | Description | Default |
|---|---|---|
show_all_outputs |
Show windows from all monitors | false |
only_current_workspace |
Show only current workspace windows | false |
show_window_titles |
Display window titles next to icons | true |
truncate_titles |
Truncate long titles with ellipsis | true |
allow_title_linebreaks |
Allow line breaks in window titles (expands button height) | false |
drag_style |
Drag reorder style: "browser" (grabbed button follows cursor) or "classic" (GTK drag-and-drop) |
"browser" |
drag_hover_focus |
Focus window when external drag hovers over button | true |
drag_hover_focus_delay |
Delay in milliseconds before hover triggers focus | 500 |
show_tooltip |
Show window title tooltip on hover | true |
tooltip_delay |
Delay in milliseconds before tooltip appears | 300 |
button_alignment |
Align buttons within the taskbar: "left", "center", or "right" |
"left" |
left_click_focus_on_press |
Focus unfocused windows on mouse-down instead of mouse-up. When false, dragging an unfocused window reorders it without stealing focus |
false |
| Option | Description | Default |
|---|---|---|
min_button_width |
Minimum button width in pixels | 150 |
max_button_width |
Maximum button width in pixels | 235 |
max_taskbar_width |
Total taskbar width limit in pixels | 1200 |
icon_size |
Icon dimensions in pixels | 24 |
icon_spacing |
Space between icon and title in pixels | 6 |
proportional_button_width |
Size each button to match its window's on-screen width | false |
proportional_icon_size |
Shrink icons along with proportional buttons (only applies when proportional_button_width is enabled) |
true |
With proportional_button_width enabled, each button is sized to reflect how wide its window
currently is on screen, mirroring the niri layout. A full-width window gets a button near
max_button_width; a window taking half the screen gets roughly half that width, and so on
(clamped between min_button_width and max_button_width). Buttons update live as you split,
resize, or maximize columns.
By default the app icons shrink along with the buttons. Set proportional_icon_size to false
to keep icons at a fixed icon_size while the button widths still scale.
{
"proportional_button_width": true,
"proportional_icon_size": true
}
For more granular control, configure all button dimensions per output:
{
"min_button_width": 150,
"max_button_width": 235,
"max_taskbar_width": 1200,
"dimensions_per_output": {
"eDP-1": {
"min_button_width": 100,
"max_button_width": 150,
"max_taskbar_width": 800
},
"DP-1": {
"min_button_width": 200,
"max_button_width": 300,
"max_taskbar_width": 1600
}
}
}
The top-level dimension settings are used as defaults. For each output, you can override any combination of min_button_width, max_button_width, and max_taskbar_width. Output names can be found using niri msg outputs.
When window buttons exceed max_taskbar_width, the taskbar becomes scrollable. Arrow buttons appear at the edges for navigation.
Scrolling methods:
| Method | Behavior |
|---|---|
| Click arrow buttons | Scrolls taskbar by one page |
| Mouse wheel on buttons | Scrolls taskbar (when scroll_up/scroll_down are "none") |
When scroll_up/scroll_down are set to a window action (e.g., "move-column-left"), that action executes instead of scrolling the taskbar. Set them to "none" to enable mouse wheel taskbar scrolling.
Arrow customization:
{
"scroll_arrow_left": "←",
"scroll_arrow_right": "→"
}
Defaults are "◀" and "▶". You can use any unicode characters, emoji, or Nerd Font icons. The arrows can also be styled via CSS using the .scroll-arrow-left and .scroll-arrow-right classes.
Configure what happens when you click buttons. All click types can be assigned any action, including the context menu. Right-click and middle-click support separate actions for focused vs unfocused windows:
"click_actions": {
"left_click_unfocused": "focus-window",
"left_click_focused": "maximize-column",
"double_click": "maximize-window-to-edges",
"right_click_unfocused": "menu",
"right_click_focused": "menu",
"middle_click_unfocused": "focus-window",
"middle_click_focused": "close-window",
"scroll_up": "move-column-left",
"scroll_down": "move-column-right"
}
Click actions can also run custom shell commands using object syntax:
"click_actions": {
"left_click_focused": { "command": "notify-send 'Clicked {app_id}'" },
"middle_click_focused": { "command": "my-script.sh {window_id}" }
}
Placeholders: {window_id}, {app_id}, {title}
Available actions:
| Action | Description |
|---|---|
none |
Do nothing (for scroll_up/scroll_down: enables taskbar scrolling) |
menu |
Show context menu |
focus-window |
Focus the window |
close-window |
Close the window |
| Column/Window Sizing | |
maximize-column |
Maximize column width |
maximize-window-to-edges |
Maximize window to screen edges |
expand-column-to-available-width |
Expand column to fill available space |
reset-window-height |
Reset window to default height |
switch-preset-column-width |
Cycle through preset column widths |
switch-preset-window-height |
Cycle through preset window heights |
| Centering | |
center-column |
Center column on screen |
center-window |
Center window on screen |
center-visible-columns |
Center all visible columns |
| Display | |
toggle-title |
Toggle title label visibility for this button |
| Fullscreen/Floating | |
fullscreen-window |
Toggle fullscreen |
toggle-windowed-fullscreen |
Toggle windowed fullscreen |
toggle-window-floating |
Toggle floating mode |
| Column Stacking | |
consume-window-into-column |
Stack window into adjacent column |
expel-window-from-column |
Unstack window from column |
toggle-column-tabbed-display |
Toggle tabbed display mode |
| Movement | |
move-column-left |
Move column left |
move-column-right |
Move column right |
move-column-to-first |
Move column to first position |
move-column-to-last |
Move column to last position |
move-window-up |
Move window up in column |
move-window-down |
Move window down in column |
| Workspace/Monitor Movement | |
move-window-to-workspace-up |
Move window to workspace above |
move-window-to-workspace-down |
Move window to workspace below |
move-window-up-or-to-workspace-up |
Move up, or to workspace above if at top |
move-window-down-or-to-workspace-down |
Move down, or to workspace below if at bottom |
move-window-to-monitor-left |
Move window to left monitor |
move-window-to-monitor-right |
Move window to right monitor |
move-column-left-or-to-monitor-left |
Move column left, or to left monitor |
move-column-right-or-to-monitor-right |
Move column right, or to right monitor |
| Focus | |
focus-workspace-previous |
Focus previously active workspace |
Customize which actions appear in the context menu and their order:
"context_menu": [
{"label": " Fullscreen", "action": "fullscreen-window"},
{"label": " Maximize Column", "action": "maximize-column"},
{"label": " Maximize to Edges", "action": "maximize-window-to-edges"},
{"label": " Toggle Floating", "action": "toggle-window-floating"},
{"label": " Custom Script", "command": "my-script.sh {window_ids}"},
{"label": " Toggle Title", "action": "toggle-title"},
{"label": " Close Window", "action": "close-window"}
]
Menu items can also run custom shell commands with placeholders:
- {window_id} - Window ID
- {app_id} - Application ID
- {title} - Window title
Example: {"label": " Run Script", "command": "notify-send 'Window: {app_id}'"}
The menu can be triggered via any click action by setting it to "menu".
Select multiple windows using a modifier key, then perform batch actions via right-click menu:
{
"multi_select_modifier": "ctrl",
"multi_select_menu": [
{"label": " Move All Up", "action": "move-to-workspace-up"},
{"label": " Move All Down", "action": "move-to-workspace-down"},
{"label": " Float All", "action": "toggle-floating"},
{"label": " Fullscreen All", "action": "fullscreen-windows"},
{"label": " Custom Script", "command": "my-script.sh {window_ids}"},
{"label": " Close All", "action": "close-windows"}
]
}
Modifier options: ctrl, shift, alt, super
**Requirements:
$ claude mcp add niri_window_buttons \
-- python -m otcore.mcp_server <graph>