windowmaker-wl/X11_IMPLEMENTATION.md

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 WMBackend abstraction exists only in the ported version at ../wmaker/.


Table of Contents

  1. Startup & Initialization
  2. Event Loop & Dispatch
  3. Phase A: Core Window Management
    • A1: Stacking
    • A2: Maximize
    • A3: Shade
    • A4: Workspace Switching
    • A5: Alt-Tab Window Cycling
    • A6: Iconification
    • A7: Window Placement
  4. Phase B: Dock & App Icons
  5. Phase C: Visual Polish & Animations
  6. Phase G: Session & Persistence
  7. Phase I: EWMH Compliance
  8. 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:

  1. Modifier detection — getOffendingModifiers() uses XGetModifierMapping() to find NumLock/ScrollLock keycodes, enabling grabs that work regardless of lock state.

  2. Atom interning — XInternAtoms() batch-creates ~60 atoms: WM_STATE, WM_PROTOCOLS, WM_DELETE_WINDOW, WM_TAKE_FOCUS, _WINDOWMAKER_*, _NET_WM_*, _GNUSTEP_WM_ATTR, etc.

  3. Cursor creation — XCreateFontCursor() for all WM cursors (arrow, move, resize corners, wait, question, etc.).

  4. Signal handlers — SIGTERM→exit, SIGUSR1→restart, SIGUSR2→reread defaults, SIGCHLD→buryChild() (waitpid reaper).

  5. Event hook — WMHookEventHandler(DispatchEvent) connects the WINGs event pump to the WM's dispatch function.

  6. Defaults — Reads WindowMaker, WMRootMenu, WMWindowAttributes domains.

  7. Extensions — Queries XShape, XRandR, XKB availability and event bases.

  8. Per-screen init — For each X screen: wScreenInit() → wScreenRestoreState() → manageAllWindows().

Screen Initialization (screen.c)

wScreenInit(int screen_number):

  1. 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.

  2. 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.

  3. Raster context — RCreateContext() creates the wrlib rendering context (Visual, Colormap, GC for image operations).

  4. WINGs screen — WMCreateScreenWithRContext() initializes the widget toolkit.

  5. Graphics contexts — allocGCs() creates 7 GCs: stipple, icon_select, frame (XOR mode for wireframes), line, copy, draw, mono.

  6. Internal windows — createInternalWindows():

    • no_focus_win — InputOnly window as focus fallback (prevents focus going to root)
    • dock_shadow — For dock icon drag feedback
    • workspace_name — Overlay for workspace switch display
  7. EWMH — wNETWMInitStuff() sets _NET_SUPPORTED, _NET_WM_NAME, etc.

  8. Existing windows — manageAllWindows() calls XQueryTree(root) then wManageWindow() 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:

  1. XGrabServer() — Atomic operation (prevents races with client).
  2. XGetWindowAttributes() — Validate window exists, get geometry, check override_redirect (skip if true — popups/tooltips manage themselves).
  3. Fetch all hints:
    • XGetWMHints() — urgency, icon pixmap, group leader, input model
    • PropGetWMClass() — instance + class name (for per-app settings)
    • PropGetGNUstepWMAttr() — GNUstep-specific attributes
    • PropGetProtocols() — WM_DELETE_WINDOW, WM_TAKE_FOCUS support
    • XGetTransientForHint() — transient-for relationship
    • wClientGetNormalHints() — WM_NORMAL_HINTS (min/max/inc size, gravity)
    • EWMH properties — _NET_WM_WINDOW_TYPE, _NET_WM_STATE, etc.
  4. XSaveContext() — Register client window → WWindow mapping.
  5. XChangeWindowAttributes() — Set event mask on client (StructureNotify, PropertyChange, Colormap, FocusChange).
  6. 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.
  7. wFrameWindowCreate() — Create the decoration frame (see framewin.c below).
  8. XReparentWindow(client, frame, x, y) — THE fundamental WM operation. Client becomes a child of the frame window.
  9. wWindowConfigure() — Apply computed geometry (respecting size hints, gravity).
  10. Application tracking — wApplicationCreate() groups windows by leader.
  11. XAddToSaveSet() — Ensures client survives WM crash (X server reparents it back to root).
  12. Map — XMapWindow() frame (and client if appropriate).
  13. XUngrabServer() — Release atomic lock.
  14. Notify — WMPostNotificationName(WMNManaged, ...).

