This commit is contained in:
Window Maker 2026-05-23 11:10:25 +02:00
parent e41dcc7d38
commit a519435bfa
3 changed files with 992 additions and 3 deletions

989
X11_IMPLEMENTATION.md Normal file
View File

@ -0,0 +1,989 @@
# 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.

View File

@ -368,7 +368,7 @@ static void updateDockNumbers(WScreen *scr)
snprintf(ws_numbers, sizeof(ws_numbers), "%i [ %i ]", scr->current_workspace + 1, ((scr->current_workspace / 10) + 1));
length = strlen(ws_numbers);
wm_backend->window_clear(dicon->icon->core->window);
wm_backend->window_clear_area(dicon->icon->core->window, 2, 2, 50, 50);
WMDrawString(scr->wmscreen, dicon->icon->core->window, scr->black,
scr->icon_title_font, 4, 3, ws_numbers, length);

View File

@ -779,8 +779,8 @@ static void handleUnmapNotify(WMEvent * event)
/* whether the event is a Withdrawal request */
if (event->u.lifecycle.event == wwin->screen_ptr->root_win) {
XEvent *xev = (XEvent *)event->_x11_raw;
if (xev && xev->xunmap.send_event)
WMEvent *xev = event;
if (xev && xev->u.unmap.send_event)
withdraw = True;
}