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 @@ + + + Controller S + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + X + + + + B + + + + A + + + + Y + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 //