Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,20 @@ All notable changes to TideWM are documented here. Format loosely follows [Keep
- `rule { pin_output = "<connector>" }` and the `toggle-output-pin` action persistently pin a window to a named output, closing the config-customization audit's last open `rule { }`-breadth item. Neither niri nor Hyprland actually expose this at the window level -- niri's `open-on-output` and Hyprland's `monitor:` windowrule are both spawn-time-only placement, exactly what TideWM's existing `output` field already does; niri's real persistent binding is a *named workspace's* `open-on-output`, which has nowhere to hang in TideWM's per-output-numbered-workspace model (a workspace number already exists independently on every output). This is TideWM's own adaptation, at the window instead of the workspace. `pin_output` takes priority over `output` for initial placement and is remembered in the new `Smallvil::output_pins` map (one entry per pinned window, empty and free until something pins) independent of whether the named output is connected yet. `toggle-output-pin` pins/unpins the focused window to whichever output it's currently on -- unlike the existing `toggle-pin` (workspace-sticky, floating-only by necessity), this has nothing to do with tiling state. A disconnected pinned output still falls its windows back like any other window there (physically nothing else to do), but reconnecting the same output now recalls them automatically via the new `Smallvil::recall_pinned_windows_to`/`move_window_to_output`, a single-surface counterpart to the existing bulk `migrate_output_windows` reusing the same tiled-remove/insert, floating retag-and-translate, and fullscreen/maximized `move_to_output` idioms -- called from the udev backend's output-added path alongside the existing `adopt_orphaned_output_windows` (winit's one simulated output never hotplugs, so there's nothing to wire there). `swap-workspaces-with-output` is deliberately left alone rather than given a per-window pin exemption: it's an explicit user action with no default keybind, and it already moves everything on the swapped workspace -- pins included is the honest, symmetric behavior, not a silent partial exception. Additive Wave key and action; every existing `rule { }` block keeps parsing and behaving exactly as before. Unit-tested: the parser, the `resolve_window_rules` fold, and the action string parse all have direct test coverage; `toggle_output_pin`/`move_window_to_output`/`recall_pinned_windows_to` are ordinary `Smallvil` methods with no pure seam to unit-test in isolation, the same shape `toggle_pin`/`mark_urgent` already have.

### Changed
- **Automatic attention depth is now opt-in.** `depth { enabled }` defaults to `false` in code and in the generated first-run config, which gains an explicit `depth { enabled = false }` block. Unfocused windows no longer fade into the blue `cool_color` wash after 30 s or turn into title cards after 60 s unless a config sets `enabled = true`. `depth_defaults_match_generated_config` now asserts the disabled default for both.
- Backend and protocol source documentation now keeps the operational contracts for DRM surface state, output transaction ordering, lock-safe capture, XDG map sequencing, cursor scale caches, Xwayland startup, and PipeWire/zbus ownership without retaining incident narratives or comparison notes. Core-state comments likewise retain focus, workspace, migration, rendering, and physics invariants without their historical framing; the large misplaced desktop-stack comment now sits on the function it actually describes. Diagnostics also stop reading and discarding `TZ` before producing their explicitly UTC timestamp.
- Visual source documentation for caustics, backdrop capture, water glass, float physics, workspace transitions, swim, compass cues, tab strips, welcome hints, and the configuration-error overlay now states the active render, damage, shader, geometry, and simulation contracts directly. Roadmap phases, implementation chronology, compositor comparisons, metaphors, and bug-story narration were removed; the safety-critical EGL ordering, texture ownership, damage identity, opacity, coordinate-space, bounds, and settling invariants remain beside the code.
- High-priority source documentation now describes current ownership, coordinate, threading, merge, shader, and input contracts directly. Historical rollout stories and obsolete roadmap framing were removed from the minimap, overview, IPC, accessibility, portal, output-management, tearing-control, tiled-drag, floating-physics, output-selection, input-method, ripple-shader, and Wave-merge paths; verification history remains in this changelog and `report.md`.

### Fixed
- **`output { }` blocks apply on config reload.** Moving, scaling or rotating a monitor in the config used to be accepted by the reload and then ignored until the next output setup. Reload now runs the same validated transaction as wlr-output-management (shared `apply_output_changes`): transform, scale and position change live, floating windows follow their output, layer shells re-arrange, and the desktop retiles. Outputs without a block keep their state. A different `mode` or `enabled = false` still needs a DRM modeset at output setup and now shows a notice instead of being ignored silently.
- **`spawn` takes quoted arguments.** Commands were split on whitespace only, so no keybind, startup `spawn` or `tidectl spawn` could pass an argument containing a space, and the documented `sh -c "..."` workaround could not work. `'...'`, `"..."` (with `\"`/`\\`) and backslash escapes are honored, still with no shell expansion; an unterminated quote falls back to the old whitespace split.
- **tidectl argument handling.** `--json`, `--socket` and `--help` are only read before the command, so `tidectl spawn mpv --help` passes `--help` to mpv; `spawn` re-quotes arguments that need it; and `tidectl help` prints help instead of being sent to the compositor as an unknown action.
- **`cursor_hide_after = 0`** (or `0ms`) is accepted as documented (never hide) instead of being ignored with a warning.
- **IPC requests ending at EOF** without a trailing newline are answered (or rejected with the usual `invalid request` error) instead of being dropped with no reply.
- **`tidectl doctor` gpu check** enumerates `/dev/dri` and reports render nodes and card nodes separately; it listed `card0` as a render node and missed card-only layouts it did not hard-code.
- **Glass and captures show the wallpaper at the right size on fractional scales.** Offscreen captures (frost/water glass and layer glass backdrops, custom shader sources, workspace-transition snapshots) render at scale 1.0 in physical pixels, but reused the on-screen wallpaper element, which is sized in logical pixels. At 1.25x the wallpaper inside every capture was drawn at 80% size anchored top-left, so glass showed a shrunken, shifted wallpaper that drifted further from the real one across the output. Captures now use `wallpaper_capture_element`, sized to the output's physical size with the same rounding as the visible frame.
- **Client drag'n'drop works.** `WaylandDndGrabHandler` was an empty impl, so Smithay's default `dnd_requested` cancelled every drag source the moment it started: nothing could be dragged between apps (files from Dolphin into Firefox, text, links) and Firefox treated every tab drag as a drop outside the window, detaching the tab into a new one. Drags now start a Smithay `DnDGrab` for pointer or touch; the client's drag icon is drawn at the pointer with the cursor elements on the udev backend (never over the lock screen) and receives frame callbacks; the icon is cleared on drop or cancel. Nested/winit runs get working drags but no compositor-drawn icon, since the host draws the cursor there, and screenshots do not include the icon yet.
- Screenshots and screencasts of a whole output now include layer frost (`layer_rule { blur = true }`) and layer shaders. Both visible backends added the layer glass layer to the frame, but the output capture path only built window glass, so a blurred bar showed up unblurred in every capture.
- A refused `spring(mass, stiffness, damping)` curve now says why in its journal warning: a coefficient outside its range, a spring that never settles within ten seconds, or not three numbers. Both refusals used to log the same generic easing warning.
- Focus cycling no longer lifts a tiled window above floating ones. `cycle_focus` raised whichever window it focused, and raising a tiled window puts it over every overlapping floater, so a floating window could end up behind its tiled neighbor until something happened to retile. It now raises only floating windows, like directional focus already did. This is the likely cause of the "freshly floated window behind a tiled sibling" report from the 2026-09-12 live session, but that report's exact steps weren't recorded, so it still needs a live check.
Expand Down
9 changes: 5 additions & 4 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Full reference for configuring and controlling TideWM: every config key, every a
| Flag | Notes |
| --- | --- |
| `-c, --config <path>` | Load this file instead of `$XDG_CONFIG_HOME/tidewm/config.wave`. Applies to the hot-reload watcher too, not just the initial load. |
| `-s, --spawn <command>` | Spawn one specific command right after startup, instead of nothing. No shell parsing (same rule as the `spawn` config key). |
| `-s, --spawn <command>` | Spawn one specific command right after startup, instead of nothing. Quoted arguments work, but no shell parsing (same rule as the `spawn` config key). |
| `-v, --version` | Print the version and exit. |
| `-h, --help` | Print usage and exit. |

Expand Down Expand Up @@ -85,6 +85,7 @@ TideWM always provides the bundled `assets/tide-aqua-4k.png` artwork, so a fresh
| Key | Type | Default | Notes |
| --- | --- | --- | --- |
| `terminal` | string | `"kitty"` | Spawned by the shipped `$mod+Return` bind (`@mod = SUPER` in the generated file). The terminal fallback is `wave(kitty, alacritty, foot, xterm)` — see the Wave format section above. |
| `pointer_modifier` (aliases `drag_modifier`, `mouse_modifier`) | modifier | `SUPER` | Held with left-drag to move and right-drag to resize windows, and for pointer-anchored zoom/pan. Independent of `@mod`: the generated config sets `drag_modifier = mod` so it follows `@mod`, but a config without the key keeps `SUPER` even when `@mod = ALT`. |
| `engine` (aliases `spatial_engine`, `wm_mode`) | `classic` \| `ocean` | `classic` | Selects one of TideWM's two WM ownership models. Classic keeps numbered workspaces. Ocean has no workspaces: outputs are cameras into one continuous 2D world. Hot-reloadable: a change migrates every live window in place. Classic→Ocean turns each output's populated workspace trees into reefs on the lateral line at `X = (N-1) * (output width + 128)` with the camera at the previously-active workspace; depth-deck windows are recalled to their tiles, floating windows translate to world coordinates around their workspace's reef, and pinned windows become Ocean screen pins. Ocean→Classic turns reefs sorted left-to-right into workspaces `1..N` on the output whose camera is nearest, selects the active workspace from each camera, clamps floating windows into the visible area, and restores pins. Tab groups and fullscreen/maximized entries carry across both directions; Ocean bookmarks and camera history are dropped (no Classic counterpart). |
| `drag_modifier` | modifier or `+`-joined modifiers | `super` | Modifier physically held for compositor mouse actions: left-drag moves floating windows or drag-swaps tiles; right-drag resizes floating or tiled windows. Accepts `super`/`logo`/`mod4`, `alt`/`mod1`, `ctrl`/`control`, and `shift`. The shipped config sets it to `mod`. |
| `welcome_hint` | bool | `true` | Shows a persistent empty-desktop card reminding you to use your configured terminal bind. Disappears when a real window maps; delete this key (or set it `false`) to stop it returning. |
Expand All @@ -105,7 +106,7 @@ TideWM always provides the bundled `assets/tide-aqua-4k.png` artwork, so a fresh
| `split_bias` | `auto` \| `horizontal` \| `vertical` | `auto` | Manual override for `layout = bsp`'s per-split axis choice. `auto` is the existing aspect-ratio-driven behavior, unchanged. `horizontal`/`vertical` force every split one way regardless of window/output shape (Hyprland dwindle's `force_split` idea). One global setting, not per-workspace. |
| `pseudo_tile_scale` | float, `0.05`–`1.0` | `0.7` | Fraction of its tile a pseudo-tiled window keeps, centered within it. Out-of-range values are clamped, not rejected. |
| `adaptive_sync` (aliases `vrr`, `variable_refresh_rate`) | `off` \| `on` \| `on-demand` | `off` | Global adaptive-sync (VRR) preference; a `[[output]]`'s own `adaptive_sync` beats it. The udev backend applies the resolved value through DRM; `on-demand` enables VRR only while an unlocked fullscreen window owns the output. The nested winit backend has no DRM surface, so this setting is inert there. |
| `spawn` | list | none | Commands launched once at startup, as a real list: `spawn = [waybar, "swaybg -i ~/wallpaper.png -m fill"]`. Args split on whitespace — no shell involved, so quoting/globs/pipes aren't supported; wrap in `sh -c "..."` yourself if you need those. |
| `spawn` | list | none | Commands launched once at startup, as a real list: `spawn = [waybar, "swaybg -i ~/wallpaper.png -m fill"]`. Args split on whitespace, with quoting for arguments that contain spaces: `'...'` is literal, `"..."` honors `\"` and `\\`, and `\` escapes the next character outside quotes. No shell is involved, so `$VARS`, globs and pipes stay literal; use `sh -c '...'` when you need those, e.g. `spawn = ["kitty sh -c 'fastfetch; exec fish'"]`. An unterminated quote falls back to a plain whitespace split. |

### Classic edge and corner snap

Expand Down Expand Up @@ -833,7 +834,7 @@ A matching `rule { depth = false }` pins that window buoyant: it stays at tier 0

| Key | Type | Default | Notes |
| --- | --- | --- | --- |
| `enabled` | bool | `true` | Disables only automatic depth/buoyancy; other water effects remain active. |
| `enabled` | bool | `false` | Opt-in. Enables only automatic depth/buoyancy; other water effects are unaffected. |
| `sink_after_ms` | integer, `0`–`86400000` | `30000` | Inactivity before entering tier 1. `delay_ms` is an alias. |
| `tier_interval_ms` | integer, `1`–`86400000` | `30000` | Additional inactivity per deeper tier. `interval_ms` is an alias. |
| `max_tier` | integer, `1`–`8` | `2` | Deepest tier a window may reach. `tiers` is an alias. |
Expand Down Expand Up @@ -1851,7 +1852,7 @@ rewriting a line removes or changes it completely.
- `toggle-dpms` — toggle every output's power together (all on, or all off)

**Process and session**
- `spawn:<command>` — args split on whitespace, no shell
- `spawn:<command>` — args split on whitespace with `'...'`/`"..."` quoting and `\` escapes (see the `spawn` key); no shell
- `quit`

## IPC and `tidectl`
Expand Down
6 changes: 3 additions & 3 deletions TECHNICAL_REPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,15 @@ Current release: **0.90.104**, second major pre-release. 1.0 is intentionally re
| Floating-window ocean physics (`float_physics { tier = light|full }`): disturbance-driven bob/drift, `full` adds mass/collisions/a traveling wave field | Done, nested-verified only |
| Ocean currents: bounded render-only downstream drift for visible unfocused floating windows, with focus/drag pause | Done, release-GLES nested-verified on AMD |
| Weighted buoyancy: per-app render-only mass/sink for Classic and Ocean floaters, with Ocean flow attenuation | Done, release-GLES nested-verified on AMD under Ocean; Classic shares the validated render path |
| Nvidia support | Nested (EGL/GLES) verified on a real RTX 3060; standalone DRM backend not yet run natively on Nvidia |
| Nvidia support | Standalone DRM backend verified on a real RTX 3060 (proprietary 595.99.02, two outputs at mixed scale, 2026-09-25); nested verified earlier |
| AUR package | Not yet |

## Hardware verification

- **AMD**: primary development and test hardware. The standalone `udev`/DRM backend, the full water/decoration render stack, swim's real-touchpad gesture path, and Ocean's core navigation (reefs, cameras, freeform drag) are all verified here.
- **Latest standalone AMD health pass (2026-08-13)**: release 0.90.72 on Renoir completed full-output and cursor-overlay screencopy, reversible workspace transitions with ordered IPC events, invalid-action/batch/eval handling, and 100 one-shot plus 20 interrupted subscription connections without descriptor or PSS growth. Doctor remained all-pass with no TideWM panic/error or coredump. One-window PSS settled at 86.8 MiB after the reload/capture pass. The configured 24-fps caustics were the dominant continuous CPU/GPU cost; see `report.md` for the measured A/B and its whole-GPU caveat.
- **Nvidia**: nested backend verified on a real RTX 3060 (proprietary driver): clean EGL/GLES context, correct rendering, no crashes. The standalone DRM backend and its Nvidia overlay-plane workaround are unverified on real Nvidia hardware.
- **Latest standalone NVIDIA pass (2026-09-25)**: 0.90.106 (`13b104e`, `fiw/testing-fixes`) on the RTX 3060 above: doctor all-pass; full-output and cursor-overlay `grim`; unknown actions, an invalid mixed batch, eval syntax errors and a runaway eval loop (execution budget) all failed cleanly; oversized, garbage and truncated IPC requests left the compositor running; 100 one-shot queries plus 20 interrupted subscribers left PSS and descriptors unchanged. Idle ~1% of one core, 131 MiB TideWM VRAM; nine frost windows ~0-1% CPU and 139 MiB. PSS plateaus near 180 MiB after warm-up, of which ~60-95 MiB is `/dev/nvidia*` mappings; open/close cycles returned to the same value with a flat heap, so this is not a leak.
- **Nvidia**: standalone `udev`/DRM backend verified on a real RTX 3060 (proprietary 595.99.02 with the open kernel modules, Gentoo, kernel 7.2.7), logged in from the display manager as a real session. GLES renders on the dGPU with the Radeon iGPU excluded through `gpu { render = vendor:nvidia; exclude = [vendor:amd] }`; two outputs run together (2560×1440@144 at 1.25×, 1920×1080@180 at 1.0×). The overlay-plane workaround branch runs for every output on this driver and scanout is correct on both; no overlay-specific test was done. Frost glass, custom shaders, screencopy, xwayland-satellite, portal and drag and drop worked. Measured cost is in `report.md`. The nested backend was verified earlier on the same card.
- **Hybrid GPU**: selection/exclusion, render-node feedback, cross-device import, hot add/remove, and VT lifecycle compile and unit-test clean. The required end-to-end AMD+Nvidia PRIME/offload run is pending on the handoff laptop; do not call this hardware-verified until a non-primary game buffer displays correctly there at sustained frame rate.
- **Intel**: untested so far.
- **Still nested-only**: Ocean compass/overview, floating-window ocean physics (both tiers), `canvas_pan_button`, `modifier_pan_fingers`.
Expand Down Expand Up @@ -134,7 +135,6 @@ sudo cp share/xdg-desktop-portal/tidewm-portals.conf /usr/share/xdg-desktop-port
- **Non-water motion presets**: smooth window move/resize and workspace motion that works with `water_effects = false`, exposed as Wave-selectable presets and tunable fields. Exact feel and defaults require maintainer approval before implementation.
- **Feel-tuning** across viscosity, sway, depth timings, cascade's drag feel, floating-window ocean physics, and the transition/ripple presets. All ship with working defaults; the actual feel still gets refined against real use.
- **Standalone hardware pass** for what's still nested-only: the Ocean compass/overview, and both floating-window ocean physics tiers.
- **Nvidia native run**: the standalone DRM backend and its overlay-plane workaround still need a real TTY session on Nvidia.
- **AUR package**: build from source for now.
- **Design-pending identity features** (parking lot, needs a design conversation before pickup): tide contexts and workspace depth-moods. Currents, Cascade's pour/drain visual, and non-sorting weighted buoyancy are implemented with their approved contracts.

Expand Down
Loading
Loading