|
| 1 | +# VICE Joystick API |
| 2 | + |
| 3 | +> To get a nicely formatted HTML version of this document, including syntax |
| 4 | +> highlighting, use: |
| 5 | +> |
| 6 | +> `pandoc -s -t html -f gfm joystick.md > joystick.html` |
| 7 | +
|
| 8 | + |
| 9 | +## Preface |
| 10 | + |
| 11 | +This document describes the updated joystick API, which currently is a **work in |
| 12 | +progress**. All information herein is subject to change while the joystick code |
| 13 | +is being worked on. The inner workings of the actual emulation of the I/O system |
| 14 | +will not be described, just the translation of host device input to emulated |
| 15 | +joystick device, so no actual CIA/VIA emulation. |
| 16 | + |
| 17 | + |
| 18 | +## Overview of the joystick system in VICE |
| 19 | + |
| 20 | +The joystick system in VICE is split into two parts: **common code** and |
| 21 | +**driver code**. The driver code is specific to an OS/UI, while the common code, |
| 22 | +as the name implies, is used for every OS/UI. |
| 23 | + |
| 24 | +### Common code |
| 25 | + |
| 26 | +The common code (in `src/joyport/`) is responsible for interpreting data from |
| 27 | +the drivers and passing that to the emulation, as well as handling mapping and |
| 28 | +calibration of host inputs to emulated inputs. It is also responsible for |
| 29 | +providing the UI with information on host and emulated devices, and at a later |
| 30 | +point, passing host input to the UI for mapping and calibration dialogs. |
| 31 | + |
| 32 | +### Driver code |
| 33 | + |
| 34 | +The driver code is responsible for reading data from a host device and passing |
| 35 | +that back to the common code, as well as providing the common code with a list |
| 36 | +of available host devices and their properties. |
| 37 | + |
| 38 | + |
| 39 | +## Changes in the separation of driver and common code |
| 40 | + |
| 41 | +I've tried to keep the code required to implement a driver as small as possible, |
| 42 | +moving a number of responsibilities from the drivers to the common code. |
| 43 | + |
| 44 | +* The old code would let the driver interpret raw axis and button values and |
| 45 | + send that back to the emulation (during the `poll()` callback). The drivers |
| 46 | + now simply pass the raw values to the common code, and the common code |
| 47 | + interprets those values with the help of the information on the host devices |
| 48 | + provided by the driver (while also doing calibration). |
| 49 | + |
| 50 | +* A driver no longer needs to concern itself with ordering inputs, the common |
| 51 | + code handles that. |
| 52 | + |
| 53 | +* Every input now has a unique *code*, which can be an event code (like with |
| 54 | + Linux' evdev), a simple index of the input (as in SDL) or a HID usage code |
| 55 | + (as on FreeBSD/NetBSD). The API provides drivers with methods of looking up |
| 56 | + axis, button and hat objects through their respective code. |
| 57 | + |
| 58 | +* Event handlers in the common code now refer to inputs by instance, not index. |
| 59 | + So for an axis event a driver would call `joy_axis_event()` with a host device |
| 60 | + instance, axis instance and raw axis value. |
| 61 | + |
| 62 | + |
| 63 | +**TODO**: Proper (simple) description of `joystick_device_t` and its members |
| 64 | + `joystick_axis_t`, `joystick_button_t` and `joystick_hat_t`. |
| 65 | + |
| 66 | +**TODO**: Explain ownership of objects (container assumes ownership of its |
| 67 | + elements and is responsible for freeing them after use, etc). |
| 68 | + |
| 69 | + |
| 70 | +## Implementing a driver |
| 71 | + |
| 72 | +Implementing a driver should be fairly straightforward. A driver registers |
| 73 | +itself with the joystick system and adds host devices it has detected. |
| 74 | + |
| 75 | +During joystick system initialization an arch-specific initialization function |
| 76 | +is called (and expected to be implemented by the driver), where the driver |
| 77 | +registers itself and adds host devices: |
| 78 | + |
| 79 | +```C |
| 80 | +void joystick_arch_init(void) |
| 81 | +``` |
| 82 | +
|
| 83 | +The function to register the driver is: |
| 84 | +
|
| 85 | +```C |
| 86 | +void joystick_driver_register(const joystick_driver_t *driver) |
| 87 | +``` |
| 88 | + |
| 89 | +Where `joystick_driver_t` is defined as: |
| 90 | +```C |
| 91 | +typedef struct joystick_driver_s { |
| 92 | + /** \brief Open host device for use */ |
| 93 | + bool (*open) (joystick_device_t *); |
| 94 | + |
| 95 | + /** \brief Poll host device */ |
| 96 | + void (*poll) (joystick_device_t *); |
| 97 | + |
| 98 | + /** \brief Close host device */ |
| 99 | + void (*close) (joystick_device_t *); |
| 100 | + |
| 101 | + /** \brief Optional method to free arch-specific device data */ |
| 102 | + void (*priv_free)(void *); |
| 103 | + |
| 104 | + /** \brief Function to call after registering a device |
| 105 | + * |
| 106 | + * This function is called after #joystick_device_register has processed |
| 107 | + * its argument. It can be used to customize mappings or calibration if so |
| 108 | + * required. |
| 109 | + */ |
| 110 | + void (*customize)(joystick_device_t *); |
| 111 | + |
| 112 | +} joystick_driver_t; |
| 113 | +``` |
| 114 | + |
| 115 | +> Currently (re)opening a device hasn't been implemented yet, so the `open()` |
| 116 | +> method can be ignored, for now. |
| 117 | +
|
| 118 | +### Driver methods |
| 119 | + |
| 120 | +The `poll()` method is called by the emulation at the end of *every emulated |
| 121 | +scanline*, and is expected to process any pending event data and pass that |
| 122 | +along to `joy_axis_event()`, `joy_button_event()` or `joy_hat_event()`. |
| 123 | + |
| 124 | +The `close()` method should close the host device (e.g. close file descriptor) |
| 125 | +and put the device in a proper state for opening again. It should **not** free |
| 126 | +its private data in the `priv` member of the `joystick_device_t`, that is done |
| 127 | +in the `priv_free()` method, called by the joystick system on shutdown. |
| 128 | +It should also **not** free the joystick device instance, that again is done by |
| 129 | +the joystick system. |
| 130 | + |
| 131 | +The `priv_free()` method (if used) is, as mentioned above, called on emulator |
| 132 | +shutdown (or once we implement plug-n-pray, on device unplugging), and can be |
| 133 | +used to free any arch-specific resources that cannot be contained in the |
| 134 | +`joystick_device_t` instance or its members. |
| 135 | +> For example: the DirectInput driver for Windows stores a `GUID` and an |
| 136 | +> `LPDIRECTINPUTDEVICE8` instance in `priv`. |
| 137 | +
|
| 138 | +The `customize()` method can be used to customize the default mapping and |
| 139 | +calibration applied by the joystick system when `joystick_device_register()` is |
| 140 | +called. |
| 141 | + |
| 142 | + |
| 143 | +### Example of driver implementation |
| 144 | + |
| 145 | +The basic structure of a driver is the following: |
| 146 | + |
| 147 | +```C |
| 148 | + |
| 149 | +/* Some arch-specific data of a device (obvious pseudo code) */ |
| 150 | +typedef struct foo_priv_s { |
| 151 | + FOO_DEVICE *foodev; |
| 152 | +} foo_priv_t; |
| 153 | + |
| 154 | + |
| 155 | +/* Declaration of driver methods */ |
| 156 | +static joystick_driver_t foo_driver = { |
| 157 | + .poll = foo_poll, |
| 158 | + .close = foo_close |
| 159 | + .priv_free = foo_priv_free |
| 160 | +}; |
| 161 | + |
| 162 | + |
| 163 | +/* |
| 164 | + * Called after the joystick system has initialized during emulator boot |
| 165 | + */ |
| 166 | +void joystick_arch_init(void) |
| 167 | +{ |
| 168 | + /* Arch-specific initialization, if required */ |
| 169 | + FOO_JOYSTICK_SYSTEM_INIT(); |
| 170 | + |
| 171 | + /* Register driver */ |
| 172 | + joystick_driver_register(&foo_driver); |
| 173 | + |
| 174 | + /* Iterate devices and register them with the joystick system */ |
| 175 | + for (int i = 0; i < NUM_HOST_DEVICES; i++) { |
| 176 | + |
| 177 | + joystick_device_t *joydev = joystick_device_new(); |
| 178 | + |
| 179 | + FOO_DEVICE *foodev = OPEN_FOO_DEVICE(i); |
| 180 | + |
| 181 | + joystick_device_set_name(joydev, foodev->name); |
| 182 | + joystick_device_set_node(joydev, foodev->...); /* filesystem node of |
| 183 | + device, GUID string, |
| 184 | + whatever */ |
| 185 | + joydev->vendor = foodev->vendor_id; /* USB HID vendor ID */ |
| 186 | + joydev->product = foodev->product_id; /* USB HID product ID */ |
| 187 | + |
| 188 | + /* Iterate axes, buttons and perhaps hats of a device and add them */ |
| 189 | + for (int a = 0; a < NUM_AXES(foodev); a++) { |
| 190 | + |
| 191 | + joystick_axis_t *axis = joystick_axis_new(foodev->AXES[a].name); |
| 192 | + axis->code = foodev->AXES[a].code; /* some unique event code, can |
| 193 | + be HID usage, or just index |
| 194 | + of axis */ |
| 195 | + /* set limits if available */ |
| 196 | + axis->minimum = foodev->AXES[a].min; /* default is INT16_MIN */ |
| 197 | + axis->maximum = foodev->AXES[a].max; /* default is INT16_MAX */ |
| 198 | + |
| 199 | + /* store arch-specific data in `priv` member */ |
| 200 | + foo_priv_t *priv = lib_malloc(sizeof *priv); |
| 201 | + priv->foodev = foodev; |
| 202 | + joydev->priv = priv; |
| 203 | + |
| 204 | + /* add axis to device: device takes ownership */ |
| 205 | + joystick_device_add_axis(joydev, axis); |
| 206 | + } |
| 207 | + |
| 208 | + /* |
| 209 | + * ... Do the same for buttons and hats, if available ... |
| 210 | + */ |
| 211 | + |
| 212 | + /* Now register the device with the joystick system: the joystick |
| 213 | + * system takes ownership of the device and its members |
| 214 | + */ |
| 215 | + joystick_device_register(joydev); |
| 216 | + } |
| 217 | +} |
| 218 | + |
| 219 | + |
| 220 | +/* |
| 221 | + * Clean up any arch-specific resources here on emulator shutdown |
| 222 | + */ |
| 223 | +void joystick_arch_shutdown(void) |
| 224 | +{ |
| 225 | + FOO_JOYSTICK_SYSTEM_CLOSE(); |
| 226 | +} |
| 227 | + |
| 228 | + |
| 229 | +static void foo_poll(joystick_device_t *joydev) |
| 230 | +{ |
| 231 | + foo_priv_t *priv = joydev->priv; |
| 232 | + |
| 233 | + while (HAS_EVENT_PENDING(priv->foodev) { |
| 234 | + FOO_EVENT event = GET_EVENT(priv->foodev); |
| 235 | + |
| 236 | + switch (event.type) { |
| 237 | + case FOO_AXIS: |
| 238 | + joystick_axis_t *axis = joystick_axis_from_code(joydev, event.code); |
| 239 | + joy_axis_event(axis, event.value); |
| 240 | + break; |
| 241 | + case FOO_BUTTON: |
| 242 | + joystick_button_t *button = joystick_button_from_code(joydev, event.code); |
| 243 | + joy_button_event(button, event.value); |
| 244 | + break; |
| 245 | + } |
| 246 | + } |
| 247 | +} |
| 248 | + |
| 249 | + |
| 250 | +static void foo_close(joystick_device_t *joydev) |
| 251 | +{ |
| 252 | + foo_priv_t *priv = joydev->priv; |
| 253 | + |
| 254 | + if (priv->foodev != NULL) { |
| 255 | + FOO_DEVICE_CLOSE(priv->foodev); |
| 256 | + priv->foodev = NULL; |
| 257 | + } |
| 258 | +} |
| 259 | + |
| 260 | + |
| 261 | +static void foo_priv_free(void *priv) |
| 262 | +{ |
| 263 | + foo_priv_t *p = priv; |
| 264 | + |
| 265 | + FOO_DEVICE_FREE(p->foodev); |
| 266 | + lib_free(p); |
| 267 | +} |
| 268 | +``` |
| 269 | +
|
0 commit comments