a control for when the input state goes wrong
`POST /reset-input` and a "clicks acting wrong?" button in the panel's status line. It releases every modifier and mouse button the display thinks is held. Named for the SYMPTOM rather than the mechanism, because nobody experiencing this knows a modifier is stuck — they know their clicks stopped doing the right thing. ── both sides, and that ordering matters ── Releasing on the server alone leaves the panel's own button mask stale and the next mouse move re-asserts it, so the fix would appear not to work. Local state is cleared first, a pointer event with an empty mask says so, and only then does the server let go. ── a fixed list, not a query ── `xinput --query-state` would report exactly what is held, which is better. It is also a seventh host binary to declare and to require, for a repair. Releasing a key that was not pressed is a no-op, so the fixed list costs nothing. Modifiers and buttons only: a stuck letter types, which is obvious and harmless; a stuck modifier silently changes what every click means. ── not guarded like the others ── Every other command refuses while a dialog is open, because its clicks would fall through onto the board. This one clicks nothing, and refusing a repair in the state that most needs it would be the wrong way round. Verified by stranding `Shift_L` on the display with `xdotool keydown`, hitting the route, and watching `xinput --query-state` go from one key down to none. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: de3ed4e6-f88a-429c-94e6-a760336ba83b
This commit is contained in:
@@ -5,6 +5,7 @@ import { VNC_SOCKET, ensureDirs } from './paths';
|
||||
import { activeDisplay, isRunning, start, status, stop } from './session';
|
||||
import { DIFFICULTIES, currentDifficulty, isDifficulty, playReplay, selectDifficulty } from './menu';
|
||||
import { isKnownReplay, listReplays } from './replays';
|
||||
import { resetInput } from './input';
|
||||
|
||||
/*
|
||||
officer-minesweeper — owns the game session, and serves both halves of the plugin from one listener.
|
||||
@@ -18,6 +19,7 @@ import { isKnownReplay, listReplays } from './replays';
|
||||
POST /difficulty { level: beginner | intermediate | expert } — drives the game's own Game menu
|
||||
GET /replays ?level=<one of those> — the saved games, best first
|
||||
POST /replay { file } — opens one, by name, and only a name /replays just returned
|
||||
POST /reset-input releases every modifier and mouse button the display thinks is held
|
||||
GET /ws the display — an RFB stream, relayed frame-for-byte onto x11vnc's unix socket
|
||||
|
||||
─────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
@@ -144,6 +146,17 @@ const server = Bun.serve({
|
||||
return Response.json({ replays: listReplays(level ?? undefined) });
|
||||
}
|
||||
|
||||
// Deliberately NOT guarded by `blockingDialog` like the others: this is the repair for a session
|
||||
// that is misbehaving, and refusing to run it because something is on screen would refuse it in the
|
||||
// case it is most needed.
|
||||
if (req.method === 'POST' && url.pathname === '/reset-input') {
|
||||
const display = activeDisplay();
|
||||
if (!isRunning() || !display) {
|
||||
return Response.json({ error: 'the session is not running' }, { status: 409 });
|
||||
}
|
||||
return Response.json(await resetInput(display));
|
||||
}
|
||||
|
||||
if (req.method === 'POST' && url.pathname === '/replay') {
|
||||
const display = activeDisplay();
|
||||
if (!isRunning() || !display) {
|
||||
|
||||
Reference in New Issue
Block a user