Casey Tunturi

August 2, 2026

August 2, 2026

69 recorded commits · 6 named repos · 7 private repos

Work threads
compositor21task-396sessiond5lock-dpms3docs3tasks2task-432TASK-432
  • Private work 7 private repos

    Activity was recorded across 7 private repos. Names, subjects, counts, and links stay private.

  • souveraine-viewtop30 changes
    • compositor: windows resize when the keyboard takes their room
      10:22 p.m. · ba4bd03 · internal Gitea record only

      Opening the on-screen keyboard maps a layer surface with an exclusive zone, so every window has less room than it did — and nothing re-laid them out. The app kept the size it had and the keyboard was drawn over the bottom of it.

    • compositor: three fingers float a window out of the layout
      10:06 p.m. · a4a96d3 · internal Gitea record only

      gesture.rs recognised taps and drags, and nothing ever constructed a Recognizer — the module was orphaned, not merely unbound.

    • compositor: tile windows instead of stacking them
      9:58 p.m. · da1c7a0 · internal Gitea record only

      Every toplevel was configured to the whole usable zone at the zone's origin, so a second window landed exactly on top of the first and nothing on the glass told them apart. place/pose could arrange them; nothing decided.

    • compositor: a completed unlock must not leave a black screen
      7:10 p.m. · 1fa1ea1 · internal Gitea record only

      disclosure_locked() was `authority.locked || position.hides_session()`, and the OR was harmless only because NOTHING EVER SET LockedHint — the authority half was false forever and could not contribute. Making viewtop report the hint made it true for the first time and turned the OR into a trap:

    • compositor: recognise multi-finger gestures
      6:31 p.m. · 0611b88 · internal Gitea record only

      There were none. input::Router tracks slots and counts contacts — the raw material — and nothing ever turned a run of contacts into "three fingers tapped". The pill's swipes were QML reading a single drag and anything multi-finger was Hyprland's own, so under viewtop there is no way to reach a window with your hands at all.

    • compositor: serve screen capture, asked at every frame
      6:26 p.m. · 247bf88 · internal Gitea record only

      ext-image-copy-capture-v1 and ext-output-image-capture-source-manager-v1, ported from the scratchpad tree they were written in. Without them every UI change ships unverified, which is how an unverified change shipped on 2026-08-02.

    • compositor: hang the door — every scene intent now reaches the compositor
      5:46 p.m. · 276bb3e · internal Gitea record only

      Nothing dispatched ToCompositor. `place` existed and the other eleven intents round-tripped through tests and reached nothing, so the agent's whole vocabulary for the scene was a table describing a surface that did not answer — §13's definition of a defect, thirteen times over.

    • compositor: report wakes, do not perform them
      5:35 p.m. · 31a9ef8 · internal Gitea record only

      The direct wake worked and was wrong. The log says "power button on a dark panel: waking" and the screen came back; three seconds later sessiond resolved the same press into a Tap, found the panel lit — because viewtop had just lit it — and did what a tap on a lit panel means. It locked the session and blanked it again. One press woke the phone and put it back to sleep.

    • compositor: report LockedHint, because nothing else could do it correctly
      4:01 p.m. · 284fbcc · internal Gitea record only

      sessiond takes `locked` from logind (doctrine §4). Something has to set it, and the shell did — Session.qml watched WlSessionLock.secure and shelled out to busctl. That path failed four ways in one afternoon on blueline: it wrote to /session/auto (the caller's session, and the shell is not in the seat's — viewtop in logind 66, qs in 70); the report was dropped when hasLoginctl had not flipped and never replayed; resolving the path cost a subprocess and two D-Bus round trips, landing after sessiond's 2s LOCK_ACK_BUDGET; and firing on a QML edge meant a hint cleared by anything else was never...

    • docs: the scale, the panel, the window list, and the invariant that was not held
      3:45 p.m. · cadc466 · internal Gitea record only
    • compositor: serve wlr foreign-toplevel, so the shell can see windows
      3:37 p.m. · d82569b · internal Gitea record only

      The dock, the overview and every window-switching gesture need the list of open windows and a way to raise or close one. Under Hyprland that came from `hyprctl clients` and `Hyprland.activeToplevel`; neither survived the move, so on viewtop nothing on the device could name a window.

    • compositor: a control socket, and the panel it lets the authority turn off
      3:01 p.m. · d3f8885 · internal Gitea record only

      The screen never went off under viewtop. sessiond decided correctly and its DPMS executor ends in `hyprctl dispatch hl.dsp.dpms(...)`, which does not exist here — and with `set -eu` the script died on that line before reporting anything, so the daemon's panel field never learned it failed.

    • compositor: scale, the arrange that never ran, and the buttons
      2:40 p.m. · 789776d · internal Gitea record only

      Panel comes up at scale 2 (540x1080 logical) instead of 1. Derived from the connector's physical size, not hardcoded: the panel reports 62x124mm = 442 dpi. Same scale now feeds the render path and the hit test, which had to move together or touches land where nothing is drawn.

    • docs: the day viewtop became the session
      2:02 p.m. · bb09206 · internal Gitea record only

      The record TASK-43 now points at: what changed, the five bugs only hardware could find, why the two smithay patches are not the same kind of patch, and what is honestly still missing.

    • compositor: window geometry, and who is allowed to ask for it
      1:59 p.m. · bceb0ac · internal Gitea record only

      Every window-management request fell through to smithay's defaults, so maximize, fullscreen, move, resize and the window menu were silent no-ops. Silence is protocol-legal for move and resize and a hang for the other two: xdg-shell requires a configure in reply to a state request, and a client that never gets one waits forever on a change that is not coming.

    • deps: smithay 403a3d0 — the lock surface Qt commits before acking
      1:51 p.m. · 48ef2e2 · internal Gitea record only

      `ext-session-lock-v1` says outright: "Committing the surface before acking the first configure is a protocol error." Qt does it anyway — once, while setting a window up, the xdg-shell habit of committing empty to solicit a configure on a protocol that configures on bind instead.

    • compositor: give someone the keyboard
      1:41 p.m. · 1959bda · internal Gitea record only

      `set_focus` was never called, anywhere. No surface ever held keyboard focus, and that is bigger than where key events land: focus is what `text-input-v3` binds to, so the on-screen keyboard had nothing to enable against and the keys a virtual keyboard synthesizes were delivered to nobody.

    • compositor: ext-data-control, xdg-activation, single-pixel-buffer
      1:17 p.m. · 9c6ad05 · internal Gitea record only

      `ext-data-control-v1` is the standardized successor to the wlr protocol we already serve, and TASK-43 named it owed. Its `DataControlState` and `DataControlHandler` collide by name with wlr's, so the new import is aliased and every existing wlr use is untouched — they are different traits, so implementing both for `Viewtop` needs nothing else. Created against `primary_selection_state` for the same reason the wlr one is: a data-control client has to observe primary selections without focus.

    • deps: fork smithay for the layer-shell teardown kill
      12:58 p.m. · 9084ce7 · internal Gitea record only

      Upstream's wlr-layer-shell pre-commit hook outlives the role object. Destroying zwlr_layer_surface_v1 and then committing the wl_surface with a null buffer is how the protocol says to unmap a layer surface, and at that commit the pending LayerSurfaceCachedState no longer describes a live role — so the size/anchor checks read defaults, width 0 with no anchors, and post InvalidSize to the destroyed object.

    • compositor: say why a client disconnected
      12:37 p.m. · 1ce5307 · internal Gitea record only

      The default `ClientData` is silent, and silence cost an afternoon: the shell was dying against viewtop with "The Wayland connection broke" on its side and not one line on ours, so every theory about the cause was a guess. Three separate wrong hypotheses came out of that gap.

    • viewtop: do not claim the shell lease it is not filling
      10:57 a.m. · 86201bf · internal Gitea record only

      Running the real shell against viewtop on blueline: quickshell loaded its whole tree — dock, background, bar, bootbloom, navigation-rail, screenCorners, all classified and z-ordered by `layers.rs` — then logged `shell_ready timed out — assuming locked`, requested a session lock that was already held, and qFatal'd. systemd restarted it. Eleven seconds later it did it again.

    • viewtop: follow the authority, instead of freezing its first answer
      10:32 a.m. · 2bf8d26 · internal Gitea record only

      `Viewtop::observe_authority` and `LockState::observe_authority` both existed and were tested, and **nothing called either**. `initial_facts()` read `device_state` once at startup; nothing ever asked again.

    • kms: keep the libseat notifier, or the seat dies with this function
      10:28 a.m. · eefbdfb · internal Gitea record only

      On the phone every input device enumerated and was revoked in the same millisecond — "New device event0 … Removed device event0" for all eight. The panel lit and nothing could be touched.

    • compositor: serve input-method-v2, so the keyboard can answer
      9:49 a.m. · 6925b34 · internal Gitea record only

      text-input is what an app asks with; input-method is what the OSK answers with, and it carries preedit, commit and the candidate popup. The previous commit served the asking half and the raw-key half and left an app with somewhere to send a request and nothing that could reply.

    • compositor: serve the idle chain, fractional scale, and the OSK's halves
      9:46 a.m. · 68ed7c3 · internal Gitea record only

      Nine globals, in two groups.

    • evidence: heartbeat touch, or sessiond calls it dead
      9:46 a.m. · a09c4d8 · internal Gitea record only

      Touch reports on contact edges only, which is right — per-event on a 120 Hz drag is DEVICE-STATE-MACHINE §10's un-deduped heartbeat one sensor over. But §10's other half was missing: `evaluate_source_health` iterates all four sources keyed on a last-seen stamp, so ninety seconds after a finger lifts, `sensor_health.touch` goes `Down`, `any_down()` turns true and every forensic snapshot reads `sensors_degraded`.

    • compositor: ask disclosure_locked before drawing the session
      9:31 a.m. · 9d2f41e · internal Gitea record only

      `frame_elements` gated on `position().hides_session()`, which asks only whether a client holds the protocol lock. `disclosure_locked()` is the union with sessiond's own answer, and its doc says callers asking "may I show this" want it — the render path is the most consequential such caller and was the only one not using it. The gate and layers already do.

    • compositor: put a client's pixels on the panel
      9:24 a.m. · f1118b8 · internal Gitea record only

      Five defects stood between a client and the glass, each of which alone was enough to leave it blank, so fixing any one of them would have looked like no change at all.

    • docs: the touch fork, and what hardware has not seen yet
      8:54 a.m. · 0940c11 · internal Gitea record only

      The third settled fork after the lock and shell boundaries. Records why provenance starts at the event source rather than being recovered later, why the uinput bridge cannot be the model, and what the router refuses to decide (hit testing is the caller's; stacking order is not geometry).

    • compositor: serve touch, and keep the agent out of the evidence stream
      8:34 a.m. · 458a354 · internal Gitea record only

      The panel is blueline's only input device, and the seat had no touch capability, no libinput source and no translation — `backend_libinput` was already switched on for this crate and had never been used.

  • SouveraineOS16 changes
    • START-HERE: the page to open when something is broken
      7:13 p.m. · 9e2e193 · internal Gitea record only

      Doctrine explains why the system is shaped the way it is and is the wrong thing to open when the screen is black. This is for that: USB unreachability (with the ARP test that proves the phone is fine), the black-screen regression and its mechanism, what to do when a Hyprland binding stops existing, where to build, the three different ways things reach the phone and why an overlay-only fix never arrives, and the five traps that have each cost a session.

    • tasks: 52 atmosphere, and narrow the haptics question to one a hand can answer
      6:04 p.m. · d104797 · internal Gitea record only

      52 maps the fifteen-technique attention vocabulary onto what viewtop can actually do. Two are already landed — expose IS negative space, pose IS gravity wells — and compile_custom_pixel_shader + PixelShaderElement are already in our smithay, so #1/#6/#9/#11/#12/#13/#14 need no new machinery.

    • tasks: 50 and 51 — her hand on the glass, and viewtop to daily driver
      5:59 p.m. · 53edffa · internal Gitea record only

      The gaps were prose in TASK-43 and a summary message. They are tasks now.

    • task-43: the scene door is hung; synthesized touch is the bridge still owed
      5:47 p.m. · 4476d48 · internal Gitea record only
    • lock-dpms: waking is the machine's, and the compositor must not do it itself
      5:37 p.m. · b2ce5d7 · internal Gitea record only

      The panel had no way back: apply_gesture answers a tap on a dark panel with Restore, which is brightness, and there was no unblank in the action table. Invisible while hyprland bound the keys straight to the toggle; under viewtop it became a phone that could sleep and never wake.

    • lock-dpms: the compositor reports LockedHint now, and the ordering is verified
      4:02 p.m. · 2e77669 · internal Gitea record only

      All four faults were consequences of reporting the hint from the shell. viewtop reports it instead: it is in the seat's session, SendLocked is the moment the locked event goes out, and it can publish a level rather than an edge so a cleared hint corrects itself.

    • task-43: what landed, and where the unmerged capture tree actually is
      3:51 p.m. · c34274b · internal Gitea record only
    • lock-dpms: the ordering invariant held in the code and not on the device
      3:43 p.m. · 0f2f1a9 · internal Gitea record only

      Three independent faults, any one enough: greetd ran the desktop in its greeter slot so logind refused SetLockedHint outright; the hint was written to the caller's session and read from User.Display, which an ssh login redirects; and the one report that mattered was swallowed by a guard that never replayed it. Every blank went out unlocked and recorded blank-without-lock.

    • TASK-43: viewtop is the session compositor
      2:01 p.m. · e46c658 · internal Gitea record only

      greetd launches it, Hyprland does not run, the shell's whole surface tree composites on it. Points at the session record in the viewtop repo rather than restating it here.

    • TASK-43: touch served; next lane is KMS on device
      8:55 a.m. · 96094c7 · internal Gitea record only

      viewtop at 458a354. Records the touch fork (provenance from the event source; the agent reaches the seat but never the evidence stream) and marks lanes 1 and 2 done — the Smithay frontend and the session-lock server both landed.

    • task-39: the in-call speaker button is fixed
      8:52 a.m. · 09e818b · internal Gitea record only

      souveraine-callaudiod routes ENABLE_SPEAKER through the hook, which writes the amps directly instead of switching a sink port that cannot exist on this card. Verified over D-Bus: enable -> both amps, disable -> earpiece, not headset. A real call is the outstanding confirmation.

    • task-39: retract the HiFi-capture-is-dead finding
      8:35 a.m. · a711ce4 · internal Gitea record only

      HiFi captures fine - 204136 nonzero, peak 32726 - once the channel map is clean. The zeros were stale SLIM map state, which PAF/audio.md already documents as the alternation law and the post-call unmapped-leg bug, and the TX7 poke used to "test the decimator" recreated that bug on all three AIFs. Device is back on HiFi; the Voice Call workaround is reverted.

    • task-39: point at the eight unmerged mic branches before theorising again
      8:28 a.m. · 05c03ac · internal Gitea record only

      None reached blueline. The new measurement rules out the decimator and the front-end mixer, which favours the sysclk/SLIM-lifecycle direction of mic-fix over the pad-configuration branches.

    • task-39: the in-call speaker button routes to a headset, and why
      8:23 a.m. · 9c25588 · internal Gitea record only

      gnome-calls asks org.mobian_project.CallAudio, which callaudiod owns, and callaudiod switches a sink port - which cannot work when ACP splits earpiece and speaker across two profiles. The working toggle is the hook's speaker on|off and nothing in the UI calls it. Fix is a shim that claims the name; it needs a real call to test before enabling.

    • task-39: HiFi capture is silent; the DMIC needs the Q6 voice session
      8:20 a.m. · 2e23e4b · internal Gitea record only

      92133 frames at peak 0. Codec routing is correct and forcing the call profile's TX7 decimator changes nothing, so it is neither the decimator nor the front end - what is left in the 11-control diff is VoiceMMode1. Voice Call (Mic, Speaker) gives working mic plus speakerphone and is the workaround. Capture gain is UCM's to set; runtime amixer does not survive profile activation.

    • task-39: profile selection is state-machine work, not a pactl poke
      8:12 a.m. · 5f53bf7 · internal Gitea record only

      The card was stuck in the modem call profile, so audio came out of the earpiece and read as a weak speaker. Restoring HiFi is manual; classifying when each profile is wanted is the actual task. Also notes that the device runs pipewire while pipewire-abi-provider claims PulseAudio owns the stack.

  • souveraine9 changes
    • sessiond: the press that wakes the panel must not also blank it
      10:20 p.m. · 8c7567e · internal Gitea record only

      Press the power button on a sleeping phone: the screen comes on, the lock screen appears, and it goes black again.

    • sessiond: double-tap-to-wake wakes, and the volume keys work again
      6:28 p.m. · 266af98 · internal Gitea record only

      Two things that worked under Hyprland and stopped when viewtop took the session, both for the same reason: the binding lived in hyprland.lua and the daemon was never in the path.

    • ci: build sessiond for x86_64 too
      5:49 p.m. · d2a0547 · internal Gitea record only

      It was skipped with a comment calling it deliberate; it never was — the laptop hit lock-screen errors once and it stayed phone-only by inertia. Building is not enabling: the binary ships, the user unit stays aarch64-only in PKGBUILD.prebuilt, so this cannot wedge a machine by arriving on it. Verified it compiles.

    • sessiond: put the panel doc comments back on their own functions
      5:36 p.m. · 1f1fc5c · internal Gitea record only

      A scripted edit stacked three doc comments onto panel_on() and left set_panel() with none. Comments are load-bearing here — set_panel is the report half and request_screen is the ask half, and the whole reason both exist is that a report must not actuate and an ask must not silently edit the machine's idea of the hardware. That distinction was sitting on the wrong function.

    • sessiond: the machine can wake the panel, and so can she
      5:29 p.m. · b45fb70 · internal Gitea record only

      The state machine could turn the screen off and had no way to turn it back on. `apply_gesture` answers a power tap on a dark panel with `Restore` — which is brightness — and there was no unblank in the action table at all. That was invisible because the wake never came through the daemon: hyprland.lua bound the physical key straight to blueline-screen-toggle, so the compositor woke the panel and sessiond only heard about it afterwards.

    • lockhint: say so when the session can never hold a lock hint
      3:44 p.m. · 106c3bb · internal Gitea record only

      logind refuses SetLockedHint for any session that is not Class=user. When that happens LockedHint is pinned at no, is_locked() is permanently false, every request_blank() times out its ack budget, and the panel goes dark on a session nobody could confirm was locked — the machine keeps working and only the security half is gone.

    • lock hint: resolve the seat's session, and replay the report
      3:26 p.m. · 9498813 · internal Gitea record only

      LockedHint was never set on this device, so sessiond's `locked` (which comes from logind per doctrine §4) was permanently false, request_blank() timed out LOCK_ACK_BUDGET on every blank, and the panel went dark on a session nobody could confirm was locked. LOCK-DPMS-LESSONS §1 held in the code and not on the glass — every blank recorded blank-without-lock.

    • sessiond: create lock surfaces before the verdict, not after it
      11:24 a.m. · 5edf0c4 · internal Gitea record only

      `ensure_surfaces` returned early unless `state.locked`, and `locked` is what the compositor sends once lock frames are up. ext-session-lock-v1 is explicit: "The locked event must not be sent until a new 'locked' frame has been presented on all outputs." So a compositor honouring the spec waits for the surfaces this function refused to create until it had heard from the compositor. Neither side can move.

    • shell: wait longer for shell_ready than sessiond takes to answer it
      11:15 a.m. · 870d882 · internal Gitea record only

      `shell_ready` blocks in the daemon for up to 5s waiting for its lock-session thread to drop its Wayland connection, because the compositor refuses a second locker while the first is alive (server.rs `wait_timeout_while`). The shell's timer was 3s, so it gave up on a handshake sessiond was still answering, assumed locked, and asked for a lock that had not been released — straight into TASK-48's "Tried to show lockscreen surfaces without active lock".

  • Pixel3Arch9 changes
    • triage: reach a phone that is not at home, and stop inventing a dead shell
      10:09 p.m. · 58612d0 · internal Gitea record only

      Two failures, both found by running it against a working phone on a foreign SSID at [host] with the VPN down.

    • tools: phone-triage.sh — answer in 30s what a session spends an hour on
      7:13 p.m. · 38ce3df · internal Gitea record only

      Reachability (and crucially: booted-but-silent vs actually-down, which the ARP check settles), which gadget enumerated, session class, panel state, whether lock-then-blank is holding, and what is actually installed.

    • usb-gadget: ship the tether config as a package, because the overlay never arrived
      7:11 p.m. · 50947d0 · internal Gitea record only

      IgnoreCarrierLoss=5s landed in rootfs-overlay on 2026-08-01 with a full diagnosis of the stale-usb0 hijack. It has been correct in the repo ever since and wrong on the phone the entire time: rootfs-overlay reaches a device only on a flash, this phone updates with pacman, and NO package shipped anything under /etc/systemd/network. There was no path for the fix to travel.

    • greetd: always take the initial_session slot, never the greeter one
      3:26 p.m. · d37ab4d · internal Gitea record only

      greetd runs default_session as THE GREETER and stamps XDG_SESSION_CLASS=greeter on it; logind then refuses SetLockedHint for that session outright — "Session does not support lock screen".

    • screen-toggle: ask viewtop for DPMS, hyprctl only as fallback
      3:02 p.m. · 9b69599 · internal Gitea record only

      The hyprctl line is the whole reason the screen never went off under viewtop, and set -eu killed the script there before it could report the panel state back to sessiond. viewtop first, hyprland second so the fallback session still works, and a hard failure when neither answers rather than telling the authority the panel did something it did not.

    • packages: publish souveraine-callaudiod and blueline-camera
      8:57 a.m. · 47aadee · internal Gitea record only

      Both were built and installed by hand, which is how the camera stack ended up owned by nothing and a reprovision would have silently undone it. They go through the pipeline like everything else now, with the same builds-but-ships-the-wrong-thing asserts.

    • souveraine-callaudiod: pin the glib mkenums pair to host natives
      8:48 a.m. · 102517d · internal Gitea record only

      meson resolves them from glib-2.0's pkg-config tool variables, which come back sysroot-prefixed under PKG_CONFIG_SYSROOT_DIR. The shared cross file pins the other codegen tools but not these; supply them in a supplementary cross file so the fix travels with the package instead of living unversioned on the builder.

    • souveraine-callaudiod: route the in-call speaker toggle through the amps
      8:47 a.m. · 69435c0 · internal Gitea record only

      Upstream switches a sink port, which needs one sink carrying both an earpiece and a speaker port. blueline has both CS35L36 amps behind one PCM, so ACP splits them across two profiles and no such sink exists - asking for "any port that is not the speaker" resolves to the headset, which is what a call did when the speaker button was tapped.

    • blueline-camera: own the userspace half of the camera stack
      8:12 a.m. · aec9f49 · internal Gitea record only

      camss hands out raw Bayer on multiplanar nodes; libcamera soft-ISPs it and the PipeWire SPA plugin exports it. pipewire-libcamera was simply not installed, so the portal answered IsCameraPresent=false and Firefox showed no cameras at all. These four were installed by hand and owned by nothing.

  • smithay5 changes
    • session-lock: tolerate a bufferless commit before the first ack
      2 distinct commits share this description1:50 p.m. · 56042fd · internal Gitea record only1:50 p.m. · 403a3d0 · internal Gitea record only

      Qt commits a window surface once while setting it up - the xdg-shell habit of committing empty to solicit a configure - on a protocol that configures on bind instead. That commit carries no buffer, so it puts no content on a locked screen, but the check kills the client for it. On a phone that takes the lock screen down and locks the owner out.

    • wlr-layer-shell: do not validate a role the client already destroyed
      2 distinct commits share this description12:55 p.m. · 44cdc80 · internal Gitea record only12:55 p.m. · 8772275 · internal Gitea record only

      Destroying zwlr_layer_surface_v1 and then committing the wl_surface with a null buffer is how the protocol says to unmap a layer surface. The pre-commit hook stays attached to the wl_surface, and at that commit the pending LayerSurfaceCachedState no longer describes a live role, so the size/anchor checks read defaults - width 0 with no anchors - and post InvalidSize to the destroyed object. The client is killed for doing exactly what it was told to do.

    • new repocreated repo Fimeg/smithay
      12:53 p.m. · forge event · internal Gitea record only
  • linux-blueline1 change
    • arm64: dts: sdm845-google: rear camera is rotated 90, not 270
      8:10 a.m. · 457a667 · internal Gitea record only

      270 came from beryllium, which carries the same IMX363 in a different chassis. It reaches userspace as V4L2_CID_CAMERA_SENSOR_ROTATION and every rotation-aware consumer turns the image by it, so Firefox rendered the rear camera upside down - 180 out. Front pair is untouched; it renders correctly.