Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Accessibility recipes

Make any keyboard and mouse work the way you need — at the hardware level, in every application. The scripts on this page run on the Rebind device and come out as standard USB input, so they keep working in apps that ignore or block software assistive tools, and they move with you across Windows, macOS, and Linux.

Each recipe explains what it helps with, gives you a complete script to paste into the Rebind UI, and lists the settings worth tuning. Every script exposes those settings through the UI panel, so you can dial in timings and thresholds with sliders and toggles without editing code.

Dwell-click

For anyone who can move a pointer but can’t reliably press a button. Rest the cursor in one place and, after a short pause, Rebind clicks for you. Moving away before the timer finishes cancels the click.

--[[
  rebind: min_sdk=3.0.0
  rebind: name=Dwell Click
--]]
local cfg = UI.Schema({
  enabled = UI.Toggle(true),
  dwell_time = UI.Slider(1000, { min = 300, max = 3000, suffix = "ms" }),
  tolerance = UI.Slider(10, { min = 5, max = 50, suffix = "px" }),
  click_type = UI.Select(
    "Left Click",
    { "Left Click", "Right Click", "Double Click" }
  ),
  sound = UI.Toggle(true, { label = "Audio Feedback" }),
})

local dwellStart = nil
local dwellPos = { x = 0, y = 0 }

function OnTick()
  if not cfg.enabled then
    dwellStart = nil
    return
  end

  local pos = Input.GetMousePos()
  local dx = pos.x - dwellPos.x
  local dy = pos.y - dwellPos.y
  local distance = math.sqrt(dx * dx + dy * dy)

  if distance > cfg.tolerance then
    dwellPos = pos
    dwellStart = System.Time()
    return
  end

  if not dwellStart then
    dwellStart = System.Time()
    return
  end

  if System.Time() - dwellStart >= cfg.dwell_time then
    if cfg.click_type == "Left Click" then
      Run(function()
        HID.Press("Mouse1")
      end)
    elseif cfg.click_type == "Right Click" then
      Run(function()
        HID.Press("Mouse2")
      end)
    elseif cfg.click_type == "Double Click" then
      Run(function()
        HID.Press("Mouse1", 30)
        Sleep(50)
        HID.Press("Mouse1", 30)
      end)
    end

    if cfg.sound then
      Audio.Beep()
    end
    dwellStart = nil
  end
end

function OnBlur()
  dwellStart = nil
end

Tuning notes

  • dwell_time — how long the cursor must rest before a click. Start at one second. Lower it as you build confidence; raise it if clicks fire too eagerly.
  • tolerance — how much wobble is allowed while you hold position, in pixels. Larger forgives unsteady movement; smaller makes the dwell stricter.
  • click_type — switch between left, right, and double click without changing any code.
  • sound — a short beep confirms each click so you don’t have to watch closely.

Click assist

Three modes for people who can reach a button but struggle to hold it or time a double click. Hold Assist turns one press into a timed hold that releases itself. Toggle Click latches a single click down until you click again — useful for dragging without holding. Double Click fires two clicks from one press.

--[[
  rebind: min_sdk=3.0.0
  rebind: name=Click Assist
--]]
local cfg = UI.Schema({
  enabled = UI.Toggle(true),
  mode = UI.Select(
    "Hold Assist",
    { "Hold Assist", "Toggle Click", "Double Click" }
  ),
  tune_hold = UI.Toggle(false, { label = "Tune hold duration" }),
  hold_time = UI.Slider(500, {
    min = 100,
    max = 3000,
    suffix = "ms",
    showIf = "tune_hold",
  }),
  tune_double = UI.Toggle(false, { label = "Tune double-click gap" }),
  double_delay = UI.Slider(50, {
    min = 20,
    max = 200,
    suffix = "ms",
    showIf = "tune_double",
  }),
})

local toggleState = false

function OnStop()
  if toggleState then
    HID.Up("Mouse1")
  end
end

