13 KiB
WindowMaker Multi-Monitor Implementation Plan
Purpose
Implement multi-monitor support in a way that another coding agent can execute with minimal guesswork.
This plan is written for the current wmaker tree and assumes:
- X11 and Wayland backends both remain supported
- shared policy code should continue to use the existing
headabstraction - the initial goal is correct multi-monitor geometry and behavior, not a redesign of workspaces
Scope
In scope
- Populate
WScreen.heads[],head_count, andprimary_headcorrectly on both backends - Make Wayland support multiple outputs instead of only one
- Recompute monitor geometry on output changes
- Keep placement, maximize, fullscreen, icons, dock reservations, and menus head-aware
- Handle output hotplug without leaving windows off-screen
- Preserve existing single-monitor behavior
Out of scope for phase 1
- Per-monitor workspaces
- Per-monitor dock/clip instances
- Full mixed-DPI polish beyond basic correctness
Existing architecture to preserve
Shared abstraction
The core already has a backend-neutral monitor/head abstraction.
Relevant files:
src/screen.hsrc/xinerama.hsrc/xinerama.c
Important fields in WScreen:
headshead_countprimary_headusableAreatotalUsableArea
Important helper APIs:
wGetHeadForRectwGetHeadForWindowwGetHeadRelativeToCurrentHeadwGetHeadForPointwGetHeadForPointerLocationwGetRectForHeadwGetUsableAreaForHeadwGetPointToCenterRectInHead
Existing backend support
X11
- Monitor enumeration already exists in:
src/backend/x11/x11_monitor.c
- Uses Xinerama with RandR screen-change subscriptions
Wayland
- Output layout support already exists via wlroots:
src/backend/wayland/wl_backend.csrc/backend/wayland/wl_output.csrc/backend/wayland/wl_screen.c
- Current limitation:
wl_screen.cpopulates onlyheads[0]- some Wayland code assumes
usableArea[0]
Non-goals and policy choices
Workspace policy
Do not implement per-monitor workspaces in this effort.
Use this policy instead:
- a workspace still spans the full virtual desktop
- placement/maximize/focus/icon logic remains head-aware within that workspace
This keeps the patch set tractable and aligns with the current helper model.
Dock / Clip policy
For now:
- keep one logical Dock and one logical Clip model
- make their geometry reservations and placement logic head-aware
Do not introduce per-head docks in this plan.
Canonical monitor contract
Before changing behavior, normalize the meaning of heads[].
Required invariants
scr->headscontains monitor rectangles in global root coordinates.scr->head_count >= 1after screen initialization succeeds.scr->primary_headis always a valid index whenhead_count > 0.scr->scr_width/scr->scr_heightrepresent the bounding box of the full virtual desktop.wGetRectForHead(scr, i)must always return a valid monitor rectangle for0 <= i < head_count.- Single-monitor mode should still behave correctly.
Preferred normalization
Use one explicit head even in single-monitor mode.
That means avoiding the old pattern where head_count == 0 implies fallback-to-single-head behavior.
If changing all callers at once is too risky, do this in two steps:
- preserve compatibility in helpers
- gradually move callers to assume
head_count >= 1
Execution plan
Phase 1 — Audit and normalize head initialization
Goal
Make head data reliable and backend-neutral before extending Wayland.
Files
src/screen.hsrc/xinerama.hsrc/xinerama.csrc/screen.csrc/backend/x11/x11_monitor.c
Tasks
- Audit all places that read:
scr->headsscr->head_countscr->primary_head
- Decide whether to enforce
head_count >= 1immediately or via compatibility layer. - Ensure the X11 path populates a valid single fallback head when Xinerama is inactive.
- Ensure head arrays are allocated/freed consistently.
- Add comments documenting the head contract.
Acceptance criteria
- X11 single-monitor behavior is unchanged.
- There is a clear contract for
head_count,heads, andprimary_head. - Helper functions behave correctly with one or many heads.
Phase 2 — Implement Wayland monitor enumeration
Goal
Populate all heads from wlr_output_layout, not just one.
Files
src/backend/wayland/wl_screen.csrc/backend/wayland/wl_output.csrc/backend/wayland/wl_backend.csrc/backend/backend_types.h
Tasks
- Audit the Wayland global state for available outputs and layout access.
- In
wl_screen_open()or the appropriate geometry refresh path:- enumerate all active outputs
- query each output's box via
wlr_output_layout_get_box - build a
WHeadGeometry[]
- Set:
scr->headsscr->head_countscr->primary_head
- Set
scr->scr_widthandscr->scr_heightfrom the union of all head rectangles, not from a single output. - Preserve deterministic ordering of heads.
- Recommended: sort by
(x, y)or preserve wlroots layout iteration order if stable.
- Recommended: sort by
- Choose primary head policy.
- Recommended initial policy: first active output or currently selected backend primary output.
Acceptance criteria
- On Wayland with two outputs,
scr->head_count == 2. wGetRectForHead()returns both outputs with correct geometry.scr->scr_width/scr->scr_heightreflect the virtual desktop bounds.
Notes for the implementing agent
- Keep this backend-only.
- Do not add special placement logic here.
- Just make monitor geometry correct and complete.
Phase 3 — Add dynamic output refresh / hotplug support
Goal
Rebuild monitor geometry whenever outputs change.
Files
src/backend/wayland/wl_output.csrc/backend/wayland/wl_screen.csrc/screen.c- optionally
src/window.c,src/dock.c
Tasks
- Identify Wayland output lifecycle hooks:
- new output
- destroyed/removed output
- layout or mode change
- Add a shared helper to rebuild screen head data.
- Example helper name:
wl_screen_refresh_heads(scr)
- Example helper name:
- Recompute after each relevant event:
- heads
- virtual bounds
- usable areas
- If an output disappears, repair windows/icons that now lie outside all heads.
Suggested repair policy
For each affected window:
- find nearest surviving head
- preserve relative position when possible
- clamp inside that head if necessary
- fall back to centering in
primary_head
For icons/appicons:
- do the same, but prefer the head they touched before removal
Acceptance criteria
- Plugging/unplugging outputs does not leave windows permanently off-screen.
- Head helpers reflect the new output topology immediately.
Phase 4 — Make usable-area and reservations per-head
Goal
Ensure placement/maximize respects per-head reserved areas.
Files
src/xinerama.csrc/screen.csrc/dock.csrc/backend/wayland/wl_dock.c
Known problem to fix
Wayland dock code currently assumes scr->usableArea[0] in places.
Tasks
- Audit all reads/writes of:
scr->usableAreascr->totalUsableAreascr->reservedAreas
- Ensure these arrays are sized for all heads.
- Ensure dock/clip reservations affect the correct head.
- When monitor geometry changes, recompute all per-head usable areas.
- Remove hardcoded
[0]assumptions.
Acceptance criteria
- Docks and clips reserve space only on the relevant head.
- Maximize avoids reserved space on the current head.
- Single-monitor behavior remains unchanged.
Phase 5 — Audit all head-aware placement and movement paths
Goal
Make all geometry-sensitive actions consistent across monitors.
Files to audit
src/placement.csrc/window.csrc/actions.csrc/cycling.csrc/rootmenu.csrc/dialog.csrc/dock.c
Tasks
Audit and fix the following behaviors:
New window placement
- New windows should open on:
- the pointer head, or
- the owner/transient head, or
- the current head policy already used by helpers
Restore/session placement
- Restored geometry should be clamped or translated to a valid head.
Maximize/fullscreen
- Maximize should apply to the window's current head unless multi-head fullscreen is explicitly requested.
- Existing fullscreen-monitor helpers in
actions.cshould keep working.
Menus/dialogs/transients
- Menus should open on the head that contains the invocation point.
- Transients should prefer the owner head.
- Dialog centering should use the correct head.
Icons / miniwindows
- Icon placement should remain on the window's head.
- Auto-arrange should consider all heads, not just head 0.
Acceptance criteria
- Placement/maximize/fullscreen/menus/transients/icons all behave correctly on two monitors.
Phase 6 — Add explicit monitor navigation commands
Goal
Expose useful multi-monitor actions to users.
Files
src/actions.csrc/defaults.cWPrefs.app/KeyboardShortcuts.c- possibly menu/defaults docs
Tasks
- Audit existing actions using:
wGetHeadRelativeToCurrentHeadwGetHeadForWindow
- Finish or add commands such as:
- move window to next monitor
- move window to previous monitor
- move focus to next monitor
- maximize on current monitor
- Expose them through defaults and WPrefs if not already exposed.
Acceptance criteria
- A user can move windows between monitors with shortcuts.
- The actions behave correctly on X11 and Wayland.
Phase 7 — Mixed scale / DPI follow-up (optional but recommended)
Goal
Avoid rendering problems on mixed-scale Wayland outputs.
Files
src/backend/wayland/wl_output.csrc/backend/wayland/wl_screen.c- WINGs font/render paths
Tasks
- Track output scale per output/head.
- Set output scale in wlroots where appropriate.
- Feed DPI/scale into font/layout code.
- Validate titlebars, menus, icons, screenshots, and WINGs rendering.
Acceptance criteria
- Multi-output works correctly even when outputs have different scales.
Concrete code audit checklist
Use this as a literal checklist while implementing.
Shared monitor/head code
src/xinerama.csrc/xinerama.hsrc/screen.csrc/screen.h
X11 backend
src/backend/x11/x11_monitor.c
Wayland backend
src/backend/wayland/wl_backend.csrc/backend/wayland/wl_output.csrc/backend/wayland/wl_screen.csrc/backend/wayland/wl_dock.c
Placement/window policy
src/placement.csrc/window.csrc/actions.csrc/cycling.csrc/rootmenu.csrc/dialog.csrc/dock.c
User-facing configuration
src/defaults.cWPrefs.app/KeyboardShortcuts.c- docs / release notes
Suggested implementation order for an agent
If working incrementally, use this order:
- Normalize head contract
- Enumerate all Wayland outputs into
heads[] - Set virtual desktop bounds correctly
- Recompute heads on hotplug / output change
- Fix per-head usable area and dock reservations
- Audit maximize/placement/fullscreen
- Audit menus/transients/icons
- Add monitor-navigation actions
- Do scale/DPI follow-up
Do not start with user-facing shortcuts or DPI. Start with geometry correctness.
Test matrix
X11 tests
- single monitor
- dual monitor horizontal
- dual monitor vertical
- mixed resolutions
- RandR hotplug/unplug
- maximize on each head
- fullscreen on each head
- menu placement
- dialog placement
- icon placement
Wayland tests
Use nested mode if needed.
Suggested setup:
WMAKER_WAYLAND_NESTED=1- adjust outputs with
wlr-randr
Run:
- single output
- two outputs horizontal
- two outputs vertical
- asymmetric offset layout
- output removal with windows present
- output addition after startup
- maximize/fullscreen correctness
- dock/clip reservation correctness
- pointer-head placement correctness
- WINGs app placement correctness
- XWayland client placement correctness
Debugging guidance for the implementing agent
When behavior is wrong, log these values first:
scr->head_count- all
scr->heads[i] scr->primary_headscr->scr_width,scr->scr_height- result of
wGetHeadForWindow()for the affected window - result of
wGetHeadForPointerLocation()during placement - per-head
usableAreaandtotalUsableArea
For Wayland output changes, log:
- output add/remove
- output layout box for each output
- geometry refresh entry/exit
- window migration decisions on output removal
Definition of done
This effort is complete when all of the following are true:
heads[]is correct on X11 and Wayland- Wayland supports more than one output
- output hotplug updates geometry correctly
- windows never remain stranded off-screen after output removal
- maximize/fullscreen/placement/icons/menus are head-aware
- dock/clip reservations are per-head
- single-monitor behavior is unchanged
Minimal deliverable if time is limited
If this must be split across multiple PRs, the minimum useful first PR is:
- normalize monitor contract
- populate all Wayland heads
- set correct virtual desktop bounds
- fix obvious
usableArea[0]assumptions
That alone provides the base needed for all follow-up work.