feat(kiosk): display-size presets in Settings (Auto/Large/Balanced/Native)

system.kiosk-display.get/set RPCs write /etc/archipelago/kiosk-display.conf
(sourced by the kiosk launcher) and try-restart the kiosk so the choice
applies immediately. The Settings section only appears on nodes that have
the kiosk unit installed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-07-21 13:27:04 -04:00
co-authored by Claude Fable 5
parent 1b78da1d7a
commit 97358d2314
4 changed files with 176 additions and 0 deletions
@@ -456,6 +456,8 @@ impl RpcHandler {
"system.factory-reset" => self.handle_system_factory_reset(params).await,
"system.settings.get" => self.handle_system_settings_get(params).await,
"system.settings.set" => self.handle_system_settings_set(params).await,
"system.kiosk-display.get" => self.handle_system_kiosk_display_get().await,
"system.kiosk-display.set" => self.handle_system_kiosk_display_set(params).await,
// Opt-in anonymous analytics
"analytics.get-status" => self.handle_analytics_get_status().await,
@@ -719,4 +719,94 @@ impl RpcHandler {
_ => anyhow::bail!("Unknown setting: {}", key),
}
}
/// system.kiosk-display.get — Current kiosk display preset + whether this
/// node has a kiosk at all (no kiosk unit -> the Settings section hides).
pub(in crate::api::rpc) async fn handle_system_kiosk_display_get(
&self,
) -> Result<serde_json::Value> {
let has_kiosk = tokio::fs::metadata("/etc/systemd/system/archipelago-kiosk.service")
.await
.is_ok();
let conf = tokio::fs::read_to_string(KIOSK_DISPLAY_CONF)
.await
.unwrap_or_default();
let preset = if conf.contains("ARCHIPELAGO_KIOSK_SCALE=1") {
"native"
} else if conf.contains("ARCHIPELAGO_KIOSK_TARGET_CSS_WIDTH=1920") {
"balanced"
} else if conf.contains("ARCHIPELAGO_KIOSK_TARGET_CSS_WIDTH=1280") {
"large"
} else {
"auto"
};
Ok(serde_json::json!({ "has_kiosk": has_kiosk, "preset": preset }))
}
/// system.kiosk-display.set — Write the kiosk display preset and restart
/// the kiosk (only if it is running) so it takes effect immediately. The
/// launcher sources /etc/archipelago/kiosk-display.conf at startup.
pub(in crate::api::rpc) async fn handle_system_kiosk_display_set(
&self,
params: Option<serde_json::Value>,
) -> Result<serde_json::Value> {
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
let preset = params
.get("preset")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing preset"))?;
let conf = match preset {
// Resolution-derived default: 4K -> 2.0 (1920-wide layout),
// 1080p TV -> 1.5, laptop panels -> 1.0.
"auto" => String::new(),
// Biggest UI: every panel targets a 1280-wide layout.
"large" => "ARCHIPELAGO_KIOSK_TARGET_CSS_WIDTH=1280\n".to_string(),
// Full-HD layout on any panel that can carry it.
"balanced" => "ARCHIPELAGO_KIOSK_TARGET_CSS_WIDTH=1920\n".to_string(),
// No scaling: native CSS viewport, most content, smallest UI.
"native" => "ARCHIPELAGO_KIOSK_SCALE=1\n".to_string(),
other => anyhow::bail!("Unknown display preset: {other}"),
};
host_sudo(&["/usr/bin/mkdir", "-p", "/etc/archipelago"]).await?;
if conf.is_empty() {
let _ = host_sudo(&["/usr/bin/rm", "-f", KIOSK_DISPLAY_CONF]).await;
} else {
// tee via sudo — the backend runs unprivileged and /etc is root's.
let mut child = tokio::process::Command::new("/usr/bin/sudo")
.args(["-n", "/usr/bin/tee", KIOSK_DISPLAY_CONF])
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::piped())
.spawn()
.context("spawn sudo tee for kiosk display conf")?;
use tokio::io::AsyncWriteExt;
if let Some(mut stdin) = child.stdin.take() {
stdin.write_all(conf.as_bytes()).await?;
}
let out = child.wait_with_output().await?;
if !out.status.success() {
anyhow::bail!(
"writing {} failed: {}",
KIOSK_DISPLAY_CONF,
String::from_utf8_lossy(&out.stderr).trim()
);
}
}
// try-restart: only bounces a kiosk that is actually running, so this
// never starts a kiosk an operator disabled.
let _ = host_sudo(&[
"/usr/bin/systemctl",
"try-restart",
"archipelago-kiosk.service",
])
.await;
info!(preset, "Kiosk display preset applied");
Ok(serde_json::json!({ "preset": preset, "applied": true }))
}
}
const KIOSK_DISPLAY_CONF: &str = "/etc/archipelago/kiosk-display.conf";