git.lucas.co / cce-grid
desktop grid client
git clone https://git.lucas.co/cce-grid.git

CLAUDE.md (7.7K)

  1 # CLAUDE.md
  2 
  3 `cce-grid` is the desktop-grid client of the cce desktop: a cce-ui app the
  4 compositor world-anchors to the virtual desktop. It renders grid *patches* —
  5 virtual-rect regions at a compositor-chosen resolution — and is never in the
  6 pan/zoom loop: the compositor transforms the committed buffer per frame like
  7 any window content.
  8 
  9 The contract (cce window-management protocol, manager v6 / toplevel v4):
 10 `Application::grid() -> true` declares the role; `grid_patch` events say what
 11 to render; cce-ui's runner resizes, forwards to `Application::grid_patch`,
 12 and acks so the next commit latches at the new anchor. The compositor keeps
 13 its own rect grid as the fallback whenever this client is absent or has not
 14 latched a patch yet, and its gap-colored backdrop always draws beneath as
 15 the safety net beyond patch edges.
 16 
 17 Rendering is a pure function of (patch, style config): flat rounded cells,
 18 with the relief on the LINES — ONE `Prim::Lattice` for the whole patch
 19 (cce-ui shader mode 13): the pixel is folded into the grid period and the
 20 wall is measured from the NEAREST cell's edge outward over the roll, so
 21 the rail between two cells and the crossing where four meet are a single
 22 profile evaluation and join as true mitres. It replaced one `Recess` ring
 23 per cell (2026-09-14): those were N free overlays whose rounded corners
 24 stacked in colour space at every crossing and read as several overlapping
 25 effects, and whose walls — straddling a boundary inflated by the roll —
 26 overlapped each other down the rail centre whenever the roll exceeded a
 27 quarter gap. Cell floors carry NO relief (user decision); the rails read as
 28 raised grout. The compositor fallback still draws its expanded-ring scenefx
 29 chamfer (a different renderer, shown only until this client latches). `style.surface.desktop.line_relief` overrides
 30 the lip for the grid alone: a plain integer is the lip width in logical
 31 px (0 = no lip; unset = follow the DE-wide relief material), and a
 32 `(relief)` value carries a full custom material — width, depth, and wall
 33 profile (`cce_ui::relief_spec::ReliefSpec`), installed process-wide by
 34 this client (the grid and the desktop items below are all it draws) and
 35 edited in place with
 36 `cce-relief --key style.surface.desktop.line_relief`. The compositor
 37 fallback honors the integer form and a `(relief)` value's width (its
 38 scenefx chamfer has no custom profile to install). It reads the same `style.surface.desktop.*` /
 39 root-plate-radius keys as the fallback. Keep it that way — no camera
 40 state, no timers (`tick` is empty). Input is the one exception, and only
 41 over the desktop items below; the surface is transparent to the pointer
 42 everywhere else.
 43 
 44 ## Desktop items (`src/items.rs`)
 45 
 46 Images pinned to the world canvas. The compositor routes a drag over the
 47 desktop background onto this client (its `Scene::at`, which has hit-tested the
 48 grid layer through its input region since cce-compositor@b82a0ee — the separate
 49 `at_including_grid` entry point it used to need is gone), so a
 50 drop arrives at `handle_drop`; `drop_mimes()` declares the accepted flavors in
 51 preference order. Pixels win whenever they are offered (`image/png`,
 52 `image/jpeg`, `image/gif`, `image/webp`) — no fetch, no ambiguity. Below them
 53 `text/html` is preferred over `text/uri-list` because it names the IMAGE: a
 54 thumbnail wrapped in a link (Google Images' exact markup) puts the result page
 55 in uri-list, and fetching that yields HTML rather than a picture. For an
 56 unwrapped image the two agree, so the preference never does worse.
 57 
 58 A dropped item is saved into the desktop folder (`$XDG_DESKTOP_DIR` when
 59 user-dirs exports one, else `~/Desktop`) AND recorded in a sidecar,
 60 `$XDG_DATA_HOME/cce/desktop-items.json`, with the VIRTUAL-canvas position it
 61 landed at — so it comes back in the same world spot next session. Fetching
 62 shells out to `curl` rather than linking an HTTP stack: this process is a
 63 background renderer that otherwise needs no network at all, and for a
 64 once-in-a-while user action an async runtime plus a TLS stack would be the
 65 largest thing in the binary.
 66 
 67 Items draw as GPU-textured quads. Decode happens on a worker thread and the
 68 pixels return through `Message::ItemReady`, because the upload
 69 (`cce_ui::vk::upload_rgba`) has to happen on the main loop — which is also why
 70 `renderer_init` re-uploads everything restored from the sidecar.
 71 
 72 They are the only reason this client takes input at all. `input_regions()`
 73 returns exactly the item rects (and an empty list when there is no patch), so
 74 the pointer passes straight through everywhere else. Over an item, left-drag
 75 moves it — the grab offset is held in VIRTUAL units, so the gesture survives a
 76 pan or zoom mid-drag — and right-click opens a one-button `cce-cloud --json`
 77 popup at `ccectl pointer-location`, whose reply comes back as
 78 `Message::RemoveItem(path)`. It carries the path rather than an index because
 79 that menu blocks on its own thread, and the list can be reordered by a drag or
 80 grown by a drop while it is open.
 81 
 82 **Resize handles.** The compositor's `adjust` status topic (`on`/`off` as
 83 window-adjust mode — overview, or Super held — comes and goes) is the one
 84 thing this client subscribes to (`spawn_adjust_listener`, a plain std
 85 thread on the status socket, reconnecting with backoff): it never holds
 86 keyboard focus, so it cannot read Super for itself. While on, the item
 87 UNDER THE POINTER (`hover_item` — the compositor shows its ring on the
 88 hovered window the same way) draws four corner discs (`handle_discs`) in
 89 the same `style.surface.border` colours and `handle_width` as the windows'
 90 handles, the hovered one lit; a leave arrives as the off-screen move cce-ui
 91 synthesizes and clears it;
 92 a press on a disc starts a `Resize`, which scales the image
 93 PROPORTIONALLY (the mean of the two edge ratios the drag asks for),
 94 anchored on the opposite corner, and saves the sidecar on release. A press
 95 on the body still moves. The discs are sized in virtual units, so they
 96 scale with the canvas rather than holding a screen size the way the
 97 compositor's do — this client never learns the camera zoom.
 98 
 99 One trap, spelled out on `Patch::surface_per_virtual`: pointer events and
