990 lines
40 KiB
Markdown
990 lines
40 KiB
Markdown
# 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.
|