diff options
| author | Gabriel Schneider <[email protected]> | 2026-07-08 12:25:04 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-01 15:02:07 -0300 |
| commit | 172eb56fee0b6bde698e72be2bca55a90fe81ce9 (patch) | |
| tree | f2fd94709facf134c3670db4c1a20a5eaca1d53d /src/deck.zig | |
| parent | b91e5ebf8b529d0ced0bf18e83b17df0bd2374ce (diff) | |
| download | pardes-172eb56fee0b6bde698e72be2bca55a90fe81ce9.tar.gz pardes-172eb56fee0b6bde698e72be2bca55a90fe81ce9.zip | |
steam deck nice version
Diffstat (limited to 'src/deck.zig')
| -rw-r--r-- | src/deck.zig | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/src/deck.zig b/src/deck.zig new file mode 100644 index 00000000..365c420c --- /dev/null +++ b/src/deck.zig @@ -0,0 +1,157 @@ +//! 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; + } +}; |
