-
-
Notifications
You must be signed in to change notification settings - Fork 357
Steam Controller (2026, "Triton") — full wireless support (BLE, no dongle, no root) #1706
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
JonahSimon
wants to merge
59
commits into
utkarshdalal:master
Choose a base branch
from
JonahSimon:steam-controller-2026
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 57 commits
Commits
Show all changes
59 commits
Select commit
Hold shift + click to select a range
557fd34
Steam Controller (2026 "Triton") support + in-game Steam-Input editor
JonahSimon 6cfabaf
Merge remote-tracking branch 'origin/master' into steam-controller-2026
JonahSimon d365105
SC editor: full controller navigation (d-pad + A/B + LB/RB + pad-mouse)
JonahSimon 0e245d6
SC editor: controller-nav the last 3 hub sub-editors (Labels/Tuning/O…
JonahSimon 0a23d14
Merge remote-tracking branch 'origin/master' into steam-controller-2026
JonahSimon 14cbcce
Merge remote-tracking branch 'origin/master' into steam-controller-2026
JonahSimon 8841cec
SC: ponytail minimization pass (net -31 lines)
JonahSimon 3ff94a2
SC: isolate Steam Controller config to the in-game QuickMenu
JonahSimon dc15654
Remove comments about applicationIdSuffix in debug config
JonahSimon 0e2e853
Merge remote-tracking branch 'upstream/master' into steam-controller-…
JonahSimon 3680a8f
Merge remote-tracking branch 'origin/steam-controller-2026' into stea…
JonahSimon 6225a1b
SC keyboard: add symbol page (?123 / ABC toggle)
JonahSimon 8ca488e
SC editor: fold Menu Labels into the bindings editor
JonahSimon 948d66a
SC overlay: per-menu HUD placement
JonahSimon afd5bdc
SC: real absolute/region mouse (was approximated as relative)
JonahSimon 0e54dcb
SC: gyro touch-to-aim activation gates
JonahSimon 6e031b0
SC coverage: mouse_region params + single_button + directional_swipe …
JonahSimon 4bacb08
SC coverage: MOUSE_POSITION absolute-warp output
JonahSimon 0f7b39c
SC coverage: gyro-to-joystick modes (camera/deflection)
JonahSimon 26fda97
SC: name imported configs from the vdf title (fix "(.vdf) (.vdf)")
JonahSimon 8bd942f
SC: hand-authored DOOM + DOOM II test config + parse test
JonahSimon 978d317
Add known-good region+single_button test config (B1/B2)
JonahSimon a79f20a
SC coverage: pad-as-joystick (Steam pad joystick_move)
JonahSimon 2f29bae
SC coverage: stick-as-dpad (Steam stick `dpad`)
JonahSimon c2d2d42
SC: hand-authored new-modes test config (pad-joystick + stick-dpad)
JonahSimon d0cd1e0
SC: per-binding delays + toggle + macros
JonahSimon a0d1460
SC: bind-settings device test config (delays/toggle/macro)
JonahSimon 5e0a1fb
SC fix: absolute_mouse is the relative trackpad mouse, not a region
JonahSimon a0cf3f4
SC: mouse-family mode audit - accept mouse_joystick name variant on a…
JonahSimon af576c2
SC batch: gyro axis-flip + invert, stick-touch gates, gyro output-sti…
JonahSimon dc50ab8
SC: decode gyro ratchet touch-gate mask -> ANY_TOUCH gate (from real …
JonahSimon 3b113af
SC: clean gyro touch-to-aim device test config (ANY_TOUCH validation)
JonahSimon 1262a18
SC: touch-surface binding (bind an output to a pad/stick touch)
JonahSimon cbd2556
SC: add GyroGate.ALL_TOUCH (require-all touch gate)
JonahSimon 3e54c93
SC: gyro camera/deflection split + d-pad layout modes
JonahSimon 79af826
SC: mouse_region Rotate Output (batch 5 start)
JonahSimon 71d1ddd
SC: import stick response curve (batch 5)
JonahSimon 8aa2703
SC: relative-mouse Rotate Output + H/V scale (batch 5)
JonahSimon c53b5c0
SC: gyro Enable/Suppress/Toggle activation (batch 2)
JonahSimon 668b8ff
SC: fix gyro activation key — gyro_button_invert, not gyro_button
JonahSimon ff7441b
SC: gyro editor mode picker + any-button gate
JonahSimon 8334957
SC: gyro feel-set + joystick output shaping + editor authoring
JonahSimon b0650fb
SC: fix stuck nav cursor + slim the bindings-editor header
JonahSimon de6d783
SC: wire Y = Help in menu nav (was dead — Y went to the game)
JonahSimon 7a5e034
docs: describe the Steam Controller (2026) branch feature in the README
JonahSimon 7f37bd5
Revert "docs: describe the Steam Controller (2026) branch feature in …
JonahSimon 4a0ac69
Merge remote-tracking branch 'upstream/master' into steam-controller-…
JonahSimon 6fa78cb
Revert format mistake
JonahSimon 1fde861
SC editor: auto-save everywhere, styling pass, overlay direct-manip, …
JonahSimon d28a07b
Merge remote-tracking branch 'upstream/master' into steam-controller-…
JonahSimon d4d5f22
SC: forward in-game rumble to the controller's own motors
JonahSimon 00178d5
Fix QuickMenuAction merge: defer to upstream's SHOOTER_MODE = 9
JonahSimon f771036
SC: cut the on-device self-test scaffolding
JonahSimon 27a8b05
Revert PrivacyPolicy/README.md to upstream
JonahSimon bbb37f1
Merge remote-tracking branch 'upstream/master' into steam-controller-…
JonahSimon 6ebc6d9
Restore pre-existing dead imports in upstream files
JonahSimon 9f907d4
SC: address CodeRabbit review (transport dead-code, importer, thread-…
JonahSimon c76f023
SC: address CodeRabbit follow-ups on the review batch
JonahSimon 610cc7e
Merge remote-tracking branch 'upstream/master' into steam-controller-…
JonahSimon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1,200 changes: 1,200 additions & 0 deletions
1,200
app/src/main/java/app/gamenative/steamcontroller/ProfileInterpreter.kt
Large diffs are not rendered by default.
Oops, something went wrong.
426 changes: 426 additions & 0 deletions
426
app/src/main/java/app/gamenative/steamcontroller/ScConfigStore.kt
Large diffs are not rendered by default.
Oops, something went wrong.
275 changes: 275 additions & 0 deletions
275
app/src/main/java/app/gamenative/steamcontroller/ScKeyboard.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,275 @@ | ||
| package app.gamenative.steamcontroller | ||
|
|
||
| import com.winlator.xserver.XKeycode | ||
|
|
||
| /** | ||
| * Steam-Controller-style split on-screen keyboard (see docs/SC-KEYBOARD.md). The keyboard is two grids — the | ||
| * **left trackpad** drives a cursor over the left half, the **right trackpad** over the right half — and a key is | ||
| * typed by **clicking that pad or pulling that side's trigger** while the cursor is over it. Output goes straight | ||
| * to the game via [ScOutputSink.key] (`XServer.injectKeyPress`), so no Android IME is involved. | ||
| * | ||
| * Pure logic (unit-testable); rendering is [ScKeyboardOverlayView] via the [ScKeyboardOverlay] seam. v1 commits on | ||
| * click/trigger (not thumb-lift) and supports a sticky Shift; a symbol layer / release-to-type are future work. | ||
| */ | ||
|
|
||
| /** One key on the on-screen keyboard. */ | ||
| sealed class KbKey(val label: String) { | ||
| /** A character key that injects [code]. Shift is held when the keyboard's sticky shift is on, OR when | ||
| * [forceShift] is set (used by symbol-page keys that are always the shifted glyph, e.g. `!` = Shift+1). */ | ||
| class Chr(val code: XKeycode, label: String, val forceShift: Boolean = false) : KbKey(label) | ||
| object Shift : KbKey("⇧ Shift") // sticky shift (capital for the next letter) | ||
| object Backspace : KbKey("⌫") // ⌫ | ||
| object Space : KbKey("space") | ||
| object Enter : KbKey("⏎ Enter") | ||
| object Close : KbKey("✕") // ✕ close the keyboard | ||
| object Sym : KbKey("?123") // switch to the symbol page | ||
| object Abc : KbKey("ABC") // switch back to the letter page | ||
| object Empty : KbKey("") // unused cell | ||
| } | ||
|
|
||
| /** The fixed split-QWERTY layout: two [COLS]×[ROWS] grids, row 0 = TOP, row-major. */ | ||
| object ScKeyboardLayout { | ||
| const val COLS = 5 | ||
| const val ROWS = 5 | ||
|
|
||
| private fun k(ch: Char): KbKey = KbKey.Chr(XKeycode.valueOf("KEY_" + ch.uppercaseChar()), ch.toString()) | ||
| /** A symbol key: injects [code], holding Shift for the shifted glyph (e.g. `!` = Shift+KEY_1). */ | ||
| private fun s(code: XKeycode, label: String, shift: Boolean = false) = KbKey.Chr(code, label, forceShift = shift) | ||
|
|
||
| val LEFT: List<KbKey> = listOf( | ||
| k('1'), k('2'), k('3'), k('4'), k('5'), | ||
| k('q'), k('w'), k('e'), k('r'), k('t'), | ||
| k('a'), k('s'), k('d'), k('f'), k('g'), | ||
| k('z'), k('x'), k('c'), k('v'), k('b'), | ||
| KbKey.Shift, KbKey.Chr(XKeycode.KEY_COMMA, ","), KbKey.Chr(XKeycode.KEY_PERIOD, "."), KbKey.Chr(XKeycode.KEY_MINUS, "-"), KbKey.Space, | ||
| ) | ||
|
|
||
| val RIGHT: List<KbKey> = listOf( | ||
| k('6'), k('7'), k('8'), k('9'), k('0'), | ||
| k('y'), k('u'), k('i'), k('o'), k('p'), | ||
| k('h'), k('j'), k('k'), k('l'), KbKey.Enter, | ||
| k('n'), k('m'), KbKey.Backspace, KbKey.Close, KbKey.Sym, | ||
| KbKey.Space, KbKey.Empty, KbKey.Empty, KbKey.Empty, KbKey.Empty, | ||
| ) | ||
|
|
||
| // Symbol page — same 5×5 split; functional keys (Shift/Space/Enter/Backspace/Close/toggle) stay in place, the | ||
| // character cells become symbols. Shifted glyphs (! @ # …) hold Shift over the base keycode. | ||
| val LEFT_SYM: List<KbKey> = listOf( | ||
| s(XKeycode.KEY_1, "!", true), s(XKeycode.KEY_2, "@", true), s(XKeycode.KEY_3, "#", true), s(XKeycode.KEY_4, "$", true), s(XKeycode.KEY_5, "%", true), | ||
| s(XKeycode.KEY_6, "^", true), s(XKeycode.KEY_7, "&", true), s(XKeycode.KEY_8, "*", true), s(XKeycode.KEY_9, "(", true), s(XKeycode.KEY_0, ")", true), | ||
| s(XKeycode.KEY_MINUS, "-"), s(XKeycode.KEY_MINUS, "_", true), s(XKeycode.KEY_EQUAL, "="), s(XKeycode.KEY_EQUAL, "+", true), s(XKeycode.KEY_BACKSLASH, "\\"), | ||
| s(XKeycode.KEY_BACKSLASH, "|", true), s(XKeycode.KEY_SEMICOLON, ";"), s(XKeycode.KEY_SEMICOLON, ":", true), s(XKeycode.KEY_APOSTROPHE, "'"), s(XKeycode.KEY_APOSTROPHE, "\"", true), | ||
| KbKey.Shift, s(XKeycode.KEY_GRAVE, "`"), s(XKeycode.KEY_GRAVE, "~", true), s(XKeycode.KEY_SLASH, "/"), KbKey.Space, | ||
| ) | ||
|
|
||
| val RIGHT_SYM: List<KbKey> = listOf( | ||
| s(XKeycode.KEY_BRACKET_LEFT, "["), s(XKeycode.KEY_BRACKET_RIGHT, "]"), s(XKeycode.KEY_BRACKET_LEFT, "{", true), s(XKeycode.KEY_BRACKET_RIGHT, "}", true), s(XKeycode.KEY_SLASH, "?", true), | ||
| s(XKeycode.KEY_COMMA, "<", true), s(XKeycode.KEY_PERIOD, ">", true), s(XKeycode.KEY_COMMA, ","), s(XKeycode.KEY_PERIOD, "."), KbKey.Empty, | ||
| KbKey.Empty, KbKey.Empty, KbKey.Empty, KbKey.Empty, KbKey.Enter, | ||
| KbKey.Empty, KbKey.Empty, KbKey.Backspace, KbKey.Close, KbKey.Abc, | ||
| KbKey.Space, KbKey.Empty, KbKey.Empty, KbKey.Empty, KbKey.Empty, | ||
| ) | ||
|
|
||
| fun leftFor(symbols: Boolean): List<KbKey> = if (symbols) LEFT_SYM else LEFT | ||
| fun rightFor(symbols: Boolean): List<KbKey> = if (symbols) RIGHT_SYM else RIGHT | ||
|
|
||
| /** Pad position (raw ±32768, +Y up) → cell index in a [COLS]×[ROWS] grid (row 0 = top), or -1 if out of range. */ | ||
| fun cellAt(x: Int, y: Int): Int { | ||
| val nx = (x.toFloat() / 65536f + 0.5f).coerceIn(0f, 0.999f) | ||
| val ny = (y.toFloat() / 65536f + 0.5f).coerceIn(0f, 0.999f) | ||
| val col = (nx * COLS).toInt().coerceIn(0, COLS - 1) | ||
| val rowTop = (ROWS - 1) - (ny * ROWS).toInt().coerceIn(0, ROWS - 1) | ||
| return rowTop * COLS + col | ||
| } | ||
| } | ||
|
|
||
| /** Render seam for the keyboard HUD; the layout is static ([ScKeyboardLayout]), the spec carries live cursor/shift. */ | ||
| interface ScKeyboardOverlay { | ||
| fun show(spec: ScKeyboardSpec) | ||
| fun hide() | ||
| } | ||
|
|
||
| /** | ||
| * Live keyboard state for the overlay: highlighted cell on each half (-1 = none) + sticky-shift state, plus the | ||
| * continuous finger position within each half ([leftX]/[leftY], [rightX]/[rightY]; 0..1, x left→right, y top→ | ||
| * bottom; -1 = that pad isn't touched) so the HUD can draw a cursor dot, not just a cell highlight. | ||
| */ | ||
| data class ScKeyboardSpec( | ||
| val leftCursor: Int, val rightCursor: Int, val shift: Boolean, | ||
| val leftX: Float = -1f, val leftY: Float = -1f, | ||
| val rightX: Float = -1f, val rightY: Float = -1f, | ||
| val symbols: Boolean = false, | ||
| ) | ||
|
|
||
| object NoOpScKeyboardOverlay : ScKeyboardOverlay { | ||
| override fun show(spec: ScKeyboardSpec) {} | ||
| override fun hide() {} | ||
| } | ||
|
|
||
| /** | ||
| * Drives the split keyboard from [TritonState]s while [active]. Typing fires keys through [sink]; the visual is | ||
| * pushed to [overlay]. Toggled by [ProfileInterpreter] on a [ScOutput.ShowKeyboard] binding. | ||
| */ | ||
| class ScKeyboard( | ||
| private val sink: ScOutputSink, | ||
| private val overlay: ScKeyboardOverlay = NoOpScKeyboardOverlay, | ||
| /** Trackpad haptics: detent ticks as the cursor crosses cells + a click on type. Keyboard mode suppresses | ||
| * the interpreter's normal pad-feel path, so the keyboard fires its own. Null = no haptics. */ | ||
| private val haptics: TritonHaptics? = null, | ||
| /** Time source (ms) for key-repeat timing; override in tests with a virtual clock. */ | ||
| private val clock: () -> Long = { System.currentTimeMillis() }, | ||
| /** Touchpad smoothing 0–100 (shared knob); low-passes the cursor position so the dot/cell don't jitter. */ | ||
| smoothing: Int = 0, | ||
| ) { | ||
| // @Volatile so live dial-in (ProfileInterpreter.setPadTuning) can retune the cursor low-pass mid-game. | ||
| @Volatile private var smoothing: Int = smoothing | ||
| fun setSmoothing(value: Int) { smoothing = value } | ||
|
|
||
| var active = false | ||
| private set | ||
| private var shift = false | ||
| private var symbols = false // symbol page vs the letter page (toggled by Sym/Abc) | ||
| private var leftCursor = -1 | ||
| private var rightCursor = -1 | ||
| private var leftX = -1f | ||
| private var leftY = -1f | ||
| private var rightX = -1f | ||
| private var rightY = -1f | ||
| private var prevButtons = 0 | ||
| // EMA-smoothed raw pad position per half (NaN = not currently touched / unseeded). | ||
| private var smLeftX = Float.NaN | ||
| private var smLeftY = Float.NaN | ||
| private var smRightX = Float.NaN | ||
| private var smRightY = Float.NaN | ||
| private val clickGain = HapticSettings().clickGain | ||
| private val tickGain = HapticSettings().tickGain | ||
|
|
||
| // Hold-to-repeat: holding a pad-click/trigger over a key types it once, then (after [REPEAT_DELAY_MS]) repeats | ||
| // every [REPEAT_INTERVAL_MS] while held on the same cell. Per-side state so each thumb repeats independently. | ||
| private val lRepeat = SideRepeat() | ||
| private val rRepeat = SideRepeat() | ||
|
|
||
| fun activate() { | ||
| active = true; shift = false; symbols = false; leftCursor = -1; rightCursor = -1 | ||
| leftX = -1f; leftY = -1f; rightX = -1f; rightY = -1f; prevButtons = 0 | ||
| smLeftX = Float.NaN; smLeftY = Float.NaN; smRightX = Float.NaN; smRightY = Float.NaN | ||
| lRepeat.reset(); rRepeat.reset() | ||
| push() | ||
| } | ||
|
|
||
| fun deactivate() { | ||
| active = false | ||
| runCatching { overlay.hide() } | ||
| } | ||
|
|
||
| /** Process one report while the keyboard is up. */ | ||
| fun update(s: TritonState) { | ||
| if (!active) return | ||
| val now = clock() | ||
| val a = ScTuningStore.emaAlpha(smoothing) | ||
| // LEFT half: smooth the raw pad position (Touchpad smoothing), then track cursor (+detent tick on cell | ||
| // change) and type on pad-click/trigger (hold to repeat). | ||
| val lTouch = s.has(TritonProtocol.BTN_LPAD_TOUCH) | ||
| if (lTouch) { | ||
| smLeftX = if (smLeftX.isNaN()) s.leftPadX.toFloat() else smLeftX * (1f - a) + s.leftPadX * a | ||
| smLeftY = if (smLeftY.isNaN()) s.leftPadY.toFloat() else smLeftY * (1f - a) + s.leftPadY * a | ||
| } else { smLeftX = Float.NaN; smLeftY = Float.NaN } | ||
| val lx = smLeftX.toInt(); val ly = smLeftY.toInt() | ||
| val newLeft = if (lTouch) ScKeyboardLayout.cellAt(lx, ly) else -1 | ||
| if (lTouch && newLeft >= 0 && newLeft != leftCursor) haptics?.tick(TritonHaptics.SIDE_LEFT_PAD, tickGain) | ||
| leftCursor = newLeft | ||
| leftX = if (lTouch) normX(lx) else -1f | ||
| leftY = if (lTouch) normY(ly) else -1f | ||
| fireSide(s, leftCursor, ScKeyboardLayout.leftFor(symbols), TritonHaptics.SIDE_LEFT_PAD, now, lRepeat, | ||
| TritonProtocol.BTN_LPAD_CLICK, TritonProtocol.BTN_LTRIG_CLICK) | ||
| // RIGHT half | ||
| val rTouch = s.has(TritonProtocol.BTN_RPAD_TOUCH) | ||
| if (rTouch) { | ||
| smRightX = if (smRightX.isNaN()) s.rightPadX.toFloat() else smRightX * (1f - a) + s.rightPadX * a | ||
| smRightY = if (smRightY.isNaN()) s.rightPadY.toFloat() else smRightY * (1f - a) + s.rightPadY * a | ||
| } else { smRightX = Float.NaN; smRightY = Float.NaN } | ||
| val rx = smRightX.toInt(); val ry = smRightY.toInt() | ||
| val newRight = if (rTouch) ScKeyboardLayout.cellAt(rx, ry) else -1 | ||
| if (rTouch && newRight >= 0 && newRight != rightCursor) haptics?.tick(TritonHaptics.SIDE_RIGHT_PAD, tickGain) | ||
| rightCursor = newRight | ||
| rightX = if (rTouch) normX(rx) else -1f | ||
| rightY = if (rTouch) normY(ry) else -1f | ||
| if (active) fireSide(s, rightCursor, ScKeyboardLayout.rightFor(symbols), TritonHaptics.SIDE_RIGHT_PAD, now, rRepeat, | ||
| TritonProtocol.BTN_RPAD_CLICK, TritonProtocol.BTN_RTRIG_CLICK) | ||
| prevButtons = s.buttons | ||
| if (active) push() else runCatching { overlay.hide() } | ||
| } | ||
|
|
||
| /** | ||
| * Type from one half: fire on the rising edge of any [commitBits] (pad-click / trigger) over [cursor], then | ||
| * auto-repeat while the button stays held on the same cell (initial [REPEAT_DELAY_MS], then [REPEAT_INTERVAL_MS]). | ||
| * Sliding to a different cell while held suppresses repeat until release, so dragging doesn't machine-gun keys. | ||
| */ | ||
| private fun fireSide(s: TritonState, cursor: Int, keys: List<KbKey>, side: Int, now: Long, rep: SideRepeat, vararg commitBits: Int) { | ||
| val down = commitBits.any { (s.buttons and it) != 0 } | ||
| val rising = commitBits.any { (s.buttons and it) != 0 && (prevButtons and it) == 0 } | ||
| val key = keys.getOrNull(cursor) | ||
| when { | ||
| rising && cursor >= 0 -> { | ||
| haptics?.click(side, clickGain); fire(key) | ||
| rep.cell = cursor; rep.nextFire = now + REPEAT_DELAY_MS | ||
| } | ||
| down && cursor >= 0 && cursor == rep.cell && repeatable(key) && now >= rep.nextFire -> { | ||
| haptics?.tick(side, tickGain); fire(key) | ||
| rep.nextFire = now + REPEAT_INTERVAL_MS | ||
| } | ||
| !down -> rep.reset() | ||
| cursor != rep.cell -> rep.cell = -2 // moved off the pressed cell while held → no repeat until release | ||
| } | ||
| } | ||
|
|
||
| /** Which keys auto-repeat when held: characters + backspace/space/enter; not Shift/Close/Empty (one-shot). */ | ||
| private fun repeatable(key: KbKey?): Boolean = when (key) { | ||
| is KbKey.Chr, KbKey.Backspace, KbKey.Space, KbKey.Enter -> true | ||
| else -> false | ||
| } | ||
|
|
||
| // Pad position (raw ±32768, +Y up) → normalized half coords: x left→right, y top→bottom (screen order). | ||
| private fun normX(x: Int) = (x.toFloat() / 65536f + 0.5f).coerceIn(0f, 0.999f) | ||
| private fun normY(y: Int) = (1f - (y.toFloat() / 65536f + 0.5f)).coerceIn(0f, 0.999f) | ||
|
|
||
| private fun fire(key: KbKey?) { | ||
| when (key) { | ||
| is KbKey.Chr -> { | ||
| if (shift || key.forceShift) { | ||
| sink.key(XKeycode.KEY_SHIFT_L, true) | ||
| sink.key(key.code, true); sink.key(key.code, false) | ||
| sink.key(XKeycode.KEY_SHIFT_L, false) | ||
| if (shift) shift = false // sticky one-shot (forceShift keys don't consume it) | ||
| } else { | ||
| sink.key(key.code, true); sink.key(key.code, false) | ||
| } | ||
| } | ||
| KbKey.Shift -> shift = !shift | ||
| KbKey.Sym, KbKey.Abc -> symbols = !symbols | ||
| KbKey.Space -> pulse(XKeycode.KEY_SPACE) | ||
| KbKey.Backspace -> pulse(XKeycode.KEY_BKSP) | ||
| KbKey.Enter -> pulse(XKeycode.KEY_ENTER) | ||
| KbKey.Close -> deactivate() | ||
| KbKey.Empty, null -> {} | ||
| } | ||
| } | ||
|
|
||
| private fun pulse(code: XKeycode) { sink.key(code, true); sink.key(code, false) } | ||
|
|
||
| private fun push() { | ||
| runCatching { overlay.show(ScKeyboardSpec(leftCursor, rightCursor, shift, leftX, leftY, rightX, rightY, symbols)) } | ||
| } | ||
|
|
||
| /** Per-side key-repeat state: [cell] = the cell the held button pressed (-1 idle, -2 = slid off, suppressed). */ | ||
| private class SideRepeat { | ||
| var cell = -1 | ||
| var nextFire = 0L | ||
| fun reset() { cell = -1; nextFire = 0L } | ||
| } | ||
|
|
||
| private companion object { | ||
| const val REPEAT_DELAY_MS = 350L // hold this long before the first repeat | ||
| const val REPEAT_INTERVAL_MS = 90L // then repeat at this cadence | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this indirection exists: adding a parameter to the very large
XServerScreencomposable pushed it past ART's bytecode-verifier method-size limit (aVerifyErrorthat crashed all launches). Persisting the diagnostics flag here and reading it insetupXEnvironmentavoids threading a new parameter through that method. Unrelated to the SC feature per se — surfaced by the merge that grew the composable.