//! Steam Deck controls -> editor input, as a pure state machine: gui.zig //! feeds SDL gamepad events in and applies the returned actions (cursor //! moves, synthetic clicks, wheel ticks, keys, rumble), so the whole //! mapping replays off-device from deckcap recordings (test/deck/). //! //! Layout: right pad moves the pointer (relative, laptop-touchpad-style), //! left pad scrolls, R2 = select (button 1), L2 = execute (button 2), //! L1 = look (button 3), dpad down/up = n/N, A = Enter, Y = Tab; execute //! and look presses also request a brief rumble ack. const std = @import("std"); // SDL_GamepadButton / SDL_GamepadAxis / touchpad indices (SDL3), verified // against real deck recordings (test/deck/*.zon) and the pinned SDL's // hidapi deck driver (touchpad 0 = left pad, 1 = right pad). pub const BTN_A = 0; // SOUTH pub const BTN_Y = 3; // NORTH pub const BTN_L1 = 9; // LEFT_SHOULDER pub const BTN_DPAD_UP = 11; pub const BTN_DPAD_DOWN = 12; pub const AXIS_L2 = 4; // LEFT_TRIGGER, analog 0..32767 pub const AXIS_R2 = 5; // RIGHT_TRIGGER pub const PAD_LEFT = 0; pub const PAD_RIGHT = 1; /// feel knobs: a full-pad finger sweep covers this many screen pixels /// (the deck's screen is 1280x800) / this many wheel ticks pub const POINTER_SCALE: f32 = 600.0; pub const SCROLL_SCALE: f32 = 24.0; /// the pinned SDL 3.4.4 deck driver skips upstream's pad-Y negation, so /// touch y arrives 0 = pad bottom; flip this when the sdl dep is bumped pub const PAD_Y_SIGN: f32 = -1.0; // analog triggers click at 30% and release at 20%: the gap keeps a value // hovering near one threshold from chattering press/release pairs pub const TRIG_PRESS: i16 = 9830; pub const TRIG_RELEASE: i16 = 6553; pub const Input = union(enum) { button: struct { idx: u8, down: bool }, axis: struct { idx: u8, value: i16 }, touch: struct { pad: u8, phase: enum { down, motion, up }, x: f32, y: f32 }, }; pub const Click = enum { select, execute, look }; // mouse buttons 1/2/3 pub const Keytap = enum { n, cap_n, enter, tab }; pub const Action = union(enum) { move: struct { dx: f32, dy: f32 }, // pointer delta in screen pixels click: struct { which: Click, down: bool }, wheel: i32, // whole ticks, positive = wheel_down key: Keytap, rumble, }; pub const Deck = struct { rpad_on: bool = false, rpad_x: f32 = 0, rpad_y: f32 = 0, lpad_on: bool = false, lpad_y: f32 = 0, scroll: f32 = 0, // fractional wheel ticks carried between motions l2_held: bool = false, r2_held: bool = false, look_held: bool = false, pub fn feed(d: *Deck, in: Input, out: *[2]Action) usize { var n: usize = 0; switch (in) { .button => |b| switch (b.idx) { BTN_A => if (b.down) { out[0] = .{ .key = .enter }; n = 1; }, BTN_Y => if (b.down) { out[0] = .{ .key = .tab }; n = 1; }, BTN_DPAD_UP => if (b.down) { out[0] = .{ .key = .cap_n }; n = 1; }, BTN_DPAD_DOWN => if (b.down) { out[0] = .{ .key = .n }; n = 1; }, BTN_L1 => { d.look_held = b.down; out[0] = .{ .click = .{ .which = .look, .down = b.down } }; n = 1; if (b.down) { out[1] = .rumble; n = 2; } }, else => {}, }, .axis => |a| { if (a.idx != AXIS_L2 and a.idx != AXIS_R2) return 0; const held = if (a.idx == AXIS_L2) &d.l2_held else &d.r2_held; const which: Click = if (a.idx == AXIS_L2) .execute else .select; if (!held.* and a.value >= TRIG_PRESS) { held.* = true; out[0] = .{ .click = .{ .which = which, .down = true } }; n = 1; if (which == .execute) { out[1] = .rumble; n = 2; } } else if (held.* and a.value <= TRIG_RELEASE) { held.* = false; out[0] = .{ .click = .{ .which = which, .down = false } }; n = 1; } }, .touch => |t| switch (t.pad) { PAD_RIGHT => switch (t.phase) { // a fresh touch only re-anchors — deltas start from the // landing point, so the pointer never jumps to it .down => { d.rpad_on = true; d.rpad_x = t.x; d.rpad_y = t.y; }, .motion => if (d.rpad_on) { out[0] = .{ .move = .{ .dx = (t.x - d.rpad_x) * POINTER_SCALE, .dy = (t.y - d.rpad_y) * PAD_Y_SIGN * POINTER_SCALE, } }; n = 1; d.rpad_x = t.x; d.rpad_y = t.y; }, .up => d.rpad_on = false, }, PAD_LEFT => switch (t.phase) { .down => { d.lpad_on = true; d.lpad_y = t.y; }, .motion => if (d.lpad_on) { d.scroll += (t.y - d.lpad_y) * PAD_Y_SIGN * SCROLL_SCALE; d.lpad_y = t.y; const ticks: i32 = @intFromFloat(d.scroll); if (ticks != 0) { d.scroll -= @floatFromInt(ticks); out[0] = .{ .wheel = ticks }; n = 1; } }, .up => d.lpad_on = false, }, else => {}, }, } return n; } };