1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
# companion
A chibi 2B in the bottom-right corner of the focused monitor, on SUPER+\.
The design, with what was measured on the way, is
`docs/superpowers/specs/2026-10-23-companion-design.md`.
## Files
| File | |
| --- | --- |
| `Companion.qml` | the window, inputs, timers, speech, IPC (`companion toggle/show/hide`) |
| `Figure.qml` | draws a frame: crossfade, bob, drop out and back, explosion |
| `Mood.js` | which state wins, from a snapshot; all the timings are its `T` |
| `Lines.js` | her lines and the opening monologue |
| `prep-frames` | generated images to display frames |
| `gen-frames`, `frames.txt` | the generated images, through sd-server |
`Figure.qml` is not `Sprite.qml` because QtQuick has a `Sprite` type, and
it wins over the directory's own file (see AGENTS.md).
## Art
Not in the repo: the character is Square Enix's and this repo is GPLv2.
It lives in `~/.local/share/keystone/companion/2b/`:
dress/<name>.png, leotard/<name>.png generated, 1024x1024 on white
frames/<outfit>/<name>.png what she shows, from prep-frames
recipe.txt how each was made, and what failed
Run `prep-frames` after adding or replacing any generated image. A missing
frame falls back to its outfit's `base`.
## Inputs
- Typing and BackSpace come from `sections/companion.lua` in the
hypr-theme repo, as `custom>>keystone-typing` and
`custom>>keystone-delete` on socket2. Nothing else about the keyboard
reaches her. The BackSpace bind is `non_consuming`; checked by hand that
it still deletes in a terminal and in the browser.
- The typing pulse skips modifiers and any key pressed with Ctrl, Alt or
Super held, so a shortcut is not typing; Shift and AltGr still count.
She watches only from the second pulse within 2.5 s, so a lone key is
not typing either.
- She is on the Overlay layer and remaps whenever another layer surface
opens (Hyprland's `openlayer` event: the assistant's dim, rofi, the
keybind reminder), vanishing and hopping back in, because Hyprland
stacks a later surface above an earlier one on a layer, and 0.56.2 has
no layer rule to order them (no `order` key in the binary; an unknown
key raises no config error).
- She only feels the pointer while it is over her: Quickshell has no
global pointer and Hyprland sends no motion events. Hence the 0.6 s
dodge rather than moving away as the pointer approaches.
- Idle stages are 20, 40 and 70% of wscreensavers' `idleTimeoutMinutes`,
read live from its config, so she lies down before the saver starts.
- `IdleMonitor.enabled` read false on creation when left out, so each of
the three sets it. It also reads false at `Component.onCompleted` even
when set, and true a moment later; do not trust that first reading.
- The monitors ignore inhibitors. No lock holds an idle inhibitor for as
long as it is on, and with `respectInhibitors: true` she never idled
(measured: a 5 s monitor respecting them never fired, one ignoring them
did). Presentation and gaming mode keep her awake by name instead.
- Fullscreen is `lastIpcObject.fullscreen === 2`. Maximised is 1, and
most windows here are maximised.
- The lock screen covers every layer, so locking needs no code.
## Check
node desktop/companion/test_mood.js
|