diff --git a/config_spec.yml b/config_spec.yml index d013c806ac..e552e5b8ee 100644 --- a/config_spec.yml +++ b/config_spec.yml @@ -132,6 +132,154 @@ input: rtrigger: type: integer default: 18 # w + keyboard_sbc_scancode_map: + eject: + type: integer + default: 41 # esc + cockpit_hatch: + type: integer + default: 62 # F5 + ignition: + type: integer + default: 63 # F6 + start: + type: integer + default: 64 # F7 + open_close: + type: integer + default: 74 # home + map_zoom_in_out: + type: integer + default: 77 # end + mode_select: + type: integer + default: 73 # insert + sub_monitor_mode_select: + type: integer + default: 76 # delete + zoom_in: + type: integer + default: 75 # Page Up + zoom_out: + type: integer + default: 78 # Page Down + fss: + type: integer + default: 97 # numpad 9 + manipulator: + type: integer + default: 94 # numpad 6 + line_color_change: + type: integer + default: 91 # numpad 3 + washing: + type: integer + default: 45 # - + extinguisher: + type: integer + default: 8 # e + chaff: + type: integer + default: 6 # c + tank_detach: + type: integer + default: 96 # numpad 8 + override: + type: integer + default: 93 # numpad 5 + night_scope: + type: integer + default: 90 # numpad 2 + func1: + type: integer + default: 95 # numpad 7 + func2: + type: integer + default: 92 # numpad 4 + func3: + type: integer + default: 89 # numpad 1 + main_weapon_control: + type: integer + default: 35 # 6 + sub_weapon_control: + type: integer + default: 36 # 7 + magazine_change: + type: integer + default: 21 # r + com1: + type: integer + default: 30 # 1 + com2: + type: integer + default: 31 # 2 + com3: + type: integer + default: 32 # 3 + com4: + type: integer + default: 33 # 4 + com5: + type: integer + default: 34 # 5 + sight_change: + type: integer + default: 20 # q + filt_control_system: + type: integer + default: 65 # F8 + oxygen_supply_system: + type: integer + default: 66 # F9 + fuel_flow_rate: + type: integer + default: 67 # F10 + buffer_material: + type: integer + default: 68 # F11 + vt_location_measurement: + type: integer + default: 69 # F12 + gear_up: + type: integer + default: 225 # lshift + gear_down: + type: integer + default: 224 # lctrl + tuner_left: + type: integer + default: 54 # < + tuner_right: + type: integer + default: 55 # > + sight_change_up: + type: integer + default: 82 # up + sight_change_down: + type: integer + default: 81 # down + sight_change_left: + type: integer + default: 80 # left + sight_change_right: + type: integer + default: 79 # right + rotation_left: + type: integer + default: 4 # a + rotation_right: + type: integer + default: 7 # d + left_pedal: + type: integer + default: 44 #space + right_pedal: + type: integer + default: 26 # w + middle_pedal: + type: integer + default: 22 # s display: renderer: diff --git a/data/meson.build b/data/meson.build index 4a82350d44..86f4b07f55 100644 --- a/data/meson.build +++ b/data/meson.build @@ -1,4 +1,5 @@ pfiles = [ + 'sb_controller_mask.png', 'controller_mask.png', 'controller_mask_s.png', 'xmu_mask.png', diff --git a/data/sb_controller_mask.png b/data/sb_controller_mask.png new file mode 100644 index 0000000000..bfa39c775a Binary files /dev/null and b/data/sb_controller_mask.png differ diff --git a/hw/xbox/meson.build b/hw/xbox/meson.build index dce2d3729e..b3b18e4c64 100644 --- a/hw/xbox/meson.build +++ b/hw/xbox/meson.build @@ -17,6 +17,7 @@ specific_ss.add(files( 'xid.c', 'xblc.c', 'xid-gamepad.c', + 'xid-steel-battalion.c', )) subdir('nv2a') subdir('mcpx') diff --git a/hw/xbox/xid-steel-battalion.c b/hw/xbox/xid-steel-battalion.c new file mode 100644 index 0000000000..29b2f6479d --- /dev/null +++ b/hw/xbox/xid-steel-battalion.c @@ -0,0 +1,438 @@ +/* + * 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_CAPCOM 0x0a7b + +#define STEEL_BATTALION_IN_ENDPOINT_ID 0x02 +#define STEEL_BATTALION_OUT_ENDPOINT_ID 0x01 + +typedef struct XIDSteelBattalionReport { + uint8_t bReportId; + uint8_t bLength; + uint32_t dwButtons; + uint8_t bMoreButtons; + uint16_t wPadding; + uint8_t bAimingX; + uint8_t bPadding; + uint8_t bAimingY; + int16_t sRotationLever; // only high byte is used + int16_t sSightChangeX; // only high byte is used + int16_t sSightChangeY; // only high byte is used + uint16_t wLeftPedal; // only high byte is used + uint16_t wMiddlePedal; // only high byte is used + uint16_t wRightPedal; // only high byte is used + uint8_t ucTunerDial; // low nibble, The 9 o'clock postion is 0, and the 6 + // o'clock position is 12 + uint8_t ucGearLever; // gear lever 1~5 for gear 1~5, 7~13 for gear R,N,1~5, + // 15 for gear R +} QEMU_PACKED XIDSteelBattalionReport; + +// Based on: +// https://github.com/Ryzee119/ogx360/blob/master/Firmware/src/usbd/usbd_xid.h:195 +typedef struct XIDSteelBattalionOutputReport { + uint8_t report_id; + uint8_t length; + uint8_t EmergencyEject : 4; + uint8_t CockpitHatch : 4; + uint8_t Ignition : 4; + uint8_t Start : 4; + uint8_t OpenClose : 4; + uint8_t MapZoomInOut : 4; + uint8_t ModeSelect : 4; + uint8_t SubMonitorModeSelect : 4; + uint8_t MainMonitorZoomIn : 4; + uint8_t MainMonitorZoomOut : 4; + uint8_t ForecastShootingSystem : 4; + uint8_t Manipulator : 4; + uint8_t LineColorChange : 4; + uint8_t Washing : 4; + uint8_t Extinguisher : 4; + uint8_t Chaff : 4; + uint8_t TankDetach : 4; + uint8_t Override : 4; + uint8_t NightScope : 4; + uint8_t F1 : 4; + uint8_t F2 : 4; + uint8_t F3 : 4; + uint8_t MainWeaponControl : 4; + uint8_t SubWeaponControl : 4; + uint8_t MagazineChange : 4; + uint8_t Comm1 : 4; + uint8_t Comm2 : 4; + uint8_t Comm3 : 4; + uint8_t Comm4 : 4; + uint8_t Comm5 : 4; + uint8_t : 4; + uint8_t GearR : 4; + uint8_t GearN : 4; + uint8_t Gear1 : 4; + uint8_t Gear2 : 4; + uint8_t Gear3 : 4; + uint8_t Gear4 : 4; + uint8_t Gear5 : 4; + uint8_t not_used; +} QEMU_PACKED XIDSteelBattalionOutputReport; + +typedef struct USBXIDSteelBattalionState { + USBDevice dev; + USBEndpoint *intr; + const XIDDesc *xid_desc; + XIDSteelBattalionReport in_state; + XIDSteelBattalionReport in_state_capabilities; + XIDSteelBattalionOutputReport out_state; + XIDSteelBattalionOutputReport out_state_capabilities; + uint8_t device_index; +} USBXIDSteelBattalionState; + +#define USB_XID_SB(obj) \ + OBJECT_CHECK(USBXIDSteelBattalionState, (obj), TYPE_USB_XID_STEEL_BATTALION) + +static const USBDescIface desc_iface_steel_battalion = { + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_XID, + .bInterfaceSubClass = 0x42, + .bInterfaceProtocol = 0x00, + .eps = + (USBDescEndpoint[]){ + { + .bEndpointAddress = USB_DIR_IN | STEEL_BATTALION_IN_ENDPOINT_ID, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = 0x20, + .bInterval = 4, + }, + { + .bEndpointAddress = + USB_DIR_OUT | STEEL_BATTALION_OUT_ENDPOINT_ID, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = 0x20, + .bInterval = 4, + }, + }, +}; + +static const USBDescDevice desc_device_steel_battalion = { + .bcdUSB = 0x0110, + .bMaxPacketSize0 = 0x40, + .bNumConfigurations = 1, + .confs = + (USBDescConfig[]){ + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .bmAttributes = USB_CFG_ATT_ONE, + .bMaxPower = 50, + .nif = 1, + .ifs = &desc_iface_steel_battalion, + }, + }, +}; + +static const USBDesc desc_xbox_steel_battalion = { + .id = { + .idVendor = USB_VENDOR_CAPCOM, + .idProduct = 0xd000, + .bcdDevice = 0x0100, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT, + .iSerialNumber = STR_SERIALNUMBER, + }, + .full = &desc_device_steel_battalion, + .str = desc_strings, +}; + +static const XIDDesc desc_xid_steel_battalion = { + .bLength = 0x10, + .bDescriptorType = USB_DT_XID, + .bcdXid = 0x100, + .bType = XID_DEVICETYPE_STEEL_BATTALION, + .bSubType = XID_DEVICESUBTYPE_GAMEPAD, + .bMaxInputReportSize = 26, + .bMaxOutputReportSize = 32, + .wAlternateProductIds = { 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF }, +}; + +static void update_sbc_input(USBXIDSteelBattalionState *s) +{ + if (xemu_input_get_test_mode()) { + // Don't report changes if we are testing the controller while running + return; + } + + ControllerState *state = xemu_input_get_bound(s->device_index); + assert(state); + xemu_input_update_controller(state); + + s->in_state.dwButtons = (uint32_t)(state->sbc.buttons & 0xFFFFFFFF); + s->in_state.bMoreButtons = (uint8_t)((state->sbc.buttons >> 32) & 0x7F); + s->in_state.bMoreButtons |= state->sbc.toggleSwitches; + + s->in_state.sSightChangeX = state->sbc.axis[SBC_AXIS_SIGHT_CHANGE_X]; + s->in_state.sSightChangeY = state->sbc.axis[SBC_AXIS_SIGHT_CHANGE_Y]; + s->in_state.bAimingX = + (uint8_t)(128 + (state->sbc.axis[SBC_AXIS_AIMING_X] / + 256)); // Convert from int16_t to uint8_t + s->in_state.bAimingY = + (uint8_t)(128 + (state->sbc.axis[SBC_AXIS_AIMING_Y] / + 256)); // Convert from int16_t to uint8_t + s->in_state.sRotationLever = state->sbc.axis[SBC_AXIS_ROTATION_LEVER]; + s->in_state.wLeftPedal = (uint16_t)state->sbc.axis[SBC_AXIS_LEFT_PEDAL]; + s->in_state.wMiddlePedal = (uint16_t)state->sbc.axis[SBC_AXIS_MIDDLE_PEDAL]; + s->in_state.wRightPedal = (uint16_t)state->sbc.axis[SBC_AXIS_RIGHT_PEDAL]; + + s->in_state.ucGearLever = state->sbc.gearLever; + s->in_state.ucTunerDial = state->sbc.tunerDial; +} + +static void usb_xid_steel_battalion_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, + int index, int length, + uint8_t *data) +{ + USBXIDSteelBattalionState *s = + DO_UPCAST(USBXIDSteelBattalionState, dev, dev); + + DPRINTF("xid handle_control 0x%x 0x%x\n", request, value); + + int ret = + usb_desc_handle_control(dev, p, request, value, index, length, data); + if (ret >= 0) { + DPRINTF("xid handled by usb_desc_handle_control: %d\n", ret); + return; + } + + switch (request) { + /* HID requests */ + case ClassInterfaceRequest | HID_GET_REPORT: + DPRINTF("xid GET_REPORT 0x%x\n", value); + update_sbc_input(s); + if (value == 0x0100) { /* input */ + if (length <= s->in_state.bLength) { + memcpy(data, &s->in_state, s->in_state.bLength); + p->actual_length = length; + } else { + p->status = USB_RET_STALL; + } + } else { + p->status = USB_RET_STALL; + assert(false); + } + break; + case ClassInterfaceOutRequest | HID_SET_REPORT: + DPRINTF("xid SET_REPORT 0x%x\n", value); + if (value == 0x0200) { /* output */ + /* Read length, then the entire packet */ + if (length == s->out_state.length) { + memcpy(&s->out_state, data, sizeof(s->out_state)); + + /* FIXME: This should also be a STALL */ + assert(s->out_state.length == sizeof(s->out_state)); + + p->actual_length = length; + } else { + p->status = USB_RET_STALL; + } + // update_output(s); + } else { + p->status = USB_RET_STALL; + assert(false); + } + break; + /* XID requests */ + case VendorInterfaceRequest | USB_REQ_GET_DESCRIPTOR: + DPRINTF("xid GET_DESCRIPTOR 0x%x\n", value); + if (value == 0x4200) { + assert(s->xid_desc->bLength <= length); + memcpy(data, s->xid_desc, s->xid_desc->bLength); + p->actual_length = s->xid_desc->bLength; + } else { + p->status = USB_RET_STALL; + assert(false); + } + break; + case VendorInterfaceRequest | XID_GET_CAPABILITIES: + DPRINTF("xid XID_GET_CAPABILITIES 0x%x\n", value); + if (value == 0x0100) { + if (length > s->in_state_capabilities.bLength) { + length = s->in_state_capabilities.bLength; + } + memcpy(data, &s->in_state_capabilities, length); + p->actual_length = length; + } else if (value == 0x0200) { + if (length > s->out_state_capabilities.length) { + length = s->out_state_capabilities.length; + } + memcpy(data, &s->out_state_capabilities, length); + p->actual_length = length; + } else { + p->status = USB_RET_STALL; + assert(false); + } + break; + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE) << 8) | + USB_REQ_GET_DESCRIPTOR: + /* FIXME: ! */ + DPRINTF("xid unknown xpad request 0x%x: value = 0x%x\n", request, + value); + memset(data, 0x00, length); + // FIXME: Intended for the hub: usbd_get_hub_descriptor, UT_READ_CLASS?! + p->status = USB_RET_STALL; + // assert(false); + break; + case ((USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_ENDPOINT) << 8) | + USB_REQ_CLEAR_FEATURE: + /* FIXME: ! */ + DPRINTF("xid unknown xpad request 0x%x: value = 0x%x\n", request, + value); + memset(data, 0x00, length); + p->status = USB_RET_STALL; + break; + default: + DPRINTF("xid USB stalled on request 0x%x value 0x%x\n", request, value); + p->status = USB_RET_STALL; + assert(false); + break; + } +} + + +static void usb_xid_steel_battalion_handle_data(USBDevice *dev, USBPacket *p) +{ + USBXIDSteelBattalionState *s = + DO_UPCAST(USBXIDSteelBattalionState, dev, dev); + + DPRINTF("xid handle_steel_battalion_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 == STEEL_BATTALION_IN_ENDPOINT_ID) { + update_sbc_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 == STEEL_BATTALION_OUT_ENDPOINT_ID) { + usb_packet_copy(p, &s->out_state, s->out_state.length); + // TODO: Update output for Steel Battalion Controller here, if we + // want to. It's LED data, so, maybe use it for RGB integration with + // RGB Keyboards? + } else { + assert(false); + } + break; + default: + p->status = USB_RET_STALL; + assert(false); + break; + } +} + +static void usb_xid_steel_battalion_class_initfn(ObjectClass *klass, void *data) +{ + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->handle_reset = usb_xid_handle_reset; + uc->handle_control = usb_xid_steel_battalion_handle_control; + uc->handle_data = usb_xid_steel_battalion_handle_data; + // uc->handle_destroy = usb_xid_handle_destroy; + uc->handle_attach = usb_desc_attach; +} + +static void usb_steel_battalion_realize(USBDevice *dev, Error **errp) +{ + USBXIDSteelBattalionState *s = USB_XID_SB(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_steel_battalion; + + 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", USBXIDSteelBattalionState, device_index, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static const VMStateDescription vmstate_usb_sb = { + .name = TYPE_USB_XID_STEEL_BATTALION, + .version_id = 1, + .minimum_version_id = 1, + .fields = + (VMStateField[]){ VMSTATE_USB_DEVICE(dev, USBXIDSteelBattalionState), + // FIXME + VMSTATE_END_OF_LIST() }, +}; + +static void usb_steel_battalion_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->product_desc = "Steel Battalion Controller"; + uc->usb_desc = &desc_xbox_steel_battalion; + uc->realize = usb_steel_battalion_realize; + uc->unrealize = usb_xbox_gamepad_unrealize; + usb_xid_steel_battalion_class_initfn(klass, data); + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); + dc->vmsd = &vmstate_usb_sb; + device_class_set_props(dc, xid_properties); + dc->desc = "Steel Battalion Controller"; +} + +static const TypeInfo usb_steel_battalion_info = { + .name = TYPE_USB_XID_STEEL_BATTALION, + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBXIDSteelBattalionState), + .class_init = usb_steel_battalion_class_initfn, +}; + +static void usb_xid_register_types(void) +{ + type_register_static(&usb_steel_battalion_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 c37142bc0d..6580f29236 100644 --- a/hw/xbox/xid.c +++ b/hw/xbox/xid.c @@ -47,8 +47,8 @@ void update_output(USBXIDGamepadState *s) ControllerState *state = xemu_input_get_bound(s->device_index); assert(state); - state->rumble_l = s->out_state.left_actuator_strength; - state->rumble_r = s->out_state.right_actuator_strength; + state->gp.rumble_l = s->out_state.left_actuator_strength; + state->gp.rumble_r = s->out_state.right_actuator_strength; xemu_input_update_rumble(state); } @@ -84,23 +84,23 @@ void update_input(USBXIDGamepadState *s) }; for (int i = 0; i < 6; i++) { - int pressed = state->buttons & button_map_analog[i][1]; + int pressed = state->gp.buttons & button_map_analog[i][1]; s->in_state.bAnalogButtons[button_map_analog[i][0]] = pressed ? 0xff : 0; } s->in_state.wButtons = 0; for (int i = 0; i < 8; i++) { - if (state->buttons & button_map_binary[i][1]) { + if (state->gp.buttons & button_map_binary[i][1]) { s->in_state.wButtons |= BUTTON_MASK(button_map_binary[i][0]); } } - s->in_state.bAnalogButtons[GAMEPAD_LEFT_TRIGGER] = state->axis[CONTROLLER_AXIS_LTRIG] >> 7; - s->in_state.bAnalogButtons[GAMEPAD_RIGHT_TRIGGER] = state->axis[CONTROLLER_AXIS_RTRIG] >> 7; - s->in_state.sThumbLX = state->axis[CONTROLLER_AXIS_LSTICK_X]; - s->in_state.sThumbLY = state->axis[CONTROLLER_AXIS_LSTICK_Y]; - s->in_state.sThumbRX = state->axis[CONTROLLER_AXIS_RSTICK_X]; - s->in_state.sThumbRY = state->axis[CONTROLLER_AXIS_RSTICK_Y]; + s->in_state.bAnalogButtons[GAMEPAD_LEFT_TRIGGER] = state->gp.axis[CONTROLLER_AXIS_LTRIG] >> 7; + s->in_state.bAnalogButtons[GAMEPAD_RIGHT_TRIGGER] = state->gp.axis[CONTROLLER_AXIS_RTRIG] >> 7; + s->in_state.sThumbLX = state->gp.axis[CONTROLLER_AXIS_LSTICK_X]; + s->in_state.sThumbLY = state->gp.axis[CONTROLLER_AXIS_LSTICK_Y]; + s->in_state.sThumbRX = state->gp.axis[CONTROLLER_AXIS_RSTICK_X]; + s->in_state.sThumbRY = state->gp.axis[CONTROLLER_AXIS_RSTICK_Y]; } void usb_xid_handle_reset(USBDevice *dev) diff --git a/hw/xbox/xid.h b/hw/xbox/xid.h index 44406eaf91..5acbed0d82 100644 --- a/hw/xbox/xid.h +++ b/hw/xbox/xid.h @@ -48,12 +48,14 @@ #define XID_GET_CAPABILITIES 0x01 #define XID_DEVICETYPE_GAMEPAD 0x01 +#define XID_DEVICETYPE_STEEL_BATTALION 0x80 #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 TYPE_USB_XID_STEEL_BATTALION "usb-steel-battalion" #define GAMEPAD_A 0 #define GAMEPAD_B 1 diff --git a/ui/xemu-input.c b/ui/xemu-input.c index 31a51eda9d..a76eacc92f 100644 --- a/ui/xemu-input.c +++ b/ui/xemu-input.c @@ -34,6 +34,9 @@ #include "sysemu/blockdev.h" +extern SDL_Window *m_window; +extern int viewport_coords[4]; + // #define DEBUG_INPUT #ifdef DEBUG_INPUT @@ -58,33 +61,34 @@ static void xemu_input_print_controller_state(ControllerState *state) "LTrig = %.3f, RTrig = %.3f\n" "LStickX = %.3f, RStickX = %.3f\n" "LStickY = %.3f, RStickY = %.3f\n\n", - !!(state->buttons & CONTROLLER_BUTTON_A), - !!(state->buttons & CONTROLLER_BUTTON_B), - !!(state->buttons & CONTROLLER_BUTTON_X), - !!(state->buttons & CONTROLLER_BUTTON_Y), - !!(state->buttons & CONTROLLER_BUTTON_DPAD_LEFT), - !!(state->buttons & CONTROLLER_BUTTON_DPAD_UP), - !!(state->buttons & CONTROLLER_BUTTON_DPAD_RIGHT), - !!(state->buttons & CONTROLLER_BUTTON_DPAD_DOWN), - !!(state->buttons & CONTROLLER_BUTTON_BACK), - !!(state->buttons & CONTROLLER_BUTTON_START), - !!(state->buttons & CONTROLLER_BUTTON_WHITE), - !!(state->buttons & CONTROLLER_BUTTON_BLACK), - !!(state->buttons & CONTROLLER_BUTTON_LSTICK), - !!(state->buttons & CONTROLLER_BUTTON_RSTICK), - !!(state->buttons & CONTROLLER_BUTTON_GUIDE), - state->axis[CONTROLLER_AXIS_LTRIG], - state->axis[CONTROLLER_AXIS_RTRIG], - state->axis[CONTROLLER_AXIS_LSTICK_X], - state->axis[CONTROLLER_AXIS_RSTICK_X], - state->axis[CONTROLLER_AXIS_LSTICK_Y], - state->axis[CONTROLLER_AXIS_RSTICK_Y] + !!(state->gp.buttons & CONTROLLER_BUTTON_A), + !!(state->gp.buttons & CONTROLLER_BUTTON_B), + !!(state->gp.buttons & CONTROLLER_BUTTON_X), + !!(state->gp.buttons & CONTROLLER_BUTTON_Y), + !!(state->gp.buttons & CONTROLLER_BUTTON_DPAD_LEFT), + !!(state->gp.buttons & CONTROLLER_BUTTON_DPAD_UP), + !!(state->gp.buttons & CONTROLLER_BUTTON_DPAD_RIGHT), + !!(state->gp.buttons & CONTROLLER_BUTTON_DPAD_DOWN), + !!(state->gp.buttons & CONTROLLER_BUTTON_BACK), + !!(state->gp.buttons & CONTROLLER_BUTTON_START), + !!(state->gp.buttons & CONTROLLER_BUTTON_WHITE), + !!(state->gp.buttons & CONTROLLER_BUTTON_BLACK), + !!(state->gp.buttons & CONTROLLER_BUTTON_LSTICK), + !!(state->gp.buttons & CONTROLLER_BUTTON_RSTICK), + !!(state->gp.buttons & CONTROLLER_BUTTON_GUIDE), + state->gp.axis[CONTROLLER_AXIS_LTRIG], + state->gp.axis[CONTROLLER_AXIS_RTRIG], + state->gp.axis[CONTROLLER_AXIS_LSTICK_X], + state->gp.axis[CONTROLLER_AXIS_RSTICK_X], + state->gp.axis[CONTROLLER_AXIS_LSTICK_Y], + state->gp.axis[CONTROLLER_AXIS_RSTICK_Y] ); } #endif 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 }; @@ -127,6 +131,7 @@ static const char **peripheral_params_settings_map[4][2] = { }; static int sdl_kbd_scancode_map[25]; +static int sdl_sbc_kbd_scancode_map[52]; static const char *get_bound_driver(int port) { @@ -136,13 +141,15 @@ static const char *get_bound_driver(int port) // If the driver in the config is NULL, empty, or unrecognized // then default to DRIVER_DUKE if (driver == NULL) - return DRIVER_DUKE; + 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; + if (strcmp(driver, DRIVER_STEEL_BATTALION) == 0) + return DRIVER_STEEL_BATTALION; return DRIVER_DUKE; } @@ -204,6 +211,115 @@ void xemu_input_init(void) sdl_kbd_scancode_map[i] = SDL_SCANCODE_UNKNOWN; } } + + sdl_sbc_kbd_scancode_map[3] = + g_config.input.keyboard_sbc_scancode_map.eject; + sdl_sbc_kbd_scancode_map[4] = + g_config.input.keyboard_sbc_scancode_map.cockpit_hatch; + sdl_sbc_kbd_scancode_map[5] = + g_config.input.keyboard_sbc_scancode_map.ignition; + sdl_sbc_kbd_scancode_map[6] = + g_config.input.keyboard_sbc_scancode_map.start; + sdl_sbc_kbd_scancode_map[7] = + g_config.input.keyboard_sbc_scancode_map.open_close; + sdl_sbc_kbd_scancode_map[8] = + g_config.input.keyboard_sbc_scancode_map.map_zoom_in_out; + sdl_sbc_kbd_scancode_map[9] = + g_config.input.keyboard_sbc_scancode_map.mode_select; + sdl_sbc_kbd_scancode_map[10] = + g_config.input.keyboard_sbc_scancode_map.sub_monitor_mode_select; + sdl_sbc_kbd_scancode_map[11] = + g_config.input.keyboard_sbc_scancode_map.zoom_in; + sdl_sbc_kbd_scancode_map[12] = + g_config.input.keyboard_sbc_scancode_map.zoom_out; + sdl_sbc_kbd_scancode_map[13] = g_config.input.keyboard_sbc_scancode_map.fss; + sdl_sbc_kbd_scancode_map[14] = + g_config.input.keyboard_sbc_scancode_map.manipulator; + sdl_sbc_kbd_scancode_map[15] = + g_config.input.keyboard_sbc_scancode_map.line_color_change; + sdl_sbc_kbd_scancode_map[16] = + g_config.input.keyboard_sbc_scancode_map.washing; + sdl_sbc_kbd_scancode_map[17] = + g_config.input.keyboard_sbc_scancode_map.extinguisher; + sdl_sbc_kbd_scancode_map[18] = + g_config.input.keyboard_sbc_scancode_map.chaff; + sdl_sbc_kbd_scancode_map[19] = + g_config.input.keyboard_sbc_scancode_map.tank_detach; + sdl_sbc_kbd_scancode_map[20] = + g_config.input.keyboard_sbc_scancode_map.override; + sdl_sbc_kbd_scancode_map[21] = + g_config.input.keyboard_sbc_scancode_map.night_scope; + sdl_sbc_kbd_scancode_map[22] = + g_config.input.keyboard_sbc_scancode_map.func1; + sdl_sbc_kbd_scancode_map[23] = + g_config.input.keyboard_sbc_scancode_map.func2; + sdl_sbc_kbd_scancode_map[24] = + g_config.input.keyboard_sbc_scancode_map.func3; + sdl_sbc_kbd_scancode_map[25] = + g_config.input.keyboard_sbc_scancode_map.main_weapon_control; + sdl_sbc_kbd_scancode_map[26] = + g_config.input.keyboard_sbc_scancode_map.sub_weapon_control; + sdl_sbc_kbd_scancode_map[27] = + g_config.input.keyboard_sbc_scancode_map.magazine_change; + sdl_sbc_kbd_scancode_map[28] = + g_config.input.keyboard_sbc_scancode_map.com1; + sdl_sbc_kbd_scancode_map[29] = + g_config.input.keyboard_sbc_scancode_map.com2; + sdl_sbc_kbd_scancode_map[30] = + g_config.input.keyboard_sbc_scancode_map.com3; + sdl_sbc_kbd_scancode_map[31] = + g_config.input.keyboard_sbc_scancode_map.com4; + sdl_sbc_kbd_scancode_map[32] = + g_config.input.keyboard_sbc_scancode_map.com5; + sdl_sbc_kbd_scancode_map[33] = + g_config.input.keyboard_sbc_scancode_map.sight_change; + sdl_sbc_kbd_scancode_map[34] = + g_config.input.keyboard_sbc_scancode_map.filt_control_system; + sdl_sbc_kbd_scancode_map[35] = + g_config.input.keyboard_sbc_scancode_map.oxygen_supply_system; + sdl_sbc_kbd_scancode_map[36] = + g_config.input.keyboard_sbc_scancode_map.fuel_flow_rate; + sdl_sbc_kbd_scancode_map[37] = + g_config.input.keyboard_sbc_scancode_map.buffer_material; + sdl_sbc_kbd_scancode_map[38] = + g_config.input.keyboard_sbc_scancode_map.vt_location_measurement; + sdl_sbc_kbd_scancode_map[39] = + g_config.input.keyboard_sbc_scancode_map.gear_up; + sdl_sbc_kbd_scancode_map[40] = + g_config.input.keyboard_sbc_scancode_map.gear_down; + sdl_sbc_kbd_scancode_map[41] = + g_config.input.keyboard_sbc_scancode_map.tuner_left; + sdl_sbc_kbd_scancode_map[42] = + g_config.input.keyboard_sbc_scancode_map.tuner_right; + sdl_sbc_kbd_scancode_map[43] = + g_config.input.keyboard_sbc_scancode_map.sight_change_up; + sdl_sbc_kbd_scancode_map[44] = + g_config.input.keyboard_sbc_scancode_map.sight_change_down; + sdl_sbc_kbd_scancode_map[45] = + g_config.input.keyboard_sbc_scancode_map.sight_change_left; + sdl_sbc_kbd_scancode_map[46] = + g_config.input.keyboard_sbc_scancode_map.sight_change_right; + sdl_sbc_kbd_scancode_map[47] = + g_config.input.keyboard_sbc_scancode_map.rotation_left; + sdl_sbc_kbd_scancode_map[48] = + g_config.input.keyboard_sbc_scancode_map.rotation_right; + sdl_sbc_kbd_scancode_map[49] = + g_config.input.keyboard_sbc_scancode_map.left_pedal; + sdl_sbc_kbd_scancode_map[50] = + g_config.input.keyboard_sbc_scancode_map.right_pedal; + sdl_sbc_kbd_scancode_map[51] = + g_config.input.keyboard_sbc_scancode_map.middle_pedal; + + for (int i = 0; i < 49; i++) { + if ((sdl_sbc_kbd_scancode_map[i] < SDL_SCANCODE_UNKNOWN) || + (sdl_sbc_kbd_scancode_map[i] >= SDL_NUM_SCANCODES)) { + fprintf(stderr, + "WARNING: Keyboard steel battalion controller map scancode " + "out of range (%d) : Disabled\n", + sdl_sbc_kbd_scancode_map[i]); + sdl_sbc_kbd_scancode_map[i] = SDL_SCANCODE_UNKNOWN; + } + } bound_drivers[0] = get_bound_driver(0); bound_drivers[1] = get_bound_driver(1); @@ -277,7 +393,7 @@ void xemu_input_process_sdl_events(const SDL_Event *event) memset(new_con, 0, sizeof(ControllerState)); new_con->type = INPUT_DEVICE_SDL_GAMECONTROLLER; new_con->name = SDL_GameControllerName(sdl_con); - new_con->rumble_enabled = true; + new_con->gp.rumble_enabled = true; new_con->sdl_gamecontroller = sdl_con; new_con->sdl_joystick = SDL_GameControllerGetJoystick(new_con->sdl_gamecontroller); new_con->sdl_joystick_id = SDL_JoystickInstanceID(new_con->sdl_joystick); @@ -422,75 +538,339 @@ void xemu_input_update_controllers(void) void xemu_input_update_sdl_kbd_controller_state(ControllerState *state) { - state->buttons = 0; - memset(state->axis, 0, sizeof(state->axis)); + state->gp.buttons = 0; + state->sbc.buttons = 0; + memset(state->gp.axis, 0, sizeof(state->gp.axis)); + memset(state->sbc.axis, 0, sizeof(state->sbc.axis)); const uint8_t *kbd = SDL_GetKeyboardState(NULL); - for (int i = 0; i < 15; i++) { - state->buttons |= kbd[sdl_kbd_scancode_map[i]] << i; + if (state->bound < 0) + return; + + const char *bound_driver = get_bound_driver(state->bound); + if (strcmp(bound_driver, DRIVER_STEEL_BATTALION) == 0) { + + if (state->sbc.gearLever == 0) + state->sbc.gearLever = 255; + + // Update SBC Buttons + for (int i = 3; i < 43; i++) { + if (kbd[sdl_sbc_kbd_scancode_map[i]]) + state->sbc.buttons |= (1ULL << i); + } + + const uint64_t toggles[5] = { SBC_BUTTON_FILT_CONTROL_SYSTEM, + SBC_BUTTON_OXYGEN_SUPPLY_SYSTEM, + SBC_BUTTON_FUEL_FLOW_RATE, + SBC_BUTTON_BUFFER_MATERIAL, + SBC_BUTTON_VT_LOCATION_MEASUREMENT }; + + for (int i = 0; i < 5; i++) { + if ((state->sbc.buttons & toggles[i]) && + !(state->sbc.previousButtons & + toggles[i])) { // When the for the toggle is pressed + uint8_t byteMask = (uint8_t)(toggles[i] >> 32); + // Toggle the toggle switch + state->sbc.toggleSwitches ^= byteMask; + } + } + + // Tuner Dial Left + if ((state->sbc.buttons & SBC_BUTTON_TUNER_LEFT) && + !(state->sbc.previousButtons & SBC_BUTTON_TUNER_LEFT)) { + if (state->sbc.tunerDial == 0) + state->sbc.tunerDial = 15; + else + state->sbc.tunerDial--; + } + + // Tuner Dial Right + if ((state->sbc.buttons & SBC_BUTTON_TUNER_RIGHT) && + !(state->sbc.previousButtons & SBC_BUTTON_TUNER_RIGHT)) { + if (state->sbc.tunerDial == 15) + state->sbc.tunerDial = 0; + else + state->sbc.tunerDial++; + } + + // Gear Lever Up + if ((state->sbc.buttons & SBC_BUTTON_GEAR_UP) && + !(state->sbc.previousButtons & SBC_BUTTON_GEAR_UP)) { + if (state->sbc.gearLever != 5) { + if (state->sbc.gearLever == 255) + state->sbc.gearLever = 1; + else + state->sbc.gearLever++; + } + } + + // Gear Lever Down + if ((state->sbc.buttons & SBC_BUTTON_GEAR_DOWN) && + !(state->sbc.previousButtons & SBC_BUTTON_GEAR_DOWN)) { + if (state->sbc.gearLever != 254) { + if (state->sbc.gearLever == 1) + state->sbc.gearLever = 255; + else + state->sbc.gearLever--; + } + } + + // Update SBC Axes + int mouseX, mouseY; + uint32_t mouseBtn = SDL_GetMouseState(&mouseX, &mouseY); + + if (mouseBtn & SDL_BUTTON(SDL_BUTTON_LEFT)) { + state->sbc.buttons |= SBC_BUTTON_MAIN_WEAPON; + } + if (mouseBtn & SDL_BUTTON(SDL_BUTTON_RIGHT)) { + state->sbc.buttons |= SBC_BUTTON_LOCK_ON; + } + if(mouseBtn & SDL_BUTTON(SDL_BUTTON_X1) || + mouseBtn & SDL_BUTTON(SDL_BUTTON_X2)) { + state->sbc.buttons |= SBC_BUTTON_SUB_WEAPON; + } + + int32_t windowWidth, windowHeight; + SDL_GL_GetDrawableSize( + m_window, &windowWidth, + &windowHeight); // get the mouse location relative to the Viewport, + // not the Window + + // Calculate the position of the mouse coordinates in [-32768,32768] + DPRINTF("[Steel Battalion] Window Coordinates: %d, %d\n", mouseX, mouseY); + + // Check that the mouse position is within the window coordinates + if (mouseX >= 0 && mouseX <= windowWidth && mouseY >= 0 && + mouseY <= windowHeight) { + if (viewport_coords[2] > 0 && viewport_coords[3] > 0) { + // Switch from Window coordinates to Viewport Coordinates + mouseX -= viewport_coords[0]; + mouseY -= viewport_coords[1]; + windowWidth = viewport_coords[2]; + windowHeight = viewport_coords[3]; + } + + DPRINTF("[Steel Battalion] Viewport Coordinates: %d, %d\n", mouseX, mouseY); + int32_t x = (int32_t)((mouseX - (windowWidth / 2)) * + 65535 / windowWidth); + int32_t y = (int32_t)((mouseY - (windowHeight / 2)) * + 65535 / windowHeight); + + state->sbc.axis[SBC_AXIS_AIMING_X] = + (int16_t)MIN(MAX(x, -32768), 32767); + state->sbc.axis[SBC_AXIS_AIMING_Y] = + (int16_t)MIN(MAX(y, -32768), 32767); + + DPRINTF("[Steel Battalion] X: %d, Y: %d", + state->sbc.axis[SBC_AXIS_AIMING_X], + state->sbc.axis[SBC_AXIS_AIMING_Y]); + } + + if (kbd[sdl_sbc_kbd_scancode_map[43]]) + state->sbc.axis[SBC_AXIS_SIGHT_CHANGE_Y] = -32768; + if (kbd[sdl_sbc_kbd_scancode_map[44]]) + state->sbc.axis[SBC_AXIS_SIGHT_CHANGE_Y] = 32767; + if (kbd[sdl_sbc_kbd_scancode_map[45]]) + state->sbc.axis[SBC_AXIS_SIGHT_CHANGE_X] = -32768; + if (kbd[sdl_sbc_kbd_scancode_map[46]]) + state->sbc.axis[SBC_AXIS_SIGHT_CHANGE_X] = 32767; + + if (kbd[sdl_sbc_kbd_scancode_map[47]]) + state->sbc.axis[SBC_AXIS_ROTATION_LEVER] = -32768; + if (kbd[sdl_sbc_kbd_scancode_map[48]]) + state->sbc.axis[SBC_AXIS_ROTATION_LEVER] = 32767; + + if (kbd[sdl_sbc_kbd_scancode_map[49]]) + state->sbc.axis[SBC_AXIS_LEFT_PEDAL] = 32767; + if (kbd[sdl_sbc_kbd_scancode_map[50]]) + state->sbc.axis[SBC_AXIS_RIGHT_PEDAL] = 32767; + if (kbd[sdl_sbc_kbd_scancode_map[51]]) + state->sbc.axis[SBC_AXIS_MIDDLE_PEDAL] = 32767; + + state->sbc.previousButtons = state->sbc.buttons; + } else { + // Update Gamepad Buttons + for (int i = 0; i < 15; i++) { + state->gp.buttons |= kbd[sdl_kbd_scancode_map[i]] << i; + } + + // Update Gamepad Axes + if (kbd[sdl_kbd_scancode_map[15]]) + state->gp.axis[CONTROLLER_AXIS_LSTICK_Y] = 32767; + if (kbd[sdl_kbd_scancode_map[16]]) + state->gp.axis[CONTROLLER_AXIS_LSTICK_X] = -32768; + if (kbd[sdl_kbd_scancode_map[17]]) + state->gp.axis[CONTROLLER_AXIS_LSTICK_X] = 32767; + if (kbd[sdl_kbd_scancode_map[18]]) + state->gp.axis[CONTROLLER_AXIS_LSTICK_Y] = -32768; + if (kbd[sdl_kbd_scancode_map[19]]) + state->gp.axis[CONTROLLER_AXIS_LTRIG] = 32767; + if (kbd[sdl_kbd_scancode_map[20]]) + state->gp.axis[CONTROLLER_AXIS_RSTICK_Y] = 32767; + if (kbd[sdl_kbd_scancode_map[21]]) + state->gp.axis[CONTROLLER_AXIS_RSTICK_X] = -32768; + if (kbd[sdl_kbd_scancode_map[22]]) + state->gp.axis[CONTROLLER_AXIS_RSTICK_X] = 32767; + if (kbd[sdl_kbd_scancode_map[23]]) + state->gp.axis[CONTROLLER_AXIS_RSTICK_Y] = -32768; + if (kbd[sdl_kbd_scancode_map[24]]) + state->gp.axis[CONTROLLER_AXIS_RTRIG] = 32767; } - - if (kbd[sdl_kbd_scancode_map[15]]) state->axis[CONTROLLER_AXIS_LSTICK_Y] = 32767; - if (kbd[sdl_kbd_scancode_map[16]]) state->axis[CONTROLLER_AXIS_LSTICK_X] = -32768; - if (kbd[sdl_kbd_scancode_map[17]]) state->axis[CONTROLLER_AXIS_LSTICK_X] = 32767; - if (kbd[sdl_kbd_scancode_map[18]]) state->axis[CONTROLLER_AXIS_LSTICK_Y] = -32768; - if (kbd[sdl_kbd_scancode_map[19]]) state->axis[CONTROLLER_AXIS_LTRIG] = 32767; - - if (kbd[sdl_kbd_scancode_map[20]]) state->axis[CONTROLLER_AXIS_RSTICK_Y] = 32767; - if (kbd[sdl_kbd_scancode_map[21]]) state->axis[CONTROLLER_AXIS_RSTICK_X] = -32768; - if (kbd[sdl_kbd_scancode_map[22]]) state->axis[CONTROLLER_AXIS_RSTICK_X] = 32767; - if (kbd[sdl_kbd_scancode_map[23]]) state->axis[CONTROLLER_AXIS_RSTICK_Y] = -32768; - if (kbd[sdl_kbd_scancode_map[24]]) state->axis[CONTROLLER_AXIS_RTRIG] = 32767; } void xemu_input_update_sdl_controller_state(ControllerState *state) { - state->buttons = 0; - memset(state->axis, 0, sizeof(state->axis)); + state->gp.buttons = 0; + state->sbc.buttons = 0; + memset(state->gp.axis, 0, sizeof(state->gp.axis)); + memset(state->sbc.axis, 0, sizeof(state->sbc.axis)); - const SDL_GameControllerButton sdl_button_map[15] = { - SDL_CONTROLLER_BUTTON_A, - SDL_CONTROLLER_BUTTON_B, - SDL_CONTROLLER_BUTTON_X, - SDL_CONTROLLER_BUTTON_Y, - SDL_CONTROLLER_BUTTON_DPAD_LEFT, - SDL_CONTROLLER_BUTTON_DPAD_UP, - SDL_CONTROLLER_BUTTON_DPAD_RIGHT, - SDL_CONTROLLER_BUTTON_DPAD_DOWN, - SDL_CONTROLLER_BUTTON_BACK, - SDL_CONTROLLER_BUTTON_START, - SDL_CONTROLLER_BUTTON_LEFTSHOULDER, - SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, - SDL_CONTROLLER_BUTTON_LEFTSTICK, - SDL_CONTROLLER_BUTTON_RIGHTSTICK, - SDL_CONTROLLER_BUTTON_GUIDE - }; + if (state->bound < 0) + return; - for (int i = 0; i < 15; i++) { - state->buttons |= SDL_GameControllerGetButton(state->sdl_gamecontroller, sdl_button_map[i]) << i; + const char *bound_driver = get_bound_driver(state->bound); + if (strcmp(bound_driver, DRIVER_STEEL_BATTALION) == 0) { + state->sbc.buttons = 0; + + const uint64_t sdl_button_map_sbc[8][2] = { + { SDL_CONTROLLER_BUTTON_A, SBC_BUTTON_MAIN_WEAPON }, + { SDL_CONTROLLER_BUTTON_B, SBC_BUTTON_LOCK_ON }, + { SDL_CONTROLLER_BUTTON_LEFTSHOULDER, SBC_BUTTON_FUNC1 }, + { SDL_CONTROLLER_BUTTON_LEFTSTICK, SBC_BUTTON_SIGHT_CHANGE }, + { SDL_CONTROLLER_BUTTON_DPAD_UP, SBC_BUTTON_GEAR_UP }, + { SDL_CONTROLLER_BUTTON_DPAD_DOWN, SBC_BUTTON_GEAR_DOWN }, + { SDL_CONTROLLER_BUTTON_DPAD_LEFT, SBC_BUTTON_TUNER_LEFT }, + { SDL_CONTROLLER_BUTTON_DPAD_RIGHT, SBC_BUTTON_TUNER_RIGHT } + }; + + if (state->sbc.gearLever == 0) + state->sbc.gearLever = 255; + + for (int i = 0; i < 8; i++) { + if (SDL_GameControllerGetButton(state->sdl_gamecontroller, + sdl_button_map_sbc[i][0])) + state->sbc.buttons |= sdl_button_map_sbc[i][1]; + } + + const uint64_t toggles[5] = { SBC_BUTTON_FILT_CONTROL_SYSTEM, + SBC_BUTTON_OXYGEN_SUPPLY_SYSTEM, + SBC_BUTTON_FUEL_FLOW_RATE, + SBC_BUTTON_BUFFER_MATERIAL, + SBC_BUTTON_VT_LOCATION_MEASUREMENT }; + + for (int i = 0; i < 5; i++) { + if ((state->sbc.buttons & toggles[i]) && + !(state->sbc.previousButtons & + toggles[i])) { // When the for the toggle is pressed + uint8_t byteMask = (uint8_t)(toggles[i] >> 32); + // Toggle the toggle switch + state->sbc.toggleSwitches ^= byteMask; + } + } + + // Tuner Dial Left + if ((state->sbc.buttons & SBC_BUTTON_TUNER_LEFT) && + !(state->sbc.previousButtons & SBC_BUTTON_TUNER_LEFT)) { + if (state->sbc.tunerDial == 0) + state->sbc.tunerDial = 15; + else + state->sbc.tunerDial--; + } + + // Tuner Dial Right + if ((state->sbc.buttons & SBC_BUTTON_TUNER_RIGHT) && + !(state->sbc.previousButtons & SBC_BUTTON_TUNER_RIGHT)) { + if (state->sbc.tunerDial == 15) + state->sbc.tunerDial = 0; + else + state->sbc.tunerDial++; + } + + // Gear Lever Up + if ((state->sbc.buttons & SBC_BUTTON_GEAR_UP) && + !(state->sbc.previousButtons & SBC_BUTTON_GEAR_UP)) { + if (state->sbc.gearLever != 5) { + if (state->sbc.gearLever == 255) + state->sbc.gearLever = 1; + else + state->sbc.gearLever++; + } + } + + // Gear Lever Down + if ((state->sbc.buttons & SBC_BUTTON_GEAR_DOWN) && + !(state->sbc.previousButtons & SBC_BUTTON_GEAR_DOWN)) { + if (state->sbc.gearLever != 254) { + if (state->sbc.gearLever == 1) + state->sbc.gearLever = 255; + else + state->sbc.gearLever--; + } + } + + state->sbc.axis[SBC_AXIS_SIGHT_CHANGE_X] = SDL_GameControllerGetAxis( + state->sdl_gamecontroller, SDL_CONTROLLER_AXIS_LEFTX); + state->sbc.axis[SBC_AXIS_SIGHT_CHANGE_Y] = SDL_GameControllerGetAxis( + state->sdl_gamecontroller, SDL_CONTROLLER_AXIS_LEFTY); + state->sbc.axis[SBC_AXIS_AIMING_X] = SDL_GameControllerGetAxis( + state->sdl_gamecontroller, SDL_CONTROLLER_AXIS_RIGHTX); + state->sbc.axis[SBC_AXIS_AIMING_Y] = SDL_GameControllerGetAxis( + state->sdl_gamecontroller, SDL_CONTROLLER_AXIS_RIGHTY); + state->sbc.axis[SBC_AXIS_MIDDLE_PEDAL] = SDL_GameControllerGetAxis( + state->sdl_gamecontroller, SDL_CONTROLLER_AXIS_TRIGGERLEFT); + state->sbc.axis[SBC_AXIS_RIGHT_PEDAL] = SDL_GameControllerGetAxis( + state->sdl_gamecontroller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT); + + state->sbc.previousButtons = state->sbc.buttons; + } else { + const SDL_GameControllerButton sdl_button_map[15] = { + SDL_CONTROLLER_BUTTON_A, + SDL_CONTROLLER_BUTTON_B, + SDL_CONTROLLER_BUTTON_X, + SDL_CONTROLLER_BUTTON_Y, + SDL_CONTROLLER_BUTTON_DPAD_LEFT, + SDL_CONTROLLER_BUTTON_DPAD_UP, + SDL_CONTROLLER_BUTTON_DPAD_RIGHT, + SDL_CONTROLLER_BUTTON_DPAD_DOWN, + SDL_CONTROLLER_BUTTON_BACK, + SDL_CONTROLLER_BUTTON_START, + SDL_CONTROLLER_BUTTON_LEFTSHOULDER, + SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, + SDL_CONTROLLER_BUTTON_LEFTSTICK, + SDL_CONTROLLER_BUTTON_RIGHTSTICK, + SDL_CONTROLLER_BUTTON_GUIDE + }; + + for (int i = 0; i < 15; i++) { + state->gp.buttons |= + SDL_GameControllerGetButton(state->sdl_gamecontroller, sdl_button_map[i]) << i; + } + + const SDL_GameControllerAxis sdl_axis_map[6] = { + SDL_CONTROLLER_AXIS_TRIGGERLEFT, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, + SDL_CONTROLLER_AXIS_LEFTX, SDL_CONTROLLER_AXIS_LEFTY, + SDL_CONTROLLER_AXIS_RIGHTX, SDL_CONTROLLER_AXIS_RIGHTY, + }; + + for (int i = 0; i < 6; i++) { + state->gp.axis[i] = + SDL_GameControllerGetAxis(state->sdl_gamecontroller, sdl_axis_map[i]); + } + + // FIXME: Check range + state->gp.axis[CONTROLLER_AXIS_LSTICK_Y] = + -1 - state->gp.axis[CONTROLLER_AXIS_LSTICK_Y]; + state->gp.axis[CONTROLLER_AXIS_RSTICK_Y] = + -1 - state->gp.axis[CONTROLLER_AXIS_RSTICK_Y]; + + // xemu_input_print_controller_state(state); } - - const SDL_GameControllerAxis sdl_axis_map[6] = { - SDL_CONTROLLER_AXIS_TRIGGERLEFT, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, - SDL_CONTROLLER_AXIS_LEFTX, SDL_CONTROLLER_AXIS_LEFTY, - SDL_CONTROLLER_AXIS_RIGHTX, SDL_CONTROLLER_AXIS_RIGHTY, - }; - - for (int i = 0; i < 6; i++) { - state->axis[i] = SDL_GameControllerGetAxis(state->sdl_gamecontroller, sdl_axis_map[i]); - } - - // FIXME: Check range - state->axis[CONTROLLER_AXIS_LSTICK_Y] = -1 - state->axis[CONTROLLER_AXIS_LSTICK_Y]; - state->axis[CONTROLLER_AXIS_RSTICK_Y] = -1 - state->axis[CONTROLLER_AXIS_RSTICK_Y]; - - // xemu_input_print_controller_state(state); } void xemu_input_update_rumble(ControllerState *state) { - if (!state->rumble_enabled) { + if (!state->gp.rumble_enabled) { return; } @@ -500,7 +880,7 @@ void xemu_input_update_rumble(ControllerState *state) return; } - SDL_GameControllerRumble(state->sdl_gamecontroller, state->rumble_l, state->rumble_r, 250); + SDL_GameControllerRumble(state->sdl_gamecontroller, state->gp.rumble_l, state->gp.rumble_r, 250); state->last_rumble_updated_ts = qemu_clock_get_us(QEMU_CLOCK_REALTIME); } @@ -568,16 +948,22 @@ void xemu_input_bind(int index, ControllerState *state, int save) bound_controllers[index]->bound = index; char *tmp; + QDict *usbhub_qdict = NULL; + DeviceState *usbhub_dev = NULL; - // Create controller's internal USB hub. - QDict *usbhub_qdict = qdict_new(); - qdict_put_str(usbhub_qdict, "driver", "usb-hub"); - tmp = g_strdup_printf("1.%d", port_map[index]); - qdict_put_str(usbhub_qdict, "port", tmp); - qdict_put_int(usbhub_qdict, "ports", 3); - QemuOpts *usbhub_opts = qemu_opts_from_qdict(qemu_find_opts("device"), usbhub_qdict, &error_abort); - DeviceState *usbhub_dev = qdev_device_add(usbhub_opts, &error_abort); - g_free(tmp); + bool hasInternalHub = strcmp(bound_drivers[index], DRIVER_STEEL_BATTALION) != 0; + + if (hasInternalHub) { + // Create controller's internal USB hub. + usbhub_qdict = qdict_new(); + qdict_put_str(usbhub_qdict, "driver", "usb-hub"); + tmp = g_strdup_printf("1.%d", port_map[index]); + qdict_put_str(usbhub_qdict, "port", tmp); + qdict_put_int(usbhub_qdict, "ports", 3); + QemuOpts *usbhub_opts = qemu_opts_from_qdict(qemu_find_opts("device"), usbhub_qdict, &error_abort); + usbhub_dev = qdev_device_add(usbhub_opts, &error_abort); + g_free(tmp); + } // Create XID controller. This is connected to Port 1 of the controller's internal USB Hub QDict *qdict = qdict_new(); @@ -593,7 +979,8 @@ void xemu_input_bind(int index, ControllerState *state, int save) // Specify index/port qdict_put_int(qdict, "index", index); - tmp = g_strdup_printf("1.%d.1", port_map[index]); + const char *fmt = hasInternalHub ? "1.%d.1" : "1.%d"; + tmp = g_strdup_printf(fmt, port_map[index]); qdict_put_str(qdict, "port", tmp); g_free(tmp); @@ -603,12 +990,14 @@ void xemu_input_bind(int index, ControllerState *state, int save) assert(dev); // Unref for eventual cleanup - qobject_unref(usbhub_qdict); - object_unref(OBJECT(usbhub_dev)); + if (usbhub_qdict != NULL) + qobject_unref(usbhub_qdict); + if (usbhub_dev != NULL) + object_unref(OBJECT(usbhub_dev)); qobject_unref(qdict); object_unref(OBJECT(dev)); - state->device = usbhub_dev; + state->device = hasInternalHub ? usbhub_dev : dev; } } @@ -618,6 +1007,10 @@ bool xemu_input_bind_xmu(int player_index, int expansion_slot_index, assert(player_index >= 0 && player_index < 4); assert(expansion_slot_index >= 0 && expansion_slot_index < 2); + bool hasInternalHub = + strcmp(bound_drivers[player_index], DRIVER_STEEL_BATTALION) != 0; + assert(hasInternalHub); + ControllerState *player = bound_controllers[player_index]; enum peripheral_type peripheral_type = player->peripheral_types[expansion_slot_index]; @@ -740,6 +1133,11 @@ void xemu_input_unbind_xmu(int player_index, int expansion_slot_index) void xemu_input_rebind_xmu(int port) { + bool hasInternalHub = + strcmp(bound_drivers[port], DRIVER_STEEL_BATTALION) != 0; + if (!hasInternalHub) + return; + // Try to bind peripherals back to controller for (int i = 0; i < 2; i++) { enum peripheral_type peripheral_type = diff --git a/ui/xemu-input.h b/ui/xemu-input.h index 23c1a9f91b..3fecc7ec29 100644 --- a/ui/xemu-input.h +++ b/ui/xemu-input.h @@ -32,9 +32,11 @@ #define DRIVER_DUKE "usb-xbox-gamepad" #define DRIVER_S "usb-xbox-gamepad-s" +#define DRIVER_STEEL_BATTALION "usb-steel-battalion" #define DRIVER_DUKE_DISPLAY_NAME "Xbox Controller" #define DRIVER_S_DISPLAY_NAME "Xbox Controller S" +#define DRIVER_STEEL_BATTALION_DISPLAY_NAME "Steel Battalion Controller" enum controller_state_buttons_mask { CONTROLLER_BUTTON_A = (1 << 0), @@ -56,6 +58,54 @@ enum controller_state_buttons_mask { #define CONTROLLER_STATE_BUTTON_ID_TO_MASK(x) (1<buttons & CONTROLLER_BUTTON_GUIDE) { - state->animate_guide_button_end = now + animate_guide_button_duration; + if (state->gp.buttons & CONTROLLER_BUTTON_GUIDE) { + state->gp.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; + if (now < state->gp.animate_guide_button_end) { + t = 1.0f - (float)(state->gp.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 @@ -549,7 +579,7 @@ static void RenderDukeController(float frame_x, float frame_y, uint32_t primary_ // 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)) { + if (state->gp.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); @@ -563,14 +593,14 @@ static void RenderDukeController(float frame_x, float frame_y, uint32_t primary_ 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; + float lstick_x = (float)state->gp.axis[CONTROLLER_AXIS_LSTICK_X]/32768.0; + float lstick_y = (float)state->gp.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 : + (state->gp.buttons & CONTROLLER_BUTTON_LSTICK) ? secondary_color : primary_color, - (state->buttons & CONTROLLER_BUTTON_LSTICK) ? primary_color : + (state->gp.buttons & CONTROLLER_BUTTON_LSTICK) ? primary_color : secondary_color, 0); @@ -579,33 +609,33 @@ static void RenderDukeController(float frame_x, float frame_y, uint32_t primary_ 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; + float rstick_x = (float)state->gp.axis[CONTROLLER_AXIS_RSTICK_X]/32768.0; + float rstick_y = (float)state->gp.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 : + (state->gp.buttons & CONTROLLER_BUTTON_RSTICK) ? secondary_color : primary_color, - (state->buttons & CONTROLLER_BUTTON_RSTICK) ? primary_color : + (state->gp.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; + float ltrig = state->gp.axis[CONTROLLER_AXIS_LTRIG] / 32767.0; + float rtrig = state->gp.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; + state->gp.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; + if (state->gp.animate_trigger_end > now) { + t = 1.0f - (float)(state->gp.animate_trigger_end-now)/(float)animate_trigger_duration; float sin_wav = (1-sin(M_PI * t / 2.0f)); alpha += fmin(sin_wav * 0x40, 0x80); } @@ -619,8 +649,8 @@ static void RenderDukeController(float frame_x, float frame_y, uint32_t primary_ 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); + state->gp.rumble_l = (int)(rumble_l * (float)0xffff); + state->gp.rumble_r = (int)(rumble_r * (float)0xffff); glBindVertexArray(0); glUseProgram(0); @@ -676,13 +706,13 @@ static void RenderControllerS(float frame_x, float frame_y, uint32_t primary_col // 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 = + if (state->gp.buttons & CONTROLLER_BUTTON_GUIDE) { + state->gp.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) / + if (now < state->gp.animate_guide_button_end) { + t = 1.0f - (float)(state->gp.animate_guide_button_end - now) / (float)animate_guide_button_duration; float sin_wav = (1 - sin(M_PI * t / 2.0f)); @@ -712,7 +742,7 @@ static void RenderControllerS(float frame_x, float frame_y, uint32_t primary_col // 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)) { + if (state->gp.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); @@ -726,15 +756,15 @@ static void RenderControllerS(float frame_x, float frame_y, uint32_t primary_col 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; + float lstick_x = (float)state->gp.axis[CONTROLLER_AXIS_LSTICK_X] / 32768.0; + float lstick_y = (float)state->gp.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 : + (state->gp.buttons & CONTROLLER_BUTTON_LSTICK) ? secondary_color : primary_color, - (state->buttons & CONTROLLER_BUTTON_LSTICK) ? primary_color : + (state->gp.buttons & CONTROLLER_BUTTON_LSTICK) ? primary_color : secondary_color, 0); @@ -743,15 +773,15 @@ static void RenderControllerS(float frame_x, float frame_y, uint32_t primary_col 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; + float rstick_x = (float)state->gp.axis[CONTROLLER_AXIS_RSTICK_X] / 32768.0; + float rstick_y = (float)state->gp.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 : + (state->gp.buttons & CONTROLLER_BUTTON_RSTICK) ? secondary_color : primary_color, - (state->buttons & CONTROLLER_BUTTON_RSTICK) ? primary_color : + (state->gp.buttons & CONTROLLER_BUTTON_RSTICK) ? primary_color : secondary_color, 0); @@ -759,19 +789,19 @@ static void RenderControllerS(float frame_x, float frame_y, uint32_t primary_col 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; + float ltrig = state->gp.axis[CONTROLLER_AXIS_LTRIG] / 32767.0; + float rtrig = state->gp.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; + state->gp.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) / + if (state->gp.animate_trigger_end > now) { + t = 1.0f - (float)(state->gp.animate_trigger_end - now) / (float)animate_trigger_duration; float sin_wav = (1 - sin(M_PI * t / 2.0f)); alpha += fmin(sin_wav * 0x40, 0x80); @@ -786,8 +816,300 @@ static void RenderControllerS(float frame_x, float frame_y, uint32_t primary_col 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); + state->gp.rumble_l = (int)(rumble_l * (float)0xffff); + state->gp.rumble_r = (int)(rumble_r * (float)0xffff); + + glBindVertexArray(0); + glUseProgram(0); +} + +void RenderSteelBattalionController(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 lstick_ctr = { 122, 263, 0, 0 }; + const struct rect rstick_ctr = { 349, 263, 0, 0 }; + const struct rect accel_pedal = { 281, 92, 0, 0 }; + const struct rect brake_pedal = { 216, 96, 0, 0 }; + const struct rect slide_step_pedal = { 133, 92, 0, 0 }; + const struct rect radio_dial_ctr = { 205, 243, 0, 0 }; + const struct rect sight_change_ctr = { 123, 329, 0, 0 }; + const struct rect transmission_lever_ctr_R = { 44, 210, 0, 0 }; + const struct rect transmission_lever_ctr_N = { 44, 219, 0, 0 }; + const struct rect transmission_lever_ctr_1 = { 44, 228, 0, 0 }; + const struct rect transmission_lever_ctr_2 = { 44, 238, 0, 0 }; + const struct rect transmission_lever_ctr_3 = { 44, 248, 0, 0 }; + const struct rect transmission_lever_ctr_4 = { 44, 258, 0, 0 }; + const struct rect transmission_lever_ctr_5 = { 44, 268, 0, 0 }; + const struct rect filt_ctrl_sys_ctr = { 103, 194, 0, 0 }; + const struct rect oxygen_supply_system_ctr = { 112, 205, 0, 0 }; + const struct rect fuel_flow_rate_ctr = { 126, 188, 0, 0 }; + const struct rect buffer_material_ctr = { 135, 200, 0, 0 }; + const struct rect vt_location_measurement_ctr = { 145, 210, 0, 0 }; + const struct rect buttons[33] = { + { 350, 309, 11, 29 }, // SBC_BUTTON_MAIN_WEAPON + { 380, 308, 9, 35 }, // SBC_BUTTON_SUB_WEAPON + { 336, 316, 12, 12 }, // SBC_BUTTON_LOCK_ON + { 418, 263, 16, 15 }, // SBC_BUTTON_EJECT + { 418, 228, 16, 15 }, // SBC_BUTTON_COCKPIT_HATCH + { 418, 206, 16, 15 }, // SBC_BUTTON_IGNITION + { 418, 184, 16, 15 }, // SBC_BUTTON_START + { 339, 209, 22, 6 }, // SBC_BUTTON_OPEN_CLOSE + { 375, 209, 22, 6 }, // SBC_BUTTON_MAP_ZOOM_IN_OUT + { 339, 198, 22, 6 }, // SBC_BUTTON_MODE_SELECT + { 375, 198, 22, 6 }, // SBC_BUTTON_SUB_MONITOR_MODE_SELECT + { 339, 186, 22, 6 }, // SBC_BUTTON_ZOOM_IN + { 375, 186, 22, 6 }, // SBC_BUTTON_ZOOM_OUT + { 279, 274, 7, 13 }, // SBC_BUTTON_FSS + { 279, 252, 7, 13 }, // SBC_BUTTON_MANIPULATOR + { 279, 230, 7, 13 }, // SBC_BUTTON_LINE_COLOR_CHANGE + { 190, 204, 22, 6 }, // SBC_BUTTON_WASHING + { 223, 204, 22, 6 }, // SBC_BUTTON_EXTINGUISHER + { 256, 204, 22, 6 }, // SBC_BUTTON_CHAFF + { 268, 274, 7, 13 }, // SBC_BUTTON_TANK_DETACH + { 268, 252, 7, 13 }, // SBC_BUTTON_OVERRIDE + { 268, 230, 7, 13 }, // SBC_BUTTON_NIGHT_SCOPE + { 257, 274, 7, 13 }, // SBC_BUTTON_FUNC1 + { 257, 252, 7, 13 }, // SBC_BUTTON_FUNC2 + { 257, 230, 7, 13 }, // SBC_BUTTON_FUNC3 + { 190, 189, 22, 6 }, // SBC_BUTTON_MAIN_WEAPON_CONTROL + { 223, 189, 22, 6 }, // SBC_BUTTON_SUB_WEAPON_CONTROL + { 256, 189, 22, 6 }, // SBC_BUTTON_MAGAZINE_CHANGE + { 181, 272, 7, 13 }, // SBC_BUTTON_COM1 + { 192, 272, 7, 13 }, // SBC_BUTTON_COM2 + { 202, 272, 7, 13 }, // SBC_BUTTON_COM3 + { 213, 272, 7, 13 }, // SBC_BUTTON_COM4 + { 223, 272, 7, 13 } // SBC_BUTTON_COM5 + }; + + glUseProgram(g_decal_shader->prog); + glBindVertexArray(g_decal_shader->vao); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, g_sb_controller_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; + + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_ONE, GL_ZERO); + + // Render controller texture + RenderDecal(g_decal_shader, frame_x + 0, frame_y + 0, + sb_tex_items[obj_controller].w, sb_tex_items[obj_controller].h, + sb_tex_items[obj_controller].x, sb_tex_items[obj_controller].y, + sb_tex_items[obj_controller].w, sb_tex_items[obj_controller].h, + primary_color, secondary_color, 0); + + glBlendFunc(GL_ONE_MINUS_DST_ALPHA, + GL_ONE); // Blend with controller cutouts + + // The controller has alpha cutouts where the buttons are. Draw a surface + // behind the buttons if they are activated + for (int i = 0; i < 33; i++) { + if (state->sbc.buttons & (1ULL << 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 sight change joystick + float w = sb_tex_items[obj_sight_change_stick].w; + float h = sb_tex_items[obj_sight_change_stick].h; + float c_x = frame_x + sight_change_ctr.x; + float c_y = frame_y + sight_change_ctr.y; + float scstick_x = (float)state->sbc.axis[SBC_AXIS_SIGHT_CHANGE_X] / 32768.0; + float scstick_y = (float)state->sbc.axis[SBC_AXIS_SIGHT_CHANGE_Y] / 32768.0; + RenderDecal( + g_decal_shader, (int)(c_x - w / 2.0f + 5.0f * scstick_x), + (int)(c_y - h / 2.0f - 5.0f * scstick_y), w, h, + sb_tex_items[obj_sight_change_stick].x, + sb_tex_items[obj_sight_change_stick].y, w, h, + (state->sbc.buttons & SBC_BUTTON_SIGHT_CHANGE) ? secondary_color : + primary_color, + (state->sbc.buttons & SBC_BUTTON_SIGHT_CHANGE) ? primary_color : + secondary_color, + 0); + + // Render left joystick + w = sb_tex_items[obj_left_stick].w; + h = sb_tex_items[obj_left_stick].h; + c_x = frame_x + lstick_ctr.x; + c_y = frame_y + lstick_ctr.y; + float lstick_x = (float)state->sbc.axis[SBC_AXIS_ROTATION_LEVER] / 32768.0; + RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f + 23.0f * lstick_x), + (int)(c_y - h / 2.0f), w, h, sb_tex_items[obj_left_stick].x, + sb_tex_items[obj_left_stick].y, w, h, primary_color, + secondary_color, 0); + + // Render right joystick + w = sb_tex_items[obj_right_stick].w; + h = sb_tex_items[obj_right_stick].h; + c_x = frame_x + rstick_ctr.x; + c_y = frame_y + rstick_ctr.y; + float rstick_x = (float)state->sbc.axis[SBC_AXIS_AIMING_X] / 32768.0; + float rstick_y = (float)state->sbc.axis[SBC_AXIS_AIMING_Y] / 32768.0; + RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f + 23.0f * rstick_x), + (int)(c_y - h / 2.0f - 20.0f * rstick_y), w, h, + sb_tex_items[obj_right_stick].x, + sb_tex_items[obj_right_stick].y, w, h, primary_color, + secondary_color, 0); + + // Render accel pedal + w = sb_tex_items[obj_accel_pedal].w; + h = sb_tex_items[obj_accel_pedal].h; + c_x = frame_x + accel_pedal.x; + c_y = frame_y + accel_pedal.y; + RenderDecal(g_decal_shader, c_x, + c_y + 10.0f * state->sbc.axis[SBC_AXIS_RIGHT_PEDAL] / 32768.0f, + w, h, sb_tex_items[obj_accel_pedal].x, + sb_tex_items[obj_accel_pedal].y, w, h, primary_color, + secondary_color, 0); + + // Brake accel pedal + w = sb_tex_items[obj_brake_pedal].w; + h = sb_tex_items[obj_brake_pedal].h; + c_x = frame_x + brake_pedal.x; + c_y = frame_y + brake_pedal.y; + RenderDecal(g_decal_shader, c_x, + c_y + 10.0f * state->sbc.axis[SBC_AXIS_MIDDLE_PEDAL] / 32768.0f, + w, h, sb_tex_items[obj_brake_pedal].x, + sb_tex_items[obj_brake_pedal].y, w, h, primary_color, + secondary_color, 0); + + // Slide step pedal + w = sb_tex_items[obj_slide_step_pedal].w; + h = sb_tex_items[obj_slide_step_pedal].h; + c_x = frame_x + slide_step_pedal.x; + c_y = frame_y + slide_step_pedal.y; + RenderDecal(g_decal_shader, c_x, + c_y + 10.0f * state->sbc.axis[SBC_AXIS_LEFT_PEDAL] / 32768.0f, + w, h, sb_tex_items[obj_slide_step_pedal].x, + sb_tex_items[obj_slide_step_pedal].y, w, h, primary_color, + secondary_color, 0); + + // Render the radio dial + w = sb_tex_items[obj_radio_dial].w; + h = sb_tex_items[obj_radio_dial].h; + c_x = frame_x + radio_dial_ctr.x; + c_y = frame_x + radio_dial_ctr.y; + float tunerStep = 0.125f * 3.14159f; + // TODO: Figure out a way to either rotate the decal or remove the dot and + // move the dot based on current radio channel + RenderDecal( + g_decal_shader, + (int)(c_x - w / 2.0f - 9 * cosf(tunerStep * state->sbc.tunerDial)), + (int)(c_y - h / 2.0f + 11 * sinf(tunerStep * state->sbc.tunerDial)), w, + h, sb_tex_items[obj_radio_dial].x, sb_tex_items[obj_radio_dial].y, w, h, + primary_color, secondary_color, 0); + + // Render the transmission lever + w = sb_tex_items[obj_transmission_lever].w; + h = sb_tex_items[obj_transmission_lever].h; + c_x = frame_x + transmission_lever_ctr_1.x; + c_y = frame_x + transmission_lever_ctr_1.y; + switch (state->sbc.gearLever) { + case 254: + c_y = frame_y + transmission_lever_ctr_R.y; + break; + case 255: + c_y = frame_y + transmission_lever_ctr_N.y; + break; + case 1: + c_y = frame_y + transmission_lever_ctr_1.y; + break; + case 2: + c_y = frame_y + transmission_lever_ctr_2.y; + break; + case 3: + c_y = frame_y + transmission_lever_ctr_3.y; + break; + case 4: + c_y = frame_y + transmission_lever_ctr_4.y; + break; + case 5: + c_y = frame_y + transmission_lever_ctr_5.y; + break; + } + // Determine the correct value for c_y based on the currently selected gear + RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f), (int)(c_y - h / 2.0f), w, + h, sb_tex_items[obj_transmission_lever].x, + sb_tex_items[obj_transmission_lever].y, w, h, primary_color, + secondary_color, 0); + + // Filter Control System + w = sb_tex_items[obj_toggle].w; + h = sb_tex_items[obj_toggle].h; + c_x = frame_x + filt_ctrl_sys_ctr.x; + c_y = frame_y + filt_ctrl_sys_ctr.y; + if (state->sbc.toggleSwitches & (SBC_BUTTON_FILT_CONTROL_SYSTEM >> 32)) { + c_x -= 3; + c_y += 4; + } + RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f), (int)(c_y - h / 2.0f), w, + h, sb_tex_items[obj_toggle].x, sb_tex_items[obj_toggle].y, w, h, + primary_color, secondary_color, 0); + + // Oxygen Supply System + w = sb_tex_items[obj_toggle].w; + h = sb_tex_items[obj_toggle].h; + c_x = frame_x + oxygen_supply_system_ctr.x; + c_y = frame_y + oxygen_supply_system_ctr.y; + if (state->sbc.toggleSwitches & (SBC_BUTTON_OXYGEN_SUPPLY_SYSTEM >> 32)) { + c_x -= 3; + c_y += 4; + } + RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f), (int)(c_y - h / 2.0f), w, + h, sb_tex_items[obj_toggle].x, sb_tex_items[obj_toggle].y, w, h, + primary_color, secondary_color, 0); + + // Fuel Flow Rate + w = sb_tex_items[obj_toggle].w; + h = sb_tex_items[obj_toggle].h; + c_x = frame_x + fuel_flow_rate_ctr.x; + c_y = frame_y + fuel_flow_rate_ctr.y; + if (state->sbc.toggleSwitches & (SBC_BUTTON_FUEL_FLOW_RATE >> 32)) { + c_x -= 3; + c_y += 4; + } + RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f), (int)(c_y - h / 2.0f), w, + h, sb_tex_items[obj_toggle].x, sb_tex_items[obj_toggle].y, w, h, + primary_color, secondary_color, 0); + + // Buffer Material + w = sb_tex_items[obj_toggle].w; + h = sb_tex_items[obj_toggle].h; + c_x = frame_x + buffer_material_ctr.x; + c_y = frame_y + buffer_material_ctr.y; + if (state->sbc.toggleSwitches & (SBC_BUTTON_BUFFER_MATERIAL >> 32)) { + c_x -= 3; + c_y += 4; + } + RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f), (int)(c_y - h / 2.0f), w, + h, sb_tex_items[obj_toggle].x, sb_tex_items[obj_toggle].y, w, h, + primary_color, secondary_color, 0); + + // VT Location Measurement + w = sb_tex_items[obj_toggle].w; + h = sb_tex_items[obj_toggle].h; + c_x = frame_x + vt_location_measurement_ctr.x; + c_y = frame_y + vt_location_measurement_ctr.y; + if (state->sbc.toggleSwitches & + (SBC_BUTTON_VT_LOCATION_MEASUREMENT >> 32)) { + c_x -= 3; + c_y += 4; + } + RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f), (int)(c_y - h / 2.0f), w, + h, sb_tex_items[obj_toggle].x, sb_tex_items[obj_toggle].y, w, h, + primary_color, secondary_color, 0); + + glBlendFunc(GL_ONE, + GL_ZERO); // Don't blend, just overwrite values in buffer glBindVertexArray(0); glUseProgram(0); @@ -799,6 +1121,9 @@ void RenderController(float frame_x, float frame_y, uint32_t primary_color, if (strcmp(bound_drivers[state->bound], DRIVER_S) == 0) RenderControllerS(frame_x, frame_y, primary_color, secondary_color, state); + if (strcmp(bound_drivers[state->bound], DRIVER_STEEL_BATTALION) == 0) + RenderSteelBattalionController(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); @@ -933,6 +1258,7 @@ void RenderFramebuffer(GLint tex, int width, int height, bool flip) { int tw, th; float scale[2]; + int viewport_width, viewport_height; glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex); @@ -961,6 +1287,14 @@ void RenderFramebuffer(GLint tex, int width, int height, bool flip) } } + viewport_width = (int)(width * scale[0]); + viewport_height = (int)(height * scale[1]); + + viewport_coords[0] = (width - viewport_width) / 2; + viewport_coords[1] = (height - viewport_height) / 2; + viewport_coords[2] = viewport_width; + viewport_coords[3] = viewport_height; + RenderFramebuffer(tex, width, height, flip, scale); } diff --git a/ui/xui/input-manager.cc b/ui/xui/input-manager.cc index 786b54b176..646b67c776 100644 --- a/ui/xui/input-manager.cc +++ b/ui/xui/input-manager.cc @@ -21,11 +21,11 @@ void InputManager::Update() ControllerState *iter; QTAILQ_FOREACH(iter, &available_controllers, entry) { if (iter->type != INPUT_DEVICE_SDL_GAMECONTROLLER) continue; - m_buttons |= iter->buttons; + m_buttons |= iter->gp.buttons; // We simply take any axis that is >10 % activation for (int i = 0; i < CONTROLLER_AXIS__COUNT; i++) { - if ((iter->axis[i] > 3276) || (iter->axis[i] < -3276)) { - axis[i] = iter->axis[i]; + if ((iter->gp.axis[i] > 3276) || (iter->gp.axis[i] < -3276)) { + axis[i] = iter->gp.axis[i]; } } } diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index 9bb5dcf33f..d642e18857 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -172,14 +172,22 @@ void MainMenuInputView::Draw() driver = DRIVER_DUKE_DISPLAY_NAME; else if (strcmp(driver, DRIVER_S) == 0) driver = DRIVER_S_DISPLAY_NAME; + else if (strcmp(driver, DRIVER_STEEL_BATTALION) == 0) + driver = DRIVER_STEEL_BATTALION_DISPLAY_NAME; ImGui::SetNextItemWidth(-FLT_MIN); if (ImGui::BeginCombo("###InputDrivers", driver, ImGuiComboFlags_NoArrowButton)) { - const char *available_drivers[] = { DRIVER_DUKE, DRIVER_S }; + const char *available_drivers[] = { + DRIVER_DUKE, + DRIVER_S, + DRIVER_STEEL_BATTALION + }; const char *driver_display_names[] = { DRIVER_DUKE_DISPLAY_NAME, - DRIVER_S_DISPLAY_NAME + DRIVER_S_DISPLAY_NAME, + DRIVER_STEEL_BATTALION_DISPLAY_NAME + }; bool is_selected = false; int num_drivers = sizeof(driver_display_names) / sizeof(driver_display_names[0]); @@ -324,162 +332,169 @@ void MainMenuInputView::Draw() ImGui::SetCursorPos(pos); if (bound_state) { - SectionTitle("Expansion Slots"); - // Begin a 2-column layout to render the expansion slots - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, - g_viewport_mgr.Scale(ImVec2(0, 12))); - ImGui::Columns(2, "mixed", false); + bool hasInternalHub = + strcmp(bound_drivers[active], DRIVER_STEEL_BATTALION) != 0; + if (hasInternalHub) { + SectionTitle("Expansion Slots"); + // Begin a 2-column layout to render the expansion slots + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, + g_viewport_mgr.Scale(ImVec2(0, 12))); + ImGui::Columns(2, "mixed", false); - xmu_fbo->Target(); - id = (ImTextureID)(intptr_t)xmu_fbo->Texture(); + xmu_fbo->Target(); + id = (ImTextureID)(intptr_t)xmu_fbo->Texture(); - const char *img_file_filters = ".img Files\0*.img\0All Files\0*.*\0"; - const char *comboLabels[2] = { "###ExpansionSlotA", - "###ExpansionSlotB" }; - for (int i = 0; i < 2; i++) { - // Display a combo box to allow the user to choose the type of - // peripheral they want to use - enum peripheral_type selected_type = - bound_state->peripheral_types[i]; - const char *peripheral_type_names[2] = { "None", "Memory Unit" }; - const char *selected_peripheral_type = - peripheral_type_names[selected_type]; - ImGui::SetNextItemWidth(-FLT_MIN); - if (ImGui::BeginCombo(comboLabels[i], selected_peripheral_type, - ImGuiComboFlags_NoArrowButton)) { - // Handle all available peripheral types - for (int j = 0; j < 2; j++) { - bool is_selected = selected_type == j; - ImGui::PushID(j); - const char *selectable_label = peripheral_type_names[j]; + const char *img_file_filters = ".img Files\0*.img\0All Files\0*.*\0"; + const char *comboLabels[2] = { "###ExpansionSlotA", + "###ExpansionSlotB" }; + for (int i = 0; i < 2; i++) { + // Display a combo box to allow the user to choose the type of + // peripheral they want to use + enum peripheral_type selected_type = + bound_state->peripheral_types[i]; + const char *peripheral_type_names[2] = { "None", "Memory Unit" }; + const char *selected_peripheral_type = + peripheral_type_names[selected_type]; + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::BeginCombo(comboLabels[i], selected_peripheral_type, + ImGuiComboFlags_NoArrowButton)) { + // Handle all available peripheral types + for (int j = 0; j < 2; j++) { + bool is_selected = selected_type == j; + ImGui::PushID(j); + const char *selectable_label = peripheral_type_names[j]; - if (ImGui::Selectable(selectable_label, is_selected)) { - // Free any existing peripheral - if (bound_state->peripherals[i] != NULL) { - if (bound_state->peripheral_types[i] == - PERIPHERAL_XMU) { - // Another peripheral was already bound. - // Unplugging - xemu_input_unbind_xmu(active, i); + if (ImGui::Selectable(selectable_label, is_selected)) { + // Free any existing peripheral + if (bound_state->peripherals[i] != NULL) { + if (bound_state->peripheral_types[i] == + PERIPHERAL_XMU) { + // Another peripheral was already bound. + // Unplugging + xemu_input_unbind_xmu(active, i); + } + + // Free the existing state + g_free((void *)bound_state->peripherals[i]); + bound_state->peripherals[i] = NULL; } - // Free the existing state - g_free((void *)bound_state->peripherals[i]); - bound_state->peripherals[i] = NULL; + // Change the peripheral type to the newly selected type + bound_state->peripheral_types[i] = + (enum peripheral_type)j; + + // Allocate state for the new peripheral + if (j == PERIPHERAL_XMU) { + bound_state->peripherals[i] = + g_malloc(sizeof(XmuState)); + memset(bound_state->peripherals[i], 0, + sizeof(XmuState)); + } + + xemu_save_peripheral_settings( + active, i, bound_state->peripheral_types[i], NULL); } - // Change the peripheral type to the newly selected type - bound_state->peripheral_types[i] = - (enum peripheral_type)j; - - // Allocate state for the new peripheral - if (j == PERIPHERAL_XMU) { - bound_state->peripherals[i] = - g_malloc(sizeof(XmuState)); - memset(bound_state->peripherals[i], 0, - sizeof(XmuState)); + if (is_selected) { + ImGui::SetItemDefaultFocus(); } - xemu_save_peripheral_settings( - active, i, bound_state->peripheral_types[i], NULL); + ImGui::PopID(); } - if (is_selected) { - ImGui::SetItemDefaultFocus(); + ImGui::EndCombo(); + } + DrawComboChevron(); + + // Set an X offset to center the image button within the column + ImGui::SetCursorPosX( + ImGui::GetCursorPosX() + + (int)((ImGui::GetColumnWidth() - + xmu_w * g_viewport_mgr.m_scale - + 2 * port_padding * g_viewport_mgr.m_scale) / + 2)); + + selected_type = bound_state->peripheral_types[i]; + if (selected_type == PERIPHERAL_XMU) { + float x = xmu_x + i * xmu_x_stride; + float y = xmu_y; + + XmuState *xmu = (XmuState *)bound_state->peripherals[i]; + if (xmu->filename != NULL && strlen(xmu->filename) > 0) { + RenderXmu(x, y, 0x81dc8a00, 0x0f0f0f00); + + } else { + RenderXmu(x, y, 0x1f1f1f00, 0x0f0f0f00); } + ImVec2 xmu_display_size; + if (ImGui::GetContentRegionMax().x < + xmu_h * g_viewport_mgr.m_scale) { + xmu_display_size.x = ImGui::GetContentRegionMax().x / 2; + xmu_display_size.y = xmu_display_size.x * xmu_h / xmu_w; + } else { + xmu_display_size = ImVec2(xmu_w * g_viewport_mgr.m_scale, + xmu_h * g_viewport_mgr.m_scale); + } + + ImGui::SetCursorPosX( + ImGui::GetCursorPosX() + + (int)((ImGui::GetColumnWidth() - xmu_display_size.x) / + 2.0)); + + ImGui::Image(id, xmu_display_size, ImVec2(0.5f * i, 1), + ImVec2(0.5f * (i + 1), 0)); + ImVec2 pos = ImGui::GetCursorPos(); + + ImGui::SetCursorPos(pos); + + // Button to generate a new XMU + ImGui::PushID(i); + if (ImGui::Button("New Image", ImVec2(250, 0))) { + int flags = NOC_FILE_DIALOG_SAVE | + NOC_FILE_DIALOG_OVERWRITE_CONFIRMATION; + const char *new_path = PausedFileOpen( + flags, img_file_filters, NULL, "xmu.img"); + + if (new_path) { + if (create_fatx_image(new_path, DEFAULT_XMU_SIZE)) { + // XMU was created successfully. Bind it + xemu_input_bind_xmu(active, i, new_path, false); + } else { + // Show alert message + char *msg = g_strdup_printf( + "Unable to create XMU image at %s", new_path); + xemu_queue_error_message(msg); + g_free(msg); + } + } + } + + const char *xmu_port_path = NULL; + if (xmu->filename == NULL) + xmu_port_path = g_strdup(""); + else + xmu_port_path = g_strdup(xmu->filename); + if (FilePicker("Image", &xmu_port_path, img_file_filters)) { + if (strlen(xmu_port_path) == 0) { + xemu_input_unbind_xmu(active, i); + } else { + xemu_input_bind_xmu(active, i, xmu_port_path, false); + } + } + g_free((void *)xmu_port_path); + ImGui::PopID(); } - ImGui::EndCombo(); - } - DrawComboChevron(); - - // Set an X offset to center the image button within the column - ImGui::SetCursorPosX( - ImGui::GetCursorPosX() + - (int)((ImGui::GetColumnWidth() - - xmu_w * g_viewport_mgr.m_scale - - 2 * port_padding * g_viewport_mgr.m_scale) / - 2)); - - selected_type = bound_state->peripheral_types[i]; - if (selected_type == PERIPHERAL_XMU) { - float x = xmu_x + i * xmu_x_stride; - float y = xmu_y; - - XmuState *xmu = (XmuState *)bound_state->peripherals[i]; - if (xmu->filename != NULL && strlen(xmu->filename) > 0) { - RenderXmu(x, y, 0x81dc8a00, 0x0f0f0f00); - - } else { - RenderXmu(x, y, 0x1f1f1f00, 0x0f0f0f00); - } - - ImVec2 xmu_display_size; - if (ImGui::GetContentRegionMax().x < - xmu_h * g_viewport_mgr.m_scale) { - xmu_display_size.x = ImGui::GetContentRegionMax().x / 2; - xmu_display_size.y = xmu_display_size.x * xmu_h / xmu_w; - } else { - xmu_display_size = ImVec2(xmu_w * g_viewport_mgr.m_scale, - xmu_h * g_viewport_mgr.m_scale); - } - - ImGui::SetCursorPosX( - ImGui::GetCursorPosX() + - (int)((ImGui::GetColumnWidth() - xmu_display_size.x) / - 2.0)); - - ImGui::Image(id, xmu_display_size, ImVec2(0.5f * i, 1), - ImVec2(0.5f * (i + 1), 0)); - - // Button to generate a new XMU - ImGui::PushID(i); - if (ImGui::Button("New Image", ImVec2(250, 0))) { - int flags = NOC_FILE_DIALOG_SAVE | - NOC_FILE_DIALOG_OVERWRITE_CONFIRMATION; - const char *new_path = PausedFileOpen( - flags, img_file_filters, NULL, "xmu.img"); - - if (new_path) { - if (create_fatx_image(new_path, DEFAULT_XMU_SIZE)) { - // XMU was created successfully. Bind it - xemu_input_bind_xmu(active, i, new_path, false); - } else { - // Show alert message - char *msg = g_strdup_printf( - "Unable to create XMU image at %s", new_path); - xemu_queue_error_message(msg); - g_free(msg); - } - } - } - - const char *xmu_port_path = NULL; - if (xmu->filename == NULL) - xmu_port_path = g_strdup(""); - else - xmu_port_path = g_strdup(xmu->filename); - if (FilePicker("Image", &xmu_port_path, img_file_filters)) { - if (strlen(xmu_port_path) == 0) { - xemu_input_unbind_xmu(active, i); - } else { - xemu_input_bind_xmu(active, i, xmu_port_path, false); - } - } - g_free((void *)xmu_port_path); - - ImGui::PopID(); + ImGui::NextColumn(); } - ImGui::NextColumn(); + xmu_fbo->Restore(); + + ImGui::PopStyleVar(); // ItemSpacing + ImGui::Columns(1); } - - xmu_fbo->Restore(); - - ImGui::PopStyleVar(); // ItemSpacing - ImGui::Columns(1); } SectionTitle("Options"); diff --git a/ui/xui/main.cc b/ui/xui/main.cc index 699805f113..f295107872 100644 --- a/ui/xui/main.cc +++ b/ui/xui/main.cc @@ -286,9 +286,7 @@ void xemu_hud_render(void) g_scene_mgr.PushScene(g_main_menu); } else if (ImGui::IsKeyPressed(ImGuiKey_F2)) { g_scene_mgr.PushScene(g_popup_menu); - } else if (menu_button || - (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && - !ImGui::IsAnyItemFocused() && !ImGui::IsAnyItemHovered())) { + } else if (menu_button) { g_scene_mgr.PushScene(g_popup_menu); }