fix(clients): GTK + Decky polish batch from live Deck/Windows testing
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:
@@ -95,6 +95,24 @@ export const GamePickerModal: FC<{
|
||||
}> = ({ host, pins, clientUpdatePending, closeModal }) => {
|
||||
const [result, setResult] = useState<LibraryResult | null>(null);
|
||||
const [attempt, setAttempt] = useState(0); // bump to refetch (retry / after pairing)
|
||||
// The modal is a detached `showModal` portal that never re-renders from the page's pin
|
||||
// state, so `pins.isPinned` would read a frozen snapshot and the Pin/Unpin label would
|
||||
// never flip within a session. Track this host's pinned ids locally, seeded once from the
|
||||
// snapshot at open; persistence still goes through the (stale-closure-safe) pins API.
|
||||
const [pinnedIds, setPinnedIds] = useState<Set<string>>(
|
||||
() => new Set(pins.pins.filter((p) => p.host_fp === host.fp).map((p) => p.game_id)),
|
||||
);
|
||||
const togglePin = (g: GameEntry) => {
|
||||
const wasPinned = pinnedIds.has(g.id);
|
||||
setPinnedIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (wasPinned) next.delete(g.id);
|
||||
else next.add(g.id);
|
||||
return next;
|
||||
});
|
||||
if (wasPinned) pins.removePin(host.fp, g.id);
|
||||
else pins.addPin(host, g);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let stale = false;
|
||||
@@ -188,7 +206,7 @@ export const GamePickerModal: FC<{
|
||||
{sorted.length > 0 && (
|
||||
<div style={{ maxHeight: "55vh", overflowY: "auto" }}>
|
||||
{sorted.map((g: GameEntry) => {
|
||||
const pinned = pins.isPinned(host.fp, g.id);
|
||||
const pinned = pinnedIds.has(g.id);
|
||||
const safe = isSafeLaunchId(g.id);
|
||||
return (
|
||||
<Field
|
||||
@@ -199,13 +217,7 @@ export const GamePickerModal: FC<{
|
||||
}
|
||||
childrenContainerWidth="max"
|
||||
>
|
||||
<DialogButton
|
||||
style={pickButton}
|
||||
disabled={!safe}
|
||||
onClick={() =>
|
||||
pinned ? pins.removePin(host.fp, g.id) : pins.addPin(host, g)
|
||||
}
|
||||
>
|
||||
<DialogButton style={pickButton} disabled={!safe} onClick={() => togglePin(g)}>
|
||||
<FaThLarge style={{ marginRight: "0.4em" }} />
|
||||
{pinned ? "Unpin" : "Pin"}
|
||||
</DialogButton>
|
||||
|
||||
Reference in New Issue
Block a user