feat(linux-client): gamepad library launcher — console-style coverflow (--browse)
apple / swift (push) Successful in 1m9s
ci / rust (push) Successful in 1m54s
ci / web (push) Successful in 54s
android / android (push) Successful in 3m33s
ci / docs-site (push) Successful in 1m2s
windows-host / package (push) Successful in 6m43s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m12s
ci / bench (push) Successful in 4m47s
deb / build-publish (push) Successful in 4m36s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 6s
decky / build-publish (push) Successful in 15s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m10s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 51s
release / apple (push) Successful in 8m30s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 48s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 53s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m15s
flatpak / build-publish (push) Successful in 4m4s
apple / screenshots (push) Successful in 5m31s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 9m48s
docker / deploy-docs (push) Successful in 24s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 9m20s
apple / swift (push) Successful in 1m9s
ci / rust (push) Successful in 1m54s
ci / web (push) Successful in 54s
android / android (push) Successful in 3m33s
ci / docs-site (push) Successful in 1m2s
windows-host / package (push) Successful in 6m43s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m12s
ci / bench (push) Successful in 4m47s
deb / build-publish (push) Successful in 4m36s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 6s
decky / build-publish (push) Successful in 15s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m10s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 51s
release / apple (push) Successful in 8m30s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 48s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 53s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m15s
flatpak / build-publish (push) Successful in 4m4s
apple / screenshots (push) Successful in 5m31s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 9m48s
docker / deploy-docs (push) Successful in 24s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 9m20s
A controller-driven, chrome-less library launcher for the Steam Deck flow
(the Decky plugin's "Open library on screen" + pinned games, 8470419):
`--browse host[:port]` opens a paired host's game library as a coverflow
over a drifting aurora — A streams the focused title (the id rides the
Hello), session end returns to the launcher, B quits back to Gaming Mode.
`--connect` gains `--launch <id>` for direct-to-game starts; `--mgmt`
overrides the library port. Scope is deliberately library-only: host
selection/settings stay in the touch UI, pairing stays in the plugin (no
dialog can map under gamescope — every state renders in-page).
- gamepad.rs menu mode: the worker holds the active pad open while idle
(WITHOUT the Valve HIDAPI drivers — Deck lizard mode survives) and
translates it through a pure MenuNav state machine: edge-triggered
buttons, held-state snapshot on entry/detach (the escape chord that ends
a stream can't ghost-fire in the menu), 380/160 ms stick/dpad repeat,
menu rumble ticks. Keyboard fallback (arrows/Enter/Esc) drives the same
handler — fully usable with no pad, no host (PUNKTFUNK_FAKE_LIBRARY).
- Coverflow: ±38° corridor-facing tilt under per-card perspective
(gsk rotate_3d), dense overlapping side shelves with paint-order
restacking (gtk::Fixed draws in child order), opaque card faces + a
darkening veil for the recede (translucency would bleed the stack
through). The strip lives in an External-policy ScrolledWindow because
a bare gtk::Fixed measures its TRANSFORMED children and inflates the
page min-width past the window.
- Spring-driven motion: semi-implicit Euler in ≤8 ms substeps (a raw
50 ms frame leaves the stiff recoil spring ringing at ω·dt ≈ 1.2 —
regression-tested), ζ≈0.85 cursor chase + ζ≈0.55 boundary wobble;
velocity carries across retargets so held-repeat scrolling glides.
- Shot scene `gamepad-library` (GTK animations force-disabled in shot mode
— nav transitions froze mid-slide in headless captures); shared poster
fetch extracted to library::spawn_art_fetch.
Verified here: 21 unit tests (MenuNav, cursor stepping, spring
convergence/stability), clippy -D warnings clean, screenshot scene
pixel-checked, --browse smoke runs (fake-library + unpaired) on the
headless session. On-Deck validation pending (virtual-pad input, lizard
mode, rumble via Steam Input, full Decky→browse→stream→launcher loop).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,8 +6,9 @@
|
||||
//! verified by its pinned SHA-256 fingerprint (`KnownHost::fp_hex`), not a CA chain.
|
||||
|
||||
use serde::Deserialize;
|
||||
use std::collections::VecDeque;
|
||||
use std::io::Read;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
/// The management API's default port — matches `mgmt::DEFAULT_PORT` on the host. A
|
||||
@@ -181,6 +182,57 @@ pub fn fetch_art(pinned: &ureq::Agent, base: &str, url: &str) -> Result<Vec<u8>,
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// Concurrent poster fetches — a handful is plenty for a LAN art proxy without turning a
|
||||
/// big library into a connection burst.
|
||||
const ART_WORKERS: usize = 3;
|
||||
|
||||
/// Fetch poster bytes for `jobs` (entry id → candidate URLs, walked in order until one
|
||||
/// loads) on a small worker pool; results stream on the returned channel as they land.
|
||||
/// Dropping the receiver (the consuming page popped) winds the workers down. Shared by
|
||||
/// the touch grid and the gamepad launcher — the consumer does its own texture decode on
|
||||
/// the main loop.
|
||||
pub fn spawn_art_fetch(
|
||||
base: String,
|
||||
identity: (String, String),
|
||||
pin: Option<[u8; 32]>,
|
||||
jobs: VecDeque<(String, Vec<String>)>,
|
||||
) -> async_channel::Receiver<(String, Vec<u8>)> {
|
||||
let queue = Arc::new(Mutex::new(jobs));
|
||||
let (tx, rx) = async_channel::unbounded::<(String, Vec<u8>)>();
|
||||
for _ in 0..ART_WORKERS {
|
||||
let queue = queue.clone();
|
||||
let tx = tx.clone();
|
||||
let base = base.clone();
|
||||
let identity = identity.clone();
|
||||
std::thread::Builder::new()
|
||||
.name("punktfunk-lib-art".into())
|
||||
.spawn(move || {
|
||||
let Ok(agent) = agent(&identity, pin) else {
|
||||
return;
|
||||
};
|
||||
loop {
|
||||
let job = queue.lock().unwrap().pop_front();
|
||||
let Some((id, candidates)) = job else { break };
|
||||
for url in &candidates {
|
||||
match fetch_art(&agent, &base, url) {
|
||||
Ok(bytes) => {
|
||||
// Receiver gone (page popped) — stop fetching.
|
||||
if tx.send_blocking((id, bytes)).is_err() {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// 404 on a guessed CDN path is routine — try the next kind.
|
||||
Err(e) => tracing::debug!(%id, url, error = %e, "poster miss"),
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.expect("spawn art thread");
|
||||
}
|
||||
rx
|
||||
}
|
||||
|
||||
fn classify(e: ureq::Error) -> LibraryError {
|
||||
match e {
|
||||
ureq::Error::Status(401 | 403, _) => LibraryError::NotPaired,
|
||||
|
||||
Reference in New Issue
Block a user