grub-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH] usb_gamepad: Add support for Logitech Rumble Gamepad F510


From: Alexey Kutepov
Subject: Re: [PATCH] usb_gamepad: Add support for Logitech Rumble Gamepad F510
Date: Tue, 1 Oct 2019 00:30:06 +0700

1. Its class is HID, subclass and protocol are 0. It's just a regular
HID device only with its own custom report descriptor.
2. `grub_term_input` which is pretty much the only user input
mechanism in GRUB (as far as I could understand the code, I might be
wrong) expects keycodes, but the reports that we receive from the HID
device do not carry any keycodes at all. They describe the current
state of the gamepad (what button is currently pressed, how much a
stick is tilted, etc). You can find the description of that state in
the `logitech_rumble_f510_report` structure. We need a way to
translate the gamepad state changes into a sequence of keycodes if we
want to be able to control GRUB via `grub_term_input`. So that's why I
came up with a simple mapping mechanism. Please, let me know if this
is not ideal way of doing that.
3. I predicated on a particular model for now because, as far as I
could understand, different Gamepads (while being just HID devices)
may have different report descriptors (I read this tutorial on the
topic https://eleccelerator.com/tutorial-about-usb-hid-report-descriptors/).
So I just assumed that any [046d:c218] device should have the same HID
report structure (which I simply reversed engineered for my gamepad by
tracing the reports). Ideally the usb_gamepad module should first
query the HID report descriptor and parse the incoming HID reports
accordingly, but I could not find a standard GRUB mechanism that does
that, so I put that idea on the back burner for now. I used the
`usb_keyboard` module as a reference on how to do USB interactions
within the GRUB codebase.

On Mon, Sep 30, 2019 at 5:48 PM Vladimir 'phcoder' Serbinenko
<address@hidden> wrote:
>
> Patch looks good in concept but I have few questions:
> 1. How is it different from normal HID
> 2. Why do you do custom mapping I'm the driver, why not just pass the events 
> pretty much as is
> 3. Why is it predicated on a particular device model rather than on device 
> class?
>
> On Sun, 29 Sep 2019, 15:59 Alexey Kutepov, <address@hidden> wrote:
>>
>> Hello!
>>
>> I have a little bit of a strange patch to propose. Some time ago I saw
>> an interesting thread on Steam asking if it's possible to control GRUB
>> with a Gamepad 
>> https://steamcommunity.com/groups/steamuniverse/discussions/0/558751660797029626/
>> and I thought "this is a pretty interesting use case for some kind of
>> custom multimedia stations where you control everything (down to the
>> bootloader) with a gamepad!" So I implemented usb_gamepad module
>> hoping that it could be useful to somebody.
>>
>> The idea behind the module is very simple:
>>
>> - Attach to an HID USB device that is gamepad
>> - Create a GRUB terminal input
>> - Map actions from the gamepad to GRUB terminal input keys
>>
>> The module registers a bunch of commands to configure the mapping. A
>> basic usb_gamepad configuration in grub.cfg looks like this:
>>
>> ```
>> insmod usb_gamepad
>> terminal_input --append usb_gamepad0
>> gamepad_dpad U name up
>> gamepad_dpad D name down
>> gamepad_btn  1 code 13
>> ```
>>
>> It maps D-pad up/down directions to up/down keys correspondingly and
>> the first button to Enter key, so you can select an OS to boot in a
>> pretty conventional way.
>>
>> Unfortunately, it only works with Logitech Rumble Gamepad F510
>> [046d:c218] in DirectInput mode, because this is the only Gamepad I
>> have and I don't understand how XInput works. But I made a genuine
>> effort to structure the module so it's relatively easy to extend. So
>> if anybody later needs support for a different gamepad they can try to
>> extend/modify usb_gamepad module instead of implementing everything
>> from scratch.
>>
>> If this is too "out of scope" for the GRUB project, I'm ok with this
>> patch being rejected. If this is something useful I'm ready for your
>> feedback and critisim to get this patch to acceptable state.
>>
>> Regards,
>> Alexey
>> ---
>>  grub-core/Makefile.core.def  |   6 +
>>  grub-core/term/usb_gamepad.c | 683 +++++++++++++++++++++++++++++++++++
>>  2 files changed, 689 insertions(+)
>>  create mode 100644 grub-core/term/usb_gamepad.c
>>
>> diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
>> index 474a63e68..8046918e9 100644
>> --- a/grub-core/Makefile.core.def
>> +++ b/grub-core/Makefile.core.def
>> @@ -599,6 +599,12 @@ module = {
>>    enable = usb;
>>  };
>>
>> +module = {
>> +  name = usb_gamepad;
>> +  common = term/usb_gamepad.c;
>> +  enable = usb;
>> +};
>> +
>>  module = {
>>    name = usbserial_common;
>>    common = bus/usb/serial/common.c;
>> diff --git a/grub-core/term/usb_gamepad.c b/grub-core/term/usb_gamepad.c
>> new file mode 100644
>> index 000000000..c7bb25969
>> --- /dev/null
>> +++ b/grub-core/term/usb_gamepad.c
>> @@ -0,0 +1,683 @@
>> +#include <grub/dl.h>
>> +#include <grub/term.h>
>> +#include <grub/usb.h>
>> +#include <grub/command.h>
>> +
>> +GRUB_MOD_LICENSE ("GPLv3");
>> +
>> +typedef enum {
>> +    DIR_UP = 0x0,
>> +    DIR_UPRIGHT,
>> +    DIR_RIGHT,
>> +    DIR_DOWNRIGHT,
>> +    DIR_DOWN,
>> +    DIR_DOWNLEFT,
>> +    DIR_LEFT,
>> +    DIR_UPLEFT,
>> +    DIR_CENTERED,
>> +
>> +    DIR_COUNT
>> +} dir_t;
>> +
>> +typedef enum {
>> +    SIDE_LEFT = 0x0,
>> +    SIDE_RIGHT,
>> +
>> +    SIDE_COUNT
>> +} side_t;
>> +
>> +#define BUTTONS_COUNT 4
>> +#define GAMEPADS_CAPACITY 16
>> +#define KEY_QUEUE_CAPACITY 32
>> +#define USB_REPORT_SIZE 8
>> +
>> +#define LOGITECH_VENDORID 0x046d
>> +#define RUMBLEPAD_PRODUCTID 0xc218
>> +
>> +static int dpad_mapping[DIR_COUNT] = { GRUB_TERM_NO_KEY };
>> +static int button_mapping[BUTTONS_COUNT] = { GRUB_TERM_NO_KEY };
>> +static int bumper_mapping[SIDE_COUNT] = { GRUB_TERM_NO_KEY };
>> +static int trigger_mapping[SIDE_COUNT] = { GRUB_TERM_NO_KEY };
>> +static int stick_mapping[SIDE_COUNT][DIR_COUNT] = { GRUB_TERM_NO_KEY };
>> +static int stick_press_mapping[SIDE_COUNT] = { GRUB_TERM_NO_KEY };
>> +static int options_mapping[SIDE_COUNT] = { GRUB_TERM_NO_KEY };
>> +
>> +struct logitech_rumble_f510_report
>> +{
>> +    grub_uint8_t stick_axes[SIDE_COUNT * 2];
>> +    grub_uint8_t dpad: 4;
>> +    grub_uint8_t buttons: 4;
>> +    grub_uint8_t bumpers: 2;
>> +    grub_uint8_t triggers: 2;
>> +    grub_uint8_t options: 2;
>> +    grub_uint8_t sticks: 2;
>> +    grub_uint8_t mode;
>> +    grub_uint8_t padding;
>> +};
>> +
>> +struct grub_usb_gamepad_data
>> +{
>> +    grub_usb_device_t usbdev;
>> +    int configno;
>> +    int interfno;
>> +    struct grub_usb_desc_endp *endp;
>> +    grub_usb_transfer_t transfer;
>> +    grub_uint8_t prev_report[USB_REPORT_SIZE];
>> +    grub_uint8_t report[USB_REPORT_SIZE];
>> +    int key_queue[KEY_QUEUE_CAPACITY];
>> +    int key_queue_begin;
>> +    int key_queue_size;
>> +};
>> +
>> +static grub_uint8_t initial_logitech_rumble_f510_report[USB_REPORT_SIZE] = {
>> +    0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0xff
>> +};
>> +
>> +static struct grub_term_input gamepads[GAMEPADS_CAPACITY];
>> +
>> +static inline
>> +void key_queue_push(struct grub_usb_gamepad_data *data, int key)
>> +{
>> +    data->key_queue[(data->key_queue_begin + data->key_queue_size) %
>> KEY_QUEUE_CAPACITY] = key;
>> +
>> +    if (data->key_queue_size < KEY_QUEUE_CAPACITY) {
>> +        data->key_queue_size++;
>> +    } else {
>> +        data->key_queue_begin = (data->key_queue_begin + 1) %
>> KEY_QUEUE_CAPACITY;
>> +    }
>> +}
>> +
>> +static inline
>> +int key_queue_pop(struct grub_usb_gamepad_data *data)
>> +{
>> +    if (data->key_queue_size <= 0) {
>> +        return GRUB_TERM_NO_KEY;
>> +    }
>> +
>> +    int key = data->key_queue[data->key_queue_begin];
>> +    data->key_queue_begin = (data->key_queue_begin + 1) % 
>> KEY_QUEUE_CAPACITY;
>> +    data->key_queue_size--;
>> +
>> +    return key;
>> +}
>> +
>> +static dir_t
>> +dir_by_coords(grub_uint8_t x0, grub_uint8_t y0)
>> +{
>> +    grub_int32_t x = x0;
>> +    grub_int32_t y = y0;
>> +    x -= 127;
>> +    y -= 127;
>> +
>> +    const grub_int32_t t = 3276;
>> +
>> +    if (x * x + y * y > t) {
>> +        const grub_int32_t d = 40;
>> +#define POS(a) ((a) > (d))
>> +#define ZERO(a) ((grub_int32_t)grub_abs(a) <= (d))
>> +#define NEG(a) ((a) < -(d))
>> +        if (POS(x) && ZERO(y)) {
>> +            return DIR_RIGHT;
>> +        } else if (POS(x) && NEG(y)) {
>> +            return DIR_UPRIGHT;
>> +        } else if (ZERO(x) && NEG(y)) {
>> +            return DIR_UP;
>> +        } else if (NEG(x) && NEG(y)) {
>> +            return DIR_UPLEFT;
>> +        } else if (NEG(x) && ZERO(y)) {
>> +            return DIR_LEFT;
>> +        } else if (NEG(x) && POS(y)) {
>> +            return DIR_DOWNLEFT;
>> +        } else if (ZERO(x) && POS(y)) {
>> +            return DIR_DOWN;
>> +        } else if (POS(x) && POS(y)) {
>> +            return DIR_DOWNRIGHT;
>> +        }
>> +#undef POS
>> +#undef ZERO
>> +#undef NEG
>> +    }
>> +
>> +    return DIR_CENTERED;
>> +}
>> +
>> +static void logitech_rumble_f510_generate_keys(struct
>> grub_usb_gamepad_data *data)
>> +{
>> +#define IS_PRESSED(buttons, i) ((buttons) & (1 << (i)))
>> +    struct logitech_rumble_f510_report *prev_report = (struct
>> logitech_rumble_f510_report *)data->prev_report;
>> +    struct logitech_rumble_f510_report *report = (struct
>> logitech_rumble_f510_report *)data->report;
>> +
>> +    if (prev_report->dpad != report->dpad) {
>> +        key_queue_push(data, dpad_mapping[report->dpad]);
>> +    }
>> +
>> +    for (int i = 0; i < BUTTONS_COUNT; ++i) {
>> +        if (!IS_PRESSED(prev_report->buttons, i)
>> +            && IS_PRESSED(report->buttons, i)) {
>> +            key_queue_push(data, button_mapping[i]);
>> +        }
>> +    }
>> +
>> +    for (int side = 0; side < SIDE_COUNT; ++side) {
>> +        if (!IS_PRESSED(prev_report->bumpers, side)
>> +            && IS_PRESSED(report->bumpers, side)) {
>> +            key_queue_push(data, bumper_mapping[side]);
>> +        }
>> +
>> +        if (!IS_PRESSED(prev_report->triggers, side)
>> +            && IS_PRESSED(report->triggers, side)) {
>> +            key_queue_push(data, trigger_mapping[side]);
>> +        }
>> +
>> +        dir_t prev_dir = dir_by_coords(
>> +            prev_report->stick_axes[side * 2],
>> +            prev_report->stick_axes[side * 2 + 1]);
>> +
>> +        dir_t dir = dir_by_coords(
>> +            report->stick_axes[side * 2],
>> +            report->stick_axes[side * 2 + 1]);
>> +
>> +        if (prev_dir != dir) {
>> +            key_queue_push(data, stick_mapping[side][dir]);
>> +        }
>> +
>> +        if (!IS_PRESSED(prev_report->sticks, side)
>> +            && IS_PRESSED(report->sticks, side)) {
>> +            key_queue_push(data, stick_press_mapping[side]);
>> +        }
>> +
>> +        if (!IS_PRESSED(prev_report->options, side)
>> +            && IS_PRESSED(report->options, side)) {
>> +            key_queue_push(data, options_mapping[side]);
>> +        }
>> +    }
>> +#undef IS_PRESSED
>> +}
>> +
>> +static int
>> +usb_gamepad_getkey (struct grub_term_input *term)
>> +{
>> +    struct grub_usb_gamepad_data *termdata = term->data;
>> +    grub_size_t actual;
>> +
>> +    grub_usb_err_t err = grub_usb_check_transfer (termdata->transfer, 
>> &actual);
>> +
>> +    if (err != GRUB_USB_ERR_WAIT) {
>> +        logitech_rumble_f510_generate_keys(termdata);
>> +        grub_memcpy(termdata->prev_report, termdata->report, 
>> USB_REPORT_SIZE);
>> +
>> +        termdata->transfer = grub_usb_bulk_read_background (
>> +            termdata->usbdev,
>> +            termdata->endp,
>> +            sizeof (termdata->report),
>> +            (char *) &termdata->report);
>> +
>> +        if (!termdata->transfer)
>> +        {
>> +            grub_print_error ();
>> +        }
>> +    }
>> +
>> +    return key_queue_pop(termdata);
>> +}
>> +
>> +static int
>> +usb_gamepad_getkeystatus (struct grub_term_input *term __attribute__
>> ((unused)))
>> +{
>> +    return 0;
>> +}
>> +
>> +static void
>> +grub_usb_gamepad_detach (grub_usb_device_t usbdev,
>> +                         int config __attribute__ ((unused)),
>> +                         int interface __attribute__ ((unused)))
>> +{
>> +    grub_dprintf("usb_gamepad", "Detaching usb_gamepad...\n");
>> +
>> +    for (grub_size_t i = 0; i < ARRAY_SIZE(gamepads); ++i) {
>> +        if (!gamepads[i].data) {
>> +            continue;
>> +        }
>> +
>> +        struct grub_usb_gamepad_data *data = gamepads[i].data;
>> +
>> +        if (data->usbdev != usbdev) {
>> +            continue;
>> +        }
>> +
>> +        if (data->transfer) {
>> +            grub_usb_cancel_transfer(data->transfer);
>> +        }
>> +
>> +        grub_term_unregister_input(&gamepads[i]);
>> +        grub_free((char *) gamepads[i].name);
>> +        gamepads[i].name = NULL;
>> +        grub_free(gamepads[i].data);
>> +        gamepads[i].data = NULL;
>> +    }
>> +}
>> +
>> +
>> +static int
>> +grub_usb_gamepad_attach(grub_usb_device_t usbdev, int configno, int 
>> interfno)
>> +{
>> +    if ((usbdev->descdev.vendorid != LOGITECH_VENDORID)
>> +        || (usbdev->descdev.prodid != RUMBLEPAD_PRODUCTID)) {
>> +        grub_dprintf("usb_gamepad",
>> +                     "Ignoring vendor %x, product %x. "
>> +                     "Only vendor %x and product %x are supported\n",
>> +                     usbdev->descdev.vendorid,
>> +                     usbdev->descdev.prodid,
>> +                     LOGITECH_VENDORID,
>> +                     RUMBLEPAD_PRODUCTID);
>> +        return 0;
>> +    }
>> +
>> +    grub_dprintf("usb_gamepad", "usb_gamepad configno: %d, interfno:
>> %d\n", configno, interfno);
>> +
>> +    unsigned curnum = 0;
>> +    for (curnum = 0; curnum < ARRAY_SIZE(gamepads); ++curnum)
>> +        if (gamepads[curnum].data == 0)
>> +            break;
>> +
>> +    if (curnum >= ARRAY_SIZE(gamepads)) {
>> +        grub_dprintf("usb_gamepad",
>> +                     "Reached limit of attached gamepads. The limit is 
>> %d.\n",
>> +                     GAMEPADS_CAPACITY);
>> +        return 0;
>> +    }
>> +
>> +    grub_dprintf("usb_gamepad", "Endpoints: %d\n",
>> +
>> usbdev->config[configno].interf[interfno].descif->endpointcnt);
>> +
>> +    struct grub_usb_desc_endp *endp = NULL;
>> +    int j = 0;
>> +    for (j = 0;
>> +         j < usbdev->config[configno].interf[interfno].descif->endpointcnt;
>> +         j++)
>> +    {
>> +        endp = &usbdev->config[configno].interf[interfno].descendp[j];
>> +
>> +        if ((endp->endp_addr & 128) && grub_usb_get_ep_type(endp)
>> +            == GRUB_USB_EP_INTERRUPT)
>> +            break;
>> +    }
>> +
>> +    if (j == usbdev->config[configno].interf[interfno].descif->endpointcnt) 
>> {
>> +        grub_dprintf("usb_gamepad", "No fitting endpoints found.\n");
>> +        return 0;
>> +    }
>> +
>> +    grub_dprintf ("usb_gamepad", "HID usb_gamepad found! Endpoint: %d\n", 
>> j);
>> +
>> +    struct grub_usb_gamepad_data *data = grub_malloc(sizeof(struct
>> grub_usb_gamepad_data));
>> +    if (!data) {
>> +        grub_print_error();
>> +        return 0;
>> +    }
>> +
>> +    gamepads[curnum].name = grub_xasprintf("usb_gamepad%d", curnum);
>> +    gamepads[curnum].getkey = usb_gamepad_getkey;
>> +    gamepads[curnum].getkeystatus = usb_gamepad_getkeystatus;
>> +    gamepads[curnum].data = data;
>> +    gamepads[curnum].next = 0;
>> +
>> +    usbdev->config[configno].interf[interfno].detach_hook =
>> grub_usb_gamepad_detach;
>> +    data->usbdev = usbdev;
>> +    data->configno = configno;
>> +    data->interfno = interfno;
>> +    data->endp = endp;
>> +    data->key_queue_begin = 0;
>> +    data->key_queue_size = 0;
>> +    grub_memcpy(data->prev_report,
>> initial_logitech_rumble_f510_report, USB_REPORT_SIZE);
>> +    data->transfer = grub_usb_bulk_read_background (
>> +        usbdev,
>> +        data->endp,
>> +        sizeof (data->report),
>> +        (char *) &data->report);
>> +
>> +    if (!data->transfer) {
>> +        grub_print_error ();
>> +        return 0;
>> +    }
>> +
>> +    grub_term_register_input_active("usb_gamepad", &gamepads[curnum]);
>> +
>> +    return 0;
>> +}
>> +
>> +struct dir_name_t
>> +{
>> +    const char *name;
>> +    dir_t value;
>> +};
>> +
>> +static struct dir_name_t dir_names[] = {
>> +    {"U",  DIR_UP},
>> +    {"UR", DIR_UPRIGHT},
>> +    {"RU", DIR_UPRIGHT},
>> +    {"R",  DIR_RIGHT},
>> +    {"DR", DIR_DOWNRIGHT},
>> +    {"RD", DIR_DOWNRIGHT},
>> +    {"D",  DIR_DOWN},
>> +    {"DL", DIR_DOWNLEFT},
>> +    {"LD", DIR_DOWNLEFT},
>> +    {"L",  DIR_LEFT},
>> +    {"UL", DIR_UPLEFT},
>> +    {"LU", DIR_UPLEFT},
>> +    {"C" , DIR_CENTERED}
>> +};
>> +
>> +static grub_err_t
>> +parse_dir_by_name(const char *name,
>> +                  dir_t *dir)
>> +{
>> +    for (grub_size_t i = 0; i < ARRAY_SIZE(dir_names); ++i) {
>> +        if (grub_strcmp(name, dir_names[i].name) == 0) {
>> +            *dir = dir_names[i].value;
>> +            return GRUB_ERR_NONE;
>> +        }
>> +    }
>> +
>> +    return grub_error(
>> +        GRUB_ERR_BAD_ARGUMENT,
>> +        N_("%s is not a valid direction name"),
>> +        name);
>> +}
>> +
>> +typedef struct {
>> +    const char *name;
>> +    int keycode;
>> +} key_mapping_t;
>> +
>> +static key_mapping_t key_mapping[] = {
>> +    {"up",       GRUB_TERM_KEY_UP},
>> +    {"down",     GRUB_TERM_KEY_DOWN},
>> +    {"left",     GRUB_TERM_KEY_LEFT},
>> +    {"right",    GRUB_TERM_KEY_RIGHT},
>> +    {"home",     GRUB_TERM_KEY_HOME},
>> +    {"end",      GRUB_TERM_KEY_END},
>> +    {"dc",       GRUB_TERM_KEY_DC},
>> +    {"ppage",    GRUB_TERM_KEY_PPAGE},
>> +    {"npage",    GRUB_TERM_KEY_NPAGE},
>> +    {"f1",       GRUB_TERM_KEY_F1},
>> +    {"f2",       GRUB_TERM_KEY_F2},
>> +    {"f3",       GRUB_TERM_KEY_F3},
>> +    {"f4",       GRUB_TERM_KEY_F4},
>> +    {"f5",       GRUB_TERM_KEY_F5},
>> +    {"f6",       GRUB_TERM_KEY_F6},
>> +    {"f7",       GRUB_TERM_KEY_F7},
>> +    {"f8",       GRUB_TERM_KEY_F8},
>> +    {"f9",       GRUB_TERM_KEY_F9},
>> +    {"f10",      GRUB_TERM_KEY_F10},
>> +    {"f11",      GRUB_TERM_KEY_F11},
>> +    {"f12",      GRUB_TERM_KEY_F12},
>> +    {"insert",   GRUB_TERM_KEY_INSERT},
>> +    {"center",   GRUB_TERM_KEY_CENTER},
>> +    {"esc",      GRUB_TERM_ESC},
>> +    {"tab",      GRUB_TERM_TAB},
>> +    {"bspace",   GRUB_TERM_BACKSPACE},
>> +    {"space",    32},
>> +};
>> +
>> +static
>> +grub_err_t parse_keycode_name(const char *type,
>> +                              const char *input,
>> +                              int *keycode)
>> +{
>> +    if (grub_strcmp(type, "code") == 0) {
>> +        *keycode = grub_strtol(input, 0, 10);
>> +
>> +        if (grub_errno) {
>> +            return grub_error(grub_errno, N_("`%s` is not a number"), 
>> input);
>> +        }
>> +    } else if (grub_strcmp(type, "char") == 0) {
>> +        if (grub_strlen(input) <= 0) {
>> +            return grub_error(
>> +                GRUB_ERR_BAD_ARGUMENT,
>> +                N_("Cannot accept an empty string as character for 
>> mapping"));
>> +        }
>> +
>> +        *keycode = input[0];
>> +    } else if (grub_strcmp(type, "name") == 0) {
>> +        const int n = sizeof(key_mapping) / sizeof(key_mapping[0]);
>> +        for (int i = 0; i < n; ++i) {
>> +            if (grub_strcmp(input, key_mapping[i].name) == 0) {
>> +                *keycode = key_mapping[i].keycode;
>> +                return GRUB_ERR_NONE;
>> +            }
>> +        }
>> +
>> +        return grub_error(
>> +            GRUB_ERR_BAD_ARGUMENT,
>> +            N_("`%s` is not a correct key name"),
>> +            input);
>> +    } else {
>> +        return grub_error(
>> +            GRUB_ERR_BAD_ARGUMENT,
>> +            N_("`%s` is not a correct keycode mapping type"),
>> +            type);
>> +    }
>> +
>> +    return GRUB_ERR_NONE;
>> +}
>> +
>> +#define ASSERT_ARGC(argc, N)                            \
>> +    do {                                                \
>> +        if (argc < N) {                                 \
>> +            return grub_error(                          \
>> +                GRUB_ERR_BAD_ARGUMENT,                  \
>> +                N_("Expected at least %d arguments"),   \
>> +                N);                                     \
>> +        }                                               \
>> +    } while(0)
>> +
>> +static grub_err_t
>> +grub_cmd_gamepad_btn(grub_command_t cmd __attribute__((unused)),
>> +                     int argc, char **args)
>> +{
>> +    ASSERT_ARGC(argc, 3);
>> +
>> +    long button_number = grub_strtol(args[0], 0, 10);
>> +    if (grub_errno) {
>> +        return grub_error(
>> +            grub_errno,
>> +            N_("Expected button number. `%s` is not a number."),
>> +            args[0]);
>> +    }
>> +
>> +    if (!(0 <= button_number && button_number < 4)) {
>> +        return grub_error(
>> +            GRUB_ERR_BAD_ARGUMENT,
>> +            N_("Button number should be within the range of 0-3."));
>> +    }
>> +
>> +    int keycode = 0;
>> +    grub_err_t err = parse_keycode_name(args[1], args[2], &keycode);
>> +    if (err) {
>> +        return err;
>> +    }
>> +
>> +    button_mapping[button_number] = keycode;
>> +
>> +    return GRUB_ERR_NONE;
>> +}
>> +
>> +static grub_err_t
>> +grub_cmd_gamepad_dpad(grub_command_t cmd __attribute__((unused)),
>> +                      int argc, char **args)
>> +{
>> +    ASSERT_ARGC(argc, 3);
>> +
>> +    dir_t dpad_dir = 0;
>> +    grub_err_t err = parse_dir_by_name(args[0], &dpad_dir);
>> +    if (err) {
>> +        return err;
>> +    }
>> +
>> +    int keycode = 0;
>> +    err = parse_keycode_name(args[1], args[2], &keycode);
>> +    if (err) {
>> +        return err;
>> +    }
>> +
>> +    dpad_mapping[dpad_dir] = keycode;
>> +
>> +    return GRUB_ERR_NONE;
>> +}
>> +
>> +static grub_err_t
>> +grub_cmd_gamepad_sided(grub_command_t cmd, int argc, char **args)
>> +{
>> +    side_t side =
>> +        cmd->name[8] == 'l'
>> +        ? SIDE_LEFT
>> +        : SIDE_RIGHT;
>> +
>> +    dir_t dir = DIR_CENTERED;
>> +    int keycode = 0;
>> +    grub_err_t err = GRUB_ERR_NONE;
>> +
>> +    switch (cmd->name[9]) {
>> +    case 'b': {
>> +        ASSERT_ARGC(argc, 2);
>> +
>> +        err = parse_keycode_name(args[0], args[1], &keycode);
>> +        if (err) {
>> +            return err;
>> +        }
>> +
>> +        bumper_mapping[side] = keycode;
>> +    } break;
>> +
>> +    case 't': {
>> +        ASSERT_ARGC(argc, 2);
>> +
>> +        err = parse_keycode_name(args[0], args[1], &keycode);
>> +        if (err) {
>> +            return err;
>> +        }
>> +
>> +        trigger_mapping[side] = keycode;
>> +    } break;
>> +
>> +    case 's': {
>> +        ASSERT_ARGC(argc, 3);
>> +
>> +        if (grub_strcmp(args[0], "P") == 0) {
>> +            err = parse_keycode_name(args[1], args[2], &keycode);
>> +            if (err) {
>> +                return err;
>> +            }
>> +
>> +            stick_press_mapping[side] = keycode;
>> +        } else {
>> +            err = parse_dir_by_name(args[0], &dir);
>> +            if (err) {
>> +                return err;
>> +            }
>> +
>> +            err = parse_keycode_name(args[1], args[2], &keycode);
>> +            if (err) {
>> +                return err;
>> +            }
>> +
>> +            stick_mapping[side][dir] = keycode;
>> +        }
>> +    } break;
>> +    }
>> +
>> +    return GRUB_ERR_NONE;
>> +}
>> +
>> +static grub_err_t
>> +grub_cmd_gamepad_options(grub_command_t cmd, int argc, char **args)
>> +{
>> +    ASSERT_ARGC(argc, 2);
>> +
>> +    int keycode = 0;
>> +    grub_err_t err = parse_keycode_name(args[0], args[1], &keycode);
>> +    if (err) {
>> +        return err;
>> +    }
>> +
>> +    if (cmd->name[8] == 'b') {
>> +        options_mapping[SIDE_LEFT] = keycode;
>> +    } else if (cmd->name[8] == 's') {
>> +        options_mapping[SIDE_RIGHT] = keycode;
>> +    }
>> +
>> +    return GRUB_ERR_NONE;
>> +}
>> +
>> +static struct grub_usb_attach_desc attach_hook =
>> +{
>> +    .class = GRUB_USB_CLASS_HID,
>> +    .hook = grub_usb_gamepad_attach
>> +};
>> +
>> +struct command_proto {
>> +    const char *name;
>> +    grub_command_func_t func;
>> +    const char *summary;
>> +    const char *description;
>> +};
>> +
>> +static struct command_proto cmds_proto[] = {
>> +    {"gamepad_dpad",grub_cmd_gamepad_dpad,N_("<dpad-direction>
>> <key>"),N_("Map gamepad dpad direction to a key")},
>> +    {"gamepad_btn",grub_cmd_gamepad_btn,N_("<button-number>
>> <key>"),N_("Map gamepad button to a key")},
>> +    {"gamepad_lb",grub_cmd_gamepad_sided,N_("<key>"),N_("Map gamepad
>> Left Bumper to a key")},
>> +    {"gamepad_rb",grub_cmd_gamepad_sided,N_("<key>"),N_("Map gamepad
>> Right Bumper to a key")},
>> +    {"gamepad_lt",grub_cmd_gamepad_sided,N_("<key>"),N_("Map gamepad
>> Left Trigger to a key")},
>> +    {"gamepad_rt",grub_cmd_gamepad_sided,N_("<key>"),N_("Map gamepad
>> Right Trigger to a key")},
>> +    {"gamepad_ls",grub_cmd_gamepad_sided,N_("<direction|P>
>> <key>"),N_("Map gamepad Left Stick Action to a key")},
>> +    {"gamepad_rs",grub_cmd_gamepad_sided,N_("<direction|P>
>> <key>"),N_("Map gamepad Right Stick Action to a key")},
>> +    {"gamepad_back",grub_cmd_gamepad_options,N_("<key>"),N_("Map
>> gamepad Back button to a key")},
>> +    {"gamepad_start",grub_cmd_gamepad_options,N_("<key>"),N_("Map
>> gamepad Start button to a key")}
>> +};
>> +
>> +static grub_command_t cmds[ARRAY_SIZE(cmds_proto)];
>> +
>> +GRUB_MOD_INIT(usb_gamepad)
>> +{
>> +    grub_dprintf("usb_gamepad", "Usb_Gamepad module loaded\n");
>> +
>> +    for (grub_size_t i = 0; i < ARRAY_SIZE(cmds); ++i) {
>> +        cmds[i] = grub_register_command(
>> +            cmds_proto[i].name,
>> +            cmds_proto[i].func,
>> +            cmds_proto[i].summary,
>> +            cmds_proto[i].description);
>> +    }
>> +
>> +    grub_usb_register_attach_hook_class(&attach_hook);
>> +}
>> +
>> +GRUB_MOD_FINI(usb_gamepad)
>> +{
>> +    for (grub_size_t i = 0; i < ARRAY_SIZE(cmds); ++i) {
>> +        grub_unregister_command(cmds[i]);
>> +    }
>> +
>> +    for (grub_size_t i = 0; i < ARRAY_SIZE(gamepads); ++i) {
>> +        if (!gamepads[i].data) {
>> +            continue;
>> +        }
>> +
>> +        struct grub_usb_gamepad_data *data = gamepads[i].data;
>> +
>> +        if (data->transfer) {
>> +            grub_usb_cancel_transfer(data->transfer);
>> +        }
>> +
>> +        grub_term_unregister_input(&gamepads[i]);
>> +        grub_free((char *) gamepads[i].name);
>> +        gamepads[i].name = NULL;
>> +        grub_free(gamepads[i].data);
>> +        gamepads[i].data = NULL;
>> +    }
>> +
>> +    grub_usb_unregister_attach_hook_class (&attach_hook);
>> +
>> +    grub_dprintf("usb_gamepad", "usb_gamepad fini-ed\n");
>> +}
>> --
>> 2.19.2
>>
>> _______________________________________________
>> Grub-devel mailing list
>> address@hidden
>> https://lists.gnu.org/mailman/listinfo/grub-devel
>
> _______________________________________________
> Grub-devel mailing list
> address@hidden
> https://lists.gnu.org/mailman/listinfo/grub-devel



reply via email to

[Prev in Thread] Current Thread [Next in Thread]