Packages
Reads the game controllers attached to the machine and hands their events to any process.
Current section
Files
Jump to
Current section
Files
README.md
# Gamepad
Reads the game controllers attached to the machine and hands their events to any
process or function. No dependency on a UI framework: a terminal app, a Phoenix
channel, a Nerves device or a test can all take a pad the same way.
```elixir
{:ok, _pid} = Gamepad.start_link(sink: self())
receive do
{:gamepad, %{type: :button, button: :a, pressed: true}} -> :jump
{:gamepad, %{type: :axis, axis: :left_x, value: value}} -> steer(value)
end
```
## Installation
```elixir
def deps do
[{:gamepad, "~> 0.1"}]
end
```
Events are maps in one shape whatever the hardware — buttons and axes carry Xbox
layout names (`:a`, `:b`, `:left_bumper`, `:dpad_up`, `:left_x`, `:right_trigger`, …),
sticks run from `-1.0` (left or up) to `1.0`, triggers from `0.0` to `1.0`, and a
controller connecting or leaving is an event too. `Gamepad.Wire` lists them.
## Platforms
| OS | Reader | Build |
|---|---|---|
| Linux | `/dev/input/js*`, read directly and decoded in Elixir | none |
| macOS | `priv/gamepad_helper`, Swift on GameController.framework | `mix compile`, when `swiftc` is present |
| Windows | `priv/gamepad_helper.exe`, C on XInput | `mix compile`, with the Visual Studio Build Tools (`cl` and `nmake`) that elixir_make needs on Windows anyway |
| Browser | `priv/static/gamepad.js`, the page's Gamepad API | none |
On Linux the device files must be readable by the user, which distributions usually
grant to the `input` group or to everyone. On macOS the helper sees every controller
macOS itself supports over Bluetooth or USB. `Gamepad.available?/0` says whether the
machine has a reader; without one `start_link/1` returns `:ignore`.
## In a browser
`gamepad.js` speaks the same line protocol to a page's own JavaScript, so a server
reads a browser's controllers with the same `Gamepad.Wire.parse/1` it reads a helper's
lines with. Serve it from `Gamepad.browser_script/0` and watch it:
```html
<script src="/gamepad.js"></script>
<script>
var reader = Gamepad.watch(function (line) { socket.push("pad", { line: line }) });
</script>
```
Every controller the page sees becomes `device`, `button`, `axis` and `gone` lines as
they change, standard-mapping buttons and axes named as the wire names them and the
triggers as axes. `Gamepad.watch(sink, { every: 16 })` polls on a timer instead of
every animation frame; `reader.stop()` ends it; `Gamepad.available()` says whether the
page has the API. A page may also keep the lines to itself and map them to actions
locally.
## Options
`Gamepad.start_link/1` takes:
- `:sink` - required. A pid, sent `{:gamepad, event}` for each event, or a
one-argument function called with each event
- `:source` - a reader in place of the detected one, which `Gamepad.source/0`
returns: `{:helper, path}` runs any program that writes `Gamepad.Wire` lines, and
`{:linux, glob}` reads the joystick device files matching `glob`
It returns `:ignore` when the machine has no reader. Under a supervisor:
```elixir
children = [
{Gamepad, sink: &MyGame.Input.controller_event/1}
]
Supervisor.start_link(children, strategy: :one_for_one)
```
## With Drafter
A Drafter app on drafter 0.5 or later declares `use Drafter.App, controller: true` and
adds `:gamepad` to its deps; Drafter starts the reader for the local terminal session
and delivers the events to `handle_event/2` as `{:controller, event}`.