# 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](#1-startup--initialization) 2. [Event Loop & Dispatch](#2-event-loop--dispatch) 3. [Phase A: Core Window Management](#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](#4-phase-b-dock--app-icons) 5. [Phase C: Visual Polish & Animations](#5-phase-c-visual-polish--animations) 6. [Phase G: Session & Persistence](#6-phase-g-session--persistence) 7. [Phase I: EWMH Compliance](#7-phase-i-ewmh-compliance) 8. [X11 Mechanisms Summary](#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 ```c 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: ```c 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 ```c 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`) ```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.