05-03-26
Accessibility Scripts for Medical Conditions
Reduce strain and improve accessibility with Lua scripts for hardware input. Includes mouse smoothing, dwell click, sticky keys, macro recorder, and more. These scripts can help with medical condition
Question
Mouse smoothing filter for shaky hands
Rebind AI
-- rebind: min_sdk=0.1.0
-- rebind: name=Mouse Smoothing
-- rebind: description=Exponential smoothing filter for tremor reduction
-- rebind: mouse_block=true
local cfg = UI.Schema({
enabled = UI.Toggle(true, { label = "Enable Smoothing" }),
strength = UI.Slider(60, {
min = 0,
max = 95,
suffix = "%",
label = "Smoothing Strength",
tooltip = "Higher = smoother but more latency",
group = "Filter",
}),
threshold = UI.Slider(2, {
min = 0,
max = 10,
suffix = "px",
label = "Tremor Threshold",
tooltip = "Ignore movements smaller than this",
group = "Filter",
}),
mode = UI.Select("Exponential", { "Exponential", "Moving Average" }, {
label = "Filter Type",
group = "Advanced",
}),
window_size = UI.Slider(3, {
min = 2,
max = 10,
label = "Window Size",
showIf = "mode:Moving Average",
group = "Advanced",
}),
})
local smoothed = { x = 0, y = 0 }
local history = {}
function OnMove(dx, dy)
if not cfg.enabled then
return true
end
-- magnitude-based threshold rejection (tremor filtering)
local magnitude = math.sqrt(dx * dx + dy * dy)
if magnitude < cfg.threshold then
return false -- swallow tiny movements
end
if cfg.mode == "Exponential" then
-- exponential moving average (single-pass, low latency)
local alpha = cfg.strength / 100
smoothed.x = smoothed.x * alpha + dx * (1 - alpha)
smoothed.y = smoothed.y * alpha + dy * (1 - alpha)
HID.Move(math.floor(smoothed.x + 0.5), math.floor(smoothed.y + 0.5))
else
-- simple moving average (sliding window)
table.insert(history, { x = dx, y = dy })
if #history > cfg.window_size then
table.remove(history, 1)
end
local sumX, sumY = 0, 0
for _, point in ipairs(history) do
sumX = sumX + point.x
sumY = sumY + point.y
end
local avgX = sumX / #history
local avgY = sumY / #history
HID.Move(math.floor(avgX + 0.5), math.floor(avgY + 0.5))
end
return false
end
function OnBlur()
smoothed = { x = 0, y = 0 }
history = {}
endHow it works:
This script applies two complementary filters to reduce mouse tremor:
-
Threshold Rejection — Movements smaller than the tremor threshold (default 2px) are silently discarded, preventing tiny jitters from reaching your PC.
-
Smoothing Filter — Larger movements pass through one of two algorithms:
- Exponential (default): Each new movement is blended with the previous smoothed value using an alpha factor. Higher strength = more blending = smoother but slightly delayed. Low latency, responsive.
- Moving Average: Buffers the last N movements and outputs their average. More stable for severe tremor but slightly more lag.
-
Rounding — Converts floating-point coordinates back to integers for HID output.
The filter is transparent when you move deliberately — fast swipes pass through nearly unchanged. Only the small, involuntary oscillations get smoothed out.
Tuning:
- Strength: Start at 60%. Increase if tremor is still visible, decrease if the cursor feels sluggish.
- Threshold: Raise if you're seeing tiny jitter (try 3–5px). Lower if legitimate small movements are being filtered.
- Mode: Exponential is smoother for general use. Switch to Moving Average if you have severe tremor and don't mind slight latency.
Learn more
Question
Is this helpful for medical reasons?
Rebind AI
Yes, this script can genuinely help with medical tremor conditions. It's designed specifically to reduce the effects of:
- Essential tremor — involuntary shaking, often hereditary
- Parkinson's disease — resting tremor and movement difficulties
- Cerebellar ataxia — coordination and balance disorders
- Multiple sclerosis — tremor as a symptom
- Stroke recovery — residual tremor during rehabilitation
- Hand injuries or arthritis — involuntary micro-movements during healing
What it does medically:
- Separates signal from noise — Your intentional movements (the "signal") are preserved; involuntary shaking (the "noise") is filtered out.
- Reduces cognitive load — You don't have to consciously compensate for tremor; the filter does it automatically.
- Improves accessibility — Makes fine motor tasks (clicking small buttons, text selection, precise clicks) feasible when they would otherwise be impossible.
Limitations:
- Works best for high-frequency tremor (4–12 Hz). Low-frequency shaking or severe tremor may require higher smoothing values (80%+), though this introduces noticeable lag.
- Not a replacement for medical treatment. This is a compensation tool, like glasses for vision. Medical interventions (medication, physical therapy, devices like weighted utensils) are primary.
- Individual results vary. Some people find 40% strength enough; others need 85%+. Experiment.
Medical-specific recommendations:
- Start conservative (40–50% strength, 2px threshold) and increase gradually to avoid overshooting.
- Use Moving Average mode if tremor is severe — the extra latency is acceptable for stability.
- Pair with dwell click or sticky keys (see accessibility examples) if clicking is also difficult.
- Enable only when needed — keep the toggle visible so you can turn it off instantly if something breaks.
If you're using this for medical reasons, I'd suggest consulting with your occupational therapist or neurologist about your specific tremor profile so you can tune the parameters optimally.
Question
Thank you. What other scripts could be helpful? I assume playback macros even could be helpful to save strain.
Rebind AI
Absolutely. Here are scripts that work well together for medical accessibility and strain reduction:
1. Macro Recorder + Playback (saves repetitive strain)
-- rebind: min_sdk=0.1.0
-- rebind: name=Macro Recorder
local cfg = UI.Schema({
record = UI.Keybind("F9", { label = "Record / Save" }),
play = UI.Keybind("F10", { label = "Play" }),
stop = UI.Keybind("Escape", { label = "Stop" }),
name = UI.Text("task", { maxLength = 30 }),
})
local isRecording = false
local activeMacro = nil
function OnStart()
File.MkDir("macros")
end
function OnDown(key)
if key == cfg.record then
if isRecording then
local macro = Macro.Finish()
File.WriteJSON("macros/" .. cfg.name .. ".json", macro)
UI.Notify("Macro saved: " .. cfg.name, "success")
Audio.Beep()
isRecording = false
else
Macro.Record()
UI.Notify("Recording... press " .. cfg.record .. " to save", "info")
Audio.Beep()
isRecording = true
end
return false
end
if key == cfg.play then
local path = "macros/" .. cfg.name .. ".json"
if File.Exists(path) then
activeMacro = Macro.Play(File.ReadJSON(path))
UI.Notify("Playing: " .. cfg.name, "info")
else
UI.Notify("Macro not found", "error")
end
return false
end
if key == cfg.stop and activeMacro then
activeMacro:Stop()
activeMacro = nil
UI.Notify("Stopped", "info")
return false
end
return true
end
function OnStop()
if isRecording then Macro.Finish() end
if activeMacro then activeMacro:Stop() end
end
function OnBlur()
if activeMacro then
activeMacro:Stop()
activeMacro = nil
end
endWhy it helps: Record repetitive tasks (form filling, navigation sequences, copy-paste workflows) once, then play them back with a single keypress. Eliminates repeated strain from the same sequence.
2. Dwell Click (click without pressing)
-- rebind: min_sdk=0.1.0
-- rebind: name=Dwell Click
-- rebind: description=Click by hovering (no button press needed)
local cfg = UI.Schema({
enabled = UI.Toggle(true, { label = "Enable" }),
dwell_time = UI.Slider(800, { min = 300, max = 2000, suffix = "ms" }),
tolerance = UI.Slider(15, { min = 5, max = 50, suffix = "px" }),
click_type = UI.Select("Left Click", { "Left Click", "Right Click" }),
})
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 dist = math.sqrt(dx * dx + dy * dy)
if dist > 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
HID.Press("Mouse1", 30)
else
HID.Press("Mouse2", 30)
end
Audio.Beep()
dwellStart = nil
end
end
function OnBlur()
dwellStart = nil
endWhy it helps: Eliminates the need to physically press mouse buttons. Just hover over a target and wait. Critical for arthritis, hand weakness, or conditions where gripping is painful.
3. Sticky Modifier Keys (hold without pressing)
-- rebind: min_sdk=0.1.0
-- rebind: name=Sticky Modifiers
local cfg = UI.Schema({
shift = UI.Keybind("LShift", { label = "Shift", group = "Modifiers" }),
ctrl = UI.Keybind("LCtrl", { label = "Control", group = "Modifiers" }),
alt = UI.Keybind("LAlt", { label = "Alt", group = "Modifiers" }),
})
local locked = {}
function OnDown(key)
for name, keyName in pairs({ shift = cfg.shift, ctrl = cfg.ctrl, alt = cfg.alt }) do
if key == keyName then
locked[name] = not locked[name]
if locked[name] then
HID.Down(keyName)
UI.Notify(name:upper() .. " locked", "info")
else
HID.Up(keyName)
UI.Notify(name:upper() .. " released", "info")
end
return false
end
end
return true
end
function OnUp(key)
for name, keyName in pairs({ shift = cfg.shift, ctrl = cfg.ctrl, alt = cfg.alt }) do
if key == keyName then
return false
end
end
return true
end
function OnBlur()
for name, keyName in pairs({ shift = cfg.shift, ctrl = cfg.ctrl, alt = cfg.alt }) do
if locked[name] then
HID.Up(keyName)
end
end
locked = {}
endWhy it helps: Tap Shift once to "lock" it on, tap again to release. No need to hold multiple keys simultaneously — one-handed operation becomes possible.
4. One-Handed WASD (directional input without chording)
-- rebind: min_sdk=0.1.0 tick_rate=8000
-- rebind: name=One-Handed WASD
local cfg = UI.Schema({
enabled = UI.Toggle(true),
mode = UI.Select("Arrow Keys", { "Arrow Keys", "WASD" }),
})
local keyMap = {
Up = "W",
Down = "S",
Left = "A",
Right = "D",
}
function OnDown(key)
if not cfg.enabled then return true end
if key == "Up" or key == "Down" or key == "Left" or key == "Right" then
local outKey = cfg.mode == "WASD" and keyMap[key] or key
HID.Down(outKey)
return false
end
return true
end
function OnUp(key)
if not cfg.enabled then return true end
if key == "Up" or key == "Down" or key == "Left" or key == "Right" then
local outKey = cfg.mode == "WASD" and keyMap[key] or key
HID.Up(outKey)
return false
end
return true
endWhy it helps: Use arrow keys or numpad for movement instead of WASD, reducing hand reach and finger strain. Useful for games or navigation-heavy applications.
5. Auto-Hold / Toggle Sprint (sustain without holding)
-- rebind: min_sdk=0.1.0
-- rebind: name=Toggle Sprint
local cfg = UI.Schema({
sprint_key = UI.Keybind("LShift", { label = "Sprint Key" }),
})
local sprinting = false
Bind(cfg.sprint_key, function()
sprinting = not sprinting
if sprinting then
HID.Down(cfg.sprint_key)
UI.Notify("Sprint ON", "info")
else
HID.Up(cfg.sprint_key)
UI.Notify("Sprint OFF", "info")
end
return false
end)
function OnBlur()
if sprinting then
sprinting = false
HID.Up(cfg.sprint_key)
end
endWhy it helps: Sprint or run continuously without holding Shift. Tap once to start, tap again to stop. Reduces sustained grip fatigue.
6. Slow Mouse Mode (precision without strain)
-- rebind: min_sdk=0.1.0 mouse_block=true
-- rebind: name=Precision Mouse Hold
local cfg = UI.Schema({
trigger = UI.Keybind("Mouse3", { label = "Precision Trigger" }),
slowdown = UI.Slider(20, { min = 5, max = 70, suffix = "%" }),
})
function OnMove(dx, dy)
if Input.IsDown(cfg.trigger) then
local factor = cfg.slowdown / 100
HID.Move(dx * factor, dy * factor)
return false
end
return true
endWhy it helps: Hold a button to temporarily slow movement for fine-grained clicks. Reduces overshooting and repeated corrections.
7. Auto-Clicker (adjustable CPS) (reduce clicking strain)
-- rebind: min_sdk=0.1.0
-- rebind: name=Auto Clicker
local cfg = UI.Schema({
cps = UI.Slider(5, { min = 1, max = 20, label = "Clicks Per Second" }),
toggle = UI.Keybind("F8", { label = "Toggle" }),
})
local active = false
local timer = nil
function OnDown(key)
if key == cfg.toggle then
active = not active
if active then
local interval = 1000 / cfg.cps
timer = Timer.Every(interval, function()
HID.Press("Mouse1", 20)
end)
UI.Notify("Auto-click ON", "info")
else
if timer then timer:Cancel() end
UI.Notify("Auto-click OFF", "info")
end
return false
end
return true
end
function OnStop()
if timer then timer:Cancel() end
end
function OnBlur()
if active and timer then
timer:Cancel()
active = false
end
endWhy it helps: Hold Mouse1, script auto-clicks at your configured rate. Eliminates repetitive clicking strain (games, web scraping, data entry).
8. Text Expander (reduce typing)
-- rebind: min_sdk=0.1.0
-- rebind: name=Text Expander
local cfg = UI.Schema({
trigger = UI.Keybind("F7", { label = "Expand Hotkey" }),
})
local snippets = {
["@@"] = "user@example.com",
["/date"] = os.date("%Y-%m-%d"),
["/hello"] = "Hello, thank you for reaching out!",
["/address"] = "123 Main Street, City, State 12345",
}
local buffer = ""
Bind(cfg.trigger, Async(function()
for abbr, expansion in pairs(snippets) do
if buffer:sub(-#abbr) == abbr then
for i = 1, #abbr do
HID.Press("Backspace")
Sleep(20)
end
HID.Type(expansion)
buffer = ""
return false
end
end
return false
end))
function OnDown(key)
if #key == 1 then
buffer = buffer .. key
if #buffer > 30 then buffer = buffer:sub(-30) end
elseif key == "Backspace" and #buffer > 0 then
buffer = buffer:sub(1, -2)
elseif key == "Space" or key == "Enter" then
buffer = ""
end
return true
endWhy it helps: Type abbreviations that expand into full text. Cuts typing by 50%+ for repeated phrases, emails, addresses, etc.
9. Clipboard Paste Helper (faster multi-line input)
-- rebind: min_sdk=0.1.0
-- rebind: name=Paste Helper
local cfg = UI.Schema({
paste_key = UI.Keybind("F9", { label = "Paste Hotkey" }),
text = UI.Text("", { label = "Text to Paste", maxLength = 500 }),
})
Bind(cfg.paste_key, function()
Clipboard.Set(cfg.text)
HID.Press("LCtrl+V")
UI.Notify("Pasted", "success")
end)Why it helps: Pre-configured text blocks paste with one key. Much faster than typing multi-line content.
10. Fatigue Break Reminder (paced activity)
-- rebind: min_sdk=0.1.0
-- rebind: name=Fatigue Break Timer
local cfg = UI.Schema({
work_minutes = UI.Slider(25, { min = 5, max = 60 }),
break_minutes = UI.Slider(5, { min = 1, max = 15 }),
enabled = UI.Toggle(true),
})
local breakTimer = nil
function OnStart()
if cfg.enabled then
scheduleNextBreak()
end
end
function scheduleNextBreak()
if breakTimer then breakTimer:Cancel() end
breakTimer = Timer.After(cfg.work_minutes * 60000, function()
UI.Notify("Break time! Rest for " .. cfg.break_minutes .. " minutes.", "warning")
Audio.Beep()
breakTimer = Timer.After(cfg.break_minutes * 60000, function()
UI.Notify("Break over. Ready to resume.", "info")
Audio.Beep()
scheduleNextBreak()
end)
end)
end
function OnStop()
if breakTimer then breakTimer:Cancel() end
endWhy it helps: Paces activity with timed breaks to prevent fatigue buildup. Follows ergonomic best practices (e.g., Pomodoro).
Recommended Combination
For most medical situations, I'd suggest:
- Mouse Smoothing (tremor reduction)
- Dwell Click (no button pressing)
- Sticky Modifiers (one-handed chords)
- Text Expander (reduce typing volume)
- Fatigue Break Timer (pacing)
Load all five simultaneously — they don't interfere with each other and together cover the major strain points: fine motor control, gripping, repetitive motion, and fatigue.
Each one is configurable so you can tune thresholds to your specific needs. Would you like help adapting any of these for a particular medical condition or workflow?