detached 93b73a7

docs(config): document relative output placement and monitor navigation

commit
93b73a79b9dfcbf26b1ef9d7af20000c0cbe2a1f
author
brice · · 2026-10-03 14:09 UTC
parent
0e21ae4
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +34 -4
M docs/config.md +32 -2
@@ -56,6 +56,8 @@ NeferWL reads `$XDG_CONFIG_HOME/neferwl/config` (or `~/.config/neferwl/config`).
5656 | `output.<name>` | preferred | `WxH`, `WxH@Hz`, `preferred` or `off` |
5757 | `output.<name>.scale` | `1` | 1 to 4, e.g. `1.5` or `4/3` |
5858 | `output.<name>.primary` | `off` | Gets focus and pointer at startup |
59+| `output.<name>.right-of` / `.left-of` / `.above` / `.below` | automatic | Connector to place the output against, e.g. `DP-1` |
60+| `output.<name>.offset` | `0` | Logical px along the shared edge: down for `right-of`/`left-of`, right for `above`/`below`. Negative allowed, −65536 to 65536. Ignored without a relation, with a warning |
5961 | `output.<name>.hdr` | `off` | Turns on HDR10 on capable outputs; otherwise falls back to SDR (reason in the log) |
6062 | `output.<name>.sdr-brightness` | `203` | How bright SDR desktop content appears in HDR, in nits (80–1000) |
6163 | `render.direct-scanout` | `on` | Fullscreen buffers straight to the display |
@@ -65,6 +67,32 @@ NeferWL reads `$XDG_CONFIG_HOME/neferwl/config` (or `~/.config/neferwl/config`).
6567 | `log.level` / `log.debug` | `info` / empty | Log level / debug components or `all` |
6668 | `bind.<keys>` | see below | Action for a key combo; `none` removes a default |
6769
70+## Output placement
71+
72+An output with no relation is placed automatically, left to right after the others, at y=0. An output whose reference is not connected is placed automatically too, and returns to its relation when the reference connects. Relation cycles are ignored with a warning. Positions set by an output-management client (kanshi, wdisplays, wlr-randr) take precedence over relations until the next config reload. The pointer crosses any touching edge.
73+
74+Two rows of three, with DP-1 as the base:
75+
76+```
77+output.DP-1 = 2560x1440
78+output.DP-2 = 1920x1080
79+output.DP-2.left-of = DP-1
80+output.DP-2.offset = 360
81+output.DP-3 = 1920x1080
82+output.DP-3.right-of = DP-1
83+output.DP-3.offset = 360
84+output.HDMI-1 = 2560x1440
85+output.HDMI-1.above = DP-1
86+output.DP-4 = 1920x1200
87+output.DP-4.left-of = HDMI-1
88+output.DP-4.offset = 240
89+output.DP-5 = 1920x1200
90+output.DP-5.right-of = HDMI-1
91+output.DP-5.offset = 240
92+```
93+
94+The bottom row is DP-2 | DP-1 | DP-3 and the top row DP-4 | HDMI-1 | DP-5.
95+
6896 ## Workspace size
6997
7098 A workspace inherits its monitor size unless `workspace.<name>.size` sets a logical `WxH`. Its viewport is centered, and columns, maximization, floating windows and fullscreen use that real layout size—not scaled overview previews. Oversized dimensions are limited to the monitor; reserved layer zones still reduce usable space. Monitor mode and scale stay unchanged. Omit `size` or use `inherit` to restore the default. Workspace transitions involving a smaller viewport switch without the vertical slide, keeping scrolling columns inside their frame. Fullscreen inside a smaller viewport uses composition rather than output-covering direct scanout, so fullscreen-game VRR/tearing eligibility does not apply.
@@ -147,8 +175,8 @@ HDR requires DRM HDR connector properties, suitable KMS planes, and Vulkan fp16
147175 | `workspace <name>` | Toggle a named workspace |
148176 | `move-column-to-workspace <N>` / `-up/-down` | Move the focused column |
149177 | `move-window-to-workspace <N>` / `-up/-down` | Move only the focused window |
150-| `focus-monitor-left/right` | Focus the neighbor monitor |
151-| `move-workspace-to-monitor-left/right` | Move the workspace; it gets a new home |
178+| `focus-monitor-left/right` / `focus-monitor-up/down` | Focus the neighbor monitor in that direction |
179+| `move-workspace-to-monitor-left/right` / `move-workspace-to-monitor-up/down` | Move the workspace to the neighbor monitor in that direction; it gets a new home |
152180 | `move-workspace-up/down` | Swap the current numbered workspace with its neighbor; the view follows it. It never passes the empty workspace at the end |
153181 | `scale-up` / `scale-down` | Zoom the whole display; one second after the last press, the scale is saved to `output.<name>.scale` and a notification confirms it (`notify-send`) |
154182 | `quit` | Exit NeferWL |
@@ -259,6 +287,8 @@ With `keyboard.cmd = alt`, the `cmd+alt+…` binds have the same keys as `cmd+
259287 | `cmd+shift+code:10` | `move-column-to-workspace 9` |
260288 | `cmd+ctrl+left` | `focus-monitor-left` |
261289 | `cmd+ctrl+right` | `focus-monitor-right` |
290+| `cmd+ctrl+up` / `cmd+ctrl+k` | `focus-monitor-up` |
291+| `cmd+ctrl+down` / `cmd+ctrl+j` | `focus-monitor-down` |
262292 | `cmd+ctrl+h` | `focus-monitor-left` |
263293 | `cmd+ctrl+l` | `focus-monitor-right` |
264294 | `cmd+ctrl+shift+left` | `move-workspace-to-monitor-left` |
@@ -56,6 +56,8 @@ NeferWL reads `$XDG_CONFIG_HOME/neferwl/config` (or `~/.config/neferwl/config`).
56 | `output.<name>` | preferred | `WxH`, `WxH@Hz`, `preferred` or `off` |56 | `output.<name>` | preferred | `WxH`, `WxH@Hz`, `preferred` or `off` |
57 | `output.<name>.scale` | `1` | 1 to 4, e.g. `1.5` or `4/3` |57 | `output.<name>.scale` | `1` | 1 to 4, e.g. `1.5` or `4/3` |
58 | `output.<name>.primary` | `off` | Gets focus and pointer at startup |58 | `output.<name>.primary` | `off` | Gets focus and pointer at startup |
59+| `output.<name>.right-of` / `.left-of` / `.above` / `.below` | automatic | Connector to place the output against, e.g. `DP-1` |
60+| `output.<name>.offset` | `0` | Logical px along the shared edge: down for `right-of`/`left-of`, right for `above`/`below`. Negative allowed, −65536 to 65536. Ignored without a relation, with a warning |
59 | `output.<name>.hdr` | `off` | Turns on HDR10 on capable outputs; otherwise falls back to SDR (reason in the log) |61 | `output.<name>.hdr` | `off` | Turns on HDR10 on capable outputs; otherwise falls back to SDR (reason in the log) |
60 | `output.<name>.sdr-brightness` | `203` | How bright SDR desktop content appears in HDR, in nits (80–1000) |62 | `output.<name>.sdr-brightness` | `203` | How bright SDR desktop content appears in HDR, in nits (80–1000) |
61 | `render.direct-scanout` | `on` | Fullscreen buffers straight to the display |63 | `render.direct-scanout` | `on` | Fullscreen buffers straight to the display |
@@ -65,6 +67,32 @@ NeferWL reads `$XDG_CONFIG_HOME/neferwl/config` (or `~/.config/neferwl/config`).
65 | `log.level` / `log.debug` | `info` / empty | Log level / debug components or `all` |67 | `log.level` / `log.debug` | `info` / empty | Log level / debug components or `all` |
66 | `bind.<keys>` | see below | Action for a key combo; `none` removes a default |68 | `bind.<keys>` | see below | Action for a key combo; `none` removes a default |
67 69
70+## Output placement
71+
72+An output with no relation is placed automatically, left to right after the others, at y=0. An output whose reference is not connected is placed automatically too, and returns to its relation when the reference connects. Relation cycles are ignored with a warning. Positions set by an output-management client (kanshi, wdisplays, wlr-randr) take precedence over relations until the next config reload. The pointer crosses any touching edge.
73+
74+Two rows of three, with DP-1 as the base:
75+
76+```
77+output.DP-1 = 2560x1440
78+output.DP-2 = 1920x1080
79+output.DP-2.left-of = DP-1
80+output.DP-2.offset = 360
81+output.DP-3 = 1920x1080
82+output.DP-3.right-of = DP-1
83+output.DP-3.offset = 360
84+output.HDMI-1 = 2560x1440
85+output.HDMI-1.above = DP-1
86+output.DP-4 = 1920x1200
87+output.DP-4.left-of = HDMI-1
88+output.DP-4.offset = 240
89+output.DP-5 = 1920x1200
90+output.DP-5.right-of = HDMI-1
91+output.DP-5.offset = 240
92+```
93+
94+The bottom row is DP-2 | DP-1 | DP-3 and the top row DP-4 | HDMI-1 | DP-5.
95+
68 ## Workspace size96 ## Workspace size
69 97
70 A workspace inherits its monitor size unless `workspace.<name>.size` sets a logical `WxH`. Its viewport is centered, and columns, maximization, floating windows and fullscreen use that real layout size—not scaled overview previews. Oversized dimensions are limited to the monitor; reserved layer zones still reduce usable space. Monitor mode and scale stay unchanged. Omit `size` or use `inherit` to restore the default. Workspace transitions involving a smaller viewport switch without the vertical slide, keeping scrolling columns inside their frame. Fullscreen inside a smaller viewport uses composition rather than output-covering direct scanout, so fullscreen-game VRR/tearing eligibility does not apply.98 A workspace inherits its monitor size unless `workspace.<name>.size` sets a logical `WxH`. Its viewport is centered, and columns, maximization, floating windows and fullscreen use that real layout size—not scaled overview previews. Oversized dimensions are limited to the monitor; reserved layer zones still reduce usable space. Monitor mode and scale stay unchanged. Omit `size` or use `inherit` to restore the default. Workspace transitions involving a smaller viewport switch without the vertical slide, keeping scrolling columns inside their frame. Fullscreen inside a smaller viewport uses composition rather than output-covering direct scanout, so fullscreen-game VRR/tearing eligibility does not apply.
@@ -147,8 +175,8 @@ HDR requires DRM HDR connector properties, suitable KMS planes, and Vulkan fp16
147 | `workspace <name>` | Toggle a named workspace |175 | `workspace <name>` | Toggle a named workspace |
148 | `move-column-to-workspace <N>` / `-up/-down` | Move the focused column |176 | `move-column-to-workspace <N>` / `-up/-down` | Move the focused column |
149 | `move-window-to-workspace <N>` / `-up/-down` | Move only the focused window |177 | `move-window-to-workspace <N>` / `-up/-down` | Move only the focused window |
150-| `focus-monitor-left/right` | Focus the neighbor monitor |178+| `focus-monitor-left/right` / `focus-monitor-up/down` | Focus the neighbor monitor in that direction |
151-| `move-workspace-to-monitor-left/right` | Move the workspace; it gets a new home |179+| `move-workspace-to-monitor-left/right` / `move-workspace-to-monitor-up/down` | Move the workspace to the neighbor monitor in that direction; it gets a new home |
152 | `move-workspace-up/down` | Swap the current numbered workspace with its neighbor; the view follows it. It never passes the empty workspace at the end |180 | `move-workspace-up/down` | Swap the current numbered workspace with its neighbor; the view follows it. It never passes the empty workspace at the end |
153 | `scale-up` / `scale-down` | Zoom the whole display; one second after the last press, the scale is saved to `output.<name>.scale` and a notification confirms it (`notify-send`) |181 | `scale-up` / `scale-down` | Zoom the whole display; one second after the last press, the scale is saved to `output.<name>.scale` and a notification confirms it (`notify-send`) |
154 | `quit` | Exit NeferWL |182 | `quit` | Exit NeferWL |
@@ -259,6 +287,8 @@ With `keyboard.cmd = alt`, the `cmd+alt+…` binds have the same keys as `cmd+
259 | `cmd+shift+code:10` | `move-column-to-workspace 9` |287 | `cmd+shift+code:10` | `move-column-to-workspace 9` |
260 | `cmd+ctrl+left` | `focus-monitor-left` |288 | `cmd+ctrl+left` | `focus-monitor-left` |
261 | `cmd+ctrl+right` | `focus-monitor-right` |289 | `cmd+ctrl+right` | `focus-monitor-right` |
290+| `cmd+ctrl+up` / `cmd+ctrl+k` | `focus-monitor-up` |
291+| `cmd+ctrl+down` / `cmd+ctrl+j` | `focus-monitor-down` |
262 | `cmd+ctrl+h` | `focus-monitor-left` |292 | `cmd+ctrl+h` | `focus-monitor-left` |
263 | `cmd+ctrl+l` | `focus-monitor-right` |293 | `cmd+ctrl+l` | `focus-monitor-right` |
264 | `cmd+ctrl+shift+left` | `move-workspace-to-monitor-left` |294 | `cmd+ctrl+shift+left` | `move-workspace-to-monitor-left` |
M internal/core/screens.go +1 -1
@@ -8,7 +8,7 @@ import (
88
99 // screen is one connected output: its monitor tree, scale, layer surfaces and
1010 // place in the global layout (ADR 011). Outputs are ordered by config
11-// (output.<name>.*), then by connection; order places them.
11+// (output.<name>.*), then by connection; order places them (placeOutputs).
1212 type screen struct {
1313 info ports.OutputInfo
1414 mon *Monitor
@@ -8,7 +8,7 @@ import (
8 8
9 // screen is one connected output: its monitor tree, scale, layer surfaces and9 // screen is one connected output: its monitor tree, scale, layer surfaces and
10 // place in the global layout (ADR 011). Outputs are ordered by config10 // place in the global layout (ADR 011). Outputs are ordered by config
11-// (output.<name>.*), then by connection; order places them.11+// (output.<name>.*), then by connection; order places them (placeOutputs).
12 type screen struct {12 type screen struct {
13 info ports.OutputInfo13 info ports.OutputInfo
14 mon *Monitor14 mon *Monitor
M internal/ports/client_commands.go +1 -1
@@ -62,7 +62,7 @@ func (o OutputPlacement) Contains(x, y float64) bool {
6262 return x >= float64(o.X) && x < float64(o.X+o.Width) && y >= float64(o.Y) && y < float64(o.Y+o.Height)
6363 }
6464
65-// Layout is the global arrangement of outputs, left to right.
65+// Layout is the global arrangement of outputs in logical pixels.
6666 type Layout []OutputPlacement
6767
6868 // At returns the output under the logical point.
@@ -62,7 +62,7 @@ func (o OutputPlacement) Contains(x, y float64) bool {
62 return x >= float64(o.X) && x < float64(o.X+o.Width) && y >= float64(o.Y) && y < float64(o.Y+o.Height)62 return x >= float64(o.X) && x < float64(o.X+o.Width) && y >= float64(o.Y) && y < float64(o.Y+o.Height)
63 }63 }
64 64
65-// Layout is the global arrangement of outputs, left to right.65+// Layout is the global arrangement of outputs in logical pixels.
66 type Layout []OutputPlacement66 type Layout []OutputPlacement
67 67
68 // At returns the output under the logical point.68 // At returns the output under the logical point.

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 23

Tags 10

↑↓ moveenter openesc close