feat(host/hooks): per-app prep/undo commands (M2b — Sunshine prep-cmd parity)
apple / swift (push) Successful in 1m18s
apple / screenshots (push) Successful in 4m22s
ci / web (push) Successful in 53s
ci / docs-site (push) Successful in 1m3s
decky / build-publish (push) Successful in 20s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 9s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 33s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
ci / bench (push) Successful in 5m3s
android / android (push) Successful in 17m6s
arch / build-publish (push) Successful in 16m51s
deb / build-publish (push) Successful in 12m16s
ci / rust (push) Successful in 25m22s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m16s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 14m17s
windows-host / package (push) Has been cancelled
docker / deploy-docs (push) Failing after 19s
apple / swift (push) Successful in 1m18s
apple / screenshots (push) Successful in 4m22s
ci / web (push) Successful in 53s
ci / docs-site (push) Successful in 1m3s
decky / build-publish (push) Successful in 20s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 9s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 33s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
ci / bench (push) Successful in 5m3s
android / android (push) Successful in 17m6s
arch / build-publish (push) Successful in 16m51s
deb / build-publish (push) Successful in 12m16s
ci / rust (push) Successful in 25m22s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m16s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 14m17s
windows-host / package (push) Has been cancelled
docker / deploy-docs (push) Failing after 19s
`prep: [{"do": …, "undo": …}]` arrays on GameStream apps.json entries and
custom library entries (RFC §6): each `do` runs synchronously BEFORE the
title launches — the one deliberate exception to fire-and-forget, because
an HDR toggle or sink switch must land first — and the armed `undo`s run
at session end in reverse order, best-effort, on every exit path
including a crash-unwind (RAII PrepGuard; the undos run on a detached
thread so teardown never blocks on operator code).
- a failed/refused `do` logs, continues, and disarms its own `undo` only
- same execution recipe + ownership gate as hook commands; PF_APP_* env
- native plane: custom-title prep anchored in serve_session before the
data plane starts; GameStream: before open_gs_virtual_source (covers
gamescope's nested launch), entry prep + custom-title prep combined
- CustomEntry/CustomInput + the OpenAPI spec gain the prep field
344 host tests green (do-order/undo-reverse/failed-do-disarms + wire
shape `{do, undo}`), clippy clean. On-glass with a real client session
owed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,10 @@ pub struct CustomEntry {
|
||||
pub art: Artwork,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub launch: Option<LaunchSpec>,
|
||||
/// Per-title prep/undo steps (RFC §6): each `do` runs before this title launches, each
|
||||
/// `undo` at session end in reverse order (see [`crate::hooks::run_prep`]).
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub prep: Vec<crate::hooks::PrepCmd>,
|
||||
}
|
||||
|
||||
/// Request body to create or replace a custom entry (no `id` — the host owns it).
|
||||
@@ -24,6 +28,9 @@ pub struct CustomInput {
|
||||
pub art: Artwork,
|
||||
#[serde(default)]
|
||||
pub launch: Option<LaunchSpec>,
|
||||
/// Per-title prep/undo steps — commands run as the host user; operator-privileged config.
|
||||
#[serde(default)]
|
||||
pub prep: Vec<crate::hooks::PrepCmd>,
|
||||
}
|
||||
|
||||
impl From<CustomEntry> for GameEntry {
|
||||
@@ -89,6 +96,7 @@ pub fn add_custom(input: CustomInput) -> Result<CustomEntry> {
|
||||
title: input.title,
|
||||
art: input.art,
|
||||
launch: input.launch,
|
||||
prep: input.prep,
|
||||
};
|
||||
entries.push(entry.clone());
|
||||
save_custom(&entries)?;
|
||||
@@ -105,6 +113,7 @@ pub fn update_custom(id: &str, input: CustomInput) -> Result<Option<CustomEntry>
|
||||
slot.title = input.title;
|
||||
slot.art = input.art;
|
||||
slot.launch = input.launch;
|
||||
slot.prep = input.prep;
|
||||
let updated = slot.clone();
|
||||
save_custom(&entries)?;
|
||||
emit_changed();
|
||||
@@ -124,6 +133,19 @@ pub fn delete_custom(id: &str) -> Result<bool> {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// The prep/undo steps for a library id — `custom:<id>` entries only (the other stores have no
|
||||
/// per-title config surface; a GameStream `apps.json` entry carries its own `prep` instead).
|
||||
pub fn prep_for(library_id: &str) -> Vec<crate::hooks::PrepCmd> {
|
||||
let Some(id) = library_id.strip_prefix("custom:") else {
|
||||
return Vec::new();
|
||||
};
|
||||
load_custom()
|
||||
.into_iter()
|
||||
.find(|e| e.id == id)
|
||||
.map(|e| e.prep)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The custom-entry mutations are the only library writes today, all operator-driven — hence
|
||||
/// `source: "manual"` (RFC §4; a provider id once the provider API of RFC §8 lands).
|
||||
fn emit_changed() {
|
||||
@@ -151,6 +173,7 @@ mod tests {
|
||||
title: "My ROM".into(),
|
||||
art: Artwork::default(),
|
||||
launch: None,
|
||||
prep: Vec::new(),
|
||||
}
|
||||
.into();
|
||||
assert_eq!(g.id, "custom:abc123");
|
||||
|
||||
Reference in New Issue
Block a user