diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index e422a72480..28721f656c 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -79,7 +79,7 @@ jobs:
artifact_name: xemu-win-aarch64-release
arch: aarch64
env:
- DOCKER_IMAGE_NAME: ghcr.io/xemu-project/xemu-win64-toolchain:sha-b6d29d4
+ DOCKER_IMAGE_NAME: ghcr.io/xemu-project/xemu-win64-toolchain:sha-03e3cd0
steps:
- name: Download source package
diff --git a/Info.plist b/Info.plist
index 5303f0fa0e..ec4b235564 100644
--- a/Info.plist
+++ b/Info.plist
@@ -32,5 +32,7 @@
com.apple.security.cs.allow-jit
+ NSMicrophoneUsageDescription
+ Microphone used for peripheral emulation, e.g. Xbox Live Communicator.
diff --git a/build.sh b/build.sh
index 23e4210068..3e0ef7e8f2 100755
--- a/build.sh
+++ b/build.sh
@@ -213,12 +213,14 @@ case "$platform" in # Adjust compilation options based on platform
python3 ./scripts/download-macos-libs.py ${target_arch}
lib_prefix=${PWD}/macos-libs/${target_arch}/opt/local
- export CFLAGS="-arch ${target_arch} \
+ export CFLAGS="${CFLAGS} \
+ -arch ${target_arch} \
-target ${target_arch}-apple-macos${macos_min_ver} \
-isysroot ${sdk} \
-I${lib_prefix}/include \
-mmacosx-version-min=$macos_min_ver"
- export LDFLAGS="-arch ${target_arch} \
+ export LDFLAGS="${LDFLAGS} \
+ -arch ${target_arch} \
-isysroot ${sdk}"
if [ "$target_arch" == "x86_64" ]; then
sys_cflags='-march=ivybridge'
diff --git a/config_spec.yml b/config_spec.yml
index 087d255fae..9d96b1f48f 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 0000000000..8406effbca
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 0000000000..a3cbf84c23
--- /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 e1a7ebedf4..bb2084bc1c 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/audio/ac97.c b/hw/audio/ac97.c
index 8c0cd38b10..29490d9968 100644
--- a/hw/audio/ac97.c
+++ b/hw/audio/ac97.c
@@ -149,8 +149,11 @@ typedef struct AC97DeviceState {
OBJECT_CHECK(AC97DeviceState, (obj), "AC97")
static void po_callback(void *opaque, int free);
+
+#ifndef XBOX
static void pi_callback(void *opaque, int avail);
static void mc_callback(void *opaque, int avail);
+#endif
static void fetch_bd(AC97LinkState *s, AC97BusMasterRegs *r)
{
@@ -218,7 +221,9 @@ static void voice_set_active(AC97LinkState *s, int bm_index, int on)
{
switch (bm_index) {
case PI_INDEX:
+#ifndef XBOX
AUD_set_active_in(s->voice_pi, on);
+#endif
break;
case PO_INDEX:
@@ -226,7 +231,9 @@ static void voice_set_active(AC97LinkState *s, int bm_index, int on)
break;
case MC_INDEX:
+#ifndef XBOX
AUD_set_active_in(s->voice_mc, on);
+#endif
break;
case SO_INDEX:
@@ -294,6 +301,7 @@ static void open_voice(AC97LinkState *s, int index, int freq)
s->invalid_freq[index] = 0;
switch (index) {
case PI_INDEX:
+#ifndef XBOX
s->voice_pi = AUD_open_in(
&s->card,
s->voice_pi,
@@ -302,6 +310,7 @@ static void open_voice(AC97LinkState *s, int index, int freq)
pi_callback,
&as
);
+#endif
break;
case PO_INDEX:
@@ -316,6 +325,7 @@ static void open_voice(AC97LinkState *s, int index, int freq)
break;
case MC_INDEX:
+#ifndef XBOX
s->voice_mc = AUD_open_in(
&s->card,
s->voice_mc,
@@ -324,6 +334,7 @@ static void open_voice(AC97LinkState *s, int index, int freq)
mc_callback,
&as
);
+#endif
break;
case SO_INDEX:
@@ -333,7 +344,9 @@ static void open_voice(AC97LinkState *s, int index, int freq)
s->invalid_freq[index] = freq;
switch (index) {
case PI_INDEX:
+#ifndef XBOX
AUD_close_in(&s->card, s->voice_pi);
+#endif
s->voice_pi = NULL;
break;
@@ -343,7 +356,9 @@ static void open_voice(AC97LinkState *s, int index, int freq)
break;
case MC_INDEX:
+#ifndef XBOX
AUD_close_in(&s->card, s->voice_mc);
+#endif
s->voice_mc = NULL;
break;
@@ -357,17 +372,21 @@ static void reset_voices(AC97LinkState *s, uint8_t active[LAST_INDEX])
{
uint16_t freq;
+#ifndef XBOX
freq = mixer_load(s, AC97_PCM_LR_ADC_Rate);
open_voice(s, PI_INDEX, freq);
AUD_set_active_in(s->voice_pi, active[PI_INDEX]);
+#endif
freq = mixer_load(s, AC97_PCM_Front_DAC_Rate);
open_voice(s, PO_INDEX, freq);
AUD_set_active_out(s->voice_po, active[PO_INDEX]);
+#ifndef XBOX
freq = mixer_load(s, AC97_MIC_ADC_Rate);
open_voice(s, MC_INDEX, freq);
AUD_set_active_in(s->voice_mc, active[MC_INDEX]);
+#endif
}
static void get_volume(uint16_t vol, uint16_t mask, int inverse,
@@ -408,7 +427,9 @@ static void update_volume_in(AC97LinkState *s)
get_volume(mixer_load(s, AC97_Record_Gain_Mute), 0x0f, 0,
&mute, &lvol, &rvol);
+#ifndef XBOX
AUD_set_volume_in(s->voice_pi, mute, lvol, rvol);
+#endif
}
static void set_volume(AC97LinkState *s, int index, uint32_t val)
@@ -972,6 +993,10 @@ static int read_audio(AC97LinkState *s, AC97BusMasterRegs *r,
int to_copy = 0;
SWVoiceIn *voice = (r - s->bm_regs) == MC_INDEX ? s->voice_mc : s->voice_pi;
+#ifdef XBOX
+ return 0;
+#endif
+
temp = MIN(temp, max);
if (!temp) {
@@ -1081,6 +1106,7 @@ static void transfer_audio(AC97LinkState *s, int index, int elapsed)
}
}
+#ifndef XBOX
static void pi_callback(void *opaque, int avail)
{
transfer_audio(opaque, PI_INDEX, avail);
@@ -1090,6 +1116,7 @@ static void mc_callback(void *opaque, int avail)
{
transfer_audio(opaque, MC_INDEX, avail);
}
+#endif
static void po_callback(void *opaque, int free)
{
diff --git a/hw/xbox/meson.build b/hw/xbox/meson.build
index 907ebdc4e8..dce2d3729e 100644
--- a/hw/xbox/meson.build
+++ b/hw/xbox/meson.build
@@ -15,6 +15,8 @@ specific_ss.add(files(
'xbox.c',
'xbox_pci.c',
'xid.c',
+ 'xblc.c',
+ 'xid-gamepad.c',
))
subdir('nv2a')
subdir('mcpx')
diff --git a/hw/xbox/xblc.c b/hw/xbox/xblc.c
new file mode 100644
index 0000000000..60badb10a7
--- /dev/null
+++ b/hw/xbox/xblc.c
@@ -0,0 +1,402 @@
+/*
+ * QEMU USB Xbox Live Communicator (XBLC) Device
+ *
+ * Copyright (c) 2022 Ryan Wendland
+ *
+ * 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/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"
+#include "audio/audio.h"
+#include "qemu/fifo8.h"
+
+//#define DEBUG_XBLC
+#ifdef DEBUG_XBLC
+#define DPRINTF printf
+#else
+#define DPRINTF(...)
+#endif
+
+#define TYPE_USB_XBLC "usb-xblc"
+#define USB_XBLC(obj) OBJECT_CHECK(USBXBLCState, (obj), TYPE_USB_XBLC)
+
+#define XBLC_STR "Microsoft Xbox Live Communicator"
+#define XBLC_INTERFACE_CLASS 0x78
+#define XBLC_INTERFACE_SUBCLASS 0x00
+#define XBLC_EP_OUT 0x04
+#define XBLC_EP_IN 0x05
+
+#define XBLC_SET_SAMPLE_RATE 0x00
+#define XBLC_SET_AGC 0x01
+
+#define XBLC_MAX_PACKET 48
+#define XBLC_FIFO_SIZE (XBLC_MAX_PACKET * 100) //~100 ms worth of audio at 16bit 24kHz
+
+static const uint8_t silence[256] = {0};
+
+static const uint16_t xblc_sample_rates[5] = {
+ 8000, 11025, 16000, 22050, 24000
+};
+
+typedef struct USBXBLCState {
+ USBDevice dev;
+ uint8_t device_index;
+ uint8_t auto_gain_control;
+ uint16_t sample_rate;
+
+ QEMUSoundCard card;
+ struct audsettings as;
+
+ struct {
+ SWVoiceOut* voice;
+ uint8_t packet[XBLC_MAX_PACKET];
+ Fifo8 fifo;
+ } out;
+
+ struct {
+ SWVoiceIn *voice;
+ uint8_t packet[XBLC_MAX_PACKET];
+ Fifo8 fifo;
+ } in;
+} USBXBLCState;
+
+enum {
+ STR_MANUFACTURER = 1,
+ STR_PRODUCT,
+ STR_SERIALNUMBER,
+};
+
+static const USBDescStrings desc_strings = {
+ [STR_MANUFACTURER] = "xemu",
+ [STR_PRODUCT] = XBLC_STR,
+ [STR_SERIALNUMBER] = "1",
+};
+
+static const USBDescIface desc_iface[]= {
+ {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = XBLC_INTERFACE_CLASS,
+ .bInterfaceSubClass = XBLC_INTERFACE_SUBCLASS,
+ .bInterfaceProtocol = 0x00,
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_OUT | XBLC_EP_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC,
+ .wMaxPacketSize = XBLC_MAX_PACKET,
+ .is_audio = 1,
+ .bInterval = 1,
+ .bRefresh = 0,
+ .bSynchAddress = 0,
+ }
+ },
+ },
+ {
+ .bInterfaceNumber = 1,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = XBLC_INTERFACE_CLASS,
+ .bInterfaceSubClass = XBLC_INTERFACE_SUBCLASS,
+ .bInterfaceProtocol = 0x00,
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | XBLC_EP_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC,
+ .wMaxPacketSize = XBLC_MAX_PACKET,
+ .is_audio = 1,
+ .bInterval = 1,
+ .bRefresh = 0,
+ .bSynchAddress = 0,
+ }
+ },
+ }
+};
+
+static const USBDescDevice desc_device = {
+ .bcdUSB = 0x0110,
+ .bMaxPacketSize0 = 8,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 2,
+ .bConfigurationValue = 1,
+ .bmAttributes = USB_CFG_ATT_ONE,
+ .bMaxPower = 100,
+ .nif = ARRAY_SIZE(desc_iface),
+ .ifs = desc_iface,
+ },
+ },
+};
+
+static const USBDesc desc_xblc = {
+ .id = {
+ .idVendor = 0x045e,
+ .idProduct = 0x0283,
+ .bcdDevice = 0x0110,
+ .iManufacturer = STR_MANUFACTURER,
+ .iProduct = STR_PRODUCT,
+ .iSerialNumber = STR_SERIALNUMBER,
+ },
+ .full = &desc_device,
+ .str = desc_strings,
+};
+
+static void usb_xblc_handle_reset(USBDevice *dev)
+{
+ USBXBLCState *s = (USBXBLCState *)dev;
+
+ DPRINTF("[XBLC] Reset\n");
+ fifo8_reset(&s->in.fifo);
+ fifo8_reset(&s->out.fifo);
+}
+
+static void output_callback(void *opaque, int avail)
+{
+ USBXBLCState *s = (USBXBLCState *)opaque;
+ const uint8_t *data;
+ uint32_t processed, max_len;
+
+ // Not enough data to send, wait a bit longer, fill with silence for now
+ if (fifo8_num_used(&s->out.fifo) < XBLC_MAX_PACKET) {
+ do {
+ processed = AUD_write(s->out.voice, (void *)silence, ARRAY_SIZE(silence));
+ avail -= processed;
+ } while (avail > 0 && processed >= XBLC_MAX_PACKET);
+ return;
+ }
+
+ // Write speaker data into audio backend
+ while (avail > 0 && !fifo8_is_empty(&s->out.fifo)) {
+ max_len = MIN(fifo8_num_used(&s->out.fifo), avail);
+ data = fifo8_pop_bufptr(&s->out.fifo, max_len, &max_len);
+ processed = AUD_write(s->out.voice, (void *)data, max_len);
+ avail -= processed;
+ if (processed < max_len) return;
+ }
+}
+
+static void input_callback(void *opaque, int avail)
+{
+ USBXBLCState *s = (USBXBLCState *)opaque;
+ uint32_t processed, max_len;
+
+ // Get microphone data from audio backend
+ while (avail > 0 && !fifo8_is_full(&s->in.fifo)) {
+ max_len = MIN(sizeof(s->in.packet), fifo8_num_free(&s->in.fifo));
+ processed = AUD_read(s->in.voice, s->in.packet, max_len);
+ avail -= processed;
+ fifo8_push_all(&s->in.fifo, s->in.packet, processed);
+ if (processed < max_len) return;
+ }
+
+ // Flush excess/old data - this can happen if the user program stops the iso transfers after it
+ // has setup the xblc.
+ while (avail > 0)
+ {
+ processed = AUD_read(s->in.voice, s->in.packet, XBLC_MAX_PACKET);
+ avail -= processed;
+ if (processed == 0) break;
+ }
+}
+
+static void xblc_audio_stream_init(USBDevice *dev, uint16_t sample_rate)
+{
+ USBXBLCState *s = (USBXBLCState *)dev;
+
+ AUD_set_active_out(s->out.voice, FALSE);
+ AUD_set_active_in(s->in.voice, FALSE);
+
+ fifo8_reset(&s->in.fifo);
+ fifo8_reset(&s->out.fifo);
+
+ s->as.freq = sample_rate;
+ s->as.nchannels = 1;
+ s->as.fmt = AUDIO_FORMAT_S16;
+ s->as.endianness = 0;
+
+ s->out.voice = AUD_open_out(&s->card, s->out.voice, TYPE_USB_XBLC "-speaker",
+ s, output_callback, &s->as);
+
+ s->in.voice = AUD_open_in(&s->card, s->in.voice, TYPE_USB_XBLC "-microphone",
+ s, input_callback, &s->as);
+
+ AUD_set_active_out(s->out.voice, TRUE);
+ AUD_set_active_in(s->in.voice, TRUE);
+ DPRINTF("[XBLC] Init audio streams at %d Hz\n", sample_rate);
+}
+
+static void usb_xblc_handle_control(USBDevice *dev, USBPacket *p,
+ int request, int value, int index, int length, uint8_t *data)
+{
+ USBXBLCState *s = (USBXBLCState *)dev;
+
+ if (usb_desc_handle_control(dev, p, request, value, index, length, data) >= 0) {
+ DPRINTF("[XBLC] USB Control request handled by usb_desc_handle_control\n");
+ return;
+ }
+
+ switch (request) {
+ case VendorInterfaceOutRequest | USB_REQ_SET_FEATURE:
+ if (index == XBLC_SET_SAMPLE_RATE)
+ {
+ uint8_t rate = value & 0xFF;
+ assert(rate < ARRAY_SIZE(xblc_sample_rates));
+ DPRINTF("[XBLC] Set Sample Rate to %04x\n", rate);
+ s->sample_rate = xblc_sample_rates[rate];
+ xblc_audio_stream_init(dev, s->sample_rate);
+ break;
+ }
+ else if (index == XBLC_SET_AGC)
+ {
+ DPRINTF("[XBLC] Set Auto Gain Control to %d\n", value);
+ s->auto_gain_control = (value) ? 1 : 0;
+ break;
+ }
+ // Fallthrough
+ default:
+ DPRINTF("[XBLC] USB stalled on request 0x%x value 0x%x\n", request, value);
+ p->status = USB_RET_STALL;
+ assert(false);
+ return;
+ }
+}
+
+static void usb_xblc_handle_data(USBDevice *dev, USBPacket *p)
+{
+ USBXBLCState *s = (USBXBLCState *)dev;
+ uint32_t to_process, chunk_len;
+
+ switch (p->pid) {
+ case USB_TOKEN_IN:
+ // Microphone Data - Get data from fifo and copy into usb packet
+ assert(p->ep->nr == XBLC_EP_IN);
+ to_process = MIN(fifo8_num_used(&s->in.fifo), p->iov.size);
+ chunk_len = 0;
+
+ // fifo may not give us a contiguous packet, so may need multiple calls
+ while (to_process) {
+ const uint8_t *packet = fifo8_pop_bufptr(&s->in.fifo, to_process, &chunk_len);
+ usb_packet_copy(p, (void *)packet, chunk_len);
+ to_process -= chunk_len;
+ }
+
+ break;
+ case USB_TOKEN_OUT:
+ // Speaker data - get data from usb packet then push to fifo.
+ assert(p->ep->nr == XBLC_EP_OUT);
+ to_process = MIN(fifo8_num_free(&s->out.fifo), p->iov.size);
+ usb_packet_copy(p, s->out.packet, to_process);
+ fifo8_push_all(&s->out.fifo, s->out.packet, to_process);
+
+ break;
+ default:
+ //Iso cannot report STALL/HALT, but we shouldn't be here anyway.
+ assert(false);
+ break;
+ }
+
+ // Ensure we fill the entire packet regardless of if we have audio data so we don't
+ // cause an underrun error.
+ if (p->actual_length < p->iov.size)
+ usb_packet_copy(p, (void *)silence, p->iov.size - p->actual_length);
+
+}
+
+static void usb_xbox_communicator_unrealize(USBDevice *dev)
+{
+ USBXBLCState *s = USB_XBLC(dev);
+
+ AUD_set_active_out(s->out.voice, false);
+ AUD_set_active_in(s->in.voice, false);
+
+ fifo8_destroy(&s->out.fifo);
+ fifo8_destroy(&s->in.fifo);
+
+ AUD_close_out(&s->card, s->out.voice);
+ AUD_close_in(&s->card, s->in.voice);
+ AUD_remove_card(&s->card);
+}
+
+static void usb_xblc_class_initfn(ObjectClass *klass, void *data)
+{
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+ uc->handle_reset = usb_xblc_handle_reset;
+ uc->handle_control = usb_xblc_handle_control;
+ uc->handle_data = usb_xblc_handle_data;
+ uc->handle_attach = usb_desc_attach;
+}
+
+static void usb_xbox_communicator_realize(USBDevice *dev, Error **errp)
+{
+ USBXBLCState *s = USB_XBLC(dev);
+ usb_desc_create_serial(dev);
+ usb_desc_init(dev);
+ AUD_register_card(TYPE_USB_XBLC, &s->card, errp);
+
+ fifo8_create(&s->in.fifo, XBLC_FIFO_SIZE);
+ fifo8_create(&s->out.fifo, XBLC_FIFO_SIZE);
+}
+
+static Property xblc_properties[] = {
+ DEFINE_PROP_UINT8("index", USBXBLCState, device_index, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription usb_xblc_vmstate = {
+ .name = TYPE_USB_XBLC,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_USB_DEVICE(dev, USBXBLCState),
+ // FIXME
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void usb_xbox_communicator_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->product_desc = XBLC_STR;
+ uc->usb_desc = &desc_xblc;
+ uc->realize = usb_xbox_communicator_realize;
+ uc->unrealize = usb_xbox_communicator_unrealize;
+ usb_xblc_class_initfn(klass, data);
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+ dc->vmsd = &usb_xblc_vmstate;
+ device_class_set_props(dc, xblc_properties);
+ dc->desc = XBLC_STR;
+}
+
+static const TypeInfo info_xblc = {
+ .name = TYPE_USB_XBLC,
+ .parent = TYPE_USB_DEVICE,
+ .instance_size = sizeof(USBXBLCState),
+ .class_init = usb_xbox_communicator_class_initfn,
+};
+
+static void usb_xblc_register_types(void)
+{
+ type_register_static(&info_xblc);
+}
+
+type_init(usb_xblc_register_types)
diff --git a/hw/xbox/xid-gamepad.c b/hw/xbox/xid-gamepad.c
new file mode 100644
index 0000000000..5e27e4912a
--- /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 86f34f7de2..c37142bc0d 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)
+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)
-{
- 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 0000000000..44406eaf91
--- /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/meson.build b/meson.build
index 5067f1ac3c..060ed7f146 100644
--- a/meson.build
+++ b/meson.build
@@ -2292,14 +2292,6 @@ libglslang = not_found
if host_os == 'windows'
vulkan = declare_dependency(compile_args: ['-DVK_USE_PLATFORM_WIN32_KHR'])
- libglslang = declare_dependency(link_args: [
- '-lglslang',
- '-lMachineIndependent',
- '-lGenericCodeGen',
- '-lSPIRV',
- '-lSPIRV-Tools',
- '-lSPIRV-Tools-opt'
- ])
elif host_os == 'linux'
vulkan = dependency('vulkan')
endif
@@ -2406,8 +2398,7 @@ if have_system
# Default to native drivers first, OSS second, SDL third
audio_drivers_priority = \
- [ 'pa', 'coreaudio', 'dsound', 'sndio', 'oss' ] + \
- (host_os == 'linux' ? [] : [ 'sdl' ])
+ [ 'pa', 'coreaudio', 'dsound', 'sndio', 'oss', 'sdl' ]
audio_drivers_default = []
foreach k: audio_drivers_priority
if audio_drivers_available[k]
diff --git a/scripts/gen-license.py b/scripts/gen-license.py
index 8be7aeb3fa..31c240aaac 100755
--- a/scripts/gen-license.py
+++ b/scripts/gen-license.py
@@ -261,6 +261,12 @@ Lib('SPIRV-Reflect', 'https://github.com/KhronosGroup/SPIRV-Reflect',
submodule=Submodule('subprojects/SPIRV-Reflect.wrap')
),
+Lib('glslang', 'https://github.com/KhronosGroup/glslang',
+ bsd_3clause, 'https://raw.githubusercontent.com/KhronosGroup/glslang/main/LICENSE.txt',
+ ships_static=all_platforms,
+ submodule=Submodule('subprojects/glslang.wrap')
+ ),
+
#
# Data files included with xemu
#
@@ -374,18 +380,6 @@ Lib('miniz', 'https://github.com/richgel999/miniz',
ships_static={windows}, platform={windows},
version='2.1.0'
),
-
-Lib('glslang', 'https://github.com/KhronosGroup/glslang',
- bsd_3clause, 'https://raw.githubusercontent.com/KhronosGroup/glslang/main/LICENSE.txt',
- ships_static={windows}, platform={windows},
- version='14.3.0'
- ),
-
-Lib('SPIRV-Tools', 'https://github.com/KhronosGroup/SPIRV-Tools',
- apache2, 'https://raw.githubusercontent.com/KhronosGroup/SPIRV-Tools/main/LICENSE',
- ships_static={windows}, platform={windows},
- pkgconfig=PkgConfig('SPIRV-Tools')
- ),
]
def gen_license():
diff --git a/subprojects/SPIRV-Reflect.wrap b/subprojects/SPIRV-Reflect.wrap
index 6893ea7ecb..dded924d1d 100644
--- a/subprojects/SPIRV-Reflect.wrap
+++ b/subprojects/SPIRV-Reflect.wrap
@@ -1,4 +1,4 @@
[wrap-git]
url=https://github.com/KhronosGroup/SPIRV-Reflect
-revision=vulkan-sdk-1.3.296.0
+revision=vulkan-sdk-1.4.304.0
depth=1
diff --git a/subprojects/glslang.wrap b/subprojects/glslang.wrap
index 223723e721..29b74d8249 100644
--- a/subprojects/glslang.wrap
+++ b/subprojects/glslang.wrap
@@ -1,4 +1,4 @@
[wrap-git]
url=https://github.com/KhronosGroup/glslang
-revision=vulkan-sdk-1.3.296.0
+revision=vulkan-sdk-1.4.304.0
depth=1
diff --git a/subprojects/volk.wrap b/subprojects/volk.wrap
index 8089cbce56..46578b64ce 100644
--- a/subprojects/volk.wrap
+++ b/subprojects/volk.wrap
@@ -1,4 +1,4 @@
[wrap-git]
url=https://github.com/zeux/volk
-revision=1.3.295
+revision=1.4.304
depth=1
diff --git a/system/vl.c b/system/vl.c
index 9168fcec9a..580d0cfd28 100644
--- a/system/vl.c
+++ b/system/vl.c
@@ -2996,9 +2996,6 @@ void qemu_init(int argc, char **argv)
fake_argv[fake_argc++] = strdup("-device");
fake_argv[fake_argc++] = strdup("usb-hub,port=1,ports=4");
- fake_argv[fake_argc++] = strdup("-audio");
- fake_argv[fake_argc++] = strdup("none");
-
for (int i = 1; i < argc; i++) {
if (argv[i] != NULL) {
fake_argv[fake_argc++] = argv[i];
diff --git a/ubuntu-win64-cross/Dockerfile b/ubuntu-win64-cross/Dockerfile
index 293b09d169..468f2509c8 100644
--- a/ubuntu-win64-cross/Dockerfile
+++ b/ubuntu-win64-cross/Dockerfile
@@ -6,10 +6,10 @@ FROM ubuntu:24.04
ENV MXE_PATH=/opt/mxe
ENV MXE_REPO=https://github.com/mxe/mxe.git
-ENV MXE_VERSION=ab676e8ce5ba921daaa80a123ff2e415aac4524a
+ENV MXE_VERSION=7f054f14930d109412d2d61d019c12fa80816b8c
ENV MXE_LLVM_MINGW_REPO=https://github.com/libvips/build-win64-mxe
-ENV MXE_LLVM_MINGW_VERSION=21e02f87c282fcfe17c8376217b0a4f44f14d01b
+ENV MXE_LLVM_MINGW_VERSION=8a4e0ab8a3b574287378459356f6bdaecc727de2
ENV MXE_LLVM_MINGW_PATH=/opt/build-win64-mxe
ARG PLUGIN_DIRS="${MXE_LLVM_MINGW_PATH} ${MXE_LLVM_MINGW_PATH}/build/plugins/llvm-mingw"
@@ -78,9 +78,6 @@ RUN make \
RUN rm ${MXE_PATH}/src/sdl2*.patch
COPY vulkan-headers.mk \
- spirv-headers.mk \
- spirv-tools.mk \
- glslang.mk \
glib.mk \
sdl2.mk \
libsamplerate.mk \
@@ -102,10 +99,7 @@ RUN make \
cmake \
libslirp \
sdl2 \
- vulkan-headers \
- spirv-headers \
- spirv-tools \
- glslang
+ vulkan-headers
RUN find ${MXE_PATH}/usr -executable -type f -exec chmod a+x {} \;
diff --git a/ubuntu-win64-cross/glslang.mk b/ubuntu-win64-cross/glslang.mk
deleted file mode 100644
index 99bd7e1b47..0000000000
--- a/ubuntu-win64-cross/glslang.mk
+++ /dev/null
@@ -1,25 +0,0 @@
-PKG := glslang
-$(PKG)_WEBSITE := https://github.com/KhronosGroup/glslang
-$(PKG)_DESCR := glslang
-$(PKG)_IGNORE :=
-$(PKG)_VERSION := 15.0.0
-$(PKG)_SUBDIR := glslang-$($(PKG)_VERSION)
-$(PKG)_FILE := glslang-$($(PKG)_VERSION).tar.gz
-$(PKG)_CHECKSUM := c31c8c2e89af907507c0631273989526ee7d5cdf7df95ececd628fd7b811e064
-$(PKG)_URL := https://github.com/KhronosGroup/glslang/archive/refs/tags/$($(PKG)_VERSION).tar.gz
-$(PKG)_DEPS := cc spirv-tools
-
-define $(PKG)_BUILD
- $(TARGET)-cmake -B'$(BUILD_DIR)' -S'$(SOURCE_DIR)' \
- -G"Ninja" \
- -DBUILD_SHARED_LIBS=$(CMAKE_SHARED_BOOL) \
- -DENABLE_GLSLANG_BINARIES=OFF \
- -DGLSLANG_TESTS=OFF \
- -DBUILD_EXTERNAL=OFF \
- -DALLOW_EXTERNAL_SPIRV_TOOLS=ON \
- -DVERBOSE=1
- $(TARGET)-cmake --build '$(BUILD_DIR)'
- $(TARGET)-cmake --install '$(BUILD_DIR)'
-endef
-
-# FIXME: Shared libs
diff --git a/ubuntu-win64-cross/spirv-headers.mk b/ubuntu-win64-cross/spirv-headers.mk
deleted file mode 100644
index c7f82bc75c..0000000000
--- a/ubuntu-win64-cross/spirv-headers.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-PKG := spirv-headers
-$(PKG)_WEBSITE := https://github.com/KhronosGroup/SPIRV-Headers
-$(PKG)_DESCR := SPIRV-Headers
-$(PKG)_IGNORE :=
-$(PKG)_VERSION := vulkan-sdk-1.3.296.0
-$(PKG)_SUBDIR := SPIRV-Headers-$($(PKG)_VERSION)
-$(PKG)_FILE := spirv-headers-$($(PKG)_VERSION).tar.gz
-$(PKG)_CHECKSUM := 1423d58a1171611d5aba2bf6f8c69c72ef9c38a0aca12c3493e4fda64c9b2dc6
-$(PKG)_URL := https://github.com/KhronosGroup/SPIRV-Headers/archive/refs/tags/$($(PKG)_VERSION).tar.gz
-$(PKG)_DEPS := cc
-
-define $(PKG)_BUILD
- $(TARGET)-cmake -B'$(BUILD_DIR)' -S'$(SOURCE_DIR)'
- $(TARGET)-cmake --build '$(BUILD_DIR)'
- $(TARGET)-cmake --install '$(BUILD_DIR)'
-endef
diff --git a/ubuntu-win64-cross/spirv-tools.mk b/ubuntu-win64-cross/spirv-tools.mk
deleted file mode 100644
index 6d6d6a6148..0000000000
--- a/ubuntu-win64-cross/spirv-tools.mk
+++ /dev/null
@@ -1,27 +0,0 @@
-PKG := spirv-tools
-$(PKG)_WEBSITE := https://github.com/KhronosGroup/SPIRV-Tools
-$(PKG)_DESCR := SPIRV-Tools
-$(PKG)_IGNORE :=
-$(PKG)_VERSION := vulkan-sdk-1.3.296.0
-$(PKG)_SUBDIR := SPIRV-Tools-$($(PKG)_VERSION)
-$(PKG)_FILE := spirv-tools-$($(PKG)_VERSION).tar.gz
-$(PKG)_CHECKSUM := 75aafdf7e731b4b6bfb36a590ddfbb38ebc605d80487f38254da24fe0cb95837
-$(PKG)_URL := https://github.com/KhronosGroup/SPIRV-Tools/archive/refs/tags/$($(PKG)_VERSION).tar.gz
-$(PKG)_DEPS := cc spirv-headers
-
-define $(PKG)_BUILD
- $(TARGET)-cmake -B'$(BUILD_DIR)' -S'$(SOURCE_DIR)' \
- -G"Ninja" \
- -DBUILD_SHARED_LIBS=$(CMAKE_SHARED_BOOL) \
- -DSPIRV-Headers_SOURCE_DIR=$(PREFIX)/$(TARGET) \
- -DSPIRV_WERROR=OFF \
- -DBUILD_SHARED_LIBS=OFF \
- -DSPIRV_TOOLS_BUILD_STATIC=ON \
- -DSPIRV_SKIP_EXECUTABLES=ON \
- -DSPIRV_SKIP_TESTS=ON \
- -DVERBOSE=1
- $(TARGET)-cmake --build '$(BUILD_DIR)'
- $(TARGET)-cmake --install '$(BUILD_DIR)'
-endef
-
-# FIXME: Shared libs
diff --git a/ubuntu-win64-cross/vulkan-headers.mk b/ubuntu-win64-cross/vulkan-headers.mk
index 8e398ff7ce..01594d6c3a 100644
--- a/ubuntu-win64-cross/vulkan-headers.mk
+++ b/ubuntu-win64-cross/vulkan-headers.mk
@@ -2,10 +2,10 @@ PKG := vulkan-headers
$(PKG)_WEBSITE := https://github.com/KhronosGroup/Vulkan-Headers
$(PKG)_DESCR := Vulkan-Headers
$(PKG)_IGNORE :=
-$(PKG)_VERSION := vulkan-sdk-1.3.296.0
+$(PKG)_VERSION := vulkan-sdk-1.4.304.0
$(PKG)_SUBDIR := Vulkan-Headers-$($(PKG)_VERSION)
$(PKG)_FILE := vulkan-headers-$($(PKG)_VERSION).tar.gz
-$(PKG)_CHECKSUM := 1e872a0be3890784bbe68dcd89b7e017fed77ba95820841848718c98bda6dc33
+$(PKG)_CHECKSUM := 46f8f5b6384a36c688e0c40d28d534df41d22de406493dfb5c9b7bcc29672613
$(PKG)_URL := https://github.com/KhronosGroup/Vulkan-Headers/archive/refs/tags/$($(PKG)_VERSION).tar.gz
$(PKG)_DEPS := cc
diff --git a/ui/xemu-input.c b/ui/xemu-input.c
index d9181fe2a6..31a51eda9d 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 330ae58a7c..23c1a9f91b 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 6c07d087b4..0a06b7f16a 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 2347ded770..d34c08b00e 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
//