wUnmanageWindow(WWindow *wwin, Bool restore, Bool destroyed) — Release

  1. Close any open inspector/menu for this window.
  2. XRemoveFromSaveSet() — No longer need crash protection.
  3. XSelectInput(client, NoEventMask) — Stop receiving events.
  4. XUngrabButton() / XUngrabKey() — Release all grabs.
  5. XUnmapWindow() — Hide frame and client.
  6. If restore: wClientRestore() — XReparentWindow(client, root, x, y) back to root, XSetWindowBorderWidth() to original value.
  7. Remove from focus list, determine next focused window.
  8. Remove from workspace window list.
  9. WMPostNotificationName(WMNUnmanaged, ...).
  10. 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)

  1. Save current geometry in wwin->old_geometry.
  2. Compute target geometry based on direction flags:
    • MAX_VERTICAL — expand height to usable area
    • MAX_HORIZONTAL — expand width to usable area
    • MAX_LEFTHALF / MAX_RIGHTHALF — half-screen tiling
    • MAX_TOPHALF / MAX_BOTTOMHALF — vertical half tiling
    • MAX_MAXIMUS — expand to fill gaps between other windows
  3. Apply size constraints from WM_NORMAL_HINTS (min/max/increment).
  4. wWindowConfigure(wwin, x, y, w, h) → XMoveResizeWindow().
  5. Set wwin->flags.maximized flags.
  6. 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)

  1. XSelectInput(client, NoEventMask) — Temporarily suppress UnmapNotify.
  2. XUnmapWindow(client) — Hide client content.
  3. XSelectInput(client, original_mask) — Restore event mask.
  4. shade_animate() — Incremental resize animation (frame shrinks to titlebar height).
  5. wFrameWindowResize(frame, width, top_width - 1) — Final size = titlebar only.
  6. Set wwin->flags.shaded = 1.
  7. wClientSetState(wwin, IconicState) — Update WM_STATE property.

wUnshadeWindow(WWindow *wwin)

  1. shade_animate() — Expand animation.
  2. wFrameWindowResize(frame, width, full_height).
  3. XMapWindow(client) — Show client content again.
  4. Set wwin->flags.shaded = 0.
  5. wClientSetState(wwin, NormalState).
  6. 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:

  1. Iterate scr->focused_window linked list (all managed windows).
  2. For each window:
    • If on old workspace and NOT omnipresent → wWindowUnmap() (calls XUnmapWindow)
    • If on new workspace and NOT omnipresent → wWindowMap() (calls XMapWindow)
  3. Update scr->current_workspace.
  4. Determine focus target:
    • Click-to-focus: find last focused window on new workspace
    • Sloppy focus: XQueryPointer() → find window under pointer
  5. wSetFocusTo(scr, target).
  6. ProcessPendingEvents() — Drain stale focus/crossing events.
  7. showWorkspaceName() — Render fade-in/out name overlay.
  8. 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)

  1. XGrabKeyboard(root, True, GrabModeAsync, ...) — Modal keyboard grab.
  2. wInitSwitchPanel() — Create visual panel showing window thumbnails.
  3. 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
  4. Each selection: wSwitchPanelSelectNext() → change_focus_and_raise():
    • wWindowFocus(wwin) — Visual focus indication
    • raiseWindow() — XRestackWindows() to show highlighted window
  5. On exit:
    • XUngrabKeyboard()
    • Destroy switch panel
    • wRaiseFrame() + wSetFocusTo() on final selection

Key X11 Mechanisms

  • XGrabKeyboard — Intercepts ALL keyboard input during cycling
  • XKeysymToKeycode — Converts keysyms for comparison in event loop
  • XRestackWindows — Temporarily raises highlighted window below panel
  • WMMaskEvent — 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)

  1. Create icon — wwin->icon = wIconCreate(wwin):
    • Allocates WIcon struct
    • Creates icon X window via wCoreCreateTopLevel()
    • Loads icon image (from hints, .desktop file, or default)
    • Renders icon tile with wrlib
  2. Capture mini-preview — wWindowCaptureIcon(wwin):
    • XGetImage() on client window → RCreateImageFromXImage() → scale to icon size
  3. Unmap window — XUnmapWindow(frame) + unmap all transients.
  4. Animation — animateResize() draws XOR wireframe zoom from window rect to icon position (using XGrabServer + XDrawRectangle with frame_gc in GXxor mode).
  5. Place icon — PlaceIcon() finds slot in icon yard.
  6. Map icon — XMapWindow(icon->core->window).
  7. Update state — wClientSetState(wwin, IconicState), set flags.miniaturized.
  8. Add to stacking — AddToStackList(icon->core).