100 input regions are surface-local px, and for THIS surface that means BUFFER
101 px at every output scale — the grid surface is pinned at buffer_scale 1
102 (cce-ui ignores scale events for grid apps; patch.scale is the sole
103 resolution authority), so `Patch::scale` is the one conversion for paint,
104 regions, and pointer math alike. This replaced a `/ui` division that had
105 been calibrated against the compositor's old hit-test, which handed out raw
106 layout offsets: numerically buffer/ui only at camera zoom 1 on the pow2
107 patch quantization, and at any other camera state it displaced the input
108 region off the items (presses read as background — in overview they EXITED
109 it) and tore the press position apart from the drag deltas, flinging the
110 grabbed item thousands of virtual units. The compositor's hit-test speaks
111 true surface coordinates since cce-compositor@feab593; do not reintroduce
112 output-scale terms here.
113 
114 This directory is its own git repository whose `origin` is the local
115 *bare* repo `~/git/cce-grid.git`: **committing is not publishing —
116 `git push origin master` is**, after which `gitsite.timer` republishes it.
117 (This crate has no `published` remote; some siblings keep one for the old
118 static mirror.) `cce-grid.service` autostarts it with the session
119 (WantedBy=cce-session.target); ccebuild installs both.
120 
121 Corner radii follow the DE-wide convention: the caller widens the nominal
122 radius by `cce_ui::layout::corner_span_factor()` (superellipse span
123 compensation) before handing it to the primitives, clamped to a quarter
124 sweep — same as the compositor's `widen_corner_radius`. An unwidened radius
125 reads nearly square at corner_shape > 2 and misses the window corners.