diff --git a/config_spec.yml b/config_spec.yml index 087d255faef..9d96b1f48f3 100644 --- a/config_spec.yml +++ b/config_spec.yml @@ -21,9 +21,13 @@ general: input: bindings: + port1_driver: string port1: string + port2_driver: string port2: string + port3_driver: string port3: string + port4_driver: string port4: string peripherals: port1: diff --git a/data/controller_mask_s.png b/data/controller_mask_s.png new file mode 100644 index 00000000000..8406effbca9 Binary files /dev/null and b/data/controller_mask_s.png differ diff --git a/data/controller_mask_s.svg b/data/controller_mask_s.svg new file mode 100644 index 00000000000..a3cbf84c232 --- /dev/null +++ b/data/controller_mask_s.svg @@ -0,0 +1,86 @@ + + + Controller S + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + X + + + + B + + + + A + + + + Y + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/meson.build b/data/meson.build index e1a7ebedf4e..bb2084bc1c3 100644 --- a/data/meson.build +++ b/data/meson.build @@ -1,5 +1,6 @@ pfiles = [ 'controller_mask.png', + 'controller_mask_s.png', 'xmu_mask.png', 'logo_sdf.png', 'xemu_64x64.png', diff --git a/hw/xbox/meson.build b/hw/xbox/meson.build index 907ebdc4e8a..6149e48e424 100644 --- a/hw/xbox/meson.build +++ b/hw/xbox/meson.build @@ -15,6 +15,7 @@ specific_ss.add(files( 'xbox.c', 'xbox_pci.c', 'xid.c', + 'xid-gamepad.c', )) subdir('nv2a') subdir('mcpx') diff --git a/hw/xbox/xid-gamepad.c b/hw/xbox/xid-gamepad.c new file mode 100644 index 00000000000..5e27e4912a9 --- /dev/null +++ b/hw/xbox/xid-gamepad.c @@ -0,0 +1,293 @@ +/* + * QEMU USB XID Devices + * + * Copyright (c) 2013 espes + * Copyright (c) 2017 Jannik Vogel + * Copyright (c) 2018-2021 Matt Borgerson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "xid.h" + +// #define DEBUG_XID +#ifdef DEBUG_XID +#define DPRINTF printf +#else +#define DPRINTF(...) +#endif + +#define USB_VENDOR_MICROSOFT 0x045e + +#define GAMEPAD_IN_ENDPOINT_ID 0x02 +#define GAMEPAD_OUT_ENDPOINT_ID 0x02 + +#define USB_XID(obj) \ + OBJECT_CHECK(USBXIDGamepadState, (obj), TYPE_USB_XID_GAMEPAD) +#define USB_XID_S(obj) \ + OBJECT_CHECK(USBXIDGamepadState, (obj), TYPE_USB_XID_GAMEPAD_S) + +static const USBDescIface desc_iface_xbox_gamepad = { + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_XID, + .bInterfaceSubClass = 0x42, + .bInterfaceProtocol = 0x00, + .eps = + (USBDescEndpoint[]){ + { + .bEndpointAddress = USB_DIR_IN | GAMEPAD_IN_ENDPOINT_ID, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = 0x20, + .bInterval = 4, + }, + { + .bEndpointAddress = USB_DIR_OUT | GAMEPAD_OUT_ENDPOINT_ID, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = 0x20, + .bInterval = 4, + }, + }, +}; + +static const USBDescDevice desc_device_xbox_gamepad = { + .bcdUSB = 0x0110, + .bMaxPacketSize0 = 0x40, + .bNumConfigurations = 1, + .confs = + (USBDescConfig[]){ + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .bmAttributes = USB_CFG_ATT_ONE, + .bMaxPower = 50, + .nif = 1, + .ifs = &desc_iface_xbox_gamepad, + }, + }, +}; + +static const USBDesc desc_xbox_gamepad = { + .id = { + .idVendor = USB_VENDOR_MICROSOFT, + .idProduct = 0x0202, + .bcdDevice = 0x0100, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT, + .iSerialNumber = STR_SERIALNUMBER, + }, + .full = &desc_device_xbox_gamepad, + .str = desc_strings, +}; + +static const USBDesc desc_xbox_gamepad_s = { + .id = { + .idVendor = USB_VENDOR_MICROSOFT, + .idProduct = 0x0289, + .bcdDevice = 0x0100, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT, + .iSerialNumber = STR_SERIALNUMBER, + }, + .full = &desc_device_xbox_gamepad, + .str = desc_strings, +}; + +static const XIDDesc desc_xid_xbox_gamepad = { + .bLength = 0x10, + .bDescriptorType = USB_DT_XID, + .bcdXid = 0x100, + .bType = XID_DEVICETYPE_GAMEPAD, + .bSubType = XID_DEVICESUBTYPE_GAMEPAD, + .bMaxInputReportSize = 20, + .bMaxOutputReportSize = 6, + .wAlternateProductIds = { 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF }, +}; + +static const XIDDesc desc_xid_xbox_gamepad_s = { + .bLength = 0x10, + .bDescriptorType = USB_DT_XID, + .bcdXid = 0x100, + .bType = XID_DEVICETYPE_GAMEPAD, + .bSubType = XID_DEVICESUBTYPE_GAMEPAD_S, + .bMaxInputReportSize = 20, + .bMaxOutputReportSize = 6, + .wAlternateProductIds = { 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF }, +}; + +static void usb_xid_gamepad_handle_data(USBDevice *dev, USBPacket *p) +{ + USBXIDGamepadState *s = DO_UPCAST(USBXIDGamepadState, dev, dev); + + DPRINTF("xid handle_gamepad_data 0x%x %d 0x%zx\n", p->pid, p->ep->nr, + p->iov.size); + + switch (p->pid) { + case USB_TOKEN_IN: + if (p->ep->nr == GAMEPAD_IN_ENDPOINT_ID) { + update_input(s); + usb_packet_copy(p, &s->in_state, s->in_state.bLength); + } else { + assert(false); + } + break; + case USB_TOKEN_OUT: + if (p->ep->nr == GAMEPAD_OUT_ENDPOINT_ID) { + usb_packet_copy(p, &s->out_state, s->out_state.length); + update_output(s); + } else { + assert(false); + } + break; + default: + p->status = USB_RET_STALL; + assert(false); + break; + } +} + +static void usb_xid_gamepad_class_initfn(ObjectClass *klass, void *data) +{ + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->handle_reset = usb_xid_handle_reset; + uc->handle_control = usb_xid_handle_control; + uc->handle_data = usb_xid_gamepad_handle_data; + // uc->handle_destroy = usb_xid_handle_destroy; + uc->handle_attach = usb_desc_attach; +} + +static void usb_xbox_gamepad_realize(USBDevice *dev, Error **errp) +{ + USBXIDGamepadState *s = USB_XID(dev); + usb_desc_create_serial(dev); + usb_desc_init(dev); + s->intr = usb_ep_get(dev, USB_TOKEN_IN, 2); + + s->in_state.bLength = sizeof(s->in_state); + s->in_state.bReportId = 0; + + s->out_state.length = sizeof(s->out_state); + s->out_state.report_id = 0; + + s->xid_desc = &desc_xid_xbox_gamepad; + + memset(&s->in_state_capabilities, 0xFF, sizeof(s->in_state_capabilities)); + s->in_state_capabilities.bLength = sizeof(s->in_state_capabilities); + s->in_state_capabilities.bReportId = 0; + + memset(&s->out_state_capabilities, 0xFF, sizeof(s->out_state_capabilities)); + s->out_state_capabilities.length = sizeof(s->out_state_capabilities); + s->out_state_capabilities.report_id = 0; +} + +static void usb_xbox_gamepad_s_realize(USBDevice *dev, Error **errp) +{ + USBXIDGamepadState *s = USB_XID_S(dev); + usb_desc_create_serial(dev); + usb_desc_init(dev); + s->intr = usb_ep_get(dev, USB_TOKEN_IN, 2); + + s->in_state.bLength = sizeof(s->in_state); + s->in_state.bReportId = 0; + + s->out_state.length = sizeof(s->out_state); + s->out_state.report_id = 0; + + s->xid_desc = &desc_xid_xbox_gamepad_s; + + memset(&s->in_state_capabilities, 0xFF, sizeof(s->in_state_capabilities)); + s->in_state_capabilities.bLength = sizeof(s->in_state_capabilities); + s->in_state_capabilities.bReportId = 0; + + memset(&s->out_state_capabilities, 0xFF, sizeof(s->out_state_capabilities)); + s->out_state_capabilities.length = sizeof(s->out_state_capabilities); + s->out_state_capabilities.report_id = 0; +} + +static Property xid_properties[] = { + DEFINE_PROP_UINT8("index", USBXIDGamepadState, device_index, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static const VMStateDescription vmstate_usb_xbox = { + .name = TYPE_USB_XID_GAMEPAD, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]){ VMSTATE_USB_DEVICE(dev, USBXIDGamepadState), + // FIXME + VMSTATE_END_OF_LIST() }, +}; + +static const VMStateDescription vmstate_usb_xbox_s = { + .name = TYPE_USB_XID_GAMEPAD_S, + .minimum_version_id = 1, + .fields = (VMStateField[]){ VMSTATE_USB_DEVICE(dev, USBXIDGamepadState), + // FIXME + VMSTATE_END_OF_LIST() }, +}; + +static void usb_xbox_gamepad_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->product_desc = "Microsoft Xbox Controller"; + uc->usb_desc = &desc_xbox_gamepad; + uc->realize = usb_xbox_gamepad_realize; + uc->unrealize = usb_xbox_gamepad_unrealize; + usb_xid_gamepad_class_initfn(klass, data); + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); + dc->vmsd = &vmstate_usb_xbox; + device_class_set_props(dc, xid_properties); + dc->desc = "Microsoft Xbox Controller"; +} + +static void usb_xbox_gamepad_s_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->product_desc = "Microsoft Xbox Controller S"; + uc->usb_desc = &desc_xbox_gamepad_s; + uc->realize = usb_xbox_gamepad_s_realize; + uc->unrealize = usb_xbox_gamepad_unrealize; + usb_xid_gamepad_class_initfn(klass, data); + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); + dc->vmsd = &vmstate_usb_xbox_s; + device_class_set_props(dc, xid_properties); + dc->desc = "Microsoft Xbox Controller S"; +} + +static const TypeInfo usb_xbox_gamepad_info = { + .name = TYPE_USB_XID_GAMEPAD, + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBXIDGamepadState), + .class_init = usb_xbox_gamepad_class_initfn, +}; + +static const TypeInfo usb_xbox_gamepad_s_info = { + .name = TYPE_USB_XID_GAMEPAD_S, + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBXIDGamepadState), + .class_init = usb_xbox_gamepad_s_class_initfn, +}; + +static void usb_xid_register_types(void) +{ + type_register_static(&usb_xbox_gamepad_info); + type_register_static(&usb_xbox_gamepad_s_info); +} + +type_init(usb_xid_register_types) \ No newline at end of file diff --git a/hw/xbox/xid.c b/hw/xbox/xid.c index 86f34f7de28..c37142bc0d8 100644 --- a/hw/xbox/xid.c +++ b/hw/xbox/xid.c @@ -19,22 +19,7 @@ * License along with this library; if not, see . */ -#include "qemu/osdep.h" -#include "hw/qdev-properties.h" -#include "migration/vmstate.h" -#include "sysemu/sysemu.h" -#include "hw/hw.h" -#include "ui/console.h" -#include "hw/usb.h" -#include "hw/usb/desc.h" -#include "ui/xemu-input.h" - -//#define DEBUG_XID -#ifdef DEBUG_XID -#define DPRINTF printf -#else -#define DPRINTF(...) -#endif +#include "xid.h" /* * http://xbox-linux.cvs.sourceforge.net/viewvc/xbox-linux/kernel-2.6/drivers/usb/input/xpad.c @@ -42,156 +27,18 @@ * http://euc.jp/periphs/xbox-pad-desc.txt */ -#define USB_CLASS_XID 0x58 -#define USB_DT_XID 0x42 - -#define HID_GET_REPORT 0x01 -#define HID_SET_REPORT 0x09 -#define XID_GET_CAPABILITIES 0x01 - -#define TYPE_USB_XID "usb-xbox-gamepad" -#define USB_XID(obj) OBJECT_CHECK(USBXIDState, (obj), TYPE_USB_XID) - -enum { - STR_MANUFACTURER = 1, - STR_PRODUCT, - STR_SERIALNUMBER, -}; - typedef enum HapticEmulationMode { EMU_NONE, EMU_HAPTIC_LEFT_RIGHT } HapticEmulationMode; -static const USBDescStrings desc_strings = { +const USBDescStrings desc_strings = { [STR_MANUFACTURER] = "QEMU", [STR_PRODUCT] = "Microsoft Xbox Controller", [STR_SERIALNUMBER] = "1", }; -typedef struct XIDDesc { - uint8_t bLength; - uint8_t bDescriptorType; - uint16_t bcdXid; - uint8_t bType; - uint8_t bSubType; - uint8_t bMaxInputReportSize; - uint8_t bMaxOutputReportSize; - uint16_t wAlternateProductIds[4]; -} QEMU_PACKED XIDDesc; - -typedef struct XIDGamepadReport { - uint8_t bReportId; - uint8_t bLength; - uint16_t wButtons; - uint8_t bAnalogButtons[8]; - int16_t sThumbLX; - int16_t sThumbLY; - int16_t sThumbRX; - int16_t sThumbRY; -} QEMU_PACKED XIDGamepadReport; - -typedef struct XIDGamepadOutputReport { - uint8_t report_id; //FIXME: is this correct? - uint8_t length; - uint16_t left_actuator_strength; - uint16_t right_actuator_strength; -} QEMU_PACKED XIDGamepadOutputReport; - -typedef struct USBXIDState { - USBDevice dev; - USBEndpoint *intr; - const XIDDesc *xid_desc; - XIDGamepadReport in_state; - XIDGamepadReport in_state_capabilities; - XIDGamepadOutputReport out_state; - XIDGamepadOutputReport out_state_capabilities; - uint8_t device_index; -} USBXIDState; - -static const USBDescIface desc_iface_xbox_gamepad = { - .bInterfaceNumber = 0, - .bNumEndpoints = 2, - .bInterfaceClass = USB_CLASS_XID, - .bInterfaceSubClass = 0x42, - .bInterfaceProtocol = 0x00, - .eps = (USBDescEndpoint[]) { - { - .bEndpointAddress = USB_DIR_IN | 0x02, - .bmAttributes = USB_ENDPOINT_XFER_INT, - .wMaxPacketSize = 0x20, - .bInterval = 4, - }, - { - .bEndpointAddress = USB_DIR_OUT | 0x02, - .bmAttributes = USB_ENDPOINT_XFER_INT, - .wMaxPacketSize = 0x20, - .bInterval = 4, - }, - }, -}; - -static const USBDescDevice desc_device_xbox_gamepad = { - .bcdUSB = 0x0110, - .bMaxPacketSize0 = 0x40, - .bNumConfigurations = 1, - .confs = (USBDescConfig[]) { - { - .bNumInterfaces = 1, - .bConfigurationValue = 1, - .bmAttributes = USB_CFG_ATT_ONE, - .bMaxPower = 50, - .nif = 1, - .ifs = &desc_iface_xbox_gamepad, - }, - }, -}; - -static const USBDesc desc_xbox_gamepad = { - .id = { - .idVendor = 0x045e, - .idProduct = 0x0202, - .bcdDevice = 0x0100, - .iManufacturer = STR_MANUFACTURER, - .iProduct = STR_PRODUCT, - .iSerialNumber = STR_SERIALNUMBER, - }, - .full = &desc_device_xbox_gamepad, - .str = desc_strings, -}; - -static const XIDDesc desc_xid_xbox_gamepad = { - .bLength = 0x10, - .bDescriptorType = USB_DT_XID, - .bcdXid = 0x100, - .bType = 1, - .bSubType = 1, - .bMaxInputReportSize = 20, - .bMaxOutputReportSize = 6, - .wAlternateProductIds = { 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF }, -}; - -#define GAMEPAD_A 0 -#define GAMEPAD_B 1 -#define GAMEPAD_X 2 -#define GAMEPAD_Y 3 -#define GAMEPAD_BLACK 4 -#define GAMEPAD_WHITE 5 -#define GAMEPAD_LEFT_TRIGGER 6 -#define GAMEPAD_RIGHT_TRIGGER 7 - -#define GAMEPAD_DPAD_UP 8 -#define GAMEPAD_DPAD_DOWN 9 -#define GAMEPAD_DPAD_LEFT 10 -#define GAMEPAD_DPAD_RIGHT 11 -#define GAMEPAD_START 12 -#define GAMEPAD_BACK 13 -#define GAMEPAD_LEFT_THUMB 14 -#define GAMEPAD_RIGHT_THUMB 15 - -#define BUTTON_MASK(button) (1 << ((button) - GAMEPAD_DPAD_UP)) - -static void update_output(USBXIDState *s) +void update_output(USBXIDGamepadState *s) { if (xemu_input_get_test_mode()) { // Don't report changes if we are testing the controller while running @@ -205,7 +52,7 @@ static void update_output(USBXIDState *s) xemu_input_update_rumble(state); } -static void update_input(USBXIDState *s) +void update_input(USBXIDGamepadState *s) { if (xemu_input_get_test_mode()) { // Don't report changes if we are testing the controller while running @@ -256,15 +103,15 @@ static void update_input(USBXIDState *s) s->in_state.sThumbRY = state->axis[CONTROLLER_AXIS_RSTICK_Y]; } -static void usb_xid_handle_reset(USBDevice *dev) +void usb_xid_handle_reset(USBDevice *dev) { DPRINTF("xid reset\n"); } -static void usb_xid_handle_control(USBDevice *dev, USBPacket *p, +void usb_xid_handle_control(USBDevice *dev, USBPacket *p, int request, int value, int index, int length, uint8_t *data) { - USBXIDState *s = (USBXIDState *)dev; + USBXIDGamepadState *s = (USBXIDGamepadState *)dev; DPRINTF("xid handle_control 0x%x 0x%x\n", request, value); @@ -368,36 +215,6 @@ static void usb_xid_handle_control(USBDevice *dev, USBPacket *p, } } -static void usb_xid_handle_data(USBDevice *dev, USBPacket *p) -{ - USBXIDState *s = DO_UPCAST(USBXIDState, dev, dev); - - DPRINTF("xid handle_data 0x%x %d 0x%zx\n", p->pid, p->ep->nr, p->iov.size); - - switch (p->pid) { - case USB_TOKEN_IN: - if (p->ep->nr == 2) { - update_input(s); - usb_packet_copy(p, &s->in_state, s->in_state.bLength); - } else { - assert(false); - } - break; - case USB_TOKEN_OUT: - if (p->ep->nr == 2) { - usb_packet_copy(p, &s->out_state, s->out_state.length); - update_output(s); - } else { - assert(false); - } - break; - default: - p->status = USB_RET_STALL; - assert(false); - break; - } -} - #if 0 static void usb_xid_handle_destroy(USBDevice *dev) { @@ -406,87 +223,6 @@ static void usb_xid_handle_destroy(USBDevice *dev) } #endif -static void usb_xbox_gamepad_unrealize(USBDevice *dev) -{ -} - -static void usb_xid_class_initfn(ObjectClass *klass, void *data) -{ - USBDeviceClass *uc = USB_DEVICE_CLASS(klass); - - uc->handle_reset = usb_xid_handle_reset; - uc->handle_control = usb_xid_handle_control; - uc->handle_data = usb_xid_handle_data; - // uc->handle_destroy = usb_xid_handle_destroy; - uc->handle_attach = usb_desc_attach; -} - -static void usb_xbox_gamepad_realize(USBDevice *dev, Error **errp) -{ - USBXIDState *s = USB_XID(dev); - usb_desc_create_serial(dev); - usb_desc_init(dev); - s->intr = usb_ep_get(dev, USB_TOKEN_IN, 2); - - s->in_state.bLength = sizeof(s->in_state); - s->in_state.bReportId = 0; - - s->out_state.length = sizeof(s->out_state); - s->out_state.report_id = 0; - - s->xid_desc = &desc_xid_xbox_gamepad; - - memset(&s->in_state_capabilities, 0xFF, sizeof(s->in_state_capabilities)); - s->in_state_capabilities.bLength = sizeof(s->in_state_capabilities); - s->in_state_capabilities.bReportId = 0; - - memset(&s->out_state_capabilities, 0xFF, sizeof(s->out_state_capabilities)); - s->out_state_capabilities.length = sizeof(s->out_state_capabilities); - s->out_state_capabilities.report_id = 0; -} - -static Property xid_properties[] = { - DEFINE_PROP_UINT8("index", USBXIDState, device_index, 0), - DEFINE_PROP_END_OF_LIST(), -}; - -static const VMStateDescription vmstate_usb_xbox = { - .name = TYPE_USB_XID, - .version_id = 1, - .minimum_version_id = 1, - .fields = (VMStateField[]) { - VMSTATE_USB_DEVICE(dev, USBXIDState), - // FIXME - VMSTATE_END_OF_LIST() - }, -}; - -static void usb_xbox_gamepad_class_initfn(ObjectClass *klass, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(klass); - USBDeviceClass *uc = USB_DEVICE_CLASS(klass); - - uc->product_desc = "Microsoft Xbox Controller"; - uc->usb_desc = &desc_xbox_gamepad; - uc->realize = usb_xbox_gamepad_realize; - uc->unrealize = usb_xbox_gamepad_unrealize; - usb_xid_class_initfn(klass, data); - set_bit(DEVICE_CATEGORY_INPUT, dc->categories); - dc->vmsd = &vmstate_usb_xbox; - device_class_set_props(dc, xid_properties); - dc->desc = "Microsoft Xbox Controller"; -} - -static const TypeInfo usb_xbox_gamepad_info = { - .name = TYPE_USB_XID, - .parent = TYPE_USB_DEVICE, - .instance_size = sizeof(USBXIDState), - .class_init = usb_xbox_gamepad_class_initfn, -}; - -static void usb_xid_register_types(void) +void usb_xbox_gamepad_unrealize(USBDevice *dev) { - type_register_static(&usb_xbox_gamepad_info); } - -type_init(usb_xid_register_types) diff --git a/hw/xbox/xid.h b/hw/xbox/xid.h new file mode 100644 index 00000000000..44406eaf912 --- /dev/null +++ b/hw/xbox/xid.h @@ -0,0 +1,137 @@ +#ifndef __XID_H__ +#define __XID_H__ + +/* + * QEMU USB XID Devices + * + * Copyright (c) 2013 espes + * Copyright (c) 2017 Jannik Vogel + * Copyright (c) 2018-2021 Matt Borgerson + * Copyright (c) 2023 Fred Hallock + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "qemu/osdep.h" +#include "hw/hw.h" +#include "hw/qdev-properties.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" +#include "migration/vmstate.h" +#include "sysemu/sysemu.h" +#include "ui/console.h" +#include "ui/xemu-input.h" + +// #define DEBUG_XID +#ifdef DEBUG_XID +#define DPRINTF printf +#else +#define DPRINTF(...) +#endif + +#define USB_CLASS_XID 0x58 +#define USB_DT_XID 0x42 + +#define HID_GET_REPORT 0x01 +#define HID_SET_REPORT 0x09 +#define XID_GET_CAPABILITIES 0x01 + +#define XID_DEVICETYPE_GAMEPAD 0x01 + +#define XID_DEVICESUBTYPE_GAMEPAD 0x01 +#define XID_DEVICESUBTYPE_GAMEPAD_S 0x02 + +#define TYPE_USB_XID_GAMEPAD "usb-xbox-gamepad" +#define TYPE_USB_XID_GAMEPAD_S "usb-xbox-gamepad-s" + +#define GAMEPAD_A 0 +#define GAMEPAD_B 1 +#define GAMEPAD_X 2 +#define GAMEPAD_Y 3 +#define GAMEPAD_BLACK 4 +#define GAMEPAD_WHITE 5 +#define GAMEPAD_LEFT_TRIGGER 6 +#define GAMEPAD_RIGHT_TRIGGER 7 + +#define GAMEPAD_DPAD_UP 8 +#define GAMEPAD_DPAD_DOWN 9 +#define GAMEPAD_DPAD_LEFT 10 +#define GAMEPAD_DPAD_RIGHT 11 +#define GAMEPAD_START 12 +#define GAMEPAD_BACK 13 +#define GAMEPAD_LEFT_THUMB 14 +#define GAMEPAD_RIGHT_THUMB 15 + +#define BUTTON_MASK(button) (1 << ((button) - GAMEPAD_DPAD_UP)) + +enum { + STR_MANUFACTURER = 1, + STR_PRODUCT, + STR_SERIALNUMBER, +}; + +extern const USBDescStrings desc_strings; + +typedef struct XIDDesc { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdXid; + uint8_t bType; + uint8_t bSubType; + uint8_t bMaxInputReportSize; + uint8_t bMaxOutputReportSize; + uint16_t wAlternateProductIds[4]; +} QEMU_PACKED XIDDesc; + +typedef struct XIDGamepadReport { + uint8_t bReportId; + uint8_t bLength; + uint16_t wButtons; + uint8_t bAnalogButtons[8]; + int16_t sThumbLX; + int16_t sThumbLY; + int16_t sThumbRX; + int16_t sThumbRY; +} QEMU_PACKED XIDGamepadReport; + +typedef struct XIDGamepadOutputReport { + uint8_t report_id; // FIXME: is this correct? + uint8_t length; + uint16_t left_actuator_strength; + uint16_t right_actuator_strength; +} QEMU_PACKED XIDGamepadOutputReport; + +typedef struct USBXIDGamepadState { + USBDevice dev; + USBEndpoint *intr; + const XIDDesc *xid_desc; + XIDGamepadReport in_state; + XIDGamepadReport in_state_capabilities; + XIDGamepadOutputReport out_state; + XIDGamepadOutputReport out_state_capabilities; + uint8_t device_index; +} USBXIDGamepadState; + +void update_input(USBXIDGamepadState *s); +void update_output(USBXIDGamepadState *s); +void usb_xid_handle_reset(USBDevice *dev); +void usb_xid_handle_control(USBDevice *dev, USBPacket *p, int request, + int value, int index, int length, uint8_t *data); +void usb_xbox_gamepad_unrealize(USBDevice *dev); + +#if 0 +void usb_xid_handle_destroy(USBDevice *dev); +#endif + +#endif \ No newline at end of file diff --git a/ui/xemu-input.c b/ui/xemu-input.c index d9181fe2a62..31a51eda9d2 100644 --- a/ui/xemu-input.c +++ b/ui/xemu-input.c @@ -86,6 +86,8 @@ static void xemu_input_print_controller_state(ControllerState *state) ControllerStateList available_controllers = QTAILQ_HEAD_INITIALIZER(available_controllers); ControllerState *bound_controllers[4] = { NULL, NULL, NULL, NULL }; +const char *bound_drivers[4] = { DRIVER_DUKE, DRIVER_DUKE, DRIVER_DUKE, + DRIVER_DUKE }; int test_mode; static const char **port_index_to_settings_key_map[] = { @@ -95,6 +97,13 @@ static const char **port_index_to_settings_key_map[] = { &g_config.input.bindings.port4, }; +static const char **port_index_to_driver_settings_key_map[] = { + &g_config.input.bindings.port1_driver, + &g_config.input.bindings.port2_driver, + &g_config.input.bindings.port3_driver, + &g_config.input.bindings.port4_driver +}; + static int *peripheral_types_settings_map[4][2] = { { &g_config.input.peripherals.port1.peripheral_type_0, &g_config.input.peripherals.port1.peripheral_type_1 }, @@ -119,6 +128,25 @@ static const char **peripheral_params_settings_map[4][2] = { static int sdl_kbd_scancode_map[25]; +static const char *get_bound_driver(int port) +{ + assert(port >= 0 && port <= 3); + const char *driver = *port_index_to_driver_settings_key_map[port]; + + // If the driver in the config is NULL, empty, or unrecognized + // then default to DRIVER_DUKE + if (driver == NULL) + return DRIVER_DUKE; + if (strlen(driver) == 0) + return DRIVER_DUKE; + if (strcmp(driver, DRIVER_DUKE) == 0) + return DRIVER_DUKE; + if (strcmp(driver, DRIVER_S) == 0) + return DRIVER_S; + + return DRIVER_DUKE; +} + static const int port_map[4] = { 3, 4, 1, 2 }; void xemu_input_init(void) @@ -177,6 +205,11 @@ void xemu_input_init(void) } } + bound_drivers[0] = get_bound_driver(0); + bound_drivers[1] = get_bound_driver(1); + bound_drivers[2] = get_bound_driver(2); + bound_drivers[3] = get_bound_driver(3); + // Check to see if we should auto-bind the keyboard int port = xemu_input_get_controller_default_bind_port(new_con, 0); if (port >= 0) { @@ -520,6 +553,8 @@ void xemu_input_bind(int index, ControllerState *state, int save) } } xemu_settings_set_string(port_index_to_settings_key_map[index], guid_buf); + xemu_settings_set_string(port_index_to_driver_settings_key_map[index], + bound_drivers[index]); } // Bind new controller @@ -548,7 +583,7 @@ void xemu_input_bind(int index, ControllerState *state, int save) QDict *qdict = qdict_new(); // Specify device driver - qdict_put_str(qdict, "driver", "usb-xbox-gamepad"); + qdict_put_str(qdict, "driver", bound_drivers[index]); // Specify device identifier static int id_counter = 0; diff --git a/ui/xemu-input.h b/ui/xemu-input.h index 330ae58a7c1..23c1a9f91b7 100644 --- a/ui/xemu-input.h +++ b/ui/xemu-input.h @@ -30,6 +30,12 @@ #include "qemu/queue.h" +#define DRIVER_DUKE "usb-xbox-gamepad" +#define DRIVER_S "usb-xbox-gamepad-s" + +#define DRIVER_DUKE_DISPLAY_NAME "Xbox Controller" +#define DRIVER_S_DISPLAY_NAME "Xbox Controller S" + enum controller_state_buttons_mask { CONTROLLER_BUTTON_A = (1 << 0), CONTROLLER_BUTTON_B = (1 << 1), @@ -107,6 +113,7 @@ typedef struct ControllerState { typedef QTAILQ_HEAD(, ControllerState) ControllerStateList; extern ControllerStateList available_controllers; extern ControllerState *bound_controllers[4]; +extern const char *bound_drivers[4]; #ifdef __cplusplus extern "C" { diff --git a/ui/xui/gl-helpers.cc b/ui/xui/gl-helpers.cc index 6c07d087b41..0a06b7f16ac 100644 --- a/ui/xui/gl-helpers.cc +++ b/ui/xui/gl-helpers.cc @@ -20,6 +20,7 @@ #include "gl-helpers.hh" #include "common.hh" #include "data/controller_mask.png.h" +#include "data/controller_mask_s.png.h" #include "data/logo_sdf.png.h" #include "data/xemu_64x64.png.h" #include "data/xmu_mask.png.h" @@ -33,7 +34,7 @@ #include "ui/shader/xemu-logo-frag.h" Fbo *controller_fbo, *xmu_fbo, *logo_fbo; -GLuint g_controller_tex, g_logo_tex, g_icon_tex, g_xmu_tex; +GLuint g_controller_duke_tex, g_controller_s_tex, g_logo_tex, g_icon_tex, g_xmu_tex; enum class ShaderType { Blit, @@ -439,8 +440,10 @@ enum tex_item_names { void InitCustomRendering(void) { glActiveTexture(GL_TEXTURE0); - g_controller_tex = + g_controller_duke_tex = LoadTextureFromMemory(controller_mask_data, controller_mask_size); + g_controller_s_tex = + LoadTextureFromMemory(controller_mask_s_data, controller_mask_s_size); g_decal_shader = NewDecalShader(ShaderType::Mask); controller_fbo = new Fbo(512, 512); @@ -464,7 +467,7 @@ static void RenderMeter(DecalShader *s, float x, float y, float width, RenderDecal(s, x, y, width * p, height, 0, 0, 1, 1, 0, 0, color_fg); } -void RenderController(float frame_x, float frame_y, uint32_t primary_color, +static void RenderDukeController(float frame_x, float frame_y, uint32_t primary_color, uint32_t secondary_color, ControllerState *state) { // Location within the controller texture of masked button locations, @@ -494,7 +497,7 @@ void RenderController(float frame_x, float frame_y, uint32_t primary_color, glUseProgram(g_decal_shader->prog); glBindVertexArray(g_decal_shader->vao); glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, g_controller_tex); + glBindTexture(GL_TEXTURE_2D, g_controller_duke_tex); // Add a 5 pixel space around the controller so we can wiggle the controller // around to visualize rumble in action @@ -623,13 +626,191 @@ void RenderController(float frame_x, float frame_y, uint32_t primary_color, glUseProgram(0); } +static void RenderControllerS(float frame_x, float frame_y, uint32_t primary_color, + uint32_t secondary_color, ControllerState *state) +{ + // Location within the controller texture of masked button locations, + // relative to the origin of the controller + const struct rect jewel = { 194, 213, 84, 84 }; + const struct rect lstick_ctr = { 103, 254, 0, 0 }; + const struct rect rstick_ctr = { 295, 176, 0, 0 }; + const struct rect buttons[12] = { + { 347, 200, 34, 34 }, // A + { 381, 235, 34, 34 }, // B + { 313, 235, 34, 34 }, // X + { 347, 270, 34, 34 }, // Y + { 123, 165, 31, 26 }, // D-Left + { 150, 187, 26, 31 }, // D-Up + { 173, 165, 31, 26 }, // D-Right + { 150, 135, 26, 31 }, // D-Down + { 45, 195, 20, 24 }, // Back + { 70, 163, 26, 26 }, // Start + { 352, 145, 30, 30 }, // White + { 388, 172, 30, 30 }, // Black + }; + + uint8_t alpha = 0; + uint32_t now = SDL_GetTicks(); + float t; + + glUseProgram(g_decal_shader->prog); + glBindVertexArray(g_decal_shader->vao); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, g_controller_s_tex); + + // Add a 5 pixel space around the controller so we can wiggle the controller + // around to visualize rumble in action + frame_x += 5; + frame_y += 5; + float original_frame_x = frame_x; + float original_frame_y = frame_y; + + // Floating point versions that will get scaled + float rumble_l = 0; + float rumble_r = 0; + + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_ONE, GL_ZERO); + + uint32_t jewel_color = secondary_color; + + // Check to see if the guide button is pressed + const uint32_t animate_guide_button_duration = 2000; + if (state->buttons & CONTROLLER_BUTTON_GUIDE) { + state->animate_guide_button_end = + now + animate_guide_button_duration; + } + + if (now < state->animate_guide_button_end) { + t = 1.0f - (float)(state->animate_guide_button_end - now) / + (float)animate_guide_button_duration; + float sin_wav = (1 - sin(M_PI * t / 2.0f)); + + // Animate guide button by highlighting logo jewel and fading out over + // time + alpha = sin_wav * 255.0f; + jewel_color = primary_color + alpha; + + // Add a little extra flare: wiggle the frame around while we rumble + frame_x += ((float)(rand() % 5) - 2.5) * (1 - t); + frame_y += ((float)(rand() % 5) - 2.5) * (1 - t); + rumble_l = rumble_r = sin_wav; + } + + // Render controller texture + RenderDecal(g_decal_shader, frame_x + 0, frame_y + 0, + tex_items[obj_controller].w, tex_items[obj_controller].h, + tex_items[obj_controller].x, tex_items[obj_controller].y, + tex_items[obj_controller].w, tex_items[obj_controller].h, + primary_color, secondary_color, 0); + + glBlendFunc(GL_ONE_MINUS_DST_ALPHA, + GL_ONE); // Blend with controller cutouts + RenderDecal(g_decal_shader, frame_x + jewel.x, frame_y + jewel.y, jewel.w, + jewel.h, 0, 0, 1, 1, 0, 0, jewel_color); + + // The controller has alpha cutouts where the buttons are. Draw a surface + // behind the buttons if they are activated + for (int i = 0; i < 12; i++) { + if (state->buttons & (1 << i)) { + RenderDecal(g_decal_shader, frame_x + buttons[i].x, + frame_y + buttons[i].y, buttons[i].w, buttons[i].h, 0, + 0, 1, 1, 0, 0, primary_color + 0xff); + } + } + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Blend with controller + + // Render left thumbstick + float w = tex_items[obj_lstick].w; + float h = tex_items[obj_lstick].h; + float c_x = frame_x + lstick_ctr.x; + float c_y = frame_y + lstick_ctr.y; + float lstick_x = (float)state->axis[CONTROLLER_AXIS_LSTICK_X] / 32768.0; + float lstick_y = (float)state->axis[CONTROLLER_AXIS_LSTICK_Y] / 32768.0; + RenderDecal( + g_decal_shader, (int)(c_x - w / 2.0f + 10.0f * lstick_x), + (int)(c_y - h / 2.0f + 10.0f * lstick_y), w, h, tex_items[obj_lstick].x, + tex_items[obj_lstick].y, w, h, + (state->buttons & CONTROLLER_BUTTON_LSTICK) ? secondary_color : + primary_color, + (state->buttons & CONTROLLER_BUTTON_LSTICK) ? primary_color : + secondary_color, + 0); + + // Render right thumbstick + w = tex_items[obj_rstick].w; + h = tex_items[obj_rstick].h; + c_x = frame_x + rstick_ctr.x; + c_y = frame_y + rstick_ctr.y; + float rstick_x = (float)state->axis[CONTROLLER_AXIS_RSTICK_X] / 32768.0; + float rstick_y = (float)state->axis[CONTROLLER_AXIS_RSTICK_Y] / 32768.0; + RenderDecal( + g_decal_shader, (int)(c_x - w / 2.0f + 10.0f * rstick_x), + (int)(c_y - h / 2.0f + 10.0f * rstick_y), w, h, tex_items[obj_rstick].x, + tex_items[obj_rstick].y, w, h, + (state->buttons & CONTROLLER_BUTTON_RSTICK) ? secondary_color : + primary_color, + (state->buttons & CONTROLLER_BUTTON_RSTICK) ? primary_color : + secondary_color, + 0); + + glBlendFunc(GL_ONE, + GL_ZERO); // Don't blend, just overwrite values in buffer + + // Render trigger bars + float ltrig = state->axis[CONTROLLER_AXIS_LTRIG] / 32767.0; + float rtrig = state->axis[CONTROLLER_AXIS_RTRIG] / 32767.0; + const uint32_t animate_trigger_duration = 1000; + if ((ltrig > 0) || (rtrig > 0)) { + state->animate_trigger_end = now + animate_trigger_duration; + rumble_l = fmax(rumble_l, ltrig); + rumble_r = fmax(rumble_r, rtrig); + } + + // Animate trigger alpha down after a period of inactivity + alpha = 0x80; + if (state->animate_trigger_end > now) { + t = 1.0f - (float)(state->animate_trigger_end - now) / + (float)animate_trigger_duration; + float sin_wav = (1 - sin(M_PI * t / 2.0f)); + alpha += fmin(sin_wav * 0x40, 0x80); + } + + RenderMeter(g_decal_shader, original_frame_x + 10, + original_frame_y + tex_items[obj_controller].h + 20, 150, 5, + ltrig, primary_color + alpha, primary_color + 0xff); + RenderMeter(g_decal_shader, + original_frame_x + tex_items[obj_controller].w - 160, + original_frame_y + tex_items[obj_controller].h + 20, 150, 5, + rtrig, primary_color + alpha, primary_color + 0xff); + + // Apply rumble updates + state->rumble_l = (int)(rumble_l * (float)0xffff); + state->rumble_r = (int)(rumble_r * (float)0xffff); + + glBindVertexArray(0); + glUseProgram(0); +} + +void RenderController(float frame_x, float frame_y, uint32_t primary_color, + uint32_t secondary_color, ControllerState *state) +{ + if (strcmp(bound_drivers[state->bound], DRIVER_S) == 0) + RenderControllerS(frame_x, frame_y, primary_color, secondary_color, + state); + else if (strcmp(bound_drivers[state->bound], DRIVER_DUKE) == 0) + RenderDukeController(frame_x, frame_y, primary_color, secondary_color, + state); +} + void RenderControllerPort(float frame_x, float frame_y, int i, uint32_t port_color) { glUseProgram(g_decal_shader->prog); glBindVertexArray(g_decal_shader->vao); glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, g_controller_tex); + glBindTexture(GL_TEXTURE_2D, g_controller_duke_tex); glBlendFunc(GL_ONE, GL_ZERO); // Render port socket diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index 2347ded770a..d34c08b00e7 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -158,6 +158,49 @@ void MainMenuInputView::Draw() ImGui::PopStyleVar(); // ItemSpacing ImGui::Columns(1); + // + // Render device driver combo + // + + // List available device drivers + const char *driver = bound_drivers[active]; + + if (strcmp(driver, DRIVER_DUKE) == 0) + driver = DRIVER_DUKE_DISPLAY_NAME; + else if (strcmp(driver, DRIVER_S) == 0) + driver = DRIVER_S_DISPLAY_NAME; + + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::BeginCombo("###InputDrivers", driver, + ImGuiComboFlags_NoArrowButton)) { + const char *available_drivers[] = { DRIVER_DUKE, DRIVER_S }; + const char *driver_display_names[] = { + DRIVER_DUKE_DISPLAY_NAME, + DRIVER_S_DISPLAY_NAME + }; + bool is_selected = false; + int num_drivers = sizeof(driver_display_names) / sizeof(driver_display_names[0]); + for (int i = 0; i < num_drivers; i++) { + const char *iter = driver_display_names[i]; + is_selected = strcmp(driver, iter) == 0; + ImGui::PushID(iter); + if (ImGui::Selectable(iter, is_selected)) { + for (int j = 0; j < num_drivers; j++) { + if (iter == driver_display_names[j]) + bound_drivers[active] = available_drivers[j]; + } + xemu_input_bind(active, bound_controllers[active], 1); + } + if (is_selected) { + ImGui::SetItemDefaultFocus(); + } + ImGui::PopID(); + } + + ImGui::EndCombo(); + } + DrawComboChevron(); + // // Render input device combo //