> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rebind.gg/llms.txt
> Use this file to discover all available pages before exploring further.

# rb.hid

> Send keys, mouse movement, scroll, and controller output.

`rb.hid` sends keyboard, mouse and joypad output through the active output: the
operating system in software mode, Rebind Link in hardware mode. A script reaches for it to
fire a shortcut, remap a key, type text, move the cursor or drive a controller.

**Needs:** a coroutine for `press`, `typewriter` and `move_to` with `ms`, so
call them inside `rb.run` or `rb.async` (see
[Where code runs](https://docs.rebind.gg/reference/sdk/globals.md#where-code-runs)).

This script sends Ctrl+C on F1 and double-clicks on F2:

```luau theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
-- rebind: min_sdk=3.9.0 name=example version=1.0.0
rb.bind("F1", function()
  rb.hid.combo("LCtrl+C")
end)

-- press waits, so it runs inside rb.run
rb.bind("F2", function()
  rb.run(function()
    rb.hid.press("Mouse1", 30)
    rb.hid.press("Mouse1", 30)
  end)
end)
```

| Signature | Coroutine only | Effect |
| - | - | - |
| `rb.hid.down(code: string) -> ()` | no | press and hold a key, button or chord |
| `rb.hid.up(code: string) -> ()` | no | release it; a chord releases right to left |
| `rb.hid.press(code: string, hold_ms: number?) -> ()` | yes | down, wait `hold_ms` (default 50), up |
| `rb.hid.combo(code: string) -> ()` | no | press a chord left to right, then release right to left, in one call |
| `rb.hid.type(text: string) -> ()` | no | type a string in one call |
| `rb.hid.typewriter(text: string, delay_ms: number?) -> ()` | yes | one character at a time, `delay_ms` (default 30) between down, up and the next character |
| `rb.hid.move(dx: number, dy: number) -> ()` | no | relative mouse movement; for a move over time, play a glided macro (see [`rb.math.glide`](https://docs.rebind.gg/reference/sdk/math#rb-math-glide)) |
| `rb.hid.move_to(x: number, y: number, ms: number?) -> ()` | with `ms` | move the cursor to a screen point |
| `rb.hid.scroll(amount: number) -> ()` | no | vertical scroll, positive is up |
| `rb.hid.usage(name: string) -> number?` | no | the keyboard usage of a key name |
| `rb.hid.joypad(state: JoypadState?) -> ()` | no | set the whole joypad state |
| `rb.hid.axis(name: string, value: number) -> ()` | no | hold one controller axis; `min_sdk` 3.11.0 |
| `rb.hid.joypad_reset() -> ()` | no | drop what scripts set on the joypad |
| `rb.hid.set_mouse_mode(mode: string) -> ()` | no | deprecated, see [Mouse mode](#mouse-mode-deprecated) |
| `rb.hid.get_mouse_mode() -> string` | no | deprecated |

Outside a coroutine, a coroutine-only call raises, for example
`HID.Press() can only be called inside a Run() coroutine`.

**Key names.** `down`, `up`, `press` and `combo` take a key name, a mouse button
(`Mouse1` to `Mouse5`), a [controller button](https://docs.rebind.gg/reference/sdk/keys.md#controllers) such as `PadA`,
`usage:N`, or a chord joined with `+`. A controller button goes to Rebind Link's
joypad and needs `min_sdk=3.11.0`. A single
character that is not a key name, such as `"*"` or `"("`, sends that symbol with
the shift it needs. An unknown name raises no error and sends usage 0, which is
no key; check names against the [Key reference](https://docs.rebind.gg/reference/sdk/keys.md). Modifiers the
user is physically holding still apply, so `rb.hid.combo("shift+r")` while Cmd is
held sends Shift+Cmd+R.

**Usages.** `usage:N` names a keyboard usage, decimal or `0x` hex, from `0x04` to
`0xE7`. The media codes `0xB5` to `0xB7` and `0xCD` are refused. An invalid usage
raises `'usage:N' is not a keyboard usage (0x04-0xE7, media keys excluded)`
before any key is pressed. `rb.hid.usage` returns `nil` for unknown names and
media keys.

**Typing.** `rb.hid.type` hands the text to the output in one call. On Rebind
Link the call returns at once and the Link types each ASCII character as a key
press with its modifiers, about 15 ms per character, skipping characters it has
no key for. See [Transport key support](https://docs.rebind.gg/reference/sdk/keys.md#transport-key-support).

**Releases.** Rebind remembers what a script holds through `rb.hid` and releases
it when the script stops or errors. A key held this way for 10 seconds logs one
warning.

To hold a key for as long as another is held, send `down` and `up` from the
matching handlers; here F1 holds A:

```luau theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
-- rebind: min_sdk=3.9.0 name=example version=1.0.0
rb.on("down", function(key)
  if key == "F1" then
    rb.hid.down("A")
    return false
  end
end)

rb.on("up", function(key)
  if key == "F1" then
    rb.hid.up("A")
    return false
  end
end)
```

**Profile errors.** On a Rebind Link whose USB profile leaves out an output, the
call raises `the Rebind Link's USB profile has no keyboard output` (or `mouse`).
A controller button needs a Link whose USB profile has a joypad or the Xbox
controller; anywhere else it raises
`joypad output needs a Rebind Link whose USB profile has a joypad`.

## rb.hid.move\_to

In software mode the cursor lands on the point exactly. On a
Rebind Link, which sends only relative movement, it moves, rereads the cursor,
and corrects until within 2 px on each axis, for at most about 150 ms; inside a
coroutine it waits with `rb.sleep`, elsewhere it blocks for that time. With `ms`
greater than 0 the cursor glides there over `ms` milliseconds; outside a
coroutine that raises
`HID.MoveTo() with a duration can only be called inside a Run() coroutine`.
Each glide picks a random [`rb.math.glide`](https://docs.rebind.gg/reference/sdk/math#rb-math-glide)
speed profile, so the cursor can hold still for part of `ms`; the speed profile
needs `min_sdk=3.17.0`.

## rb.hid.joypad

Needs a Rebind Link whose [USB profile](https://docs.rebind.gg/getting-started/hardware-setup.md#change-the-usb-identity) includes a joypad: 32 buttons, an 8-way
hat, four axes and two sliders. With the Xbox controller in the profile
instead, the same state drives it: buttons 1 to 13, the hat, the four axes and
the sliders as the triggers map as in [Controllers](https://docs.rebind.gg/reference/sdk/keys.md#controllers), and buttons
14 to 32 have no Xbox control. Elsewhere it raises
`joypad output needs a Rebind Link whose USB profile has a joypad`.

| Field | Range | Default |
| - | - | - |
| `buttons` | list of pressed buttons, 1 to 32 | none pressed |
| `hat` | 0 (up) to 7, clockwise | `nil`, centered |
| `x`, `y`, `z`, `rz` | -1 to 1 | 0, center |
| `slider`, `dial` | 0 to 1 | 0 |

A value out of range raises, for example `joypad x must be between -1 and 1`.
The last state written wins, whichever script wrote it.

The joypad also carries any controller Rebind captures in hardware mode, and
what scripts set sits on top of it. `rb.hid.joypad` replaces everything scripts
set before: each stick and trigger it leaves out is held at rest, over the
controller's own value, and buttons pressed with `rb.hid.down` are dropped, so
with a controller attached, `rb.hid.joypad()` holds both sticks and triggers at
rest. A button a script presses adds to the controller's buttons and d-pad,
which still come through, and an axis a script sets with `rb.hid.joypad` or
`rb.hid.axis` wins over the controller's until it is reset. Use `rb.hid.axis`
and `rb.hid.down` to change one control and leave the rest to the controller.
`rb.hid.joypad_reset` drops what scripts set and keeps what the controller
holds; Rebind does the same when a script stops or errors.

## rb.hid.axis

**Needs:** `min_sdk=3.11.0`.

`rb.hid.axis(name, value)` holds one axis of the joypad: `LeftX`, `LeftY`,
`RightX`, `RightY` from -1 to 1, or `LT`, `RT` from 0 to 1. It maps to the joypad's
`x`, `y`, `z`, `rz`, `slider` and `dial`. An unknown name raises
`HID.Axis: unknown axis '<name>'` with the list, and a value out of range raises
`HID.Axis: LT must be between 0 and 1`. It needs the same joypad as
`rb.hid.joypad`.

This script inverts the left stick's vertical axis:

```luau theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
-- rebind: min_sdk=3.11.0 name=example version=1.0.0
rb.on("axis", function(axis, value)
  if axis == "LeftY" then
    rb.hid.axis("LeftY", -value)
    return false
  end
end)
```

## Mouse mode (deprecated)

`rb.hid.set_mouse_mode` accepts `"relative"` or `"absolute"` and stores it;
`rb.hid.get_mouse_mode` returns it. Neither call, and not the `mouse_mode` modeline
key, changes how `rb.hid.move_to` moves. They remain so existing scripts load.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.