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 @@
+
\ 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
//