wDeiconifyWindow(WWindow *wwin)

  1. Animation — Reverse zoom from icon to window position.
  2. Map window — XMapWindow(frame) + map transients.
  3. Destroy icon — RemoveFromStackList(), wIconDestroy().
  4. Raise & focus — wRaiseFrame(), wSetFocusTo().
  5. Update state — wClientSetState(wwin, NormalState), clear flags.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():

  1. Get usable area via wGetUsableAreaForHead() (accounts for dock, struts).
  2. Coarse scan: step through positions at PLACETEST_HSTEP/VSTEP intervals.
  3. At each position: calcSumOfCoveredAreas() iterates all mapped windows, computing overlap area.
  4. Track position with minimum total overlap.
  5. Fine scan: search around best coarse position at pixel granularity.
  6. 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

  1. remakeTexture() — For each state (focused, unfocused, pfocused):
    • wTextureRenderImage() → wrlib renders gradient/pixmap/solid into RImage
    • renderTexture() splits the image into button-width segments
    • RConvertImage() → converts RImage to X Pixmap
  2. updateTexture() — Applies pixmaps:
    • XSetWindowBackgroundPixmap(titlebar, pixmap) + XClearWindow()
    • Or XSetWindowBackground(titlebar, pixel) for solid colors
  3. wFrameWindowPaint() — Draws text title (with justification), resizebar bevels (XDrawLine for 3D effect), triggers button repaints.

Button Rendering

paintButton():

  • Normal: button background from texture + icon image via XCopyArea/XCopyPlane
  • Pressed: XFillRectangle darkened + icon + XDrawRectangle border

Animations

Iconify/Deiconify Zoom (actions.c)

animateResize(scr, x1, y1, w1, h1, x2, y2, w2, h2)
  1. XGrabServer() — Prevent other drawing during animation.
  2. Loop MINIATURIZE_ANIMATION_STEPS times:
    • Interpolate rectangle from source to destination
    • XDrawRectangle(root, frame_gc) — GC is in GXxor mode
    • wusleep() delay
    • XDrawRectangle(root, frame_gc) — XOR erases previous frame
  3. XUngrabServer().

Variants: animateResizeFlip (3D flip), animateResizeTwist (rotation), animateResizeZoom (expanding rectangles).

Shade Animation (actions.c)

shade_animate():

  • Incremental wFrameWindowResize() calls, stepping height by SHADE_STEPS pixels per frame with wusleep() delays.

Workspace Name Fade (workspace.c)

  • Pre-renders text into RImage with 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 WMState file
  • 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 update
  • wFrameWindowChangeState() — 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 loop
  • wKeyboardMoveResizeWindow() — keyboard grab, key loop
  • StartWindozeCycle() — keyboard grab, key loop
  • InteractivePlaceWindow() — 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:

  1. Setup:

    • XGrabPointer(root, PointerMotionMask|ButtonReleaseMask, cursor_move)
    • Build sorted edge lists from all visible windows (for edge resistance)
    • Show geometry display widget
  2. 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
  3. Modes:

    • Opaque: XMoveWindow() on every motion event (live feedback)
    • Wireframe: XGrabServer() + XDrawRectangle(root, frame_gc) in XOR mode
  4. 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:

  1. Reparenting semantics: After wManageWindow, the client window is a child of the frame window. All geometry reported to the client accounts for frame borders.

  2. Focus follows manage order: The focused_window linked list is in MRU order. wSetFocusTo moves the target to the head.

  3. Stacking is per-level: A window at WMNormalLevel can never appear above a window at WMFloatingLevel, regardless of raise/lower operations within a level.

  4. Transients stack above owners: wRaiseFrame recursively raises transients. A transient can never be below its owner within the same level.

  5. Workspace membership is exclusive: A window is on exactly one workspace (unless omnipresent). Switching workspaces maps/unmaps entire sets atomically.

  6. Maximize preserves old geometry: old_geometry is saved before maximize and restored on unmaximize. Nested maximize operations (e.g., vertical then horizontal) accumulate flags but save geometry only on first maximize.

  7. Shade preserves client state: The client window is unmapped but not destroyed. Its geometry within the frame is unchanged. Unshade simply remaps it.

  8. Icon placement is grid-based: Icons occupy fixed-size slots. The bitmap prevents overlap. Slots are reclaimed on deiconify.

  9. Edge resistance is symmetric: Both window-to-window and window-to-screen-edge resistance use the same threshold and algorithm.

  10. EWMH state is authoritative: The WM writes _NET_WM_STATE and it reflects actual window state. Clients request changes via ClientMessage; the WM decides.