function OnDown(key)
  if key ~= "Mouse1" or not cfg.enabled then
    return true
  end

  if cfg.mode == "Hold Assist" then
    Run(function()
      HID.Down("Mouse1")
      Audio.Beep()
      Sleep(cfg.hold_time)
      HID.Up("Mouse1")
    end)
    return false
  elseif cfg.mode == "Toggle Click" then
    toggleState = not toggleState
    if toggleState then
      HID.Down("Mouse1")
      Audio.Beep()
    else
      HID.Up("Mouse1")
    end
    return false
  elseif cfg.mode == "Double Click" then
    Run(function()
      HID.Press("Mouse1", 30)
      Sleep(cfg.double_delay)
      HID.Press("Mouse1", 30)
    end)
    return false
  end

  return true
end

function OnBlur()
  if toggleState then
    HID.Up("Mouse1")
    toggleState = false
  end
end

Tuning notes

  • mode — Hold Assist for tasks that need a sustained press, Toggle Click for drag-and-drop without holding, Double Click for interfaces that expect rapid double clicks.
  • hold_time — how long Hold Assist keeps the button down before releasing it. Match it to the longest hold you typically need.
  • double_delay — the gap between the two clicks in Double Click. If the application misses the second click, increase this a little.
  • The script releases the button cleanly on focus loss and on stop, so a latched toggle never gets stuck down.

Tremor smoothing

For users with hand tremor or repetitive-strain motion. Movements below a threshold are dropped, and larger movements are smoothed with an exponential filter, so the cursor follows your intent instead of the jitter. Filtering movement uses mouse_block=true, which requires a Rebind device.

--[[
  rebind: min_sdk=3.0.0
  rebind: name=Steady Hand
  rebind: mouse_block=true
--]]
local cfg = UI.Schema({
  enabled = UI.Toggle(true),
  smoothing = UI.Slider(50, {
    min = 0,
    max = 90,
    suffix = "%",
    tooltip = "Higher = smoother but more latency",
  }),
  threshold = UI.Slider(5, {
    min = 1,
    max = 20,
    suffix = "px",
    tooltip = "Filter movements smaller than this",
  }),
})

local buffer = { x = 0, y = 0 }

function OnMove(dx, dy)
  if not cfg.enabled then
    return true
  end

  local magnitude = math.sqrt(dx * dx + dy * dy)
  if magnitude < cfg.threshold then
    return false
  end

  local s = cfg.smoothing / 100
  buffer.x = buffer.x * s + dx * (1 - s)
  buffer.y = buffer.y * s + dy * (1 - s)

  HID.Move(buffer.x, buffer.y)
  return false
end

function OnBlur()
  buffer.x = 0
  buffer.y = 0
end

Tuning notes

  • threshold — the smallest movement Rebind will pass through, in pixels. Raise it to ignore more involuntary jitter; lower it if intentional small movements feel suppressed.
  • smoothing — how much each movement is averaged with the previous one. Higher gives a steadier cursor at the cost of a little latency; find the point where the pointer feels calm but still responsive.
  • The filtering happens on the device, before the movement reaches the OS.

Sticky keys

For one-handed use or limited grip strength, holding two keys at once is hard. This turns a modifier into a tap-to-lock toggle: tap once and it stays held, tap again to release. A notification tells you whether it’s active, and it releases automatically on focus change so you never get stuck in a held state.

--[[
  rebind: min_sdk=3.0.0
  rebind: name=Sticky Keys
--]]
local cfg = UI.Schema({
  target = UI.Keybind("LShift", { label = "Modifier to Lock" }),
})

local held = false

function OnDown(key)
  if key == cfg.target then
    held = not held
    if held then
      HID.Down(cfg.target)
      UI.Notify("Modifier locked", "info")
    else
      HID.Up(cfg.target)
      UI.Notify("Modifier released", "info")
    end
    return false
  end
  return true
end

function OnUp(key)
  if key == cfg.target then
    return false
  end
  return true
end

function OnBlur()
  if held then
    held = false
    HID.Up(cfg.target)
  end
end

Tuning notes

  • target — choose any modifier to make sticky: LShift, LCtrl, LAlt, LWin, or their right-hand variants. Run a separate copy of the script for each modifier you want to latch.
  • To lock several modifiers at once (for a combination like Ctrl+Shift), enable one sticky script per key; each latches independently and they combine naturally.
  • The script swallows the modifier’s own release event, so a single tap fully toggles the lock without the OS seeing an immediate key-up.

One-handed layout

