input
Input queries + keybind formatter + clipboard + hardware device output.
input.is_active(path) → bool
Parameters: path : string — control full path (hotkey control)
Returns whether this hotkey is currently active (depending on mode: Toggle = toggled on / Hold = currently held / Always = always true).
input.format(path, fn)
Attaches dynamic text + color hints to a keybind control. The formatter is only invoked while the hotkey is active (i.e. shown in the "active keybinds" overlay).
Parameters:
path : string— menu element full path or Pattern B hotkey ID (both accepted)fn : function(id : string) → string | { text = string, color = {r,g,b,a} }
Automatic path resolution
Pass the menu element full path (the natural form) — if that element is a KeybindControl that internally remaps its keybind to a stable ID, resolution follows through to that stable ID.
| Call form | Resolution |
|---|---|
input.format("settings.display.menu", fn) | → resolves to hotkey.menu_open |
input.format("hotkey.menu_open", fn) | → used directly |
input.format("KbHints.g.alarm", fn) | → user-defined KeybindControl with no remap, uses full path |
fn return type
| Return | Behavior |
|---|---|
string | Display the text in the theme color |
table {text, color={r,g,b,a}} | Custom text + color |
| other | LOG_ERR + falls back to default display |
Owner-scoped: automatically unregistered when the script unloads.
input.unformat(path)
Path resolution behaves identically to input.format.
input.clipboard_get() → string
Reads the system clipboard. Returns an empty string when empty.
input.clipboard_set(text)
Writes the system clipboard.
local key = input.clipboard_get()
if key ~= "" then gui.notify:info("read: " .. key) end
input.clipboard_set("hello")Not provided:
input.is_down(vk)— on a dual-machine setup, the OS key API reads the machine running the program, not the game player. To query player input, usegame.localplayer.*fields (e.g.is_zooming,is_grenade). See Getting Started.
Formatter examples
-- formatter #1: plain string
input.format("KbHints.g.alarm", function(id)
return alarm_armed and "ARMED" or "OFF"
end)
-- formatter #2: return {text, color} table
input.format("KbHints.g.stopwatch", function(id)
local sec = os.clock() - start_t
return {
text = string.format("%.1fs", sec),
color = { 0.55, 0.85, 1.0, 1.0 },
}
end)
-- formatter #3: dynamic color by threshold
input.format("KbHints.g.counter", function(id)
local label = string.format("× %d", counter)
if counter == 0 then return { text = label, color = { 0.6, 0.6, 0.6, 1.0 } } end
if counter < 10 then return { text = label, color = { 0.3, 1.0, 0.3, 1.0 } } end
return { text = label, color = { 1.0, 0.45, 0.25, 1.0 } }
end)Hardware device output
Sends real mouse and keyboard input to the game machine through an external input device (KMBOX Net / KMBOX B+ / MAKCU).
With no device connected everything is a no-op returning false — no error is raised, and no alternate path is taken. Check input.device_connected() before relying on any of these.
Relative movement only
input.move is a relative displacement. There is no input.move_to(x, y).
MAKCU firmware does have a moveto(x, y) command (KMBOX Net / B+ do not), but per the official API it "internally calculates the needed x,y movement to reach the requested position on the screen" — the wire format is still a relative report, and it needs a trustworthy notion of where the cursor currently is.
In an FPS that reference point does not exist: the game consumes raw input deltas with the system cursor locked to the window center, so screen coordinates have no correspondence to the in-game view angle. In a dual-machine setup this program cannot read the game machine's cursor position either. moveto is therefore useless for aiming, and the facade does not expose it.
To aim at a world position, use game.aimbot.set_predictor and let the aim pipeline read view_angle and solve for the required delta.
input.backend() → string
Name of the current input device backend. Use it to branch on capability — backends support different actions (see the table below).
| Value | Meaning |
|---|---|
"disable" | No device selected |
"write_view" | Memory takeover (no hardware; everything in this section is inert) |
"kmboxnet" | KMBOX Net (ethernet) |
"kmboxb+" | KMBOX B+ (serial) |
"makcu" | MAKCU (serial) |
input.device_connected() → bool
Whether a device is connected and usable.
Backend capability matrix
| Action | kmboxnet | kmboxb+ | makcu |
|---|---|---|---|
move / click / button (left) | ✅ | ✅ | ✅ |
button right / middle / side | ✅ | ⚠️ | ✅ |
wheel | ✅ | ⚠️ | ✅ |
move_curve (firmware-side interpolation) | ✅ | ↩︎ falls back to move | ✅ |
key_down / key_up / key_press | ✅ | ✅ | ❌ |
mask left mouse button | ✅ | ✅ | ✅ |
mask other mouse buttons / "x" / "y" | ✅ | ❌ | ✅ |
mask "wheel" | ✅ | ❌ | ❌ |
mask keyboard keys | ✅ | ❌ | ❌ |
- ⚠️ = these KMBOX B+ command names were extrapolated from the naming pattern of commands that are known to work; they are unverified on real hardware. When the firmware does not recognize a command the serial write still succeeds, so
truedoes not guarantee anything happened. - ❌ = the backend lacks the capability and returns
false. Do not read everyfalseas "no device" — check this table first. - MAKCU firmware has no keyboard HID channel, so the three keyboard functions are always
falseon it.
input.move(dx, dy) → bool
Parameters: dx : int, dy : int — relative displacement (mickeys; positive = right / down)
input.move_curve(dx, dy, ms?) → bool
Parameters: dx : int, dy : int, ms : int (optional, default 20, capped at 2000)
Hands a large displacement to the firmware to push out in segments, producing a more hand-like path than small per-frame move calls.
Blocking push
The firmware holds the device for the whole ms window, stalling any aim move queued behind it. Keep it out of per-frame loops.
input.button(name, down) → bool
Parameters: name : string, down : bool
name | Aliases |
|---|---|
"left" | "lmb" |
"right" | "rmb" |
"middle" | "mmb" / "wheel_click" |
"side1" | "mouse4" / "x1" |
"side2" | "mouse5" / "x2" |
input.click(name?) → bool
Press + release once. Omitting name means left button.
input.wheel(delta) → bool
Parameters: delta : int — detents (positive = scroll forward)
input.key_down(key) / input.key_up(key) / input.key_press(key) → bool
Parameters: key : string — a single character ("r") or a key name ("shift" / "enter" / "f1" / "space" / "lctrl", arrow keys such as "up", …)
key_press = press + release once.
The MAKCU backend has no keyboard channel; these three are always
falsethere.
input.mask(what, enable) → bool
Blocks the player's physical input from reaching the game. Useful for taking over a key — the box swallows the player's own press, and the script decides when to actually emit it.
Parameters:
what : string— a mouse button name (same asinput.button) /"x"/"y"/"wheel"/ a keyboard key nameenable : bool
Precondition for keyboard masking
Keyboard masks only take effect when the physical keyboard is plugged into the box. If the keyboard is connected directly to the game machine the box cannot intercept it, and the call returns true with no actual effect.
All masks are released automatically when the script unloads — see "Automatic cleanup" below.
Automatic cleanup
Stateful device operations are tracked per owning script and reverted automatically when the script unloads (hot reload / config switch / manual unload):
| Operation | On unload |
|---|---|
input.button(name, true) held | Released |
input.key_down(key) held | Released |
input.mask(what, true) | Cleared |
Without this layer, a script unloaded while holding the left button would leave that button stuck in the box firmware permanently — in game it reads as continuous fire that the user cannot stop by clicking their own mouse.
click / move / wheel / key_press are instantaneous and need no cleanup.
Interaction with the trigger
The trigger decides whether to yield based on whether this machine is firing, and a left click sent through the box looks exactly like the player firing manually. input.click("left") and input.button("left", false) notify the trigger that the shot came from a script — scripts do not need to handle this themselves.
While a script holds the left button the trigger yields and does not fire. That is the correct behavior: fire is already sustained, so an extra trigger shot would be meaningless.
Device output examples
-- Capability branching: backends differ in what they can do
local backend = input.backend()
if not input.device_connected() then
gui.notify:warn("no input device connected")
return
end
-- Trace a square slowly (firmware interpolation looks better than per-frame move)
for _, d in ipairs({ {200,0}, {0,200}, {-200,0}, {0,-200} }) do
input.move_curve(d[1], d[2], 150)
end
-- Side-button tap: press -> 60ms -> release
input.button("side1", true)
Delay(0.06, function() input.button("side1", false) end)
-- Take over the R key: swallow the player's physical R, emit it on our terms
-- (only works on kmboxnet with the keyboard plugged into the box)
if backend == "kmboxnet" then
input.mask("r", true)
event.on("frame_update", function()
if should_reload_now() then
input.mask("r", false)
input.key_press("r")
input.mask("r", true)
end
end)
end