fix(clients): GTK + Decky polish batch from live Deck/Windows testing
ci / rust (push) Failing after 41s
apple / swift (push) Successful in 1m8s
ci / web (push) Successful in 55s
ci / docs-site (push) Successful in 1m6s
android / android (push) Successful in 3m20s
deb / build-publish (push) Successful in 2m55s
decky / build-publish (push) Successful in 27s
apple / screenshots (push) Successful in 5m46s
ci / bench (push) Successful in 5m5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 34s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 3m20s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 2m31s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 53s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m18s
docker / deploy-docs (push) Has been cancelled
flatpak / build-publish (push) Has been cancelled
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Has been cancelled

GTK Linux client:
- hosts/library: clicking a card was dead — the handler was on
  FlowBoxChild::activate (never emitted on click); bridge child-activated
  → child.activate() on the FlowBox (ui_hosts, ui_library).
- stream: the Ctrl+Alt+Shift+D/Q/S chords (and all key forwarding) were
  dropped because the key controller sat on the overlay, which loses focus
  to the header back button after nav.push+fullscreen — move it to the
  window and remove it on teardown.
- video: a mid-session VAAPI decode error rebuilt a software decoder but
  never requested a keyframe, so under the infinite GOP the picture stayed
  gray/frozen forever. Request an IDR on any VAAPI error, keep the hardware
  decoder, and demote to software only after repeated failures.
- stream: fix a per-session Capture↔overlay reference cycle that leaked the
  overlay subtree + the Arc<NativeClient> on every session end — hold the
  overlay weakly.
- stream: accumulate the fractional wheel remainder so precision-scroll
  (Deck trackpad / hi-res wheels) sub-unit deltas aren't dropped.
- gamepad library: keep the launcher smooth on the Deck — freeze the aurora
  and trim the visible card range (fewer 3D offscreen passes) on low-power.
- gamepad: log full pad identity (vid:pid:name:type:virtual) on attach to
  diagnose an empty controller list on the Deck.
- cli: --connect host:<badport> silently did nothing; default to 9777 + warn.
- css: add the missing .pf-neutral pill rule; fix the clipped most-recent
  accent (inset outline instead of a corner-clipped box-shadow bar).

Decky plugin:
- surface the on-screen library browser: label the host-row Games button.
- fix silent pin data-loss — the detached Games modal captured a frozen
  pins array, so pinning a second game clobbered the first; mirror pins in
  a ref and track the modal's pinned ids locally for a live label.
- route pair-required hosts through the pairing modal from the fullscreen
  Stream button (parity with the QAM panel).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-04 07:37:04 +00:00
parent 882a3d57f6
commit 57ae00a9c8
12 changed files with 220 additions and 46 deletions
+19 -4
View File
@@ -93,6 +93,11 @@ struct State {
anim_active: Cell<bool>,
last_tick: Cell<i64>,
animations: bool,
/// Deck (or any low-power box): shrink the per-frame GPU work so navigation stays smooth
/// — fewer laid-out cards (fewer 3D offscreen passes) and a frozen aurora (no 30 Hz
/// full-screen CPU upscale + multi-MB texture upload contending for the iGPU's shared
/// bandwidth). The Deck iGPU otherwise drops to ~16 fps mid-navigation.
low_power: bool,
detail_title: gtk::Label,
detail_store: gtk::Label,
/// Transient error strip on the carousel scene (connect failures land here — the
@@ -300,9 +305,12 @@ fn build(app: Rc<App>, req: ConnectRequest, paired: bool, mgmt_port: u16) -> Rc<
content.append(&stack);
content.append(&hints);
let low_power = crate::gamepad::is_steam_deck();
let root = gtk::Overlay::new();
root.add_css_class("pf-gl-page");
root.set_child(Some(&build_aurora()));
// On the Deck the animated aurora's per-frame CPU upscale + texture upload starves the
// coverflow of iGPU bandwidth — freeze it (drift is centimeters/minute, unnoticeable).
root.set_child(Some(&build_aurora(low_power)));
root.add_overlay(&content);
root.set_focusable(true);
@@ -330,6 +338,7 @@ fn build(app: Rc<App>, req: ConnectRequest, paired: bool, mgmt_port: u16) -> Rc<
anim_active: Cell::new(false),
last_tick: Cell::new(0),
animations: animations_enabled(),
low_power,
detail_title,
detail_store,
status,
@@ -917,10 +926,14 @@ fn relayout(state: &State) {
}
let pos = state.anim_pos.get();
let bump = state.bump.get();
// Each laid-out side card is a non-affine (perspective + rotate_3d) transform, which GSK
// renders through its own offscreen pass — so the visible count is the per-frame GPU cost.
// Trim it hard on the Deck; desktop keeps the full deep shelf.
let range = if state.low_power { 3.0 } else { VISIBLE_RANGE };
for (i, card) in state.cards.borrow().iter().enumerate() {
let d = i as f64 - pos;
let a = d.abs();
if a > VISIBLE_RANGE {
if a > range {
card.root.set_visible(false);
continue;
}
@@ -1033,7 +1046,7 @@ fn animations_enabled() -> bool {
/// The full-bleed aurora: a DrawingArea re-rendered at ~30 Hz off the frame clock (the
/// Swift TimelineView cadence — drift is centimeters per minute, display rate would be
/// wasted heat on a couch device).
fn build_aurora() -> gtk::DrawingArea {
fn build_aurora(low_power: bool) -> gtk::DrawingArea {
let area = gtk::DrawingArea::new();
area.set_hexpand(true);
area.set_vexpand(true);
@@ -1043,7 +1056,9 @@ fn build_aurora() -> gtk::DrawingArea {
let t = t.clone();
area.set_draw_func(move |_, cr, w, h| draw_aurora(cr, w, h, t.get(), &cache));
}
if animations_enabled() {
// Deck: render once, frozen — the 30 Hz tick's CPU upscale + texture upload is the
// bandwidth cost that starves the coverflow. Desktop keeps the live drift.
if animations_enabled() && !low_power {
let start = Cell::new(0i64);
let last = Cell::new(0i64);
area.add_tick_callback(move |area, clock| {