Gamepad (gamepad v0.1.0)

Copy Markdown View Source

Reads the game controllers attached to this machine and hands their events to a process or a function.

{:ok, _pid} = Gamepad.start_link(sink: self())
receive do
  {:gamepad, %{type: :button, button: :a, pressed: true}} -> :jump
end

Events are the maps Gamepad.Wire describes: a controller connecting or going away, a button pressed or released, an axis moving, with buttons and axes named in the Xbox layout whatever the hardware. A pid sink receives each as {:gamepad, event}; a function sink is called with the event.

What reads the hardware depends on the operating system:

  • Linux — /dev/input/js* is read inside the VM, one Gamepad.Linux.Reader process per device file, linked to the reader and decoded by Gamepad.Linux. New device files are looked for every two seconds. The files must be readable by the user; distributions usually grant that to the input group or to everyone
  • macOS — priv/gamepad_helper, a Swift program on GameController.framework that mix compile builds when swiftc is present; it names every controller macOS itself supports over Bluetooth or USB
  • Windows — priv/gamepad_helper.exe, a C program on XInput that mix compile builds through Makefile.win with the Visual Studio Build Tools

A helper is a port the reader owns; it exits once the port closes, which happens when the reader stops, crashes, or its VM goes.

With no reader on the machine — a Linux box with no device files, a macOS build without the helper — start_link/1 returns :ignore. available?/0 says beforehand whether one exists.

A browser is read the same way by priv/static/gamepad.js (browser_script/0 gives the path to serve it from): Gamepad.watch(sink, options) polls the page's Gamepad API and calls sink with one Gamepad.Wire line per change, buttons and axes in the standard mapping named as the wire names them, triggers as axes; options.every polls every that many milliseconds instead of every animation frame; the handle's stop() ends it, sending gone for every controller. Gamepad.available() says whether the page has the API at all. A server given those lines reads them with Gamepad.Wire.parse/1.

Summary

Types

Where events go: a process, sent {:gamepad, event}, or a function of the event.

Functions

Whether this machine has a way to read controllers at all.

The path of gamepad.js, the reader a browser page runs.

Returns a specification to start this module under a supervisor.

The reader this machine would use: :linux, {:helper, path}, or nil.

Starts the reader.

Types

sink()

@type sink() :: pid() | (Gamepad.Wire.event() -> any())

Where events go: a process, sent {:gamepad, event}, or a function of the event.

Functions

available?()

@spec available?() :: boolean()

Whether this machine has a way to read controllers at all.

browser_script()

@spec browser_script() :: Path.t()

The path of gamepad.js, the reader a browser page runs.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

source()

@spec source() :: :linux | {:linux, String.t()} | {:helper, Path.t()} | nil

The reader this machine would use: :linux, {:helper, path}, or nil.

:linux is answered on Linux whether or not a controller is plugged in, since one may be plugged in later.

start_link(opts)

@spec start_link(keyword()) :: GenServer.on_start() | :ignore

Starts the reader.

Options

  • :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, as source/0 returns it; a {:helper, path} runs any program speaking the Gamepad.Wire protocol, and a {:linux, glob} reads the joystick device files matching glob

Returns :ignore when there is no reader.