docs: the day viewtop got its package, its titles and its borders
STATE.md was five days stale. It now carries what is on the phone (viewtop r76, souveraine r333) and the four defects fixed on the glass: per-window capture was unreachable because quickshell holds wlr handles and the ext capture source is named by an ext handle with nothing correlating them; titles were never republished on commit; the border was a filled slab that relied on the client covering its middle; capture came back flipped. Also the packaging hole, which was the important one — viewtop's PKGBUILD existed and appeared in none of packages.yml's build lists, so nothing ever built it. Half closed: it builds now, but a viewtop commit still does not trigger a rebuild. TASK-15 question 1 is answered and closed: dropping the accelerometer claim took iio-sensor-proxy from 15.3% to 1.1%, sensors overall 17.2% to 2.6% of a core. Consequence noted against 08(g) — confidence loses accel's +0.3. New: 54, 55, 56, and 57/58 (Pi dock head, SMB2 OTG boost) with the USB notes they point at.
This commit is contained in:
parent
271e22cf6a
commit
18c58fc079
10 changed files with 985 additions and 2 deletions
83
STATE.md
83
STATE.md
|
|
@ -18,8 +18,87 @@ stale the same way for the same reason. The packages are now cured
|
|||
archive, so `pacman -Sl souveraine-aarch64` is the manifest. The surface gap
|
||||
(a souveraine overlay host for the subconscious views) is still open.
|
||||
|
||||
Last updated: 2026-07-30 (every device package now publishes via CI; phone
|
||||
updates via pacman; flashing retired).
|
||||
Last updated: 2026-08-04 (viewtop now packages and publishes like everything
|
||||
else; window capture, titles and borders fixed; the accelerometer claim is
|
||||
gone).
|
||||
|
||||
## 2026-08-04 — on the phone, verified
|
||||
|
||||
Installed and running: `souveraine-viewtop 0.1.0.r76.gf25c2b2`,
|
||||
`souveraine 0.1.r333.g6b67512`.
|
||||
|
||||
**viewtop's packaging hole is closed, and it was the important one.**
|
||||
`pkgs/souveraine-viewtop/PKGBUILD` had existed since the hand-scp era and
|
||||
appeared in **none** of `packages.yml`'s build lists, so nothing ever built it
|
||||
— the device ran a hand-made package while `main` moved ahead. viewtop's own
|
||||
CI (`ci.yml`) has one job, `check`, and never publishes, so a green run there
|
||||
still says nothing about what shipped. That is the same ambiguity the retired
|
||||
`public` branch had in `souveraine`, one repo over, and it is only half closed:
|
||||
`packages.yml` now builds it, but a viewtop commit still does not trigger a
|
||||
rebuild. Someone must dispatch `packages.yml` or touch `pkgs/**`.
|
||||
|
||||
Also: this Gitea requires sign-in instance-wide, so *no* anonymous clone of a
|
||||
first-party repo works, public or not. viewtop is the only PKGBUILD whose
|
||||
`source=` points at our own Gitea — every other one fetches from github,
|
||||
gitlab, codeberg or freedesktop — which is why nothing had hit this before.
|
||||
|
||||
**Four defects fixed, each measured on the glass:**
|
||||
|
||||
- **Per-window capture was unreachable.** viewtop served the standard
|
||||
`ext-image-copy-capture` toplevel path and quickshell never used it:
|
||||
quickshell holds **wlr** toplevel handles (the protocol with `activate` and
|
||||
`close`, the only one that can carry a dock), an ext capture source is named
|
||||
by an `ext_foreign_toplevel_handle_v1`, and nothing correlates them. It
|
||||
refused client-side — `Capture source set to non captureable object` — and
|
||||
the compositor was never asked. Every overview card and dock preview was a
|
||||
transparent box. Fixed by serving `hyprland_toplevel_export_v1`, which takes
|
||||
the wlr handle directly; given both, quickshell picks it. Both paths end in
|
||||
the same `copy_window_into`. See `crates/compositor/src/toplevel_export.rs`
|
||||
for the expiry condition — if quickshell grows the standard path, the global
|
||||
drops with no renderer change.
|
||||
- **Titles were empty.** `publish_toplevel` ran at map, on focus change and on
|
||||
maximize/fullscreen but **never on commit**, and clients set their title
|
||||
after mapping. Everything read "Window", the QML fallback.
|
||||
- **Borders differed between two tiled windows** from one width and one code
|
||||
path: the frame was a filled slab drawn *behind* the window, relying on the
|
||||
client to cover its middle. Now four edge rects.
|
||||
- **Screen capture came back vertically flipped** — a top-anchored bar landed
|
||||
on the bottom edge. `GlesMapping::flipped()` really is hardcoded `true`, so
|
||||
the old row-reversal looked right; it was one inversion too many.
|
||||
|
||||
**The accelerometer claim is dropped** (TASK-15 question 1, answered and
|
||||
closed). Measured, idle, one flag apart: `iio-sensor-proxy` **15.3% → 1.1%**,
|
||||
total sensor cost **17.2% → 2.6%** of a core, 47.9 → 45.9 °C. Claiming the
|
||||
accelerometer is what makes iio-sensor-proxy poll continuously; `monitor-sensor`
|
||||
itself was 0.8% and sensord 0.1% — the subprocess was never the cost. This is
|
||||
not "accel is unwanted": a subprocess holding a continuous claim is the wrong
|
||||
way to get it, and TASK-36's SLPI batching is the right one. Restore the source
|
||||
only together with that. Consequence for **TASK-08(g)**: `observed_confidence`
|
||||
loses accel's +0.3 once the source ages out, so there is one fewer input for
|
||||
confidence to gate.
|
||||
|
||||
## Open, and the next session should start here
|
||||
|
||||
- **The shell is still keyed to Hyprland's instance signature.**
|
||||
`Persistent.qml` reads `HYPRLAND_INSTANCE_SIGNATURE`, which is unset under
|
||||
viewtop, so `isNewHyprlandInstance` compares `""` to `""` and is
|
||||
**permanently false**. It gates `LockScreen.qml:145`
|
||||
(`lock.launchOnStartup` — so lock-on-startup never fires), `LockScreen.qml:289`,
|
||||
`services/Idle.qml:59` and `ii-base/services/Idle.qml:21`. viewtop's
|
||||
single-instance mechanic is the control socket's `clear_stale`, but that is a
|
||||
*mutex*, not an identity — there is no per-start value to compare. Fix is
|
||||
two-sided: viewtop exposes an instance id on `{"op":"state"}`, the shell reads
|
||||
that instead of the env var.
|
||||
- **Captured card content is streaky / wrong-surface.** Frames arrive
|
||||
(`hasContent=true`, correct size) but the contents are not right.
|
||||
- **viewtop security audit not started.** `SECURITY-AUDIT.md` already carries
|
||||
the standing principles P1–P7, the watch list, "Three ungated boundaries in
|
||||
the compositor, found 2026-08-03", and the long-arc process-isolation goal —
|
||||
note it never uses the word "viewtop", it says "the compositor", so absence of
|
||||
the word is not absence of coverage. Scope, deliverable, and what "RedFlag"
|
||||
refers to are still unanswered.
|
||||
- SouveraineOS working tree is dirty: 08, 15 and README modified, 54/55/56
|
||||
untracked. TASK-15's accel measurement is written in but uncommitted.
|
||||
|
||||
## DEPLOYMENT — the phone updates with pacman. Do not flash.
|
||||
|
||||
|
|
|
|||
178
USB/README.md
Normal file
178
USB/README.md
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
# USB — the Pixel 3 as a KVM device
|
||||
|
||||
**Exploration opened 2026-08-04** from Casey: "start exploring the notion of
|
||||
KVM from this thing." This folder holds USB-related design and measurement;
|
||||
the KVM exploration starts here.
|
||||
|
||||
## What exists, measured on the phone (2026-08-04)
|
||||
|
||||
| Piece | State |
|
||||
|---|---|
|
||||
| Kernel gadget framework | `CONFIG_USB_GADGET=y`, `CONFIG_USB_CONFIGFS=y` — built in, no modules |
|
||||
| **HID gadget** | **`CONFIG_USB_CONFIGFS_F_HID=y` — the keyboard/mouse half of KVM already ships** |
|
||||
| FunctionFS | `CONFIG_USB_CONFIGFS_F_FS=y` (flexible userspace gadgets, MTP path) |
|
||||
| **UVC gadget** | **`CONFIG_USB_CONFIGFS_F_UVC` not set — the video half is the one missing config line** |
|
||||
| Other functions | NCM, ECM, RNDIS, EEM, ACM, serial, OBEX, mass storage; no UAC1/2, no MIDI |
|
||||
| Live configfs | Two gadgets bound: `dbg` (acm.GS0, acm.GS1, ncm.usb0) and `usb-signaller-developer` (ncm.usb0) on UDC `a600000.usb` (dwc3, **super-speed capable**, currently high-speed) |
|
||||
| Mode switching | `usb-signaller` 0.3.1-3 active (dev mode at boot via `usb-signaller-default-mode`); `blueline-usb-gadget` 1.0.0-2; dev mode = NCM usb0 (172.16.42.1) + ACM |
|
||||
| Video hardware | Venus **encoder** `/dev/video12` + **decoder** `/dev/video13`; CAMSS sensors `/dev/video0-11`; no v4l2loopback |
|
||||
| Other packages | v4l-utils, libusb, libgusb, libusbmuxd |
|
||||
|
||||
## Host mode — the dock question (2026-08-04, Casey: "if I plug a USB-C dock into the phone right now…")
|
||||
|
||||
DeX-style: the phone as the computer, a dock supplying monitors + keyboard
|
||||
+ mouse. Measured against the running kernel *and* Google's own vendor DT
|
||||
(`sdm845-b1c1-usb.dtsi`, LineageOS kernel lineage-22.2):
|
||||
|
||||
| Ask | Verdict | Evidence |
|
||||
|---|---|---|
|
||||
| **Multi-monitor (DP alt mode)** | **Unwired in every tree that exists.** Google's board DT wires the port as **DRP + USB-PD** (`pmi8998_pdphy`, source 5V@0.9A, sink 5V/9V@3A, try-sink) with **no DP alt-mode path** — no redriver, no typec mux, no altmode node; the only "mux" is test GPIO104 for HS enumeration. Matches Google's official spec (USB 3.1 Gen 1, no video out). The *silicon* is capable — mainline `sdm845.dtsi` fully wires `mdss_dp` → QMP USB3-DP PHY (`usb_1_qmpphy_dp_in`) — but no sdm845 board in mainline wires it end-to-end either | vendor DT (fetched), mainline dtsi (local tree) |
|
||||
| **USB keyboard/mouse** | **Yes — this is what LineageOS actually gives you.** DRP port + PD = real host mode. Our tree pins `dr_mode = "peripheral"` and usb-signaller holds the port as a gadget, so *we* don't do it today — but the hardware does | vendor DT, our board DT |
|
||||
| **Via a plain OTG adapter** | **Works** — classic USB-A keyboard/flash-drive case | standard OTG |
|
||||
| **Via a Dell dock (WD19…)** | **Host mode: works on LineageOS, one role-switch away on ours.** Monitors over DP alt mode: no. Monitors over **DisplayLink** (software display over USB host): only with a DisplayLink driver | per-dock, plug it in |
|
||||
| **Charging from the dock** | Yes (PD sink 5V/9V@3A wired in the vendor DT) | vendor DT |
|
||||
|
||||
**The correction (2026-08-04):** an earlier draft called the DP gap a
|
||||
"connector mux on the board — hardware." That was asserted from memory and
|
||||
is wrong as stated. The evidence: Google wired the port for data+PD and
|
||||
never enabled DP alt mode in any tree, and the *hardware question that
|
||||
remains* — whether the PCB even routes DP lanes from the QMP PHY to the
|
||||
connector — is **unproven in both directions**. If LineageOS drove real
|
||||
monitors over the dock, the lanes exist (via DisplayLink, not alt mode).
|
||||
Decisive physical test: a USB-C→HDMI **alt-mode** adapter (not DisplayLink)
|
||||
with the port in host mode; if the board routes DP, a wired-up kernel has
|
||||
something to talk to.
|
||||
|
||||
**The daily-driver catch:** the USB-C port is the management port — tethering
|
||||
and dev mode (SSH over usb0) die while it is a host. Deliberate, documented
|
||||
mode switch only; cold boots stay Casey's.
|
||||
|
||||
## Host mode — the live test (recipe, 2026-08-04)
|
||||
|
||||
Confirmed on the device: the role switch is real (`/sys/class/usb_role/
|
||||
a600000.usb-role-switch`, role currently `device`), passwordless sudo works,
|
||||
and usb-signaller owns `com.meego.usb_moded` on the system bus (its modes are
|
||||
all device-side today — Developer/Tethering/MTP — so host mode is a direct
|
||||
sysfs switch, and usb-signaller's restart is the way *back*).
|
||||
|
||||
```
|
||||
# 1. give up the gadget first — a bound gadget + host role breaks
|
||||
echo "" | sudo tee /sys/kernel/config/usb_gadget/usb-signaller-developer/UDC
|
||||
|
||||
# 2. switch the port to host
|
||||
echo host | sudo tee /sys/class/usb_role/a600000.usb-role-switch/role
|
||||
|
||||
# 3. plug the dock, then observe:
|
||||
lsusb
|
||||
dmesg | tail -30
|
||||
|
||||
# 4. back — WiFi SSH survives the whole trip, so this always works:
|
||||
echo device | sudo tee /sys/class/usb_role/a600000.usb-role-switch/role
|
||||
sudo systemctl restart usb-signaller # re-provisions the developer gadget
|
||||
cat /sys/class/usb_role/a600000.usb-role-switch/role
|
||||
```
|
||||
|
||||
Gotchas: the dock is self-powered (Dell docks carry their own PSU), so its
|
||||
hub can enumerate even if the phone's VBUS sourcing is weak or absent — the
|
||||
dwc3 node has **no `vbus-supply`**, so bus-powered OTG peripherals (plain
|
||||
keyboards) may not power, which is itself a measurement. If the dock's hub
|
||||
enumerates, its keyboard/mouse are just kernel HID input — the shell gets a
|
||||
real keyboard for free. If the dock's *monitors* ever light up, the DP lanes
|
||||
exist and we go hunt the mux wiring; with no typec/altmode wired, expect
|
||||
nothing from them.
|
||||
|
||||
## The KVM notion
|
||||
|
||||
Plug the phone into another machine's USB port. The phone controls that
|
||||
machine and sees its screen — keyboard, mouse, monitor, in one cable. Two
|
||||
halves, with very different states of readiness:
|
||||
|
||||
**Input half — HID gadget: already built.** The kernel handles the USB HID
|
||||
descriptors; userspace writes 8-byte keyboard reports to `/dev/hidg0` and
|
||||
4-byte mouse reports to `/dev/hidg1`. This is the half that reaches
|
||||
bootloaders/BIOS (the pelya project proved it) — no video ever reaches a
|
||||
BIOS, so a BIOS-stage KVM is HID plus whatever screen the machine itself has.
|
||||
|
||||
**Display half — two routes:**
|
||||
|
||||
- **Route A (works today, zero kernel change):** the live NCM link. The
|
||||
controlled machine serves its display over the USB network (VNC/waypipe);
|
||||
the phone shows it in a surface. Touches become HID reports. Worst part is
|
||||
latency, and the link is super-speed-capable.
|
||||
- **Route B (one config line away):** UVC gadget. The phone presents as a
|
||||
UVC camera; the controlled machine runs a "screen-as-webcam" tool (OBS
|
||||
virtual cam and friends) which is the stream source; the phone decodes
|
||||
(Venus decoder ✓) and shows it. The reverse direction also exists: Venus
|
||||
encoder + UVC = the PC sees the phone's *own* screen as a webcam — an
|
||||
in-the-cable scrcpy.
|
||||
|
||||
## Prior art (verified)
|
||||
|
||||
- **tejado/android-usb-gadget** — USB Gadget Tool (F-Droid): configfs roles
|
||||
for keyboard/mouse, FIDO CTAP, CCID, UVC camera; activates gadgets at boot.
|
||||
- **Android 14 "Device as Webcam"** — `CONFIG_USB_CONFIGFS_F_UVC` + configfs
|
||||
recipe (MJPEG 1080p30, `dwMaxVideoFrameBufferSize` etc.); enabled by
|
||||
default in GKI since android12-5.10. The configfs UVC setup pattern is the
|
||||
one to copy.
|
||||
- **pelya/android-keyboard-gadget** — the classic: `/dev/hidg0` keyboard,
|
||||
`/dev/hidg1` mouse, `hid-gadget-test`; works into BIOS/bootloader.
|
||||
- **Kernel docs** — `usb/gadget_hid.rst` (hid_gadget_test) and
|
||||
`usb/gadget_uvc.html`; the UVC reference app is
|
||||
`gitlab.freedesktop.org/camera/uvc-gadget` — "queue image buffers from a
|
||||
variety of sources", i.e. the phone-side userspace we would adapt to feed
|
||||
viewtop/compositor frames into the gadget.
|
||||
|
||||
## Questions to settle, in order
|
||||
|
||||
1. **Directions:** KVM-into-another-machine only, or also the reverse
|
||||
(phone screen out as webcam)? Both share the UVC config; the userspace
|
||||
differs.
|
||||
2. **Route A or B first?** A proves the whole loop today with no rebuild; B
|
||||
is the "real peripheral" feel but needs a kernel rebuild + cold boot —
|
||||
and that is Casey's call, on the daily driver.
|
||||
3. **Touch → HID mapping:** a KVM mode turns the phone's screen into a
|
||||
touchpad/screen for the target machine. The machinery is the mirror of
|
||||
TASK-50's touch intent — same input-routing problem, other direction.
|
||||
4. **Where does the mode live?** usb-signaller gains a "kvm" mode (profile
|
||||
+ service), or sessiond owns it as a mode with a verb. The agent angle
|
||||
is real: Ani gets a keyboard/mouse that reaches any host — doctrine §13
|
||||
says she should have the verb, not a config file.
|
||||
5. **The kernel path:** enabling F_UVC is one config line, but the kernel
|
||||
package still publishes unsigned raw `.pkg.tar.zst` (TASK-25's
|
||||
`repo-add --sign` is unbuilt) and installs are cold-boot events.
|
||||
|
||||
## Next steps (ordered, each measurable)
|
||||
|
||||
1. **Prove HID today** — provision a HID keyboard function into a scratch
|
||||
gadget on the phone (configfs, no reboot), bind it, write
|
||||
`hid-gadget-test` key reports, watch the laptop enumerate a second
|
||||
keyboard. One session, no rebuild.
|
||||
2. **Route A latency test** — laptop serves its screen (VNC/waypipe) over
|
||||
the live 172.16.42.1 link; phone displays it; measure.
|
||||
3. **Route B** — config one-liner + rebuild + cold boot (Casey); configfs
|
||||
UVC setup per the AOSP recipe; `uvc-gadget` userspace fed by a capture
|
||||
surface.
|
||||
4. **usb-signaller "kvm" mode** once the pieces exist, with clean
|
||||
teardown/provision against the dev mode.
|
||||
|
||||
## Acceptance (measured)
|
||||
|
||||
- Phone plugged into the laptop enumerates as keyboard+mouse; typing from
|
||||
the phone lands in the laptop's focused app.
|
||||
- Route A: the laptop's screen renders on the phone with usable latency.
|
||||
- Route B: the laptop's virtual-cam tool lists the phone as a camera; the
|
||||
phone shows the stream.
|
||||
- Switching modes never strands the daily driver's tethering/dev mode.
|
||||
|
||||
## Connects to
|
||||
|
||||
TASK-25 (kernel package signing — the rebuild path), TASK-50 (her hand on
|
||||
the glass — the touch→HID bridge is the mirror of the touch intent),
|
||||
doctrine §13 (the KVM mode as a verb), `usb-signaller` (the mode host),
|
||||
souveraine-viewtop (the display surface and capture path).
|
||||
|
||||
## The buildable work lives here
|
||||
|
||||
TASK-57 (the Pi dock head — phone as gadget, Pi as display/input head over
|
||||
NCM) and TASK-58 (host power — the SMB2 OTG boost, one kernel change that
|
||||
unlocks host-mode power, power sharing, and phone→Pi power).
|
||||
|
|
@ -114,6 +114,51 @@ is no longer "what should confidence gate" but "is anything left for it to
|
|||
gate". The honest candidate is the doze promotion rate and possibly nothing
|
||||
else. Answer that, then wire it or delete the table and record why.
|
||||
|
||||
## (h) Thermal policy — the zones are there and the policy is empty (2026-08-04)
|
||||
|
||||
Measured on the device, so this is inventory rather than plan:
|
||||
|
||||
- **23 thermal zones**, all on the **`step_wise`** governor. `power_allocator`
|
||||
(IPA — the PID that allocates a power budget across cooling devices) is
|
||||
*available* on every zone and used by none.
|
||||
- **Cooling devices:** `cpufreq-cpu0` (17 states), `cpufreq-cpu4` (23),
|
||||
`devfreq-5000000.gpu` (6), plus `cx` and `ebi`.
|
||||
- **Trip points:** passive at CPU 90/95 °C, GPU 85 °C, PMIC 105 °C; `hot` 90,
|
||||
`critical` 110. **Nothing below 85 °C.** So nothing throttles until the die is
|
||||
already hot, and there is no gentle first response at all.
|
||||
- **cpuidle:** two states (WFI, little-rail power collapse), governor `menu`,
|
||||
and `menu` is the only one built. Android downstream had far deeper cluster
|
||||
and rail collapse; upstream `teo` is not available to compare against.
|
||||
- **GPU:** `simple_ondemand` devfreq, idling at 257 MHz.
|
||||
- Battery reads 41 °C at 22% while idle — and note the existing warning above
|
||||
about `temp` = 570 while charging. Verify the sensor before building policy on
|
||||
it; a skin model fed a lying thermistor is worse than no model.
|
||||
|
||||
**The Android prior art, and which parts transfer.** The HAL types zones
|
||||
(`CPU/GPU/SKIN/BATTERY/USB_PORT/POWER_AMPLIFIER`) and maps each to a severity
|
||||
ladder — `NONE → LIGHT → MODERATE → SEVERE → CRITICAL → EMERGENCY → SHUTDOWN`.
|
||||
That ladder is the interesting part and it is *ours to define*, because it is
|
||||
policy, not hardware. `PowerManager.getThermalHeadroom()` is its app-facing half:
|
||||
a normalized 0–1 forecast some seconds ahead, so software degrades itself before
|
||||
the kernel throttles it. **Skin temperature is a virtual sensor** — a weighted
|
||||
model over die zones, because there is no thermistor on the glass; on SDM845
|
||||
those coefficients lived in Qualcomm's per-device `thermal-engine.conf`, which
|
||||
is the one piece of blueline-specific prior art worth going and reading.
|
||||
|
||||
**How it lands here, and the trap to avoid.** §12's rule is not optional and an
|
||||
outside adviser will propose breaking it: **a thermal daemon that reads a zone
|
||||
and calls a governor is the eighth blind actor.** Temperature enters through
|
||||
`sensor_input` like proximity and lux; the response leaves as an `Action` from
|
||||
`tick()`, through the executor table that already owns `Dim`/`Restore`/`Blank`.
|
||||
One writer, one trail. The severity ladder is then a `DeviceStatePolicy` field
|
||||
the agent can read and rewrite, which is what makes "the phone is getting warm"
|
||||
something she can act on instead of something that happens to her.
|
||||
|
||||
**Do the cheap thing first.** TASK-15's 2026-08-04 measurement found ~36% of a
|
||||
core burning at idle in the sensor stack, with the SoC nowhere near any trip.
|
||||
No thermal policy would have helped that. Fix the spinning before modelling the
|
||||
heat.
|
||||
|
||||
## Sub-tasks (do separately)
|
||||
|
||||
1. State machine + pub/sub (mce/datapipe shape), device-flag gating.
|
||||
|
|
|
|||
|
|
@ -15,6 +15,70 @@ notifications") plus the measured idle burn below.
|
|||
A phone that is "off" should approach zero. Nothing on the device today
|
||||
even attempts that.
|
||||
|
||||
## Re-measured 2026-08-04 — the biggest spinner is now OURS, and it is the sensors
|
||||
|
||||
41 minutes uptime, locked, screen off, viewtop session:
|
||||
|
||||
```
|
||||
23.3% iio-sensor-proxy 9m35s of CPU in 41m of uptime
|
||||
9.3% monitor-sensor --proximity --light --accel (parent: souveraine-sensord)
|
||||
3-4% dbus-broker carrying the traffic between the two
|
||||
```
|
||||
|
||||
~36% of one core, permanently, on a phone doing nothing. Dies 44–48 °C, battery
|
||||
41 °C, and **the SoC is nowhere near a trip point** — the shallowest passive
|
||||
trip on this device is the GPU's at 85 °C. So this heat is not a thermal-policy
|
||||
problem, it is a spinning userspace, and no governor will touch it.
|
||||
|
||||
**The cause is architectural, not a bug in either program.** `souveraine-sensord`
|
||||
spawns `monitor-sensor --proximity --light --accel` and holds it open forever.
|
||||
Claiming the **accelerometer** is what makes iio-sensor-proxy poll continuously —
|
||||
proximity and light are cheap and event-shaped, accel is polled to derive
|
||||
orientation. And per `DEVICE-STATE-MACHINE.md` §4, `accel.moving` is evidence
|
||||
the machine deliberately **stopped branching on**: "motion cannot tell a pocket
|
||||
from an ear from a hand". We are burning a third of a core to keep producing a
|
||||
reading no rule consumes.
|
||||
|
||||
**The prior art is the reason the SLPI exists.** Android's sensor HAL batches:
|
||||
the sensor DSP accumulates samples into a hardware FIFO and wakes the AP once
|
||||
per batch (`batch(sensor, sampling_period, max_report_latency)`), with wake-up
|
||||
and non-wake-up variants so a screen-off phone is not woken per sample. Polling
|
||||
from userspace on the application processor, through D-Bus, defeats the entire
|
||||
point of having a sensor DSP — and this device *has* one (TASK-36, TASK-34).
|
||||
|
||||
Three questions to settle, in this order, before any freeze work:
|
||||
|
||||
1. **Does anything still need the accelerometer claimed?** — **ANSWERED AND
|
||||
DONE 2026-08-04** (`6b67512`). Measured on the phone, idle, screen on, same
|
||||
session, one flag apart:
|
||||
|
||||
| `monitor-sensor` flags | iio-sensor-proxy |
|
||||
|---|---|
|
||||
| `--proximity --light --accel` | **15.3%** |
|
||||
| `--proximity --light` | **1.1%** |
|
||||
|
||||
Fourteen points of a core, continuously, for the reading §4 weights least
|
||||
(+0.3) and §9 calls a weak signal. The claim is what makes iio-sensor-proxy
|
||||
poll; nothing else here does. `monitor-sensor` itself measured 0.8% and
|
||||
sensord 0.1% — the subprocess was never the cost, the *claim* was.
|
||||
|
||||
The accelerometer source is dropped from `SOURCES` in
|
||||
`src/bin/souveraine-sensord.rs`, along with `parse_accel`, `accel_moving`
|
||||
and `MOTION_WINDOW` — all three existed only to turn orientation into a
|
||||
motion edge. The 2026-07-27 stuck-`Moving(true)` finding is kept in comment
|
||||
because whatever reports motion next owes a decay too.
|
||||
|
||||
This is not "accel is unwanted": it is that a subprocess holding a
|
||||
continuous claim is the wrong way to get it. See (2).
|
||||
2. **If it is wanted, does it go through the SLPI with batching** rather than
|
||||
iio-sensor-proxy polling? That is TASK-36's host, and it turns a continuous
|
||||
poll into an interrupt per batch.
|
||||
3. **Is `monitor-sensor` the right transport at all**, or does sensord read the
|
||||
IIO character device with a buffer and a trigger directly? A subprocess and a
|
||||
D-Bus round trip per reading is three context switches for one number.
|
||||
|
||||
This is the *before* number for this task now: 36% of a core and 45 °C at idle.
|
||||
|
||||
## Shape of the thing
|
||||
|
||||
One coordinator (substrate side, likely a sessiond sibling or sessiond
|
||||
|
|
|
|||
144
docs/tasks/54-drawing-what-nobody-sees.md
Normal file
144
docs/tasks/54-drawing-what-nobody-sees.md
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
# TASK 54 — the compositor stops drawing what nobody can see
|
||||
|
||||
**Status: open.** Created 2026-08-04, from Casey's question: *"could viewtop
|
||||
correctly stop drawing elements in a way that makes us not have to edit every
|
||||
drawing app?"* **Repo:** `souveraine-viewtop`.
|
||||
|
||||
The answer is yes, the mechanism is already in the protocol, and one half of it
|
||||
is already built and working. This task is the other half, plus the leak that
|
||||
exists today.
|
||||
|
||||
## The seam is the frame callback, and that is why no app needs editing
|
||||
|
||||
A Wayland client does not draw when it likes. It commits a buffer, requests
|
||||
`wl_surface.frame`, and waits — and it renders the next frame only when the
|
||||
compositor fires that callback. **Withholding the callback stops the client
|
||||
rendering, from the compositor, with no cooperation from the client, no protocol
|
||||
extension, and no per-app change.** Every toolkit already honours it, because
|
||||
honouring it is how you get a frame at all.
|
||||
|
||||
That is the whole answer to "without editing every drawing app". It is also why
|
||||
this belongs in the compositor and nowhere else: no daemon can do it, and a
|
||||
policy that asked apps nicely would only bind the apps we wrote.
|
||||
|
||||
`send_frame_callbacks` states the invariant in its own header:
|
||||
|
||||
> *"a surface that is composited but never called back freezes on its first
|
||||
> frame, and one that is called back without being drawn spins rendering frames
|
||||
> nobody sees"*
|
||||
|
||||
Both halves are real. The first is why you cannot just stop calling back
|
||||
anything; the second is the waste this task is about.
|
||||
|
||||
## It already works, and the lock screen is the proof
|
||||
|
||||
While the session is locked, `frame_elements` composites the lock client **and
|
||||
nothing else**, and `send_frame_callbacks` sends to the lock surfaces and then
|
||||
`continue`s. So every session client freezes on its last frame and the lock
|
||||
client keeps animating. That is exactly the shape wanted — *everything stops
|
||||
except the glance* — and it has been running on the phone for weeks.
|
||||
|
||||
**So the "different layer" for lock-screen elements that keep drawing does not
|
||||
need building.** It is `ext-session-lock`, the lock client is quickshell's `Lock`
|
||||
surface, and `LockMediaCard` / `LockAgentCard` / `LockNotifyCard` are already
|
||||
its content. A now-playing card in that surface keeps its callbacks while the
|
||||
rest of the device is frozen, and costs nothing at all while the panel is dark,
|
||||
because a dark panel produces no vblank and `on_vblank` returns before
|
||||
`send_frame_callbacks` (see `kick_render`'s power guard).
|
||||
|
||||
## What is broken today, measured
|
||||
|
||||
`frame_elements` skips a window when:
|
||||
|
||||
- its workspace is not visible (`workspaces.is_visible(ws)` — a float, so during
|
||||
a strip settle *both* neighbours are visible and neither may be skipped), or
|
||||
- its exposure resolves to `alpha <= 0.0` (`ExposureLevel::Obscured`).
|
||||
|
||||
`send_frame_callbacks` applies **neither filter**. It walks
|
||||
`self.space.elements()` unconditionally. So a window one workspace away is not
|
||||
drawn and *is* called back: it re-renders at panel rate, forever, into a buffer
|
||||
that is discarded. The render grew the workspace filter on 2026-08-03 and the
|
||||
callback path did not follow — the same "documented guarantee the code stopped
|
||||
keeping" shape as every finding in TASK-53.
|
||||
|
||||
This is the cheapest thermal win on the device: a frame not requested is CPU,
|
||||
GPU and memory bandwidth not spent, and it needs no thermal policy, no governor
|
||||
change and no new trip points.
|
||||
|
||||
## The pieces, in order
|
||||
|
||||
1. **Callbacks follow the render, exactly.** One predicate — "is this surface in
|
||||
the scene this frame" — asked once and used by both loops. Not two
|
||||
predicates that agree today: `SESSION-AUTHORITY-DOCTRINE.md` §11's
|
||||
second-decider shape is precisely what TASK-53 §1 just removed from the hit
|
||||
test, and re-introducing it here would be the same defect one loop over.
|
||||
The honest implementation is for `frame_elements` to record what it drew and
|
||||
for the callback pass to read that, rather than re-deriving it.
|
||||
|
||||
2. **Occlusion.** A window fully covered by an opaque window above it is
|
||||
composited every frame today (TASK-51 §1 names this). Skipping it saves the
|
||||
compositing *and*, with (1), stops the client rendering at all.
|
||||
**Fail open:** occlusion may only be claimed from `wl_surface.set_opaque_region`
|
||||
— a client that declares nothing is not proven to cover anything, and
|
||||
treating unknown as opaque would blank a window behind a translucent one.
|
||||
Alpha in a *pose* is the same question: a posed window at reduced scale does
|
||||
not cover what it used to.
|
||||
|
||||
3. **What must keep drawing even when off-screen.** Two cases, both real:
|
||||
- **A live capture session.** `ext-image-copy-capture` renders a window on
|
||||
demand from its last committed buffer. A client that stopped drawing hands
|
||||
the capture a stale frame — an overview card of a window that is quietly
|
||||
frozen in the past. A window with an open capture session keeps its
|
||||
callbacks, and that is not an exception to the rule so much as the rule
|
||||
applied correctly: something *is* looking at it.
|
||||
- **A window the agent has posed into a visible card.** Doctrine §13 makes
|
||||
composition hers; a scaled live window in an overview is on the glass, so
|
||||
it is visible by the same predicate as anything else. This falls out of
|
||||
(1) for free if the predicate is "in the scene", and breaks immediately if
|
||||
the predicate is "on the active workspace".
|
||||
|
||||
4. **The idle scene already costs little, and say so.** `DrmCompositor` reports
|
||||
`frame.is_empty` when nothing changed and no flip is queued, so a still scene
|
||||
already stops flipping. The waste is not the compositor's compositing — it is
|
||||
the clients rendering into buffers that are thrown away. Do not "optimise"
|
||||
the render loop looking for it.
|
||||
|
||||
## Design questions to settle before code
|
||||
|
||||
- **Subsurfaces and popups.** `Window::send_frame` walks the toplevel's
|
||||
subsurfaces and popups, which is why popups are not iterated separately. The
|
||||
filter has to be applied at the window, not per surface, or a popup of a
|
||||
hidden window keeps its parent alive.
|
||||
- **How long may a frozen client hold its buffer?** Forever is correct for
|
||||
correctness (it is what makes coming back a repaint rather than a relaunch —
|
||||
the argument `set_visible` already makes) and is memory the phone may want
|
||||
back. TASK-15's freeze tier is the other lever; decide which one owns it
|
||||
rather than having both act.
|
||||
- **Does an idle-notify inhibitor imply "keep drawing"?** No — a client holding
|
||||
an idle inhibitor is asking the *session* not to idle out, not asking to be
|
||||
composited while invisible. Worth writing down because it will be proposed.
|
||||
|
||||
## Acceptance
|
||||
|
||||
Measured, on the phone, with an unmodified client:
|
||||
|
||||
- open a client on workspace 0, swipe to workspace 1, and its process CPU falls
|
||||
to approximately zero within a second — then swipe back and it repaints
|
||||
without a relaunch and without a visible stall;
|
||||
- with two windows tiled, cover one completely (a fullscreen over a tiled pair)
|
||||
and the covered client's CPU falls the same way;
|
||||
- an overview card of an off-workspace window still shows live content, because
|
||||
its capture session keeps its callbacks;
|
||||
- the lock glance still animates while every session client is frozen — the
|
||||
behaviour that already works must not regress;
|
||||
- and the idle die temperature falls. Today, locked with the screen off, the
|
||||
dies sit at 44–48 °C and the battery at 41 °C. That number is the before.
|
||||
|
||||
## Connects to
|
||||
|
||||
TASK-51 §1 (occlusion is named there; this owns it now), TASK-53 §1 (one
|
||||
predicate, not two — the same lesson), TASK-15 (the freeze/doze tier: it stops
|
||||
*processes*, this stops *rendering*, and they must not both decide the same
|
||||
thing), TASK-08 (governor and thermal policy — none of which this needs),
|
||||
TASK-52 (Atmosphere: an animator that runs while invisible is this bug with a
|
||||
nicer name).
|
||||
164
docs/tasks/55-hand-verbs-on-the-glass.md
Normal file
164
docs/tasks/55-hand-verbs-on-the-glass.md
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
# TASK 55 — the hand's verbs: a window action sheet
|
||||
|
||||
**Status: open — design questions only, nothing decided.** Created 2026-08-04,
|
||||
from Casey: *"I asked for a Three Finger Tap overlay — a couple of buttons:
|
||||
Move/Resize, Split, Close. I got three finger tap drag stuff. And I don't have a
|
||||
good way to close a running app."* **Repo:** `souveraine-viewtop` (compositor +
|
||||
`wire`) and `souveraine` (shell surfaces).
|
||||
|
||||
The gesture recognition is built, the binding exists, and it points at the wrong
|
||||
thing. What is missing is the surface the recogniser's own header promised: *"the
|
||||
compositor recognises, the shell draws"* (`gesture.rs`). The shell was never
|
||||
given the job.
|
||||
|
||||
## What exists, measured
|
||||
|
||||
| Piece | Where | State |
|
||||
|---|---|---|
|
||||
| Tap/drag recognition | `viewtop/crates/compositor/src/gesture.rs` | Solid, tested; tap carries the centroid "so the caller can name which window was under them" |
|
||||
| Tap forwarding | `wayland.rs:3160` → `(3,"tap")` to sessiond | Works |
|
||||
| The binding | `sessiond/device_state.rs:1717` — 3-finger tap → `Action::Overview` → `qs … ipc call overview toggle` (`server.rs:375`) | Works — and this is the wrong target: the rail's swipe-up already reaches Mission Control, which opens the *same* `WindowOverview` surface (`WindowOverview.qml:64` reads `overviewOpen \|\| missionControlOpen`). The tap duplicates a gesture the thumb already has |
|
||||
| Three-finger drag | `wayland.rs:3175` — workspace strip sideways, drag-window up/down | Works. This is what shipped instead of the overlay |
|
||||
| Close | `ToCompositor::Close` → `toplevel.send_close()` (`wire` lib.rs:347, `wayland.rs:2532`) | Agent verb only. **No hand path exists** |
|
||||
| Split | `placement.rs` — automatic top/bottom tiling of two windows | No verb in `wire`; a button has nothing to say |
|
||||
| The dial | `DialHost.qml` — hand-written entries incl. "Kill window" via `hyprctl dispatch` | Hyprland-bound — dead under viewtop, and the third capability enumeration (TASK-31) |
|
||||
| Rail Home | `SystemGestureRail.qml:233` — tap = Home via `hyprctl dispatch hl.dsp.focus workspace 1` | Hyprland-bound — the "back to square one" path dies under viewtop |
|
||||
| Selection chip | `SelectionHost.qml` / `SelectionChip.qml` | The closest existing floating-action precedent — and **broken**: "tiny, illegible, not working properly at all" (Casey 2026-08-04). It has already had one untappable incarnation on device ("completely untappable" — `SelectionHost.qml` header) |
|
||||
|
||||
## The questions to settle — in order, before code
|
||||
|
||||
### Q1. What is the three-finger tap FOR now?
|
||||
|
||||
The tap currently raises a surface the rail already reaches. Either the tap
|
||||
becomes the action sheet (what Casey asked for) and the overview is left to the
|
||||
rail, or the tap keeps the overview and the sheet lives elsewhere. Decide the
|
||||
*vocabulary* first: on this device a gesture that duplicates another gesture is
|
||||
a defect by construction (TASK-53 §1's one-decider lesson, one layer down).
|
||||
|
||||
### Q2. One surface or two: the sheet and the dial
|
||||
|
||||
The dial already carries window verbs (Kill window) and phone verbs (Lock,
|
||||
Screenshot, Keyboard) in one ring. The sheet wants Move/Resize, Split, Close.
|
||||
These must "jive" — the real question is whether they are one surface with
|
||||
context-dependent entries (a window focused/under-fingertip → window verbs
|
||||
appear) or two surfaces. The wrong answer is a second hand-written list in a
|
||||
second file: TASK-30's verb tables exist to be the single source, and TASK-31
|
||||
already owes the dial's entries to them. The sheet must read the same table the
|
||||
dial will read, whichever surface it lives on.
|
||||
|
||||
### Q3. What does Split mean when tiling is automatic?
|
||||
|
||||
`placement.rs` tiles without being asked. A Split button is therefore either:
|
||||
pin this window to one half and let the next open window take the other (the
|
||||
`place`-then-tile shape), force the split axis/order, or refuse. This decides a
|
||||
new `wire` intent (`Split`?) and its legality rules — the compositor owns
|
||||
whether a placement is legal (`place`'s own doc). Not answerable in one
|
||||
sentence; design the gesture-of-intent before the button.
|
||||
|
||||
### Q4. Close: polite, or kill, or both?
|
||||
|
||||
`send_close` is a request; a client may refuse. The dial's "Kill window" was a
|
||||
Hyprland close dispatch (same polite mechanism, via Lua). Under viewtop a
|
||||
refusing client leaves the hand with nothing — and "no way to close an app" is
|
||||
the complaint that started this task. If a kill verb exists, who may reach it —
|
||||
the hand, or step-up only? (§13's columns apply: killing is in the user's
|
||||
column on the phone; on the laptop the agent owns composition. The two seats
|
||||
may legitimately differ.)
|
||||
|
||||
### Q5. Back to square one, safely
|
||||
|
||||
Android and HarmonyOS: swipe up = Home, swipe up-and-hold = Recents; the home
|
||||
screen is the always-reachable safe state. iOS: swipe up = Home. Our rail is
|
||||
already this shape (tap = Home, swipe = dock, further = Mission Control) — but
|
||||
Home is `hyprctl` under the hood, so under viewtop the safe state is
|
||||
unreachable. Questions: what IS square one on viewtop — an empty workspace, the
|
||||
dock, nothing focused? What must Home safely *leave* — the sheet open, a drag
|
||||
latched, a window posed or placed (does Home un-place?), the OSK up? `goHome()`
|
||||
today clears mission control/overview/dock/OSK; under viewtop it must be a
|
||||
compositor verb (`ToCompositor::Home`?) with defined effects on windows, not
|
||||
`hyprctl`. "Safely" means: no state the gesture leaves behind can strand the
|
||||
user (the cancelled-drag latch that shipped once is the precedent).
|
||||
|
||||
### Q6. Why is the selection chip broken — and is the sheet going to inherit it?
|
||||
|
||||
Symptoms: tiny, illegible, not working. The chip is 44 px tall with
|
||||
`font.pixelSize: smaller` and 18–20 px icons, anchored to a pointer hint
|
||||
(`SelectionHost.qml`) — and it has already had an untappable iteration. Before
|
||||
the sheet reuses that machinery: is it scale (2x panel, logical/physical mix),
|
||||
font, target size, or anchoring? **The sheet must not ship the chip's root
|
||||
cause.** Fix or name it first — the sheet is the second surface built on the
|
||||
same foundation.
|
||||
|
||||
### Q7. The sheet's input and dismissal
|
||||
|
||||
Precedent conflict in-house: the dial uses OnDemand focus and open/close
|
||||
signals; the selection host originally used a fullscreen mask and abandoned it
|
||||
for a card-sized window. The sheet floats over an app the user just touched —
|
||||
its dismissal rules (tap outside, gesture cancel, window change) are part of
|
||||
its correctness, and the recogniser already has the right shape (`cancel`
|
||||
emits nothing — the sheet must hear a cancel).
|
||||
|
||||
### Q8. Naming the window
|
||||
|
||||
The tap carries the centroid precisely so the caller can name the window under
|
||||
the fingers. A tap on the wallpaper is a different event entirely (nothing to
|
||||
act on — overview? nothing?). Decide the no-window case before it becomes a
|
||||
"why did a blank sheet appear" bug on device.
|
||||
|
||||
## Prior art on the table
|
||||
|
||||
- **Android / HarmonyOS gesture navigation** — swipe-up = Home, swipe-up-hold =
|
||||
Recents, predictive back; the home screen is the guaranteed exit. Our rail
|
||||
already implements this shape; what's missing is the viewtop-backed Home
|
||||
(Q5) and the recents/overview not being duplicated (Q1).
|
||||
- **iOS** — swipe-up = Home; action sheets present at the gesture origin, and
|
||||
dismissal by outside-tap is load-bearing.
|
||||
- **denial** (`Pixel3Arch/references/shells/denial`, assessment in
|
||||
`VIEWTOP-AND-DENIAL.md`) — the shell is an in-process Flutter scene; its
|
||||
surface would be drawn by the compositor's own shell, not a separate client.
|
||||
The in-process-vs-client fork is still open in that doc; the action sheet is
|
||||
the first surface that will be shaped by the answer.
|
||||
- **The references shells** (`~/Projects/references/`) — the Flutter reference
|
||||
overview (cards, flick to dismiss, one `progress` driving everything —
|
||||
already adopted in `WindowOverview.qml`), Phosh's `use_top_layer = !locked`
|
||||
state-gates-visibility model, and Launcher3's AllAppsEduView discovery nudge
|
||||
(already adopted in the rail).
|
||||
- **Our own SelectionHost** — the closest precedent and the caution: the
|
||||
floating-action surface has failed twice on device.
|
||||
|
||||
## Shape of the deliverable (once the questions are answered)
|
||||
|
||||
1. A `wire` intent or two (`Split` if Q3 lands it; `Home` for Q5) + their
|
||||
legality rules in the compositor.
|
||||
2. The surface: entries from the same verb-table source the dial is owed
|
||||
(TASK-30/31), anchored and dismissed per Q6/Q7, sized and scaled per Q6.
|
||||
3. The binding table change in sessiond (tap → sheet/overview per Q1).
|
||||
4. The rail's Home repointed from `hyprctl` to the compositor (Q5).
|
||||
|
||||
## Acceptance (measured, on the phone)
|
||||
|
||||
- Three-finger tap on a window presents the sheet for **that** window; the
|
||||
same tap on wallpaper does the decided no-window thing.
|
||||
- Move/Resize pins the window (the `place` path); Close closes it, and a
|
||||
refusing client produces a visible refusal rather than silence.
|
||||
- Split produces the second tile per Q3's decision.
|
||||
- The sheet dismisses on outside tap and on a cancelled gesture, without
|
||||
leaving a latch behind.
|
||||
- Rail Home returns to square one under viewtop with no hyprctl anywhere in
|
||||
the path, and no state stranded.
|
||||
- The selection chip row is either fixed or its root cause is named and the
|
||||
sheet demonstrably does not share it.
|
||||
- The sheet's entries are read from the same source the dial's will be — no
|
||||
third hand-written list.
|
||||
|
||||
## Connects to
|
||||
|
||||
TASK-30 (verb tables — the sheet's entries), TASK-31 (dial entries — same
|
||||
source question), TASK-18 (the selection menu precedent and its failures),
|
||||
TASK-50 (her hand on the glass — the agent's touch path is the mirror image of
|
||||
this), TASK-43 (the compositor's surface model), TASK-53 (one decider; a
|
||||
gesture duplicating another gesture), TASK-14/51 (the overview), TASK-19
|
||||
(settings — the sheet may open settings surfaces from its entries),
|
||||
`VIEWTOP-AND-DENIAL.md` (in-process vs client shell), doctrine §13 (whose verbs
|
||||
are these; the user's 40 vs the agent's 60), §12 (the sheet is a surface, not a
|
||||
daemon — it must read a binding table, never decide).
|
||||
125
docs/tasks/56-island-and-the-bars-honest-face.md
Normal file
125
docs/tasks/56-island-and-the-bars-honest-face.md
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
# TASK 56 — the island, and the bar's honest face
|
||||
|
||||
**Status: open — Part A is design questions only; Part B is small and buildable.
|
||||
Created 2026-08-04, from Casey: the top bar is "static only due to a lack of
|
||||
creativity and ingenuity" — the bar should clear a region ("an island") where a
|
||||
notification can sprawl banner text; laptop/phone merge is the ultimate goal,
|
||||
and "the agent is supposed to have full control on all the things soon".**
|
||||
**Repos:** `souveraine` (shell surfaces, `ii-phone` bar), sessiond for the
|
||||
island's decision side.
|
||||
|
||||
## What exists, measured
|
||||
|
||||
| Piece | Where | State |
|
||||
|---|---|---|
|
||||
| The phone bar | `surfaces/quickshell/ii-phone/modules/ii/bar/BarContent.qml` | Three anchored regions: left (sidebar button + carrier + active window), middle (clock), right (pill: wifi/mute/mic/notify-unread reveals, battery, resources, systray, weather). Floating rounded rect with border |
|
||||
| The island's seed | `BarContent.qml:268-306` — `Revealer` + `Layout.rightMargin` Behaviors | The right pill already animates contents *apart* to make room; the island is this same primitive with a real budget |
|
||||
| `bar.bottom` | `BarContent.qml:338` | Exists — an island always grows away from the screen edge, so a top bar and a bottom bar are one layout mirrored |
|
||||
| Banners today | `ii-base/modules/ii/notificationPopup/NotificationPopup.qml` (phone shares the desktop one) | Corner-stacked, entirely separate from the bar — pure client noise; the bar's only hook is `NotificationUnreadCount` |
|
||||
| Carrier | `surfaces/quickshell/services/Cellular.qml` — mmcli + gdbus `PropertiesChanged`, signal-driven; `operatorName`, `accessTech` (5G/LTE/H+/H/3G/E/G), `hasService`, `materialSymbol` | Bar shows `"<operator> <tech>"` e.g. "Fido LTE" + signal bars (`BarContent.qml:95-114`) |
|
||||
| Network face | `services/Network.qml` — `materialSymbol` is already honest about wifi (connected → strength bars, disconnected → `wifi_find`, disabled → off) | The pill's always-visible face (`BarContent.qml:312-318`) — but it never shows cellular: wifi disconnected shows a scan glyph even while LTE is the live connection |
|
||||
| Tap conflict | `BarContent.qml:50-68` | The whole left region is one `FocusedScrollMouseArea` (press = left sidebar toggle) — a carrier tap needs its own MouseArea ahead of the parent |
|
||||
|
||||
## Part A — the island
|
||||
|
||||
### A1. What is the island, exactly?
|
||||
|
||||
iPhone: the bar recedes around a pill that expands. Android: heads-up banners
|
||||
drop below the status bar. Our phone is 540×1080 portrait — an island cannot
|
||||
sprawl sideways; it grows *downward* into the screen. On the laptop it grows
|
||||
sideways from the center. Same surface, two geometries — that is the
|
||||
laptop/phone merge in miniature. Decide what class of content it carries:
|
||||
notifications only, or the agent's general attention surface (turn activity,
|
||||
timer runs, device-state changes)?
|
||||
|
||||
### A2. What happens to the displaced sections?
|
||||
|
||||
Compress or hide? Hide is where the desktop bar went wrong — modules vanish and
|
||||
the user loses their place. The right pill's Revealers already compress; the
|
||||
island is that mechanism given a budget. On the phone the left section
|
||||
(sidebar button + carrier) and the clock must survive at reduced width or move
|
||||
out of the island's sprawl path.
|
||||
|
||||
### A3. Who decides what lands in the island?
|
||||
|
||||
Today banners are client-driven noise (freedesktop notifications → corner
|
||||
popup). For the island to be the agent's surface, the display decisions are
|
||||
sessiond's — the surface reads a binding table, never decides (doctrine §12,
|
||||
§13): island-worthy vs quiet (a policy), steal-vs-queue when a second banner
|
||||
lands mid-sprawl (preemption is a decision), linger duration, whether the
|
||||
screen dims during it (touches sessiond's dim/lock contract). Shape: an intent
|
||||
reachable the way `overview toggle` is (`qs -c souveraine ipc call …`), plus a
|
||||
routing policy over the existing notifications service. "A verb she cannot
|
||||
reach is a defect."
|
||||
|
||||
### A4. Dismissal and touch
|
||||
|
||||
Tap = open the notification's app or the sidebar surface; swipe = dismiss.
|
||||
These are gesture bindings — they come from sessiond's binding table, not
|
||||
hardcoded QML. Also: does the bar remain interactive while the island is
|
||||
sprawled (the three MouseAreas)?
|
||||
|
||||
### A5. Does it replace the corner popup on the phone?
|
||||
|
||||
The ii-base `NotificationPopup` is the current home of banner text, and the
|
||||
phone still uses the desktop one. The island replaces it for island-worthy
|
||||
classes on the phone; toasts / OSK / IME are separate concerns. Decide
|
||||
coexistence before building.
|
||||
|
||||
## Part B — the bar's honest face (buildable now)
|
||||
|
||||
### B1. Carrier name, tap-to-cycle
|
||||
|
||||
Drop the tech suffix: the bar shows `operatorName` only — "Fido", no "LTE".
|
||||
Tapping the name cycles **Fido → Casey → Fimeg** — a display alias, "just for
|
||||
fun": the SIM still reports the real operator; the alias list is local.
|
||||
Questions: persist (an existing settings key?) or per-session? Where does the
|
||||
tap live — the carrier row needs its own MouseArea ahead of the left region's
|
||||
sidebar-toggle press.
|
||||
|
||||
### B2. A live network-type indicator
|
||||
|
||||
"LTE/Wifi should be the indicator on what type of network we have running
|
||||
live." The pill's wifi glyph never reflects cellular: wifi disconnected shows
|
||||
a scan glyph while LTE is the live connection. The indicator should pick the
|
||||
*active* network: wifi connected → the wifi strength glyph (existing
|
||||
`Network.materialSymbol`); wifi not connected but cellular has service → the
|
||||
tech (LTE/H/3G/5G via `Cellular.accessTech`); neither → the disconnected/off
|
||||
glyph. The tech label thus moves out of the carrier name and into the
|
||||
indicator — one source of truth, live.
|
||||
|
||||
## Acceptance (measured, on the phone)
|
||||
|
||||
Part B (buildable now):
|
||||
|
||||
- The bar shows "Fido" — no "LTE" suffix.
|
||||
- Tapping the carrier cycles Fido → Casey → Fimeg, wraps, and the alias
|
||||
survives per the persistence decision.
|
||||
- The network indicator shows the live type: wifi glyph on wifi, tech label
|
||||
(LTE/…) when wifi is down but cellular is up, off-glyph when neither — and
|
||||
it flips on connection change without a shell restart.
|
||||
- The left-region sidebar toggle still works on its own area.
|
||||
|
||||
Part A (once the questions are answered):
|
||||
|
||||
- A banner arrives and the bar clears space for it — sections compress,
|
||||
nothing hides.
|
||||
- Sessiond can put content in the island through a reachable intent — no
|
||||
hardcoded QML text paths.
|
||||
- Dismissal and open behave per the binding table; a second banner
|
||||
mid-sprawl does the decided thing.
|
||||
- The phone island grows downward, the laptop island sideways from the
|
||||
center — one surface.
|
||||
- No hyprctl anywhere in the path.
|
||||
|
||||
## Connects to
|
||||
|
||||
TASK-55 (the same bar region; gesture bindings and the sheet's dismissal
|
||||
rules), TASK-30/31 (verb tables as the single source of what the agent can
|
||||
drive — the island intent joins them), TASK-50 (the agent's touch path is the
|
||||
mirror image of the island's), TASK-53 (one decider — a banner the agent
|
||||
cannot influence is the same defect class), TASK-14/51 (the overview and the
|
||||
bar's windows), doctrine §12 (the island is a surface, not a daemon — it reads
|
||||
a binding table, never decides), §13 (the agent owns the device; "a verb she
|
||||
cannot reach is a defect"), `VIEWTOP-AND-DENIAL.md` (in-process vs client
|
||||
shell shapes where the island is drawn).
|
||||
99
docs/tasks/57-pi-dock-head.md
Normal file
99
docs/tasks/57-pi-dock-head.md
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
# TASK 57 — the Pi dock head
|
||||
|
||||
**Status: open.** Created 2026-08-04, from Casey: *"I need a 'I plugged in
|
||||
this dock and my pixel 3 screens lit up' system"* — the Pi 4 becomes the
|
||||
phone's display and input head. The phone stays a gadget (device mode —
|
||||
charging and tethering keep working), the Pi is the host that drives the
|
||||
monitor and the dock's peripherals, and the phone's desktop streams to it
|
||||
over the existing NCM link. Rejected paths, already argued: scrcpy (Android
|
||||
only — no ADB on mainline), waypipe (forwards *apps*, not a shell; our shell
|
||||
is a compositor, not forwardable), and DP alt-mode (unwired/unknown on
|
||||
blueline — the Pi bridge works today).
|
||||
|
||||
**Repos:** the phone side is `souveraine-viewtop` (capture) + the phone
|
||||
kernel/userspace; the Pi side is its own OS (bring-up state unknown — "major
|
||||
malfunctions" TBD, find them first).
|
||||
|
||||
## The topology
|
||||
|
||||
```
|
||||
[Dell dock: keyboard/mouse + monitor] [Pixel 3, device mode]
|
||||
│ HDMI / USB-A │
|
||||
▼ USB-C→A
|
||||
[Pi 4] ◄──────────────────────────────────────────────┘
|
||||
│ NCM link (usb0, 172.16.42.x) — phone compositor
|
||||
│ capture → Venus H.264 → out over the cable
|
||||
└── always-on: udev sees the phone's NIC → viewer fullscreen
|
||||
```
|
||||
|
||||
The phone's port stays in the one mode that already works. The Pi's USB-A
|
||||
port is a host: it sources VBUS (charges the phone, ~1.2 A shared), and the
|
||||
phone's default developer gadget (NCM + ACM via usb-signaller) comes up the
|
||||
moment the cable lands — no configuration on either side.
|
||||
|
||||
## What exists, measured
|
||||
|
||||
| Piece | Where | State |
|
||||
|---|---|---|
|
||||
| NCM gadget | usb-signaller default developer mode, `usb0` 172.16.42.1 | Live — WiFi SSH (`10.10.20.234` via wlan0) survives everything USB, so the management path is never at risk |
|
||||
| Venus H.264 encoder | `/dev/video12`, decoder `/dev/video13` (v4l-utils 1.32.0-2) | Present; encoder idle and unproven at 1080p — load it first |
|
||||
| Compositor capture | `souveraine-viewtop` — window capture landed (`86d0f93`) | Written and merged; **whether `zwlr_screencopy` exists on the wire is unverified** — if not, expose capture through our own IPC |
|
||||
| Pi 4 | Casey's, bring-up TBD | Pi state unknown; needs OS + network check before anything |
|
||||
| Link headroom | USB 2.0 HS, 480 Mbps vs H.264 1080p30 at ~5–10 Mbps | ~50× — bandwidth is not the constraint, latency is |
|
||||
| Phone screen | 540×1080 portrait | The Pi must scale/rotate the stream to the monitor — free the Pi to do the transform (hardware scaler on the Pi 4) |
|
||||
|
||||
## Phases
|
||||
|
||||
1. **Pi bring-up** — what's on it, what's broken, network reach. Then plug
|
||||
the phone into the Pi's USB-A: confirm the Pi sees a new NIC
|
||||
(`172.16.42.1` pingable), the phone charges, tethering still works.
|
||||
2. **Static chain** — phone: test pattern (or any source) → Venus H.264 →
|
||||
NCM; Pi: decode (hardware) → fullscreen on HDMI. Proves the transport
|
||||
before the capture exists. Latency budget: <100 ms is a usable desk;
|
||||
measure, don't assume.
|
||||
3. **Real capture** — phone: compositor capture → Venus encode → stream.
|
||||
Trigger: the phone starts streaming when `usb0` has carrier (not the typec
|
||||
udev event — blueline mainline has **no typec driver**, so usb-signaller's
|
||||
cable monitor never fires; the NIC is the honest signal). Pi: udev rule on
|
||||
the phone's gadget NIC → launch viewer.
|
||||
4. **Input** — dock keyboard/mouse → Pi evdev → over the same cable → inject
|
||||
into the phone's compositor via its IPC (both ends are ours). This is the
|
||||
step that makes it a *dock* rather than a monitor.
|
||||
5. **Parked — the car version.** A car head unit is the same problem with a
|
||||
dashboard for a far end; the same bridge applies over NCM or WiFi.
|
||||
"Android Auto" against *stock* head units is a separate universe — the AA
|
||||
transport is closed Android proprietary userspace (the `f_accessory`/AOA
|
||||
family lives in Android's kernel forks, not mainline), no open phone-side
|
||||
implementation, zero kernel work involved. Not a task until Casey wants it
|
||||
to be.
|
||||
|
||||
## Open questions
|
||||
|
||||
- Screencopy or custom capture IPC? (Determines the phone-side build.)
|
||||
- Does the Venus encoder take the compositor's buffer format directly, or is
|
||||
there a packing/stride hop (TASK-46 hit this with CAMSS)?
|
||||
- Portrait 540×1080 vs monitor: stream native and let the Pi scale, or
|
||||
rotate on the phone? (Native stream + Pi transform is simpler.)
|
||||
- Latency target per use: desk cursor (<100 ms) vs video (<200 ms).
|
||||
|
||||
## Acceptance (measured)
|
||||
|
||||
- Plug the phone into the Pi's USB-A: the monitor lights up with the phone's
|
||||
desktop within seconds, no interaction on either side, phone keeps
|
||||
charging.
|
||||
- The phone's own screen can be off/dimmed while the Pi shows the desktop.
|
||||
- The laptop's shell and the phone's wifi networking are untouched by a dock
|
||||
session.
|
||||
- Dock keyboard/mouse drive the phone's desktop (Phase 4) with no visible
|
||||
double-handling of input.
|
||||
- A Pi that loses the phone exits cleanly and waits (no zombie viewer).
|
||||
|
||||
## Connects to
|
||||
|
||||
TASK-58 (host power — when the SMB2 boost lands, the phone can power the Pi
|
||||
through the same cable, killing the Pi's wall wart), TASK-51 (the capture
|
||||
punch list — the same capture feeds both UI verification and this stream),
|
||||
TASK-54 (the frame-callback culling — the phone-side stream must not fight
|
||||
the compositor's render budget), `USB/README.md` (the exploration record and
|
||||
the rejected-paths argument), usb-signaller (the gadget side is already
|
||||
built and default).
|
||||
81
docs/tasks/58-host-power-smb2-boost.md
Normal file
81
docs/tasks/58-host-power-smb2-boost.md
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# TASK 58 — host power: the SMB2 OTG boost
|
||||
|
||||
**Status: open — investigation first, patch second.** Created 2026-08-04,
|
||||
from Casey: *"host mode still — what about the phone sharing its power to
|
||||
another device?"* One kernel change unlocks host-mode *power*, power sharing
|
||||
(power-bank mode), and docks without a Pi in between. **Repos:** `Pixel3Arch`
|
||||
(kernel build/packaging), the running kernel tree (identify first).
|
||||
|
||||
## The measured wall
|
||||
|
||||
| Fact | Evidence |
|
||||
|---|---|
|
||||
| Host *data* works | Role flip to `host` via `/sys/class/usb_role/a600000.usb-role-switch`; xHCI registered, SuperSpeed, uptime clean — proven live |
|
||||
| The missing half is power | Plugged a bus-powered NVMe: no LED, zero enumeration (only root hubs on the bus). The phone does not source VBUS |
|
||||
| Charger is mainline | `pmi8998-charger` bound by **`qcom-smbx-charger`** (`qcom,pmi8998-charger` @ `spmi c440000 pmic@2 charger@1000`) |
|
||||
| Boost not exposed | 41 regulators in `/sys/class/regulator`, zero boost/otg/vbus — the driver never registers it |
|
||||
| No typec | `/sys/class/typec` empty — no tcpm, no Rp on CC, so no C-to-C sink/source detection |
|
||||
| dwc3 wires no power | `usb_1_dwc3` has no `vbus-supply`; the only VBUS model in mainline is db845c's GPIO fixed regulator with a *"FIXME: this regulator is responsible for VBUS on the left USB port"* |
|
||||
| The hardware exists | Google's vendor DT drives the SMB2's 5 V boost device-side (`ext_5v_boost` + `usb0 { google,switch-vbus = <250>; }`, `pmi8998_pdphy` as `TYPEC_PORT_DRP` default `TYPEC_SINK`) — the boost is in silicon, hidden behind the SLPI-era vendor stack |
|
||||
| Ceiling | 5 V/~1.5 A out. USB-PD at 12/20 V lives in the SLPI firmware, which mainline does not run — that wall stands; nothing this task wants needs it |
|
||||
|
||||
Dylan Van Assche's usb-signaller (the mode host) never touches VBUS either —
|
||||
his design contract is *"the kernel owns power"*: his platforms' kernels
|
||||
auto-switch role on cable plug and source VBUS. The role half of that
|
||||
contract works on blueline; the power half is exactly what this task is.
|
||||
|
||||
## Phase 1 — investigate (do this before any patch)
|
||||
|
||||
1. **Find the running kernel's source** — `uname -r` on the phone, match the
|
||||
tree that built it. The rebuild/packaging path is the known sore spot:
|
||||
`kernel.yml` has never gone green and flashing is retired (cold boot only
|
||||
— a warm reboot poisons TZ/remoteproc).
|
||||
2. **Read `qcom-smbx-charger`** — mainline file (likely
|
||||
`drivers/power/supply/qcom_smb2.c`): does it touch the SMB2 boost/OTG
|
||||
registers at all? What does the vendor `switch-vbus` actually drive?
|
||||
Pull Google's `android_kernel_google_msm-4.9` charger source (the fetched
|
||||
vendor DT already points at `pmi8998_gpios 8` for extcon VBUS — the GPIO
|
||||
*detects*, the boost is PMIC-internal).
|
||||
3. **`.config` on the phone** — `/proc/config.gz` (or the build config):
|
||||
`CONFIG_TYPEC`, `CONFIG_TYPEC_TCPM`, `CONFIG_TYPEC_QCOM_PMIC` presence.
|
||||
The pmi8998 typec block's mainline driver is
|
||||
`drivers/usb/typec/tcpm/qcom_pmic_typec.c` — confirm whether it binds
|
||||
pmi8998 or only newer PMICs.
|
||||
4. **SMB2 register docs** — the boost enable is in the charger's register
|
||||
map; find it in the vendor kernel (that `250` in `switch-vbus` is a
|
||||
register-relative offset hint).
|
||||
|
||||
## Phase 2 — the patch
|
||||
|
||||
1. **smb2: expose the OTG boost as a vbus regulator** — the silicon is in
|
||||
the SMB2 charger; mainline's driver just never registers it.
|
||||
2. **blueline DT: `vbus-supply = <&boost>` on `usb_1_dwc3`** — role `host`
|
||||
then sources 5 V the way every other mainline board sources it.
|
||||
3. **TYPEC/TCPM + `qcom_pmic_typec`** (or the minimal path if it doesn't
|
||||
bind) — Rp on CC so C-to-C *sinks* (the Dell dock's cable, USB-C
|
||||
peripherals) see a source. Without this, only legacy USB-A targets via
|
||||
adapter can be powered; with it, proper OTG.
|
||||
|
||||
Then the phone in `role=host`: powered *and* enumerating. The same lock also
|
||||
lets the phone power the Pi in TASK-57's topology — one cable, no wall wart.
|
||||
|
||||
## Acceptance (measured on the phone)
|
||||
|
||||
- `role=host` + a bus-powered device on a USB-A adapter: device lights up
|
||||
*and* enumerates (`/sys/bus/usb/devices`), where today it does nothing.
|
||||
- The first-test NVMe lights up.
|
||||
- `/sys/class/regulator` shows the vbus/boost regulator and it toggles with
|
||||
the role.
|
||||
- Device mode untouched: plugged into a charger, `pmi8998-charger` still
|
||||
reports `online=1` charging, tethering/dev gadget still provision.
|
||||
- Role flips stay safe under the WiFi SSH path; usb-signaller restart
|
||||
re-provisions after any manual flip.
|
||||
|
||||
## Connects to
|
||||
|
||||
TASK-57 (the Pi dock head — this unlocks phone→Pi power over the same
|
||||
cable), `USB/README.md` (the exploration record: evidence table, correction,
|
||||
live role-switch recipe), `NET_KERNEL_GAPS.md` (the kernel rebuild/reflash
|
||||
path — tier 3's blocker is the same gating), TASK-25 (kernel package
|
||||
signing), usb-signaller (the userspace that will switch roles against the
|
||||
kernel's power contract).
|
||||
|
|
@ -59,6 +59,10 @@ Four that are cheap relative to what they unblock:
|
|||
|
||||
| # | Task | What's left |
|
||||
|---|------|-------------|
|
||||
| 58 | [Host power: the SMB2 OTG boost](58-host-power-smb2-boost.md) | Host data works; the boost isn't. The SMB2 5 V boost exists in silicon and mainline's `qcom-smbx-charger` never registers it. Investigation first (which tree builds the running kernel, does smb2 touch the boost, does `qcom_pmic_typec` bind pmi8998), then vbus-supply + regulator + typec. Unlocks host-mode power, power-bank mode, and the Pi without a wall wart. |
|
||||
| 57 | [The Pi dock head](57-pi-dock-head.md) | Phone stays a gadget; the Pi 4 drives the monitor + dock. NCM link is default, Venus encoder is present, compositor capture landed — the unproven seams are `zwlr_screencopy` vs our own capture IPC, and the Pi's own bring-up ("major malfunctions TBD"). Four phases: Pi bring-up, static chain, real capture + udev trigger, input injection. |
|
||||
| 56 | [The island, and the bar's honest face](56-island-and-the-bars-honest-face.md) | Bar clears a region for sprawling notification banners, agent-controlled (Part A: design questions). Part B is buildable now: drop the LTE suffix, tap-to-cycle the carrier name (Fido → Casey → Fimeg), and a network-type indicator that shows the live connection instead of a wifi glyph that never reflects cellular. |
|
||||
| 55 | [The hand's verbs: a window action sheet](55-hand-verbs-on-the-glass.md) | Design questions only. Three-finger tap currently opens the same overview the rail reaches; the sheet needs Move/Resize/Split/Close with real verbs, and Home must become a compositor verb — the rail's is `hyprctl` and dies under viewtop. |
|
||||
| 52 | [Atmosphere](52-atmosphere.md) | Attention as a compositor capability — the fifteen-technique vocabulary. Two are **already landed** (`expose` is negative space, `pose` is gravity wells), and `compile_custom_pixel_shader` + `PixelShaderElement` are already in our smithay, so most of the rest needs no new machinery. Build the attention model and one clock **first**; fifteen uncoordinated animators is §1's blind actors in the render path. |
|
||||
| 50 | [Her hand on the glass](50-agent-hand-on-the-glass.md) | Every scene intent reaches the compositor now **except touch** — so she can move, scale, dim and close a window and cannot press anything inside one. The receiving end is built (`Origin::Agent`, the disjoint slot namespace, the evidence predicate); nothing calls it, so she still reaches touch through uinput, which is the exact conflation `input.rs` exists to prevent. Also owns the inverse mapping `pose` owes the hit test. |
|
||||
| 51 | [viewtop to daily driver](51-viewtop-to-daily-driver.md) | The punch list between *runs* and *stable*, all of it found by using the phone: windows stack with nothing to tell them apart, screen capture is written and unmerged (so no UI change can be verified), volume buttons are recognised and dropped, and five named holes that are one decision each. |
|
||||
|
|
|
|||
Loading…
Reference in a new issue