Remaps keys so a single hand can reach everything it needs. The example mirrors the right side of the keyboard onto the left, brings Enter and Backspace under the home position, and frees a thumb key for Space — all configurable. Use it as a starting point and remap to whatever suits your hand.

--[[
  rebind: min_sdk=3.0.0
  rebind: name=One-Handed Layout
--]]
local cfg = UI.Schema({
  enabled = UI.Toggle(true, { label = "Enable Layout" }),
  layer_key = UI.Keybind("CapsLock", { label = "Layer Hold Key" }),
})

-- base remaps: always active when enabled
local base = {
  -- bring common edit keys under the left hand
  ["F1"] = "Backspace",
  ["F2"] = "Enter",
  ["F3"] = "Space",
}

-- layered remaps: active only while the layer key is held,
-- mirroring the right hand onto the left
local layer = {
  ["Q"] = "P",
  ["W"] = "O",
  ["E"] = "I",
  ["R"] = "U",
  ["A"] = "Semicolon",
  ["S"] = "L",
  ["D"] = "K",
  ["F"] = "J",
}

local function resolve(key)
  if Input.IsDown(cfg.layer_key) and layer[key] then
    return layer[key]
  end
  return base[key]
end

function OnDown(key)
  if not cfg.enabled then
    return true
  end
  if key == cfg.layer_key then
    return false -- the layer key itself never types
  end
  local mapped = resolve(key)
  if mapped then
    HID.Down(mapped)
    return false
  end
  return true
end

function OnUp(key)
  if not cfg.enabled then
    return true
  end
  if key == cfg.layer_key then
    return false
  end
  local mapped = resolve(key)
  if mapped then
    HID.Up(mapped)
    return false
  end
  return true
end

Tuning notes

  • base — keys remapped all the time. Add an entry as ["FromKey"] = "ToKey" for each key you want to relocate under your hand.
  • layer — a second set of remaps that only apply while layer_key is held, doubling how many keys one hand can reach. Hold the layer key with a thumb or spare finger to flip into the mirrored set.
  • layer_key — the hold key that activates the layer. CapsLock is a common choice because it sits at the home row and is rarely needed otherwise.
  • Use the SDK reference to discover the exact key names Rebind reports, then fill in the tables to match your hand.

Key-repeat control

For users whose presses linger or who trigger unintended repeats, default keyboard auto-repeat can flood an application with extra characters. Two independent controls fix this. Debounce ignores a second press of the same key within a set window, filtering accidental double-taps. Repeat suppression blocks a held key from auto-repeating after the first character, so a long press produces one keystroke.

--[[
  rebind: min_sdk=3.0.0
  rebind: name=Key Timing Control
--]]
local cfg = UI.Schema({
  debounce = UI.Toggle(
    true,
    { label = "Ignore double-presses", group = "Debounce" }
  ),
  debounce_ms = UI.Slider(200, {
    min = 50,
    max = 1000,
    suffix = "ms",
    group = "Debounce",
  }),
  no_repeat = UI.Toggle(
    true,
    { label = "Suppress auto-repeat", group = "Repeat" }
  ),
})

local lastDown = {} -- key -> timestamp of last accepted press
local downNow = {} -- key -> currently held

function OnDown(key)
  -- suppress OS auto-repeat: a key already marked down repeats no further
  if cfg.no_repeat and downNow[key] then
    return false
  end

  -- debounce: reject a fresh press too soon after the previous one
  if cfg.debounce then
    local now = System.Time()
    local prev = lastDown[key]
    if prev and now - prev < cfg.debounce_ms then
      return false
    end
    lastDown[key] = now
  end

  downNow[key] = true
  return true
end

function OnUp(key)
  downNow[key] = nil
  return true
end

Tuning notes

  • debounce / debounce_ms — turn on to ignore a repeated press of the same key that lands within the window. Widen debounce_ms if accidental double-presses still slip through; narrow it if deliberate fast presses get dropped.
  • no_repeat — when on, holding a key sends one character instead of a stream. Leave it off for keys where you do want auto-repeat, such as arrow keys.
  • The script passes accepted presses through unchanged (return true), so every keystroke it lets through is ordinary USB input — only unwanted repeats and bounces are filtered.