forked from unom/punktfunk
feat(decky): the stats overlay gets an off switch in Gaming Mode
Field report: "as of version 0.23 of this plugin, there is no setting to toggle off the stat overlay." Correct, and it never had one — no commit in `clients/decky` has ever touched a stats key. Every other client does: the GTK dialog, the Windows page, the Apple app, and the console's own settings screen all carry the four-tier picker. The tier defaults to on. `Settings::default` is `show_stats: true` and `stats_verbosity: None`, which `Settings::stats_verbosity` resolves to Normal — so a Deck that has only ever been configured through this panel streams with the overlay up and no way here to put it down. What escapes exist are not discoverable: Ctrl+Alt+Shift+S wants a keyboard, and the three-finger touchscreen tap is documented in `docs/stats`, not on the glass. The console's picker is reachable (X on console home), but that is a different shortcut than the one-tap stream this panel launches, and a user editing stream settings here has no reason to look there. So the row lands here, last in the section, matching the console's wording. It writes `stats_verbosity` AND the legacy `show_stats` in the same pairing `Settings::set_stats_verbosity` keeps, so a client too old for the tiers still honours an Off chosen here; it reads them back the way `Settings::stats_verbosity` does, so a pre-tier file — including every file this plugin wrote before today — shows the Normal the stream actually runs at. `set_settings` stops replacing the file and merges onto it instead. This JSON is shared with the desktop client and the console, and holds many more keys than this panel models (decoder, GPU, profiles, touch/mouse model). The panel reads it once when it mounts, so a wholesale write posts a snapshot that predates anything another editor stored while it sat open — silently reverting it. That was invisible until 0.23.0: `9c5af8d7` fixed the GTK shell handing the session a spec built from `Settings::default()`, and only since then does this file reach a stream at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+19
-3
@@ -1044,20 +1044,36 @@ class Plugin:
|
||||
try:
|
||||
return json.loads(_settings_path().read_text())
|
||||
except (OSError, json.JSONDecodeError):
|
||||
# The client's own defaults (native display, host-default bitrate, auto pad).
|
||||
# The client's own defaults (native display, host-default bitrate, auto pad,
|
||||
# stats overlay at Normal — `Settings::default` is `show_stats: true`).
|
||||
return {
|
||||
"width": 0, "height": 0, "refresh_hz": 0, "render_scale": 1.0,
|
||||
"bitrate_kbps": 0, "codec": "auto", "gamepad": "auto",
|
||||
"gamepad_forwarding": True, "compositor": "auto",
|
||||
"inhibit_shortcuts": True, "mic_enabled": False,
|
||||
"stats_verbosity": "normal", "show_stats": True,
|
||||
}
|
||||
|
||||
async def set_settings(self, settings: dict) -> dict:
|
||||
"""Write the stream settings JSON the (sandboxed) client reads on launch."""
|
||||
"""Write the stream settings JSON the (sandboxed) client reads on launch.
|
||||
|
||||
MERGED onto whatever is on disk, never a wholesale replace: this file is shared with
|
||||
the desktop client and the console's settings screen, and it holds far more keys than
|
||||
this panel models (decoder, GPU, profiles, touch/mouse model…). The panel reads it once
|
||||
when it mounts, so a straight write would post a snapshot that predates anything those
|
||||
other editors stored in the meantime — silently reverting it.
|
||||
"""
|
||||
try:
|
||||
d = _client_config_dir()
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
_settings_path().write_text(json.dumps(settings, indent=2))
|
||||
try:
|
||||
on_disk = json.loads(_settings_path().read_text())
|
||||
if not isinstance(on_disk, dict):
|
||||
on_disk = {}
|
||||
except (OSError, json.JSONDecodeError):
|
||||
on_disk = {} # no file yet (or an unreadable one): this write creates it
|
||||
on_disk.update(settings)
|
||||
_settings_path().write_text(json.dumps(on_disk, indent=2))
|
||||
return {"ok": True}
|
||||
except OSError as exc:
|
||||
decky.logger.exception("could not write settings")
|
||||
|
||||
@@ -122,6 +122,13 @@ export interface StreamSettings {
|
||||
// here would be a dead one. The desktop client's row still edits this same file.
|
||||
inhibit_shortcuts: boolean;
|
||||
mic_enabled: boolean;
|
||||
// Stats-overlay tier: "off" | "compact" | "normal" | "detailed". Absent in a pre-tier file,
|
||||
// which resolves through `show_stats` — read both the way the client's
|
||||
// `Settings::stats_verbosity` does, and write both the way `set_stats_verbosity` does.
|
||||
stats_verbosity?: string;
|
||||
// The legacy on/off the tier supersedes; kept written in sync so a client that predates the
|
||||
// tiers still honours an Off chosen here.
|
||||
show_stats?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateInfo {
|
||||
|
||||
@@ -55,6 +55,15 @@ const COMPOSITOR_LABELS: Record<string, string> = {
|
||||
mutter: "GNOME (Mutter)",
|
||||
gamescope: "gamescope",
|
||||
};
|
||||
// The stats-overlay tiers, in the cycle order every other client's picker uses
|
||||
// (punktfunk_core `StatsVerbosity::ALL`). Stored lowercase — the enum is `rename_all`.
|
||||
const STATS_TIERS = ["off", "compact", "normal", "detailed"];
|
||||
const STATS_LABELS: Record<string, string> = {
|
||||
off: "Off",
|
||||
compact: "Compact",
|
||||
normal: "Normal",
|
||||
detailed: "Detailed",
|
||||
};
|
||||
|
||||
export const SettingsSection: FC = () => {
|
||||
const [s, setS] = useState<StreamSettings | null>(null);
|
||||
@@ -74,6 +83,12 @@ export const SettingsSection: FC = () => {
|
||||
|
||||
if (!s) return <Spinner style={{ height: "1.5em" }} />;
|
||||
|
||||
// Mirrors `Settings::stats_verbosity`: an absent tier is a pre-tier store, which resolves
|
||||
// through the legacy `show_stats` bool — and an absent bool is the client's serde default
|
||||
// (true), so a file this plugin wrote before it carried either key reads as Normal, exactly
|
||||
// as the stream sees it.
|
||||
const statsTier = s.stats_verbosity ?? ((s.show_stats ?? true) ? "normal" : "off");
|
||||
|
||||
const resIdx = Math.max(
|
||||
0,
|
||||
RESOLUTIONS.findIndex(([w, h]) => w === s.width && h === s.height),
|
||||
@@ -206,6 +221,26 @@ export const SettingsSection: FC = () => {
|
||||
checked={s.mic_enabled}
|
||||
onChange={(v) => patch({ mic_enabled: v })}
|
||||
/>
|
||||
<Field
|
||||
label="Statistics overlay"
|
||||
description="The fps / latency / bitrate panel in the stream. Each tier is a superset of the one before; a three-finger tap on the touchscreen cycles them mid-stream."
|
||||
childrenContainerWidth="max"
|
||||
>
|
||||
<RowActions>
|
||||
<div style={selectShell}>
|
||||
<Dropdown
|
||||
rgOptions={STATS_TIERS.map((t) => ({ data: t, label: STATS_LABELS[t] ?? t }))}
|
||||
selectedOption={statsTier}
|
||||
// Both keys, in sync — the same pairing `Settings::set_stats_verbosity` keeps, so a
|
||||
// client too old for the tier still reads the on/off it understands.
|
||||
onChange={(o) => {
|
||||
const tier = o.data as string;
|
||||
patch({ stats_verbosity: tier, show_stats: tier !== "off" });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</RowActions>
|
||||
</Field>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -197,7 +197,8 @@ when you return to the host list. iPhone, iPad, Apple TV and Android have no equ
|
||||
superset of the one before. This setting only picks the tier a session *starts* at — you can cycle
|
||||
them live in-stream, with a shortcut that differs by platform. The Apple app additionally lets you
|
||||
choose which corner the overlay sits in (Top Left, Top Right, Bottom Left, Bottom Right). The Decky
|
||||
plugin has no stats setting. The shortcuts, and every number in the overlay, are in
|
||||
plugin has the tier picker too, in its Settings section. The shortcuts, and every number in the
|
||||
overlay, are in
|
||||
[Understanding the stats overlay](/docs/stats).
|
||||
|
||||
## Settings that are facts about your device
|
||||
|
||||
Reference in New Issue
Block a user