40 KiB
WindowMaker X11 Implementation — Baseline Reference
Cross-referenced against the feature breakdown in ../wmaker/WAYLAND_ROADMAP.md.
This document describes how the original upstream WindowMaker (this codebase,
wmaker-git) provides core window manager functionality using X11/Xlib directly.
It serves as the authoritative baseline for verifying correctness of the Wayland port.
Note: This codebase has NO backend abstraction layer. All X11 calls are made directly inline from the policy/logic code. The vtable-based
WMBackendabstraction exists only in the ported version at../wmaker/.
Table of Contents
- Startup & Initialization
- Event Loop & Dispatch
- Phase A: Core Window Management
- A1: Stacking
- A2: Maximize
- A3: Shade
- A4: Workspace Switching
- A5: Alt-Tab Window Cycling
- A6: Iconification
- A7: Window Placement
- Phase B: Dock & App Icons
- Phase C: Visual Polish & Animations
- Phase G: Session & Persistence
- Phase I: EWMH Compliance
- X11 Mechanisms Summary
1. Startup & Initialization
Files: src/main.c, src/startup.c, src/screen.c
Entry Point (main.c)
main() → fork monitor process (crash recovery supervisor)
→ real_main() [with --for-real flag]
→ parse CLI args (--no-dock, --no-clip, --replace, --visual-id, etc.)
→ setlocale / i18n init
→ XOpenDisplay(display_name)
→ fcntl(ConnectionNumber(dpy), F_SETFD, FD_CLOEXEC)
→ StartUp()
→ execInitScript("autostart")
→ setup inotify on ~/GNUstep/Defaults/
→ EventLoop() [never returns]
The monitor process (MonitorLoop) watches the child via waitpid() and re-execs
wmaker on abnormal exit (crash recovery).
Initialization Sequence (startup.c)
StartUp(Bool defaultScreenOnly) orchestrates:
-
Modifier detection —
getOffendingModifiers()usesXGetModifierMapping()to find NumLock/ScrollLock keycodes, enabling grabs that work regardless of lock state. -
Atom interning —
XInternAtoms()batch-creates ~60 atoms:WM_STATE,WM_PROTOCOLS,WM_DELETE_WINDOW,WM_TAKE_FOCUS,_WINDOWMAKER_*,_NET_WM_*,_GNUSTEP_WM_ATTR, etc. -
Cursor creation —
XCreateFontCursor()for all WM cursors (arrow, move, resize corners, wait, question, etc.). -
Signal handlers — SIGTERM→exit, SIGUSR1→restart, SIGUSR2→reread defaults, SIGCHLD→
buryChild()(waitpid reaper). -
Event hook —
WMHookEventHandler(DispatchEvent)connects the WINGs event pump to the WM's dispatch function. -
Defaults — Reads WindowMaker, WMRootMenu, WMWindowAttributes domains.
-
Extensions — Queries XShape, XRandR, XKB availability and event bases.
-
Per-screen init — For each X screen:
wScreenInit()→wScreenRestoreState()→manageAllWindows().
Screen Initialization (screen.c)
wScreenInit(int screen_number):
-
Claim WM role —
XSelectInput(root_win, SubstructureRedirectMask | ...). If this fails (BadAccess), another WM is running. This is THE mechanism that makes a process the window manager on X11. -
WM replacement —
replace_existing_wm()implements ICCCM 2.0 WM_Sn selection protocol: acquire selection ownership, wait for old WM to release, announce via ClientMessage. -
Raster context —
RCreateContext()creates the wrlib rendering context (Visual, Colormap, GC for image operations). -
WINGs screen —
WMCreateScreenWithRContext()initializes the widget toolkit. -
Graphics contexts —
allocGCs()creates 7 GCs: stipple, icon_select, frame (XOR mode for wireframes), line, copy, draw, mono. -
Internal windows —
createInternalWindows():no_focus_win— InputOnly window as focus fallback (prevents focus going to root)dock_shadow— For dock icon drag feedbackworkspace_name— Overlay for workspace switch display
-
EWMH —
wNETWMInitStuff()sets_NET_SUPPORTED,_NET_WM_NAME, etc. -
Existing windows —
manageAllWindows()callsXQueryTree(root)thenwManageWindow()on each child (adopting pre-existing clients at startup).
2. Event Loop & Dispatch
File: src/event.c
The Loop
void EventLoop(void)
{
while (1) {
WMNextEvent(dpy, &event); // blocks on XNextEvent()
WMHandleEvent(&event); // calls DispatchEvent()
}
}
Also polls inotify fd for config file changes between events.
Dispatch Table
DispatchEvent(XEvent *event) first checks global state flags:
WSTATE_NEED_EXIT→Shutdown(WME_NORMAL)WSTATE_NEED_RESTART→Restart(NULL, True)WSTATE_NEED_REREAD→wDefaultsCheckDomains()
Then dispatches by event->type:
| X11 Event | Handler | Action |
|---|---|---|
MapRequest |
handleMapRequest() |
New window → wManageWindow(); existing → deiconify/unshade |
ConfigureRequest |
handleConfigureRequest() |
→ wClientConfigure() |
DestroyNotify |
handleDestroyNotify() |
→ wUnmanageWindow(wwin, False, True) |
UnmapNotify |
handleUnmapNotify() |
→ wUnmanageWindow(wwin, True, False) |
PropertyNotify |
handlePropertyNotify() |
→ wClientCheckProperty() (react to hint changes) |
ClientMessage |
handleClientMessage() |
WM_CHANGE_STATE, EWMH, _WINDOWMAKER_COMMAND |
KeyPress |
handleKeyPress() |
Keyboard shortcut dispatch |
ButtonPress |
handleButtonPress() |
Mouse clicks on frames, root, icons |
EnterNotify |
handleEnterNotify() |
Focus-follows-mouse |
LeaveNotify |
handleLeaveNotify() |
Colormap/focus tracking |
FocusIn |
handleFocusIn() |
Focus state tracking |
Expose |
handleExpose() |
Repaint via WObjDescriptor |
MappingNotify |
— | XRefreshKeyboardMapping() |
| Extensions | handleExtensions() |
XShape, XRandR, XKB events |
Window Lookup
Events are routed to the correct WWindow via:
wwin = wWindowFor(event->xany.window); // uses XFindContext()
The context table (XSaveContext/XFindContext) maps X Window IDs → WWindow*
pointers. Separate contexts exist for client windows, app windows, and stack entries.
Child Process Tracking
handleDeadProcess() is called at the top of DispatchEvent to process any
children that died since the last event (collected by SIGCHLD → buryChild()
via waitpid(WNOHANG)). Death handlers registered via wAddDeathHandler() are
invoked here (used by dock app launch tracking).
3. Phase A: Core Window Management
Window Lifecycle (Manage / Unmanage)
File: src/window.c
wManageWindow(WScreen *scr, Window window) — Adopt a Client
This is the central ~500-line function that turns a raw X11 client window into a managed WWindow. Sequence:
XGrabServer()— Atomic operation (prevents races with client).XGetWindowAttributes()— Validate window exists, get geometry, checkoverride_redirect(skip if true — popups/tooltips manage themselves).- Fetch all hints:
XGetWMHints()— urgency, icon pixmap, group leader, input modelPropGetWMClass()— instance + class name (for per-app settings)PropGetGNUstepWMAttr()— GNUstep-specific attributesPropGetProtocols()— WM_DELETE_WINDOW, WM_TAKE_FOCUS supportXGetTransientForHint()— transient-for relationshipwClientGetNormalHints()— WM_NORMAL_HINTS (min/max/inc size, gravity)- EWMH properties —
_NET_WM_WINDOW_TYPE,_NET_WM_STATE, etc.
XSaveContext()— Register client window → WWindow mapping.XChangeWindowAttributes()— Set event mask on client (StructureNotify, PropertyChange, Colormap, FocusChange).wWindowSetupInitialAttributes()— Multi-layered attribute resolution: global defaults → GNUstep hints → MWM hints → EWMH → per-app user overrides. Determines: decoration flags, window level, skip-window-list, omnipresent, etc.wFrameWindowCreate()— Create the decoration frame (see framewin.c below).XReparentWindow(client, frame, x, y)— THE fundamental WM operation. Client becomes a child of the frame window.wWindowConfigure()— Apply computed geometry (respecting size hints, gravity).- Application tracking —
wApplicationCreate()groups windows by leader. XAddToSaveSet()— Ensures client survives WM crash (X server reparents it back to root).- Map —
XMapWindow()frame (and client if appropriate). XUngrabServer()— Release atomic lock.- Notify —
WMPostNotificationName(WMNManaged, ...).
wUnmanageWindow(WWindow *wwin, Bool restore, Bool destroyed) — Release
- Close any open inspector/menu for this window.
XRemoveFromSaveSet()— No longer need crash protection.XSelectInput(client, NoEventMask)— Stop receiving events.XUngrabButton()/XUngrabKey()— Release all grabs.XUnmapWindow()— Hide frame and client.- If
restore:wClientRestore()—XReparentWindow(client, root, x, y)back to root,XSetWindowBorderWidth()to original value. - Remove from focus list, determine next focused window.
- Remove from workspace window list.
WMPostNotificationName(WMNUnmanaged, ...).wWindowDestroy()— Free all resources.
A1: Stacking (Raise/Lower)
File: src/stacking.c
Roadmap ref: Phase A, task A1 — "Stacking (raise/lower via scene graph)"
Data Structure
scr->stacking_list = WMBag (sparse array indexed by window_level)
Level 0 (WMBackLevel): frame_a ↔ frame_b ↔ ...
Level 2 (WMNormalLevel): frame_c ↔ frame_d ↔ frame_e ↔ ...
Level 4 (WMFloatingLevel): frame_f ↔ ...
Level 6 (WMDockLevel): frame_g ↔ ...
...
Each level is a doubly-linked list (stacking->above / stacking->under).
Head of list = topmost window at that level.
Key Operations
| Function | X11 Mechanism | Notes |
|---|---|---|
wRaiseFrame(frame) |
Unlink from list → relink at head → CommitStackingForWindow() |
Recursively raises transients first |
wLowerFrame(frame) |
Unlink → relink at tail → CommitStackingForWindow() |
Won't lower below owner if transient |
CommitStacking(scr) |
Flatten all levels top→bottom into Window[] → XRestackWindows(dpy, wins, count) |
Full restack — expensive, used rarely |
CommitStackingForWindow(frame) |
Find window above in higher level → XRestackWindows(dpy, {above, frame}, 2) |
Pairwise restack — O(1) |
ChangeStackingLevel(frame, level) |
Remove from old level → add to new → raise/lower | Used by fullscreen, dock level changes |
RemakeStackList(scr) |
XQueryTree(root) → rebuild internal list from server state |
Recovery/sync |
Transient Handling
stacking->child_of links transients to owners. wRaiseFrame walks the transient
chain and raises each transient above its owner. wLowerFrame refuses to lower a
window below its owner.
A2: Maximize
File: src/actions.c
Roadmap ref: Phase A, task A2 — "Window maximize"
wMaximizeWindow(WWindow *wwin, int directions)
- Save current geometry in
wwin->old_geometry. - Compute target geometry based on direction flags:
MAX_VERTICAL— expand height to usable areaMAX_HORIZONTAL— expand width to usable areaMAX_LEFTHALF/MAX_RIGHTHALF— half-screen tilingMAX_TOPHALF/MAX_BOTTOMHALF— vertical half tilingMAX_MAXIMUS— expand to fill gaps between other windows
- Apply size constraints from
WM_NORMAL_HINTS(min/max/increment). wWindowConfigure(wwin, x, y, w, h)→XMoveResizeWindow().- Set
wwin->flags.maximizedflags. wWindowSynthConfigureNotify()— Send synthetic ConfigureNotify to client.
handleMaximize(WWindow *wwin, int directions)
Smart dispatcher handling:
- Toggle: if already maximized in same direction →
wUnmaximizeWindow() - Half-screen transitions: left↔right, top↔bottom
- Cross-head movement for multi-monitor
- Quarter tiling (top-left, top-right, bottom-left, bottom-right)
wUnmaximizeWindow(WWindow *wwin)
Restores wwin->old_geometry, clears maximize flags, reconfigures.
A3: Shade (Titlebar Collapse)
File: src/actions.c
Roadmap ref: Phase A, task A3 — "Window shade (titlebar collapse)"
wShadeWindow(WWindow *wwin)
XSelectInput(client, NoEventMask)— Temporarily suppress UnmapNotify.XUnmapWindow(client)— Hide client content.XSelectInput(client, original_mask)— Restore event mask.shade_animate()— Incremental resize animation (frame shrinks to titlebar height).wFrameWindowResize(frame, width, top_width - 1)— Final size = titlebar only.- Set
wwin->flags.shaded = 1. wClientSetState(wwin, IconicState)— Update WM_STATE property.
wUnshadeWindow(WWindow *wwin)
shade_animate()— Expand animation.wFrameWindowResize(frame, width, full_height).XMapWindow(client)— Show client content again.- Set
wwin->flags.shaded = 0. wClientSetState(wwin, NormalState).wSetFocusTo(scr, wwin)— Refocus.
A4: Workspace Switching
File: src/workspace.c
Roadmap ref: Phase A, task A4 — "Workspace switching (create/destroy/switch)"
Data Structure
scr->workspaces[] // array of WWorkspace* (name, clip dock)
scr->workspace_count // current count
scr->current_workspace // active workspace index
Each WWindow has wwin->frame->workspace indicating which workspace it belongs to.
Windows with wwin->window_flags.omnipresent appear on all workspaces.
wWorkspaceForceChange(WScreen *scr, int workspace)
The core switching algorithm:
- Iterate
scr->focused_windowlinked list (all managed windows). - For each window:
- If on old workspace and NOT omnipresent →
wWindowUnmap()(callsXUnmapWindow) - If on new workspace and NOT omnipresent →
wWindowMap()(callsXMapWindow)
- If on old workspace and NOT omnipresent →
- Update
scr->current_workspace. - Determine focus target:
- Click-to-focus: find last focused window on new workspace
- Sloppy focus:
XQueryPointer()→ find window under pointer
wSetFocusTo(scr, target).ProcessPendingEvents()— Drain stale focus/crossing events.showWorkspaceName()— Render fade-in/out name overlay.wArrangeIcons()— Relayout icon yard.
Workspace Name Display
showWorkspaceName() renders text into an RImage, composites it with a
semi-transparent background, converts to Pixmap via RConvertImage(), applies
as window background with XSetWindowBackgroundPixmap(), and uses
XShapeCombineMask() for non-rectangular shape. A timer-based fade animation
updates the alpha blend each step.
A5: Alt-Tab Window Cycling
File: src/cycling.c
Roadmap ref: Phase A, task A5 — "Alt-Tab window cycling + switch panel"
StartWindozeCycle(WWindow *wwin, XEvent *event, Bool next, Bool class_only)
XGrabKeyboard(root, True, GrabModeAsync, ...)— Modal keyboard grab.wInitSwitchPanel()— Create visual panel showing window thumbnails.- Modal event loop —
WMMaskEvent()filtering KeyPress, KeyRelease, Expose, MotionNotify, ButtonRelease:- Tab / Right → select next window
- Shift+Tab / Left → select previous
- Home / End → first / last
- Escape → cancel (restore original focus)
- Return → confirm selection
- Modifier key release (Alt/Super) → confirm selection
- Each selection:
wSwitchPanelSelectNext()→change_focus_and_raise():wWindowFocus(wwin)— Visual focus indicationraiseWindow()—XRestackWindows()to show highlighted window
- On exit:
XUngrabKeyboard()- Destroy switch panel
wRaiseFrame()+wSetFocusTo()on final selection
Key X11 Mechanisms
XGrabKeyboard— Intercepts ALL keyboard input during cyclingXKeysymToKeycode— Converts keysyms for comparison in event loopXRestackWindows— Temporarily raises highlighted window below panelWMMaskEvent— Selective event retrieval (motion compression)
A6: Iconification (Minimize)
File: src/actions.c, src/icon.c
Roadmap ref: Phase A, task A6 — "Window iconification (minimize) + miniwindow"
wIconifyWindow(WWindow *wwin)
- Create icon —
wwin->icon = wIconCreate(wwin):- Allocates
WIconstruct - Creates icon X window via
wCoreCreateTopLevel() - Loads icon image (from hints, .desktop file, or default)
- Renders icon tile with wrlib
- Allocates
- Capture mini-preview —
wWindowCaptureIcon(wwin):XGetImage()on client window →RCreateImageFromXImage()→ scale to icon size
- Unmap window —
XUnmapWindow(frame)+ unmap all transients. - Animation —
animateResize()draws XOR wireframe zoom from window rect to icon position (usingXGrabServer+XDrawRectanglewith frame_gc in GXxor mode). - Place icon —
PlaceIcon()finds slot in icon yard. - Map icon —
XMapWindow(icon->core->window). - Update state —
wClientSetState(wwin, IconicState), setflags.miniaturized. - Add to stacking —
AddToStackList(icon->core).
wDeiconifyWindow(WWindow *wwin)
- Animation — Reverse zoom from icon to window position.
- Map window —
XMapWindow(frame)+ map transients. - Destroy icon —
RemoveFromStackList(),wIconDestroy(). - Raise & focus —
wRaiseFrame(),wSetFocusTo(). - Update state —
wClientSetState(wwin, NormalState), clearflags.miniaturized.
A7: Window Placement
File: src/placement.c
Roadmap ref: Phase A, task A7 — "Window placement algorithms (smart/cascade/random)"
PlaceWindow(WWindow *wwin, int *x_ret, int *y_ret, unsigned w, unsigned h)
Dispatches based on wPreferences.window_placement:
| Mode | Algorithm | X11 Usage |
|---|---|---|
WPM_SMART |
Two-pass overlap minimization: coarse grid scan → fine-tune around best point | None (pure geometry) |
WPM_CASCADE |
Diagonal staircase offset from origin, wraps at screen edge | None |
WPM_CENTER |
Center of usable area for the target head | None |
WPM_AUTO |
Try center, then grid scan for non-overlapping slot, fallback to cascade | None |
WPM_RANDOM |
Random position within usable area | None |
WPM_MANUAL |
InteractivePlaceWindow() — user clicks to place |
XGrabPointer, modal event loop |
Smart Placement Detail
smartPlaceWindow():
- Get usable area via
wGetUsableAreaForHead()(accounts for dock, struts). - Coarse scan: step through positions at
PLACETEST_HSTEP/VSTEPintervals. - At each position:
calcSumOfCoveredAreas()iterates all mapped windows, computing overlap area. - Track position with minimum total overlap.
- Fine scan: search around best coarse position at pixel granularity.
- Return best position (may have non-zero overlap if screen is full).
Icon Placement
PlaceIcon(WScreen *scr, int *x_ret, int *y_ret, int head):
- Maintains a bitmap grid of occupied icon slots
- Scans for first free slot in configured direction (bottom-left, top-right, etc.)
- Grid cell size = icon tile size + padding
4. Phase B: Dock & App Icons
File: src/dock.c, src/appicon.c
Roadmap ref: Phase B — "Dock & App Icons"
Architecture
WDock (type: WM_DOCK | WM_CLIP | WM_DRAWER)
├── icon_array[0] = main icon (dock tile / clip indicator)
├── icon_array[1] = WAppIcon* (docked app)
├── icon_array[2] = WAppIcon* (docked app)
└── ...
- Dock — Fixed vertical bar (one per screen), persists across workspaces
- Clip — One per workspace, shows workspace number + directional arrows
- Drawer — Horizontal extension of the dock
Key Operations
| Operation | Implementation | X11 Mechanism |
|---|---|---|
| Create dock | wDockCreate() → mainIconCreate() |
wCoreCreateTopLevel() → XCreateWindow |
| Paint icon | dockIconPaint() / wClipIconPaint() |
wrlib render → XSetWindowBackgroundPixmap + XClearWindow |
| Launch app | execCommand() → fork() + execvp() |
Process management (not X11) |
| Track death | wAddDeathHandler() → buryChild() |
waitpid(WNOHANG) in SIGCHLD handler |
| Icon drag | iconMouseDown() modal loop |
XGrabPointer + MotionNotify loop |
| Swap sides | wDockSwap() |
XMoveWindow for each icon |
| Show/hide | wDockShowIcons() / wDockHideIcons() |
XMapWindow / XUnmapWindow per icon |
| Clip arrows | wClipIconPaint() |
XDrawLines for directional arrows |
| Auto-collapse | clipAutoCollapse() timer |
XUnmapWindow non-kept icons after delay |
| DND awareness | wXDNDMakeAwareness() |
XChangeProperty sets XdndAware atom |
Clip Workspace Switching
The clip icon shows the workspace number and directional arrows. Clicking arrows
triggers handleClipChangeWorkspace() → wWorkspaceRelativeChange().
Dock State Persistence
dock_state() serializes the dock to a WMPropList (property list):
- Position, auto-raise, auto-collapse, lowered flags
- Per-icon: command, wm_instance, wm_class, position, auto-launch, lock flags
Restored via wDockRestoreState() at startup.
5. Phase C: Visual Polish & Animations
Files: src/actions.c, src/superfluous.c, src/framewin.c
Roadmap ref: Phase C — "Visual Polish & Animations"
Frame Decoration Rendering
File: src/framewin.c
The frame window is composed of sub-windows:
WFrameWindow (top-level X window)
├── titlebar (child window, height ~19px)
│ ├── leftButton (close/miniaturize)
│ ├── rightButton (maximize/rollup)
│ └── [languageButton] (optional XKB indicator)
└── resizebar (child window at bottom, height ~8px)
Texture Rendering Pipeline
remakeTexture()— For each state (focused, unfocused, pfocused):wTextureRenderImage()→ wrlib renders gradient/pixmap/solid intoRImagerenderTexture()splits the image into button-width segmentsRConvertImage()→ convertsRImageto XPixmap
updateTexture()— Applies pixmaps:XSetWindowBackgroundPixmap(titlebar, pixmap)+XClearWindow()- Or
XSetWindowBackground(titlebar, pixel)for solid colors
wFrameWindowPaint()— Draws text title (with justification), resizebar bevels (XDrawLinefor 3D effect), triggers button repaints.
Button Rendering
paintButton():
- Normal: button background from texture + icon image via
XCopyArea/XCopyPlane - Pressed:
XFillRectangledarkened + icon +XDrawRectangleborder
Animations
Iconify/Deiconify Zoom (actions.c)
animateResize(scr, x1, y1, w1, h1, x2, y2, w2, h2)
XGrabServer()— Prevent other drawing during animation.- Loop
MINIATURIZE_ANIMATION_STEPStimes:- Interpolate rectangle from source to destination
XDrawRectangle(root, frame_gc)— GC is in GXxor modewusleep()delayXDrawRectangle(root, frame_gc)— XOR erases previous frame
XUngrabServer().
Variants: animateResizeFlip (3D flip), animateResizeTwist (rotation),
animateResizeZoom (expanding rectangles).
Shade Animation (actions.c)
shade_animate():
- Incremental
wFrameWindowResize()calls, stepping height bySHADE_STEPSpixels per frame withwusleep()delays.
Workspace Name Fade (workspace.c)
- Pre-renders text into
RImagewith semi-transparent background - Timer callback (
hideWorkspaceName) blends toward transparent each tick - Each frame:
RConvertImage()→XSetWindowBackgroundPixmap()→XClearWindow() - Uses
XShapeCombineMask()for non-rectangular window shape
Balloon Tooltips
File: src/balloon.c
- Creates an override-redirect X window
- Renders text with wrlib into shaped window
XShapeCombineMask()for rounded balloon shape- Positioned near the target (dock icon, titlebar button)
- Timer-based show/hide with delay
6. Phase G: Session & Persistence
Files: src/session.c, src/defaults.c, src/screen.c
Roadmap ref: Phase G — "Session & Persistence"
Defaults Database
- Location:
~/GNUstep/Defaults/WindowMaker,WMRootMenu,WMWindowAttributes - Format: NeXTSTEP property lists (parsed by WINGs
WMPropList) - Read at startup via
wReadDefaults()/wReadStaticDefaults() - Monitored via inotify for live reload (
handle_inotify_events()in event.c) - Zero X11 dependency — pure file I/O + proplist parsing
Session Save (session.c)
wSessionSaveState(WScreen *scr):
- Iterates all managed windows
- For each: saves geometry, workspace, shaded/miniaturized/hidden state, WM_CLASS, WM_COMMAND (for restart)
- Saves dock/clip/drawer state
- Writes to
~/GNUstep/Defaults/WMState
Session Restore
wScreenRestoreState(WScreen *scr):
- Reads
WMStatefile - Restores workspace count and names
- Restores dock state (
wDockRestoreState) - Restores clip per workspace
- Restores drawers
At manageAllWindows() time, windows are matched by WM_CLASS to saved state
and positioned/stated accordingly.
Dock State Persistence
Each dock icon stores: command, wm_instance, wm_class, position in dock, auto-launch flag, lock flag. Serialized as nested property lists in WMState.
7. Phase I: EWMH Compliance
File: src/wmspec.c
Roadmap ref: Phase I — "EWMH / Foreign Toplevel"
Initialization (wNETWMInitStuff)
Sets root window properties:
_NET_SUPPORTED— List of all supported EWMH atoms_NET_SUPPORTING_WM_CHECK— Points to a child window with_NET_WM_NAME_NET_NUMBER_OF_DESKTOPS,_NET_CURRENT_DESKTOP_NET_DESKTOP_GEOMETRY,_NET_DESKTOP_VIEWPORT_NET_WORKAREA— Usable area per workspace (accounts for struts)
Per-Window Properties
| Property | When Set | Source |
|---|---|---|
_NET_WM_STATE |
On manage, on state change | wNETWMUpdateState() |
_NET_WM_DESKTOP |
On manage, on workspace change | wNETWMUpdateDesktop() |
_NET_WM_ALLOWED_ACTIONS |
On manage | wNETWMUpdateActions() |
_NET_FRAME_EXTENTS |
On manage, on border change | wNETWMUpdateFrameExtents() |
Client Messages Handled
| Message | Action |
|---|---|
_NET_WM_STATE |
Toggle/set maximized, fullscreen, above, below, sticky, skip-taskbar, shaded |
_NET_CLOSE_WINDOW |
wClientSendProtocol(WM_DELETE_WINDOW) |
_NET_MOVERESIZE_WINDOW |
wWindowConfigure() with gravity |
_NET_WM_MOVERESIZE |
Start interactive move/resize |
_NET_ACTIVE_WINDOW |
Focus + raise the window |
_NET_CURRENT_DESKTOP |
wWorkspaceChange() |
_NET_WM_DESKTOP |
Move window to workspace |
_NET_RESTACK_WINDOW |
wRaiseFrame() / wLowerFrame() |
Strut Handling
_NET_WM_STRUT_PARTIAL / _NET_WM_STRUT properties on client windows reserve
screen edges. wScreenUpdateUsableArea() collects all struts and computes the
usable area per head, which is used by placement, maximize, and icon yard.
8. X11 Mechanisms Summary
This section catalogs the X11 mechanisms used by each feature, providing the definitive reference for what the Wayland port must replace.
Core X11 Primitives Used
| X11 Mechanism | WM Usage | Wayland Equivalent |
|---|---|---|
SubstructureRedirectMask on root |
Claim WM role; intercept Map/Configure requests | xdg_shell::new_surface signal |
XReparentWindow |
Embed client in frame | Scene graph parent (conceptual) |
XRestackWindows |
Z-order management | wlr_scene_node_raise/lower |
XSetInputFocus |
Keyboard focus delivery | wlr_seat_keyboard_notify_enter |
XGrabServer |
Atomic multi-window operations | No equivalent (async model) |
XGrabKeyboard |
Modal keyboard capture (alt-tab) | wlr_seat_keyboard_start_grab |
XGrabPointer |
Modal pointer capture (move/resize) | wlr_seat_pointer_start_grab |
XSaveContext/XFindContext |
Window ID → object lookup | Hash table |
XAddToSaveSet |
Crash recovery | No equivalent (clients die with compositor) |
XGetWindowAttributes |
Read client geometry/state | wlr_xdg_toplevel fields |
XGetWMHints/XGetTransientForHint |
Read client hints | wlr_xdg_toplevel / app_id |
XChangeProperty/XGetWindowProperty |
EWMH, WM_STATE, atoms | ext-foreign-toplevel-list |
XInternAtom |
Named constants | Not needed (protocols are typed) |
XMapWindow/XUnmapWindow |
Show/hide windows | wlr_scene_node_set_enabled |
XMoveResizeWindow |
Position/size changes | wlr_scene_node_set_position + configure |
XCreateWindow |
Frame, internal windows | wlr_scene_tree / wlr_scene_buffer |
XSetWindowBackgroundPixmap |
Decoration rendering | wlr_scene_buffer_set_buffer |
XDrawLine/XDrawRectangle (XOR GC) |
Wireframe animations | Pixman overlay buffer per frame |
XGetImage |
Icon mini-preview capture | Last committed wlr_texture blit |
XShapeCombineMask |
Non-rectangular windows | Alpha channel in buffer |
XQueryTree |
Enumerate existing windows | Not needed (compositor sees all surfaces) |
XSendEvent (ClientMessage) |
ICCCM/EWMH communication | xdg_toplevel_send_close etc. |
XSelectInput |
Per-window event subscription | Signal-based (no subscription needed) |
WM_Sn selection |
WM replacement protocol | Not applicable |
Per-Feature X11 Call Inventory
| Feature (Roadmap Phase) | Primary X11 Calls | File(s) |
|---|---|---|
| A1: Stacking | XRestackWindows, XRaiseWindow, XLowerWindow, XQueryTree |
stacking.c |
| A2: Maximize | XMoveResizeWindow, XSendEvent (ConfigureNotify) |
actions.c |
| A3: Shade | XUnmapWindow/XMapWindow (client), XResizeWindow (frame) |
actions.c |
| A4: Workspace | XMapWindow/XUnmapWindow (per window), XQueryPointer |
workspace.c |
| A5: Alt-Tab | XGrabKeyboard/XUngrabKeyboard, XRestackWindows |
cycling.c |
| A6: Iconify | XGetImage, XUnmapWindow, XGrabServer, XDrawRectangle (XOR) |
actions.c, icon.c |
| A7: Placement | XGrabPointer (manual mode only) |
placement.c |
| B: Dock | XCreateWindow, XMoveWindow, XMapWindow, XDrawLines |
dock.c, appicon.c |
| C: Animations | XGrabServer, XDrawRectangle/XDrawLines (XOR GC) |
actions.c |
| C: Decorations | XSetWindowBackgroundPixmap, XClearWindow, XDrawLine, XCopyArea |
framewin.c |
| G: Session | None (pure file I/O) | session.c |
| I: EWMH | XChangeProperty, XGetWindowProperty, XInternAtom, XSendEvent |
wmspec.c |
Focus Model Implementation
File: src/actions.c — wSetFocusTo(WScreen *scr, WWindow *wwin)
Implements ICCCM focus models:
| Client Type | Detection | WM Action |
|---|---|---|
| Passive (input=True, no TAKE_FOCUS) | WM_HINTS.input = True |
XSetInputFocus(client, RevertToParent, timestamp) |
| Locally active (input=True + TAKE_FOCUS) | Both flags set | XSetInputFocus() + send WM_TAKE_FOCUS ClientMessage |
| Globally active (input=False + TAKE_FOCUS) | WM_HINTS.input = False |
Send WM_TAKE_FOCUS only (client calls XSetInputFocus itself) |
| No input (input=False, no TAKE_FOCUS) | Neither flag | XSetInputFocus(no_focus_win) — focus goes to fallback |
Focus changes also trigger:
wWindowFocus(wwin)/wWindowUnfocus(old)— visual state updatewFrameWindowChangeState()— repaint titlebar in focused/unfocused texture- Colormap installation —
XInstallColormap(wwin->cmap_windows[0]) - App menu activation —
wAppMenuMap(wwin->main_window_desc)
Interactive Grab Pattern
All interactive operations (move, resize, alt-tab) follow the same pattern:
1. XGrabPointer/XGrabKeyboard — exclusive input capture
2. Modal event loop:
WMMaskEvent(mask, &event) — selective event retrieval
switch(event.type):
MotionNotify → update position/state
KeyPress → handle navigation
ButtonRelease → confirm
3. XUngrabPointer/XUngrabKeyboard — release
4. Apply final state
This pattern is used in:
wMouseMoveWindow()— pointer grab, motion loopwKeyboardMoveResizeWindow()— keyboard grab, key loopStartWindozeCycle()— keyboard grab, key loopInteractivePlaceWindow()— pointer grab, click to place
Cross-Reference: Roadmap Phase → Source File → X11 Mechanism
| Phase | Feature | Source File | Core X11 Mechanism | Complexity for Wayland |
|---|---|---|---|---|
| A1 | Stacking | stacking.c | XRestackWindows |
Low — scene graph is simpler |
| A2 | Maximize | actions.c | XMoveResizeWindow |
Low — geometry is policy-side |
| A3 | Shade | actions.c | XUnmapWindow + XResizeWindow |
Low — hide client node + resize frame |
| A4 | Workspace | workspace.c | XMapWindow/XUnmapWindow per window |
Medium — scene tree toggle is O(1) |
| A5 | Alt-Tab | cycling.c | XGrabKeyboard + modal loop |
Medium — compositor-internal grab |
| A6 | Iconify | actions.c, icon.c | XGetImage + XGrabServer + XOR draw |
Medium — no XOR, need pixman overlay |
| A7 | Placement | placement.c | Pure geometry (no X11 except manual) | Low — already portable |
| B | Dock | dock.c | XCreateWindow + XMoveWindow |
Medium — scene buffers instead |
| C | Animations | actions.c | XGrabServer + XOR XDrawRectangle |
High — must rewrite as per-frame renders |
| C | Decorations | framewin.c | XSetWindowBackgroundPixmap |
Medium — pixman → scene buffer |
| G | Session | session.c | None | None — already portable |
| I | EWMH | wmspec.c | XChangeProperty/XGetWindowProperty |
N/A — EWMH is X11-only; use foreign-toplevel |
Appendix A: Interactive Move/Resize Detail
File: src/moveres.c
Roadmap ref: Implicit in Phase A (core WM interaction)
wMouseMoveWindow(WWindow *wwin, XEvent *ev)
The most complex interactive operation in the WM:
-
Setup:
XGrabPointer(root, PointerMotionMask|ButtonReleaseMask, cursor_move)- Build sorted edge lists from all visible windows (for edge resistance)
- Show geometry display widget
-
Modal loop (
WMMaskEvent):- MotionNotify:
updateWindowPosition()with edge resistance:- Binary search sorted edge lists for nearby edges
- Apply resistance force (configurable pixel threshold)
- Snap to screen edges/corners
- Handle edge wrapping (pointer warps to opposite edge)
- KeyPress: Escape cancels, Return confirms
- ButtonRelease: Confirms final position
- MotionNotify:
-
Modes:
- Opaque:
XMoveWindow()on every motion event (live feedback) - Wireframe:
XGrabServer()+XDrawRectangle(root, frame_gc)in XOR mode
- Opaque:
-
Cleanup:
XUngrabPointer()- Apply final position via
wWindowConfigure() - Destroy geometry display
Edge Resistance Algorithm
updateWindowPosition():
- Maintains 4 sorted arrays:
leftList,rightList,topList,bottomList - Each contains edges of all visible windows + screen boundaries
- On each motion: binary search for edges within resistance threshold
- If found: snap proposed position to the edge (sticky behavior)
- Configurable:
wPreferences.edge_resistance(pixels)
Keyboard Move/Resize
wKeyboardMoveResizeWindow():
XGrabKeyboard()instead of pointer- Arrow keys move (or resize with Shift held)
- Step size: 1px normally, 10px with Ctrl
Appendix B: Menu System
File: src/menu.c, src/rootmenu.c
Roadmap ref: Implicit in Phase A/E
Menu Architecture
WMenu
├── frame (WCoreWindow — the menu window itself)
├── entries[] (WMenuEntry — label, shortcut, callback, submenu)
└── brother (WMenu* — shared submenu instance for cascading)
Each menu is an override-redirect X window. Submenus are separate windows positioned adjacent to the parent entry.
Key Operations
| Operation | X11 Mechanism |
|---|---|
| Show menu | XMapRaised(menu->frame->core->window) |
| Hide menu | XUnmapWindow() |
| Paint entry | XClearArea() + wDrawString() (wrlib text) |
| Highlight | XFillRectangle() with highlight color |
| Keyboard nav | XGrabKeyboard() + key event loop |
| Scroll (long menus) | XMoveWindow() to scroll menu window |
Root Menu
wRootMenuPerformShortcut() handles global keyboard shortcuts by matching
keycode+modifiers against the menu tree's shortcut bindings.
Appendix C: Data Structure Relationships
WScreen (per X screen)
├── root_win (Window — the X root window)
├── focused_window → WWindow → WWindow → ... (linked list, MRU order)
├── stacking_list (WMBag — per-level linked lists of WCoreWindow)
├── workspaces[] (WWorkspace — name, clip)
├── current_workspace (int)
├── dock (WDock* — the main dock)
├── rcontext (RContext* — wrlib rendering context)
├── w_win (WMScreen* — WINGs screen)
├── GCs: frame_gc, line_gc, copy_gc, stipple_gc, ...
└── internal windows: no_focus_win, dock_shadow, workspace_name
WWindow (per managed client)
├── client_win (Window — the actual client X window)
├── frame (WFrameWindow* — decoration frame)
├── icon (WIcon* — miniwindow when iconified)
├── main_window_desc (WWindow* — app group leader)
├── transient_for (Window — owner if transient)
├── prev/next (linked list through focused_window)
├── flags: maximized, shaded, miniaturized, hidden, fullscreen, focused, ...
├── old_geometry (saved for unmaximize)
├── normal_hints (WM_NORMAL_HINTS — size constraints)
└── wm_hints, wm_class, protocols, ...
WFrameWindow (decoration)
├── core (WCoreWindow — the frame X window)
├── titlebar (WCoreWindow — child window)
├── left_button, right_button (WCoreWindow — child windows)
├── resizebar (WCoreWindow — child window)
├── title (char* — window title text)
├── textures: focused, unfocused, pfocused (Pixmap per state)
└── callbacks: on_click_left, on_dblclick_titlebar, on_mousedown_resizebar, ...
WCoreWindow (lowest-level X window wrapper)
├── window (Window — the X window ID)
├── descriptor (WObjDescriptor — event dispatch target)
├── stacking (WStacking — above/under/level/child_of)
└── width, height
Appendix D: Key Invariants for Wayland Port Verification
These invariants hold in the X11 implementation and must be preserved:
-
Reparenting semantics: After
wManageWindow, the client window is a child of the frame window. All geometry reported to the client accounts for frame borders. -
Focus follows manage order: The
focused_windowlinked list is in MRU order.wSetFocusTomoves the target to the head. -
Stacking is per-level: A window at
WMNormalLevelcan never appear above a window atWMFloatingLevel, regardless of raise/lower operations within a level. -
Transients stack above owners:
wRaiseFramerecursively raises transients. A transient can never be below its owner within the same level. -
Workspace membership is exclusive: A window is on exactly one workspace (unless omnipresent). Switching workspaces maps/unmaps entire sets atomically.
-
Maximize preserves old geometry:
old_geometryis saved before maximize and restored on unmaximize. Nested maximize operations (e.g., vertical then horizontal) accumulate flags but save geometry only on first maximize. -
Shade preserves client state: The client window is unmapped but not destroyed. Its geometry within the frame is unchanged. Unshade simply remaps it.
-
Icon placement is grid-based: Icons occupy fixed-size slots. The bitmap prevents overlap. Slots are reclaimed on deiconify.
-
Edge resistance is symmetric: Both window-to-window and window-to-screen-edge resistance use the same threshold and algorithm.
-
EWMH state is authoritative: The WM writes
_NET_WM_STATEand it reflects actual window state. Clients request changes via ClientMessage; the WM decides.