diff options
Diffstat (limited to 'src/osd/modules/input/input_sdl.cpp')
-rw-r--r-- | src/osd/modules/input/input_sdl.cpp | 2751 |
1 files changed, 2224 insertions, 527 deletions
diff --git a/src/osd/modules/input/input_sdl.cpp b/src/osd/modules/input/input_sdl.cpp index 40fe037f8c7..f1fb0241b40 100644 --- a/src/osd/modules/input/input_sdl.cpp +++ b/src/osd/modules/input/input_sdl.cpp @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Olivier Galibert, R. Belmont, Brad Hughes +// copyright-holders:Olivier Galibert, R. Belmont, Brad Hughes, Vas Crabb //============================================================ // // input_sdl.cpp - SDL 2.0 implementation of MAME input routines @@ -12,32 +12,39 @@ //============================================================ #include "input_module.h" + #include "modules/osdmodule.h" -#if defined(SDLMAME_SDL2) +#if defined(OSD_SDL) -// standard sdl header -#include <SDL2/SDL.h> -#include <ctype.h> -// ReSharper disable once CppUnusedIncludeDirective -#include <stddef.h> -#include <mutex> -#include <memory> -#include <queue> -#include <iterator> -#include <algorithm> +#include "assignmenthelper.h" +#include "input_common.h" -// MAME headers -#include "emu.h" -#include "uiinput.h" -#include "strconv.h" +#include "interface/inputseq.h" +#include "modules/lib/osdobj_common.h" +#include "sdl/osdsdl.h" -// MAMEOS headers -#include "input_common.h" -#include "../lib/osdobj_common.h" -#include "input_sdlcommon.h" -#include "../../sdl/osdsdl.h" -#include "../../sdl/window.h" +// emu +#include "inpttype.h" + +// standard SDL header +#include <SDL2/SDL.h> + +#include <algorithm> +#include <cctype> +#include <chrono> +#include <cmath> +#include <cstddef> +#include <cstring> +#include <initializer_list> +#include <iterator> +#include <list> +#include <memory> +#include <optional> +#include <string> +#include <string_view> +#include <tuple> +#include <utility> // winnt.h defines this #ifdef DELETE @@ -45,9 +52,217 @@ #endif -// FIXME: sdl does not properly report the window for certain OS. -#define GET_FOCUS_WINDOW(ev) focus_window() -//#define GET_FOCUS_WINDOW(ev) window_from_id((ev)->windowID) +namespace osd { + +namespace { + +char const *const CONTROLLER_AXIS_XBOX[]{ + "LSX", + "LSY", + "RSX", + "RSY", + "LT", + "RT" }; + +char const *const CONTROLLER_AXIS_PS[]{ + "LSX", + "LSY", + "RSX", + "RSY", + "L2", + "R2" }; + +char const *const CONTROLLER_AXIS_SWITCH[]{ + "LSX", + "LSY", + "RSX", + "RSY", + "ZL", + "ZR" }; + +char const *const CONTROLLER_BUTTON_XBOX360[]{ + "A", + "B", + "X", + "Y", + "Back", + "Guide", + "Start", + "LSB", + "RSB", + "LB", + "RB", + "D-pad Up", + "D-pad Down", + "D-pad Left", + "D-pad Right", + "Share", + "P1", + "P2", + "P3", + "P4", + "Touchpad" }; + +char const *const CONTROLLER_BUTTON_XBOXONE[]{ + "A", + "B", + "X", + "Y", + "View", + "Logo", + "Menu", + "LSB", + "RSB", + "LB", + "RB", + "D-pad Up", + "D-pad Down", + "D-pad Left", + "D-pad Right", + "Share", + "P1", + "P2", + "P3", + "P4", + "Touchpad" }; + +char const *const CONTROLLER_BUTTON_PS3[]{ + "Cross", + "Circle", + "Square", + "Triangle", + "Select", + "PS", + "Start", + "L3", + "R3", + "L1", + "R1", + "D-pad Up", + "D-pad Down", + "D-pad Left", + "D-pad Right", + "Mute", + "P1", + "P2", + "P3", + "P4", + "Touchpad" }; + +char const *const CONTROLLER_BUTTON_PS4[]{ + "Cross", + "Circle", + "Square", + "Triangle", + "Share", + "PS", + "Options", + "L3", + "R3", + "L1", + "R1", + "D-pad Up", + "D-pad Down", + "D-pad Left", + "D-pad Right", + "Mute", + "P1", + "P2", + "P3", + "P4", + "Touchpad" }; + +char const *const CONTROLLER_BUTTON_PS5[]{ + "Cross", + "Circle", + "Square", + "Triangle", + "Create", + "PS", + "Options", + "L3", + "R3", + "L1", + "R1", + "D-pad Up", + "D-pad Down", + "D-pad Left", + "D-pad Right", + "Mute", + "P1", + "P2", + "P3", + "P4", + "Touchpad" }; + +char const *const CONTROLLER_BUTTON_SWITCH[]{ + "A", + "B", + "X", + "Y", + "-", + "Home", + "+", + "LSB", + "RSB", + "L", + "R", + "D-pad Up", + "D-pad Down", + "D-pad Left", + "D-pad Right", + "Capture", + "RSR", + "LSL", + "RSL", + "LSR", + "Touchpad" }; + +[[maybe_unused]] char const *const CONTROLLER_BUTTON_STADIA[]{ + "A", + "B", + "X", + "Y", + "Options", + "Logo", + "Menu", + "L3", + "R3", + "L1", + "R1", + "D-pad Up", + "D-pad Down", + "D-pad Left", + "D-pad Right", + "Capture", + "P1", + "P2", + "P3", + "P4", + "Touchpad" }; + +[[maybe_unused]] char const *const CONTROLLER_BUTTON_SHIELD[]{ + "A", + "B", + "X", + "Y", + "Back", + "Logo", + "Start", + "LSB", + "RSB", + "LB", + "RB", + "D-pad Up", + "D-pad Down", + "D-pad Left", + "D-pad Right", + "Share", + "P1", + "P2", + "P3", + "P4", + "Touchpad" }; struct key_lookup_table { @@ -56,12 +271,8 @@ struct key_lookup_table }; #define KE(x) { SDL_SCANCODE_ ## x, "SDL_SCANCODE_" #x }, -#define KE8(A, B, C, D, E, F, G, H) KE(A) KE(B) KE(C) KE(D) KE(E) KE(F) KE(G) KE(H) -#define KE7(A, B, C, D, E, F, G) KE(A) KE(B) KE(C) KE(D) KE(E) KE(F) KE(G) -#define KE5(A, B, C, D, E) KE(A) KE(B) KE(C) KE(D) KE(E) -#define KE3(A, B, C) KE(A) KE(B) KE(C) -static key_lookup_table sdl_lookup_table[] = +key_lookup_table const sdl_lookup_table[] = { KE(UNKNOWN) @@ -320,520 +531,1711 @@ static key_lookup_table sdl_lookup_table[] = KE(APP1) KE(APP2) +}; + +//============================================================ +// lookup_sdl_code +//============================================================ +int lookup_sdl_code(std::string_view scode) { - -1, "" + auto const found = std::find_if( + std::begin(sdl_lookup_table), + std::end(sdl_lookup_table), + [&scode] (auto const &key) { return scode == key.name; }); + return (std::end(sdl_lookup_table) != found) ? found->code : -1; } -}; + //============================================================ -// lookup_sdl_code +// sdl_device //============================================================ -static int lookup_sdl_code(const char *scode) +using sdl_device = event_based_device<SDL_Event>; + + +//============================================================ +// sdl_keyboard_device +//============================================================ + +class sdl_keyboard_device : public sdl_device { - int i = 0; +public: + sdl_keyboard_device( + std::string &&name, + std::string &&id, + input_module &module, + keyboard_trans_table const &trans_table) : + sdl_device(std::move(name), std::move(id), module), + m_trans_table(trans_table), + m_keyboard({{0}}), + m_capslock_pressed(std::chrono::steady_clock::time_point::min()) + { + } - while (sdl_lookup_table[i].code >= 0) + virtual void poll(bool relative_reset) override { - if (!strcmp(scode, sdl_lookup_table[i].name)) - return sdl_lookup_table[i].code; - i++; + sdl_device::poll(relative_reset); + +#ifdef __APPLE__ + if (m_keyboard.state[SDL_SCANCODE_CAPSLOCK] && (std::chrono::steady_clock::now() > (m_capslock_pressed + std::chrono::milliseconds(30)))) + m_keyboard.state[SDL_SCANCODE_CAPSLOCK] = 0x00; +#endif } - return -1; -} + + virtual void process_event(SDL_Event const &event) override + { + switch (event.type) + { + case SDL_KEYDOWN: + if (event.key.keysym.scancode == SDL_SCANCODE_CAPSLOCK) + m_capslock_pressed = std::chrono::steady_clock::now(); + + m_keyboard.state[event.key.keysym.scancode] = 0x80; + break; + + case SDL_KEYUP: +#ifdef __APPLE__ + if (event.key.keysym.scancode == SDL_SCANCODE_CAPSLOCK) + break; +#endif + + m_keyboard.state[event.key.keysym.scancode] = 0x00; + break; + } + } + + virtual void reset() override + { + sdl_device::reset(); + memset(&m_keyboard.state, 0, sizeof(m_keyboard.state)); + m_capslock_pressed = std::chrono::steady_clock::time_point::min(); + } + + virtual void configure(input_device &device) override + { + // populate it + for (int keynum = 0; m_trans_table[keynum].mame_key != ITEM_ID_INVALID; keynum++) + { + input_item_id itemid = m_trans_table[keynum].mame_key; + device.add_item( + m_trans_table[keynum].ui_name, + std::string_view(), + itemid, + generic_button_get_state<s32>, + &m_keyboard.state[m_trans_table[keynum].sdl_scancode]); + } + } + +private: + // state information for a keyboard + struct keyboard_state + { + s32 state[0x3ff]; // must be s32! + s8 oldkey[MAX_KEYS]; + s8 currkey[MAX_KEYS]; + }; + + keyboard_trans_table const &m_trans_table; + keyboard_state m_keyboard; + std::chrono::steady_clock::time_point m_capslock_pressed; +}; + //============================================================ -// sdl_device +// sdl_mouse_device_base //============================================================ -class sdl_device : public event_based_device<SDL_Event> +class sdl_mouse_device_base : public sdl_device { public: - sdl_device(running_machine &machine, const char *name, const char *id, input_device_class devclass, input_module &module) - : event_based_device(machine, name, id, devclass, module) + virtual void poll(bool relative_reset) override { + sdl_device::poll(relative_reset); + + if (relative_reset) + { + m_mouse.lV = std::exchange(m_v, 0); + m_mouse.lH = std::exchange(m_h, 0); + } + } + + virtual void reset() override + { + sdl_device::reset(); + memset(&m_mouse, 0, sizeof(m_mouse)); + m_v = m_h = 0; } protected: - std::shared_ptr<sdl_window_info> focus_window() + // state information for a mouse + struct mouse_state + { + s32 lX, lY, lV, lH; + s32 buttons[MAX_BUTTONS]; + }; + + sdl_mouse_device_base(std::string &&name, std::string &&id, input_module &module) : + sdl_device(std::move(name), std::move(id), module), + m_mouse({0}), + m_v(0), + m_h(0) { - return sdl_event_manager::instance().focus_window(); } + + void add_common_items(input_device &device, unsigned buttons) + { + // add horizontal and vertical axes - relative for a mouse or absolute for a gun + device.add_item( + "X", + std::string_view(), + ITEM_ID_XAXIS, + generic_axis_get_state<s32>, + &m_mouse.lX); + device.add_item( + "Y", + std::string_view(), + ITEM_ID_YAXIS, + generic_axis_get_state<s32>, + &m_mouse.lY); + + // add buttons + for (int button = 0; button < buttons; button++) + { + input_item_id itemid = (input_item_id)(ITEM_ID_BUTTON1 + button); + int const offset = button ^ (((1 == button) || (2 == button)) ? 3 : 0); + device.add_item( + default_button_name(button), + std::string_view(), + itemid, + generic_button_get_state<s32>, + &m_mouse.buttons[offset]); + } + } + + mouse_state m_mouse; + s32 m_v, m_h; }; + //============================================================ -// sdl_keyboard_device +// sdl_mouse_device //============================================================ -class sdl_keyboard_device : public sdl_device +class sdl_mouse_device : public sdl_mouse_device_base { public: - keyboard_state keyboard; + sdl_mouse_device(std::string &&name, std::string &&id, input_module &module) : + sdl_mouse_device_base(std::move(name), std::move(id), module), + m_x(0), + m_y(0) + { + } - sdl_keyboard_device(running_machine &machine, const char *name, const char *id, input_module &module) - : sdl_device(machine, name, id, DEVICE_CLASS_KEYBOARD, module), - keyboard({{0}}) + virtual void poll(bool relative_reset) override { + sdl_mouse_device_base::poll(relative_reset); + + if (relative_reset) + { + m_mouse.lX = std::exchange(m_x, 0); + m_mouse.lY = std::exchange(m_y, 0); + } } - void process_event(SDL_Event &sdlevent) override + virtual void reset() override { - switch (sdlevent.type) + sdl_mouse_device_base::reset(); + m_x = m_y = 0; + } + + virtual void configure(input_device &device) override + { + add_common_items(device, 5); + + // add scroll axes + device.add_item( + "Scroll V", + std::string_view(), + ITEM_ID_ZAXIS, + generic_axis_get_state<s32>, + &m_mouse.lV); + device.add_item( + "Scroll H", + std::string_view(), + ITEM_ID_RZAXIS, + generic_axis_get_state<s32>, + &m_mouse.lH); + } + + virtual void process_event(SDL_Event const &event) override + { + switch (event.type) { - case SDL_KEYDOWN: - keyboard.state[sdlevent.key.keysym.scancode] = 0x80; - if (sdlevent.key.keysym.sym < 0x20) - machine().ui_input().push_char_event(osd_common_t::s_window_list.front()->target(), sdlevent.key.keysym.sym); + case SDL_MOUSEMOTION: + m_x += event.motion.xrel * input_device::RELATIVE_PER_PIXEL; + m_y += event.motion.yrel * input_device::RELATIVE_PER_PIXEL; break; - case SDL_KEYUP: - keyboard.state[sdlevent.key.keysym.scancode] = 0x00; + case SDL_MOUSEBUTTONDOWN: + m_mouse.buttons[event.button.button - 1] = 0x80; break; - case SDL_TEXTINPUT: - if (*sdlevent.text.text) - { - auto window = GET_FOCUS_WINDOW(&event.text); - //printf("Focus window is %p - wl %p\n", window, osd_common_t::s_window_list); - if (window != nullptr) - { - auto ptr = sdlevent.text.text; - auto len = std::strlen(sdlevent.text.text); - while (len) - { - char32_t ch; - auto chlen = uchar_from_utf8(&ch, ptr, len); - if (0 > chlen) - { - ch = 0x0fffd; - chlen = 1; - } - ptr += chlen; - len -= chlen; - machine().ui_input().push_char_event(window->target(), ch); - } - } - } + case SDL_MOUSEBUTTONUP: + m_mouse.buttons[event.button.button - 1] = 0; + break; + + case SDL_MOUSEWHEEL: + // adjust SDL 1-per-click to match Win32 120-per-click +#if SDL_VERSION_ATLEAST(2, 0, 18) + m_v += std::lround(event.wheel.preciseY * 120 * input_device::RELATIVE_PER_PIXEL); + m_h += std::lround(event.wheel.preciseX * 120 * input_device::RELATIVE_PER_PIXEL); +#else + m_v += event.wheel.y * 120 * input_device::RELATIVE_PER_PIXEL; + m_h += event.wheel.x * 120 * input_device::RELATIVE_PER_PIXEL; +#endif break; } } - void reset() override - { - memset(&keyboard.state, 0, sizeof(keyboard.state)); - } +private: + s32 m_x, m_y; }; + //============================================================ -// sdl_mouse_device +// sdl_lightgun_device //============================================================ -class sdl_mouse_device : public sdl_device +class sdl_lightgun_device : public sdl_mouse_device_base { -private: - const std::chrono::milliseconds double_click_speed = std::chrono::milliseconds(250); - std::chrono::system_clock::time_point last_click; - int last_x; - int last_y; - public: - mouse_state mouse; + sdl_lightgun_device(std::string &&name, std::string &&id, input_module &module) : + sdl_mouse_device_base(std::move(name), std::move(id), module), + m_x(0), + m_y(0), + m_window(0) + { + } - sdl_mouse_device(running_machine &machine, const char *name, const char *id, input_module &module) - : sdl_device(machine, name, id, DEVICE_CLASS_MOUSE, module), - last_x(0), - last_y(0), - mouse({0}) + virtual void poll(bool relative_reset) override { + sdl_mouse_device_base::poll(relative_reset); + + SDL_Window *const win(m_window ? SDL_GetWindowFromID(m_window) : nullptr); + if (win) + { + int w, h; + SDL_GetWindowSize(win, &w, &h); + m_mouse.lX = normalize_absolute_axis(m_x, 0, w - 1); + m_mouse.lY = normalize_absolute_axis(m_y, 0, h - 1); + } + else + { + m_mouse.lX = 0; + m_mouse.lY = 0; + } } - void reset() override + virtual void reset() override { - memset(&mouse, 0, sizeof(mouse)); + sdl_mouse_device_base::reset(); + m_x = m_y = 0; + m_window = 0; } - void poll() override + virtual void configure(input_device &device) override { - mouse.lX = 0; - mouse.lY = 0; - sdl_device::poll(); + add_common_items(device, 5); + + // add scroll axes + device.add_item( + "Scroll V", + std::string_view(), + ITEM_ID_ADD_RELATIVE1, + generic_axis_get_state<s32>, + &m_mouse.lV); + device.add_item( + "Scroll H", + std::string_view(), + ITEM_ID_ADD_RELATIVE2, + generic_axis_get_state<s32>, + &m_mouse.lH); } - virtual void process_event(SDL_Event &sdlevent) override + virtual void process_event(SDL_Event const &event) override { - switch (sdlevent.type) + switch (event.type) { case SDL_MOUSEMOTION: - mouse.lX += sdlevent.motion.xrel * INPUT_RELATIVE_PER_PIXEL; - mouse.lY += sdlevent.motion.yrel * INPUT_RELATIVE_PER_PIXEL; - - { - int cx = -1, cy = -1; - auto window = GET_FOCUS_WINDOW(&sdlevent.motion); - - if (window != nullptr && window->xy_to_render_target(sdlevent.motion.x, sdlevent.motion.y, &cx, &cy)) - machine().ui_input().push_mouse_move_event(window->target(), cx, cy); - } + m_x = event.motion.x; + m_y = event.motion.y; + m_window = event.motion.windowID; break; case SDL_MOUSEBUTTONDOWN: - mouse.buttons[sdlevent.button.button - 1] = 0x80; - //printf("But down %d %d %d %d %s\n", event.button.which, event.button.button, event.button.x, event.button.y, devinfo->name.c_str()); - if (sdlevent.button.button == 1) - { - int cx, cy; - auto click = std::chrono::system_clock::now(); - auto window = GET_FOCUS_WINDOW(&sdlevent.button); - if (window != nullptr && window->xy_to_render_target(sdlevent.button.x, sdlevent.button.y, &cx, &cy)) - { - machine().ui_input().push_mouse_down_event(window->target(), cx, cy); + m_mouse.buttons[event.button.button - 1] = 0x80; + m_x = event.button.x; + m_y = event.button.y; + m_window = event.button.windowID; + break; - // avoid overflow with std::chrono::time_point::min() by adding rather than subtracting - if (click < last_click + double_click_speed - && (cx >= last_x - 4 && cx <= last_x + 4) - && (cy >= last_y - 4 && cy <= last_y + 4)) - { - last_click = std::chrono::time_point<std::chrono::system_clock>::min(); - machine().ui_input().push_mouse_double_click_event(window->target(), cx, cy); - } - else - { - last_click = click; - last_x = cx; - last_y = cy; - } - } - } + case SDL_MOUSEBUTTONUP: + m_mouse.buttons[event.button.button - 1] = 0; + m_x = event.button.x; + m_y = event.button.y; + m_window = event.button.windowID; + break; - else if (sdlevent.button.button == 3) - { - int cx, cy; - auto window = GET_FOCUS_WINDOW(&sdlevent.button); + case SDL_MOUSEWHEEL: + // adjust SDL 1-per-click to match Win32 120-per-click +#if SDL_VERSION_ATLEAST(2, 0, 18) + m_v += std::lround(event.wheel.preciseY * 120 * input_device::RELATIVE_PER_PIXEL); + m_h += std::lround(event.wheel.preciseX * 120 * input_device::RELATIVE_PER_PIXEL); +#else + m_v += event.wheel.y * 120 * input_device::RELATIVE_PER_PIXEL; + m_h += event.wheel.x * 120 * input_device::RELATIVE_PER_PIXEL; +#endif + break; - if (window != nullptr && window->xy_to_render_target(sdlevent.button.x, sdlevent.button.y, &cx, &cy)) - { - machine().ui_input().push_mouse_rdown_event(window->target(), cx, cy); - } - } + case SDL_WINDOWEVENT: + if ((event.window.windowID == m_window) && (SDL_WINDOWEVENT_LEAVE == event.window.event)) + m_window = 0; break; + } + } - case SDL_MOUSEBUTTONUP: - mouse.buttons[sdlevent.button.button - 1] = 0; - //printf("But up %d %d %d %d\n", event.button.which, event.button.button, event.button.x, event.button.y); +private: + s32 m_x, m_y; + u32 m_window; +}; - if (sdlevent.button.button == 1) - { - int cx, cy; - auto window = GET_FOCUS_WINDOW(&sdlevent.button); - if (window != nullptr && window->xy_to_render_target(sdlevent.button.x, sdlevent.button.y, &cx, &cy)) - { - machine().ui_input().push_mouse_up_event(window->target(), cx, cy); - } - } - else if (sdlevent.button.button == 3) - { - int cx, cy; - auto window = GET_FOCUS_WINDOW(&sdlevent.button); +//============================================================ +// sdl_dual_lightgun_device +//============================================================ + +class sdl_dual_lightgun_device : public sdl_mouse_device_base +{ +public: + sdl_dual_lightgun_device(std::string &&name, std::string &&id, input_module &module, u8 index) : + sdl_mouse_device_base(std::move(name), std::move(id), module), + m_index(index) + { + } + + virtual void configure(input_device &device) override + { + add_common_items(device, 2); + } - if (window != nullptr && window->xy_to_render_target(sdlevent.button.x, sdlevent.button.y, &cx, &cy)) + virtual void process_event(SDL_Event const &event) override + { + switch (event.type) + { + case SDL_MOUSEBUTTONDOWN: + { + SDL_Window *const win(SDL_GetWindowFromID(event.button.windowID)); + u8 const button = translate_button(event); + if (win && ((button / 2) == m_index)) { - machine().ui_input().push_mouse_rup_event(window->target(), cx, cy); + int w, h; + SDL_GetWindowSize(win, &w, &h); + m_mouse.buttons[(button & 1) << 1] = 0x80; + m_mouse.lX = normalize_absolute_axis(event.button.x, 0, w - 1); + m_mouse.lY = normalize_absolute_axis(event.button.y, 0, h - 1); } } break; - case SDL_MOUSEWHEEL: - auto window = GET_FOCUS_WINDOW(&sdlevent.wheel); - if (window != nullptr) - machine().ui_input().push_mouse_wheel_event(window->target(), 0, 0, sdlevent.wheel.y, 3); + case SDL_MOUSEBUTTONUP: + { + u8 const button = translate_button(event); + if ((button / 2) == m_index) + m_mouse.buttons[(button & 1) << 1] = 0; + } break; } } + +private: + static u8 translate_button(SDL_Event const &event) + { + u8 const index(event.button.button - 1); + return index ^ (((1 == index) || (2 == index)) ? 3 : 0); + } + + u8 const m_index; }; + //============================================================ -// sdl_joystick_device +// sdl_joystick_device_base //============================================================ -// state information for a joystick -struct sdl_joystick_state +class sdl_joystick_device_base : public sdl_device, protected joystick_assignment_helper { - int32_t axes[MAX_AXES]; - int32_t buttons[MAX_BUTTONS]; - int32_t hatsU[MAX_HATS], hatsD[MAX_HATS], hatsL[MAX_HATS], hatsR[MAX_HATS]; - int32_t balls[MAX_AXES]; -}; +public: + std::optional<std::string> const &serial() const { return m_serial; } + SDL_JoystickID instance() const { return m_instance; } -struct sdl_api_state -{ - SDL_Joystick *device; - SDL_Haptic *hapdevice; - SDL_JoystickID joystick_id; -}; + bool is_instance(SDL_JoystickID instance) const { return m_instance == instance; } -class sdl_joystick_device : public sdl_device -{ -public: - sdl_joystick_state joystick; - sdl_api_state sdl_state; + bool reconnect_match(std::string_view g, char const *s) const + { + return + (0 > m_instance) && + (id() == g) && + ((s && serial() && (*serial() == s)) || (!s && !serial())); + } - sdl_joystick_device(running_machine &machine, const char *name, const char *id, input_module &module) - : sdl_device(machine, name, id, DEVICE_CLASS_JOYSTICK, module), - joystick({{0}}), - sdl_state({ nullptr }) +protected: + sdl_joystick_device_base( + std::string &&name, + std::string &&id, + input_module &module, + char const *serial) : + sdl_device(std::move(name), std::move(id), module), + m_instance(-1) { + if (serial) + m_serial = serial; } - ~sdl_joystick_device() + void set_instance(SDL_JoystickID instance) { - if (sdl_state.device != nullptr) - { - if (sdl_state.hapdevice != nullptr) - { - SDL_HapticClose(sdl_state.hapdevice); - sdl_state.hapdevice = nullptr; - } - SDL_JoystickClose(sdl_state.device); - sdl_state.device = nullptr; - } + assert(0 > m_instance); + assert(0 <= instance); + + m_instance = instance; } - void reset() override + void clear_instance() + { + m_instance = -1; + } + +private: + std::optional<std::string> m_serial; + SDL_JoystickID m_instance; +}; + + +//============================================================ +// sdl_joystick_device +//============================================================ + +class sdl_joystick_device : public sdl_joystick_device_base +{ +public: + sdl_joystick_device( + std::string &&name, + std::string &&id, + input_module &module, + SDL_Joystick *joy, + char const *serial) : + sdl_joystick_device_base( + std::move(name), + std::move(id), + module, + serial), + m_joystick({{0}}), + m_joydevice(joy), + m_hapdevice(SDL_HapticOpenFromJoystick(joy)) { - memset(&joystick, 0, sizeof(joystick)); + set_instance(SDL_JoystickInstanceID(joy)); } - void process_event(SDL_Event &sdlevent) override + virtual void configure(input_device &device) override { - switch (sdlevent.type) + input_device::assignment_vector assignments; + char tempname[32]; + + int const axiscount = SDL_JoystickNumAxes(m_joydevice); + int const buttoncount = SDL_JoystickNumButtons(m_joydevice); + int const hatcount = SDL_JoystickNumHats(m_joydevice); + int const ballcount = SDL_JoystickNumBalls(m_joydevice); + + // loop over all axes + input_item_id axisactual[MAX_AXES]; + for (int axis = 0; (axis < MAX_AXES) && (axis < axiscount); axis++) { - case SDL_JOYAXISMOTION: - joystick.axes[sdlevent.jaxis.axis] = (sdlevent.jaxis.value * 2); - break; + input_item_id itemid; - case SDL_JOYBALLMOTION: - //printf("Ball %d %d\n", sdlevent.jball.xrel, sdlevent.jball.yrel); - joystick.balls[sdlevent.jball.ball * 2] = sdlevent.jball.xrel * INPUT_RELATIVE_PER_PIXEL; - joystick.balls[sdlevent.jball.ball * 2 + 1] = sdlevent.jball.yrel * INPUT_RELATIVE_PER_PIXEL; - break; + if (axis < INPUT_MAX_AXIS) + itemid = input_item_id(ITEM_ID_XAXIS + axis); + else if (axis < (INPUT_MAX_AXIS + INPUT_MAX_ADD_ABSOLUTE)) + itemid = input_item_id(ITEM_ID_ADD_ABSOLUTE1 + axis - INPUT_MAX_AXIS); + else + itemid = ITEM_ID_OTHER_AXIS_ABSOLUTE; + + snprintf(tempname, sizeof(tempname), "A%d", axis + 1); + axisactual[axis] = device.add_item( + tempname, + std::string_view(), + itemid, + generic_axis_get_state<s32>, + &m_joystick.axes[axis]); + } - case SDL_JOYHATMOTION: - if (sdlevent.jhat.value & SDL_HAT_UP) - { - joystick.hatsU[sdlevent.jhat.hat] = 0x80; - } + // loop over all buttons + for (int button = 0; (button < MAX_BUTTONS) && (button < buttoncount); button++) + { + input_item_id itemid; + + m_joystick.buttons[button] = 0; + + if (button < INPUT_MAX_BUTTONS) + itemid = input_item_id(ITEM_ID_BUTTON1 + button); + else if (button < INPUT_MAX_BUTTONS + INPUT_MAX_ADD_SWITCH) + itemid = input_item_id(ITEM_ID_ADD_SWITCH1 + button - INPUT_MAX_BUTTONS); else + itemid = ITEM_ID_OTHER_SWITCH; + + input_item_id const actual = device.add_item( + default_button_name(button), + std::string_view(), + itemid, + generic_button_get_state<s32>, + &m_joystick.buttons[button]); + + // there are sixteen action button types + if (button < 16) { - joystick.hatsU[sdlevent.jhat.hat] = 0; - } - if (sdlevent.jhat.value & SDL_HAT_DOWN) - { - joystick.hatsD[sdlevent.jhat.hat] = 0x80; + input_seq const seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, actual)); + assignments.emplace_back(ioport_type(IPT_BUTTON1 + button), SEQ_TYPE_STANDARD, seq); + + // assign the first few buttons to UI actions and pedals + switch (button) + { + case 0: + assignments.emplace_back(IPT_PEDAL, SEQ_TYPE_INCREMENT, seq); + assignments.emplace_back(IPT_UI_SELECT, SEQ_TYPE_STANDARD, seq); + break; + case 1: + assignments.emplace_back(IPT_PEDAL2, SEQ_TYPE_INCREMENT, seq); + assignments.emplace_back((3 > buttoncount) ? IPT_UI_CLEAR : IPT_UI_BACK, SEQ_TYPE_STANDARD, seq); + break; + case 2: + assignments.emplace_back(IPT_PEDAL3, SEQ_TYPE_INCREMENT, seq); + assignments.emplace_back(IPT_UI_CLEAR, SEQ_TYPE_STANDARD, seq); + break; + case 3: + assignments.emplace_back(IPT_UI_HELP, SEQ_TYPE_STANDARD, seq); + break; + } } + } + + // loop over all hats + input_item_id hatactual[MAX_HATS][4]; + for (int hat = 0; (hat < MAX_HATS) && (hat < hatcount); hat++) + { + input_item_id itemid; + + snprintf(tempname, sizeof(tempname), "Hat %d Up", hat + 1); + itemid = input_item_id((hat < INPUT_MAX_HATS) ? ITEM_ID_HAT1UP + (4 * hat) : ITEM_ID_OTHER_SWITCH); + hatactual[hat][0] = device.add_item( + tempname, + std::string_view(), + itemid, + generic_button_get_state<s32>, + &m_joystick.hatsU[hat]); + + snprintf(tempname, sizeof(tempname), "Hat %d Down", hat + 1); + itemid = input_item_id((hat < INPUT_MAX_HATS) ? ITEM_ID_HAT1DOWN + (4 * hat) : ITEM_ID_OTHER_SWITCH); + hatactual[hat][1] = device.add_item( + tempname, + std::string_view(), + itemid, + generic_button_get_state<s32>, + &m_joystick.hatsD[hat]); + + snprintf(tempname, sizeof(tempname), "Hat %d Left", hat + 1); + itemid = input_item_id((hat < INPUT_MAX_HATS) ? ITEM_ID_HAT1LEFT + (4 * hat) : ITEM_ID_OTHER_SWITCH); + hatactual[hat][2] = device.add_item( + tempname, + std::string_view(), + itemid, + generic_button_get_state<s32>, + &m_joystick.hatsL[hat]); + + snprintf(tempname, sizeof(tempname), "Hat %d Right", hat + 1); + itemid = input_item_id((hat < INPUT_MAX_HATS) ? ITEM_ID_HAT1RIGHT + (4 * hat) : ITEM_ID_OTHER_SWITCH); + hatactual[hat][3] = device.add_item( + tempname, + std::string_view(), + itemid, + generic_button_get_state<s32>, + &m_joystick.hatsR[hat]); + } + + // loop over all (track)balls + for (int ball = 0; (ball < (MAX_AXES / 2)) && (ball < ballcount); ball++) + { + int itemid; + + if (ball * 2 < INPUT_MAX_ADD_RELATIVE) + itemid = ITEM_ID_ADD_RELATIVE1 + ball * 2; else + itemid = ITEM_ID_OTHER_AXIS_RELATIVE; + + snprintf(tempname, sizeof(tempname), "R%d X", ball + 1); + input_item_id const xactual = device.add_item( + tempname, + std::string_view(), + input_item_id(itemid), + generic_axis_get_state<s32>, + &m_joystick.balls[ball * 2]); + + snprintf(tempname, sizeof(tempname), "R%d Y", ball + 1); + input_item_id const yactual = device.add_item( + tempname, + std::string_view(), + input_item_id(itemid + 1), + generic_axis_get_state<s32>, + &m_joystick.balls[ball * 2 + 1]); + + if (0 == ball) { - joystick.hatsD[sdlevent.jhat.hat] = 0; + // assign the first trackball to dial, trackball, mouse and lightgun inputs + input_seq const xseq(make_code(ITEM_CLASS_RELATIVE, ITEM_MODIFIER_NONE, xactual)); + input_seq const yseq(make_code(ITEM_CLASS_RELATIVE, ITEM_MODIFIER_NONE, yactual)); + assignments.emplace_back(IPT_DIAL, SEQ_TYPE_STANDARD, xseq); + assignments.emplace_back(IPT_DIAL_V, SEQ_TYPE_STANDARD, yseq); + assignments.emplace_back(IPT_TRACKBALL_X, SEQ_TYPE_STANDARD, xseq); + assignments.emplace_back(IPT_TRACKBALL_Y, SEQ_TYPE_STANDARD, yseq); + assignments.emplace_back(IPT_LIGHTGUN_X, SEQ_TYPE_STANDARD, xseq); + assignments.emplace_back(IPT_LIGHTGUN_Y, SEQ_TYPE_STANDARD, yseq); + assignments.emplace_back(IPT_MOUSE_X, SEQ_TYPE_STANDARD, xseq); + assignments.emplace_back(IPT_MOUSE_Y, SEQ_TYPE_STANDARD, yseq); + if (2 > axiscount) + { + // use it for joystick inputs if axes are limited + assignments.emplace_back(IPT_AD_STICK_X, SEQ_TYPE_STANDARD, xseq); + assignments.emplace_back(IPT_AD_STICK_Y, SEQ_TYPE_STANDARD, yseq); + } + else + { + // use for non-centring throttle control + assignments.emplace_back(IPT_AD_STICK_Z, SEQ_TYPE_STANDARD, yseq); + } } - if (sdlevent.jhat.value & SDL_HAT_LEFT) + else if ((1 == ball) && (2 > axiscount)) { - joystick.hatsL[sdlevent.jhat.hat] = 0x80; + // provide a non-centring throttle control + input_seq const yseq(make_code(ITEM_CLASS_RELATIVE, ITEM_MODIFIER_NONE, yactual)); + assignments.emplace_back(IPT_AD_STICK_Z, SEQ_TYPE_STANDARD, yseq); } - else + } + + // set up default assignments for axes and hats + add_directional_assignments( + assignments, + (1 <= axiscount) ? axisactual[0] : ITEM_ID_INVALID, // assume first axis is X + (2 <= axiscount) ? axisactual[1] : ITEM_ID_INVALID, // assume second axis is Y + (1 <= hatcount) ? hatactual[0][2] : ITEM_ID_INVALID, + (1 <= hatcount) ? hatactual[0][3] : ITEM_ID_INVALID, + (1 <= hatcount) ? hatactual[0][0] : ITEM_ID_INVALID, + (1 <= hatcount) ? hatactual[0][1] : ITEM_ID_INVALID); + if (2 <= axiscount) + { + // put pedals on the last of the second, third or fourth axis + input_item_id const pedalitem = axisactual[(std::min)(axiscount, 4) - 1]; + assignments.emplace_back( + IPT_PEDAL, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, pedalitem))); + assignments.emplace_back( + IPT_PEDAL2, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_POS, pedalitem))); + } + if (3 <= axiscount) + { + // assign X/Y to one of the twin sticks + assignments.emplace_back( + (4 <= axiscount) ? IPT_JOYSTICKLEFT_LEFT : IPT_JOYSTICKRIGHT_LEFT, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_LEFT, axisactual[0]))); + assignments.emplace_back( + (4 <= axiscount) ? IPT_JOYSTICKLEFT_RIGHT : IPT_JOYSTICKRIGHT_RIGHT, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_RIGHT, axisactual[0]))); + assignments.emplace_back( + (4 <= axiscount) ? IPT_JOYSTICKLEFT_UP : IPT_JOYSTICKRIGHT_UP, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_UP, axisactual[1]))); + assignments.emplace_back( + (4 <= axiscount) ? IPT_JOYSTICKLEFT_DOWN : IPT_JOYSTICKRIGHT_DOWN, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_DOWN, axisactual[1]))); + + // use third or fourth axis for Z + input_seq const seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NONE, axisactual[(std::min)(axiscount, 4) - 1])); + assignments.emplace_back(IPT_AD_STICK_Z, SEQ_TYPE_STANDARD, seq); + + // use this for focus next/previous to make system selection menu practical to navigate + input_seq const upseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, axisactual[2])); + input_seq const downseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, axisactual[2])); + assignments.emplace_back(IPT_UI_FOCUS_PREV, SEQ_TYPE_STANDARD, upseq); + assignments.emplace_back(IPT_UI_FOCUS_NEXT, SEQ_TYPE_STANDARD, downseq); + if (4 <= axiscount) { - joystick.hatsL[sdlevent.jhat.hat] = 0; + // use for zoom as well if there's another axis to use for previous/next group + assignments.emplace_back(IPT_UI_ZOOM_IN, SEQ_TYPE_STANDARD, downseq); + assignments.emplace_back(IPT_UI_ZOOM_OUT, SEQ_TYPE_STANDARD, upseq); } - if (sdlevent.jhat.value & SDL_HAT_RIGHT) + + // use this for twin sticks, too + assignments.emplace_back((4 <= axiscount) ? IPT_JOYSTICKRIGHT_LEFT : IPT_JOYSTICKLEFT_UP, SEQ_TYPE_STANDARD, upseq); + assignments.emplace_back((4 <= axiscount) ? IPT_JOYSTICKRIGHT_RIGHT : IPT_JOYSTICKLEFT_DOWN, SEQ_TYPE_STANDARD, downseq); + + // put previous/next group on the last of the third or fourth axis + input_item_id const groupitem = axisactual[(std::min)(axiscount, 4) - 1]; + assignments.emplace_back( + IPT_UI_PREV_GROUP, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, groupitem))); + assignments.emplace_back( + IPT_UI_NEXT_GROUP, + SEQ_TYPE_STANDARD, + input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, groupitem))); + } + if (4 <= axiscount) + { + // use this for twin sticks + input_seq const upseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, axisactual[3])); + input_seq const downseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, axisactual[3])); + assignments.emplace_back(IPT_JOYSTICKRIGHT_UP, SEQ_TYPE_STANDARD, upseq); + assignments.emplace_back(IPT_JOYSTICKRIGHT_DOWN, SEQ_TYPE_STANDARD, downseq); + } + + // set default assignments + device.set_default_assignments(std::move(assignments)); + } + + ~sdl_joystick_device() + { + close_device(); + } + + virtual void reset() override + { + sdl_joystick_device_base::reset(); + clear_buffer(); + } + + virtual void process_event(SDL_Event const &event) override + { + if (!m_joydevice) + return; + + switch (event.type) + { + case SDL_JOYAXISMOTION: + if (event.jaxis.axis < MAX_AXES) + m_joystick.axes[event.jaxis.axis] = (event.jaxis.value * 2); + break; + + case SDL_JOYBALLMOTION: + //printf("Ball %d %d\n", event.jball.xrel, event.jball.yrel); + if (event.jball.ball < (MAX_AXES / 2)) { - joystick.hatsR[sdlevent.jhat.hat] = 0x80; + m_joystick.balls[event.jball.ball * 2] = event.jball.xrel * input_device::RELATIVE_PER_PIXEL; + m_joystick.balls[event.jball.ball * 2 + 1] = event.jball.yrel * input_device::RELATIVE_PER_PIXEL; } - else + break; + + case SDL_JOYHATMOTION: + if (event.jhat.hat < MAX_HATS) { - joystick.hatsR[sdlevent.jhat.hat] = 0; + m_joystick.hatsU[event.jhat.hat] = (event.jhat.value & SDL_HAT_UP) ? 0x80 : 0; + m_joystick.hatsD[event.jhat.hat] = (event.jhat.value & SDL_HAT_DOWN) ? 0x80 : 0; + m_joystick.hatsL[event.jhat.hat] = (event.jhat.value & SDL_HAT_LEFT) ? 0x80 : 0; + m_joystick.hatsR[event.jhat.hat] = (event.jhat.value & SDL_HAT_RIGHT) ? 0x80 : 0; } break; case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: - joystick.buttons[sdlevent.jbutton.button] = (sdlevent.jbutton.state == SDL_PRESSED) ? 0x80 : 0; + if (event.jbutton.button < MAX_BUTTONS) + m_joystick.buttons[event.jbutton.button] = (event.jbutton.state == SDL_PRESSED) ? 0x80 : 0; break; + + case SDL_JOYDEVICEREMOVED: + osd_printf_verbose("Joystick: %s [ID %s] disconnected\n", name(), id()); + clear_instance(); + clear_buffer(); + close_device(); + break; + } + } + + bool has_haptic() const + { + return m_hapdevice != nullptr; + } + + void attach_device(SDL_Joystick *joy) + { + assert(joy); + assert(!m_joydevice); + + set_instance(SDL_JoystickInstanceID(joy)); + m_joydevice = joy; + m_hapdevice = SDL_HapticOpenFromJoystick(joy); + + osd_printf_verbose("Joystick: %s [ID %s] reconnected\n", name(), id()); + } + +protected: + // state information for a joystick + struct sdl_joystick_state + { + s32 axes[MAX_AXES]; + s32 buttons[MAX_BUTTONS]; + s32 hatsU[MAX_HATS], hatsD[MAX_HATS], hatsL[MAX_HATS], hatsR[MAX_HATS]; + s32 balls[MAX_AXES]; + }; + + sdl_joystick_state m_joystick; + +private: + SDL_Joystick *m_joydevice; + SDL_Haptic *m_hapdevice; + + void clear_buffer() + { + memset(&m_joystick, 0, sizeof(m_joystick)); + } + + void close_device() + { + if (m_joydevice) + { + if (m_hapdevice) + { + SDL_HapticClose(m_hapdevice); + m_hapdevice = nullptr; + } + SDL_JoystickClose(m_joydevice); + m_joydevice = nullptr; } } }; + +//============================================================ +// sdl_sixaxis_joystick_device +//============================================================ + class sdl_sixaxis_joystick_device : public sdl_joystick_device { public: - sdl_sixaxis_joystick_device(running_machine &machine, const char *name, const char *id, input_module &module) - : sdl_joystick_device(machine, name, id, module) - { - } + using sdl_joystick_device::sdl_joystick_device; - void process_event(SDL_Event &sdlevent) override + virtual void process_event(SDL_Event const &event) override { - switch (sdlevent.type) + switch (event.type) { case SDL_JOYAXISMOTION: { - int axis = sdlevent.jaxis.axis; - + int const axis = event.jaxis.axis; if (axis <= 3) { - joystick.axes[sdlevent.jaxis.axis] = (sdlevent.jaxis.value * 2); + m_joystick.axes[event.jaxis.axis] = (event.jaxis.value * 2); } else { - int magic = (sdlevent.jaxis.value / 2) + 16384; - joystick.axes[sdlevent.jaxis.axis] = magic; + int const magic = (event.jaxis.value / 2) + 16384; + m_joystick.axes[event.jaxis.axis] = magic; } } break; default: // Call the base for other events - sdl_joystick_device::process_event(sdlevent); + sdl_joystick_device::process_event(event); break; } } }; + //============================================================ -// sdl_input_module +// sdl_game_controller_device //============================================================ -class sdl_input_module : public input_module_base, public sdl_event_subscriber +class sdl_game_controller_device : public sdl_joystick_device_base { public: - sdl_input_module(const char *type) - : input_module_base(type, "sdl") + sdl_game_controller_device( + std::string &&name, + std::string &&id, + input_module &module, + SDL_GameController *ctrl, + char const *serial) : + sdl_joystick_device_base( + std::move(name), + std::move(id), + module, + serial), + m_controller({{0}}), + m_ctrldevice(ctrl) + { + set_instance(SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(ctrl))); + } + + ~sdl_game_controller_device() { + close_device(); } - void input_init(running_machine &machine) override + virtual void configure(input_device &device) override { - if (machine.debug_flags & DEBUG_FLAG_OSD_ENABLED) + input_device::assignment_vector assignments; + char const *const *axisnames = CONTROLLER_AXIS_XBOX; + char const *const *buttonnames = CONTROLLER_BUTTON_XBOX360; + bool digitaltriggers = false; + bool avoidpaddles = false; + auto const ctrltype = SDL_GameControllerGetType(m_ctrldevice); + switch (ctrltype) + { + case SDL_CONTROLLER_TYPE_UNKNOWN: + osd_printf_verbose("Game Controller: ... unknown type\n", int(ctrltype)); + break; + case SDL_CONTROLLER_TYPE_XBOX360: + osd_printf_verbose("Game Controller: ... Xbox 360 type\n"); + axisnames = CONTROLLER_AXIS_XBOX; + buttonnames = CONTROLLER_BUTTON_XBOX360; + break; + case SDL_CONTROLLER_TYPE_XBOXONE: + osd_printf_verbose("Game Controller: ... Xbox One type\n"); + axisnames = CONTROLLER_AXIS_XBOX; + buttonnames = CONTROLLER_BUTTON_XBOXONE; + break; + case SDL_CONTROLLER_TYPE_PS3: + osd_printf_verbose("Game Controller: ... PlayStation 3 type\n"); + axisnames = CONTROLLER_AXIS_PS; + buttonnames = CONTROLLER_BUTTON_PS3; + break; + case SDL_CONTROLLER_TYPE_PS4: + osd_printf_verbose("Game Controller: ... PlayStation 4 type\n"); + axisnames = CONTROLLER_AXIS_PS; + buttonnames = CONTROLLER_BUTTON_PS4; + break; + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO: + osd_printf_verbose("Game Controller: ... Switch Pro Controller type\n"); + axisnames = CONTROLLER_AXIS_SWITCH; + buttonnames = CONTROLLER_BUTTON_SWITCH; + digitaltriggers = true; + break; + //case SDL_CONTROLLER_TYPE_VIRTUAL: + case SDL_CONTROLLER_TYPE_PS5: + osd_printf_verbose("Game Controller: ... PlayStation 5 type\n"); + axisnames = CONTROLLER_AXIS_PS; + buttonnames = CONTROLLER_BUTTON_PS5; + break; +#if SDL_VERSION_ATLEAST(2, 0, 16) + //case SDL_CONTROLLER_TYPE_AMAZON_LUNA: + case SDL_CONTROLLER_TYPE_GOOGLE_STADIA: + osd_printf_verbose("Game Controller: ... Google Stadia type\n"); + axisnames = CONTROLLER_AXIS_PS; + buttonnames = CONTROLLER_BUTTON_STADIA; + break; +#endif +#if SDL_VERSION_ATLEAST(2, 24, 0) + case SDL_CONTROLLER_TYPE_NVIDIA_SHIELD: + osd_printf_verbose("Game Controller: ... NVIDIA Shield type\n"); + axisnames = CONTROLLER_AXIS_XBOX; + buttonnames = CONTROLLER_BUTTON_SHIELD; + break; + //case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: + //case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: + osd_printf_verbose("Game Controller: ... Joy-Con pair type\n"); + axisnames = CONTROLLER_AXIS_SWITCH; + buttonnames = CONTROLLER_BUTTON_SWITCH; + digitaltriggers = true; + avoidpaddles = true; + break; +#endif + default: // default to Xbox 360 names + osd_printf_verbose("Game Controller: ... unrecognized type (%d)\n", int(ctrltype)); + break; + } + + // keep track of item numbers as we add controls + std::pair<input_item_id, input_item_id> axisitems[SDL_CONTROLLER_AXIS_MAX]; + input_item_id buttonitems[SDL_CONTROLLER_BUTTON_MAX]; + std::tuple<input_item_id, SDL_GameControllerButton, SDL_GameControllerAxis> numberedbuttons[16]; + std::fill( + std::begin(axisitems), + std::end(axisitems), + std::make_pair(ITEM_ID_INVALID, ITEM_ID_INVALID)); + std::fill( + std::begin(buttonitems), + std::end(buttonitems), + ITEM_ID_INVALID); + std::fill( + std::begin(numberedbuttons), + std::end(numberedbuttons), + std::make_tuple(ITEM_ID_INVALID, SDL_CONTROLLER_BUTTON_INVALID, SDL_CONTROLLER_AXIS_INVALID)); + + // add axes + std::tuple<SDL_GameControllerAxis, input_item_id, bool> const axes[]{ + { SDL_CONTROLLER_AXIS_LEFTX, ITEM_ID_XAXIS, false }, + { SDL_CONTROLLER_AXIS_LEFTY, ITEM_ID_YAXIS, false }, + { SDL_CONTROLLER_AXIS_RIGHTX, ITEM_ID_ZAXIS, false }, + { SDL_CONTROLLER_AXIS_RIGHTY, ITEM_ID_RZAXIS, false }, + { SDL_CONTROLLER_AXIS_TRIGGERLEFT, ITEM_ID_SLIDER1, true }, + { SDL_CONTROLLER_AXIS_TRIGGERRIGHT, ITEM_ID_SLIDER2, true } }; + for (auto [axis, item, buttontest] : axes) + { + bool avail = !buttontest || !digitaltriggers; + avail = avail && SDL_GameControllerHasAxis(m_ctrldevice, axis); + if (avail) + { + auto const binding = SDL_GameControllerGetBindForAxis(m_ctrldevice, axis); + switch (binding.bindType) + { + case SDL_CONTROLLER_BINDTYPE_NONE: + avail = false; + break; + case SDL_CONTROLLER_BINDTYPE_BUTTON: + if (buttontest) + avail = false; + break; + default: + break; + } + } + if (avail) + { + axisitems[axis].first = device.add_item( + axisnames[axis], + std::string_view(), + item, + generic_axis_get_state<s32>, + &m_controller.axes[axis]); + } + } + + // add automatically numbered buttons + std::tuple<SDL_GameControllerButton, SDL_GameControllerAxis, bool> const generalbuttons[]{ + { SDL_CONTROLLER_BUTTON_A, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_B, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_X, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_Y, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_LEFTSHOULDER, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_INVALID, SDL_CONTROLLER_AXIS_TRIGGERLEFT, true }, + { SDL_CONTROLLER_BUTTON_INVALID, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, true }, + { SDL_CONTROLLER_BUTTON_LEFTSTICK, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_RIGHTSTICK, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_PADDLE1, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_PADDLE2, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_PADDLE3, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_PADDLE4, SDL_CONTROLLER_AXIS_INVALID, true }, + { SDL_CONTROLLER_BUTTON_GUIDE, SDL_CONTROLLER_AXIS_INVALID, false }, + { SDL_CONTROLLER_BUTTON_MISC1, SDL_CONTROLLER_AXIS_INVALID, false }, + { SDL_CONTROLLER_BUTTON_TOUCHPAD, SDL_CONTROLLER_AXIS_INVALID, false }, + }; + input_item_id button_item = ITEM_ID_BUTTON1; + unsigned buttoncount = 0; + for (auto [button, axis, field] : generalbuttons) + { + bool avail = true; + input_item_id actual = ITEM_ID_INVALID; + if (SDL_CONTROLLER_BUTTON_INVALID != button) + { + avail = SDL_GameControllerHasButton(m_ctrldevice, button); + if (avail) + { + auto const binding = SDL_GameControllerGetBindForButton(m_ctrldevice, button); + switch (binding.bindType) + { + case SDL_CONTROLLER_BINDTYPE_NONE: + avail = false; + break; + default: + break; + } + } + if (avail) + { + actual = buttonitems[button] = device.add_item( + buttonnames[button], + std::string_view(), + button_item++, + generic_button_get_state<s32>, + &m_controller.buttons[button]); + if (field && (std::size(numberedbuttons) > buttoncount)) + std::get<1>(numberedbuttons[buttoncount]) = button; + } + } + else + { + avail = SDL_GameControllerHasAxis(m_ctrldevice, axis); + if (avail) + { + auto const binding = SDL_GameControllerGetBindForAxis(m_ctrldevice, axis); + switch (binding.bindType) + { + case SDL_CONTROLLER_BINDTYPE_NONE: + avail = false; + break; + case SDL_CONTROLLER_BINDTYPE_BUTTON: + break; + default: + avail = digitaltriggers; + } + } + if (avail) + { + actual = axisitems[axis].second = device.add_item( + axisnames[axis], + std::string_view(), + button_item++, + [] (void *device_internal, void *item_internal) -> int + { + return (*reinterpret_cast<s32 const *>(item_internal) <= -16'384) ? 1 : 0; + }, + &m_controller.axes[axis]); + if (field && (std::size(numberedbuttons) > buttoncount)) + std::get<2>(numberedbuttons[buttoncount]) = axis; + } + } + + // add default button assignments + if (field && avail && (std::size(numberedbuttons) > buttoncount)) + { + std::get<0>(numberedbuttons[buttoncount]) = actual; + add_button_assignment(assignments, ioport_type(IPT_BUTTON1 + buttoncount++), { actual }); + } + } + + // add buttons with fixed item IDs + std::pair<SDL_GameControllerButton, input_item_id> const fixedbuttons[]{ + { SDL_CONTROLLER_BUTTON_BACK, ITEM_ID_SELECT }, + { SDL_CONTROLLER_BUTTON_START, ITEM_ID_START }, + { SDL_CONTROLLER_BUTTON_DPAD_UP, ITEM_ID_HAT1UP }, + { SDL_CONTROLLER_BUTTON_DPAD_DOWN, ITEM_ID_HAT1DOWN }, + { SDL_CONTROLLER_BUTTON_DPAD_LEFT, ITEM_ID_HAT1LEFT }, + { SDL_CONTROLLER_BUTTON_DPAD_RIGHT, ITEM_ID_HAT1RIGHT } }; + for (auto [button, item] : fixedbuttons) { - osd_printf_warning("Debug Build: Disabling input grab for -debug\n"); - set_mouse_enabled(false); + bool avail = true; + avail = SDL_GameControllerHasButton(m_ctrldevice, button); + if (avail) + { + auto const binding = SDL_GameControllerGetBindForButton(m_ctrldevice, button); + switch (binding.bindType) + { + case SDL_CONTROLLER_BINDTYPE_NONE: + avail = false; + break; + default: + break; + } + } + if (avail) + { + buttonitems[button] = device.add_item( + buttonnames[button], + std::string_view(), + item, + generic_button_get_state<s32>, + &m_controller.buttons[button]); + } } + + // try to get a "complete" joystick for primary movement controls + input_item_id diraxis[2][2]; + choose_primary_stick( + diraxis, + axisitems[SDL_CONTROLLER_AXIS_LEFTX].first, + axisitems[SDL_CONTROLLER_AXIS_LEFTY].first, + axisitems[SDL_CONTROLLER_AXIS_RIGHTX].first, + axisitems[SDL_CONTROLLER_AXIS_RIGHTY].first); + + // now set up controls using the primary joystick + add_directional_assignments( + assignments, + diraxis[0][0], + diraxis[0][1], + buttonitems[SDL_CONTROLLER_BUTTON_DPAD_LEFT], + buttonitems[SDL_CONTROLLER_BUTTON_DPAD_RIGHT], + buttonitems[SDL_CONTROLLER_BUTTON_DPAD_UP], + buttonitems[SDL_CONTROLLER_BUTTON_DPAD_DOWN]); + + // assign a secondary stick axis to joystick Z if available + bool const zaxis = add_assignment( + assignments, + IPT_AD_STICK_Z, + SEQ_TYPE_STANDARD, + ITEM_CLASS_ABSOLUTE, + ITEM_MODIFIER_NONE, + { diraxis[1][1], diraxis[1][0] }); + if (!zaxis) + { + // if both triggers are present, combine them, or failing that, fall back to a pair of buttons + if ((ITEM_ID_INVALID != axisitems[SDL_CONTROLLER_AXIS_TRIGGERLEFT].first) && (ITEM_ID_INVALID != axisitems[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].first)) + { + assignments.emplace_back( + IPT_AD_STICK_Z, + SEQ_TYPE_STANDARD, + input_seq( + make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NONE, axisitems[SDL_CONTROLLER_AXIS_TRIGGERLEFT].first), + make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_REVERSE, axisitems[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].first))); + } + else if (add_axis_inc_dec_assignment(assignments, IPT_AD_STICK_Z, buttonitems[SDL_CONTROLLER_BUTTON_LEFTSHOULDER], buttonitems[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER])) + { + // took shoulder buttons + } + else if (add_axis_inc_dec_assignment(assignments, IPT_AD_STICK_Z, axisitems[SDL_CONTROLLER_AXIS_TRIGGERLEFT].second, axisitems[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].second)) + { + // took trigger buttons + } + else if (add_axis_inc_dec_assignment(assignments, IPT_AD_STICK_Z, buttonitems[SDL_CONTROLLER_BUTTON_PADDLE1], buttonitems[SDL_CONTROLLER_BUTTON_PADDLE2])) + { + // took P1/P2 + } + else if (add_axis_inc_dec_assignment(assignments, IPT_AD_STICK_Z, buttonitems[SDL_CONTROLLER_BUTTON_PADDLE3], buttonitems[SDL_CONTROLLER_BUTTON_PADDLE4])) + { + // took P3/P4 + } + } + + // prefer trigger axes for pedals, otherwise take half axes and buttons + unsigned pedalbutton = 0; + if (!add_assignment(assignments, IPT_PEDAL, SEQ_TYPE_STANDARD, ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, { axisitems[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].first })) + { + add_assignment( + assignments, + IPT_PEDAL, + SEQ_TYPE_STANDARD, + ITEM_CLASS_ABSOLUTE, + ITEM_MODIFIER_NEG, + { diraxis[1][1], diraxis[0][1] }); + bool const incbutton = add_assignment( + assignments, + IPT_PEDAL, + SEQ_TYPE_INCREMENT, + ITEM_CLASS_SWITCH, + ITEM_MODIFIER_NONE, + { axisitems[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].second, buttonitems[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER] }); + if (!incbutton) + { + if (add_assignment(assignments, IPT_PEDAL, SEQ_TYPE_INCREMENT, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, { std::get<0>(numberedbuttons[pedalbutton]) })) + ++pedalbutton; + } + } + if (!add_assignment(assignments, IPT_PEDAL2, SEQ_TYPE_STANDARD, ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, { axisitems[SDL_CONTROLLER_AXIS_TRIGGERLEFT].first })) + { + add_assignment( + assignments, + IPT_PEDAL2, + SEQ_TYPE_STANDARD, + ITEM_CLASS_ABSOLUTE, + ITEM_MODIFIER_POS, + { diraxis[1][1], diraxis[0][1] }); + bool const incbutton = add_assignment( + assignments, + IPT_PEDAL2, + SEQ_TYPE_INCREMENT, + ITEM_CLASS_SWITCH, + ITEM_MODIFIER_NONE, + { axisitems[SDL_CONTROLLER_AXIS_TRIGGERLEFT].second, buttonitems[SDL_CONTROLLER_BUTTON_LEFTSHOULDER] }); + if (!incbutton) + { + if (add_assignment(assignments, IPT_PEDAL2, SEQ_TYPE_INCREMENT, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, { std::get<0>(numberedbuttons[pedalbutton]) })) + ++pedalbutton; + } + } + add_assignment(assignments, IPT_PEDAL3, SEQ_TYPE_INCREMENT, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, { std::get<0>(numberedbuttons[pedalbutton]) }); + + // potentially use thumb sticks and/or D-pad and A/B/X/Y diamond for twin sticks + add_twin_stick_assignments( + assignments, + axisitems[SDL_CONTROLLER_AXIS_LEFTX].first, + axisitems[SDL_CONTROLLER_AXIS_LEFTY].first, + axisitems[SDL_CONTROLLER_AXIS_RIGHTX].first, + axisitems[SDL_CONTROLLER_AXIS_RIGHTY].first, + buttonitems[SDL_CONTROLLER_BUTTON_DPAD_LEFT], + buttonitems[SDL_CONTROLLER_BUTTON_DPAD_RIGHT], + buttonitems[SDL_CONTROLLER_BUTTON_DPAD_UP], + buttonitems[SDL_CONTROLLER_BUTTON_DPAD_DOWN], + buttonitems[SDL_CONTROLLER_BUTTON_X], + buttonitems[SDL_CONTROLLER_BUTTON_B], + buttonitems[SDL_CONTROLLER_BUTTON_Y], + buttonitems[SDL_CONTROLLER_BUTTON_A]); + + // add assignments for buttons with fixed functions + add_button_assignment(assignments, IPT_SELECT, { buttonitems[SDL_CONTROLLER_BUTTON_BACK] }); + add_button_assignment(assignments, IPT_START, { buttonitems[SDL_CONTROLLER_BUTTON_START] }); + add_button_assignment(assignments, IPT_UI_MENU, { buttonitems[SDL_CONTROLLER_BUTTON_GUIDE] }); + + // the first button is always UI select + if (add_button_assignment(assignments, IPT_UI_SELECT, { std::get<0>(numberedbuttons[0]) })) + { + if (SDL_CONTROLLER_BUTTON_INVALID != std::get<1>(numberedbuttons[0])) + buttonitems[std::get<1>(numberedbuttons[0])] = ITEM_ID_INVALID; + if (SDL_CONTROLLER_AXIS_INVALID != std::get<2>(numberedbuttons[0])) + axisitems[std::get<2>(numberedbuttons[0])].second = ITEM_ID_INVALID; + } + + // try to get a matching pair of buttons for previous/next group + if (consume_button_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, axisitems[SDL_CONTROLLER_AXIS_TRIGGERLEFT].second, axisitems[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].second)) + { + // took digital triggers + } + else if (!avoidpaddles && consume_button_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, buttonitems[SDL_CONTROLLER_BUTTON_PADDLE1], buttonitems[SDL_CONTROLLER_BUTTON_PADDLE2])) + { + // took upper paddles + } + else if (consume_trigger_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, axisitems[SDL_CONTROLLER_AXIS_TRIGGERLEFT].first, axisitems[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].first)) + { + // took analog triggers + } + else if (!avoidpaddles && consume_button_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, buttonitems[SDL_CONTROLLER_BUTTON_PADDLE3], buttonitems[SDL_CONTROLLER_BUTTON_PADDLE4])) + { + // took lower paddles + } + else if (consume_axis_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, diraxis[1][1])) + { + // took secondary Y + } + else if (consume_axis_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, diraxis[1][0])) + { + // took secondary X + } + + // try to get a matching pair of buttons for page up/down + if (!avoidpaddles && consume_button_pair(assignments, IPT_UI_PAGE_UP, IPT_UI_PAGE_DOWN, buttonitems[SDL_CONTROLLER_BUTTON_PADDLE1], buttonitems[SDL_CONTROLLER_BUTTON_PADDLE2])) + { + // took upper paddles + } + else if (!avoidpaddles && consume_button_pair(assignments, IPT_UI_PAGE_UP, IPT_UI_PAGE_DOWN, buttonitems[SDL_CONTROLLER_BUTTON_PADDLE3], buttonitems[SDL_CONTROLLER_BUTTON_PADDLE4])) + { + // took lower paddles + } + else + if (consume_trigger_pair(assignments, IPT_UI_PAGE_UP, IPT_UI_PAGE_DOWN, axisitems[SDL_CONTROLLER_AXIS_TRIGGERLEFT].first, axisitems[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].first)) + { + // took analog triggers + } + else if (consume_axis_pair(assignments, IPT_UI_PAGE_UP, IPT_UI_PAGE_DOWN, diraxis[1][1])) + { + // took secondary Y + } + + // try to assign X button to UI clear + if (add_button_assignment(assignments, IPT_UI_CLEAR, { buttonitems[SDL_CONTROLLER_BUTTON_X] })) + { + buttonitems[SDL_CONTROLLER_BUTTON_X] = ITEM_ID_INVALID; + } + else + { + // otherwise try to find an unassigned button + for (auto [item, button, axis] : numberedbuttons) + { + if ((SDL_CONTROLLER_BUTTON_INVALID != button) && (ITEM_ID_INVALID != buttonitems[button])) + { + add_button_assignment(assignments, IPT_UI_CLEAR, { item }); + buttonitems[button] = ITEM_ID_INVALID; + break; + } + else if ((SDL_CONTROLLER_AXIS_INVALID != axis) && (ITEM_ID_INVALID != axisitems[axis].second)) + { + add_button_assignment(assignments, IPT_UI_CLEAR, { item }); + axisitems[axis].second = ITEM_ID_INVALID; + break; + } + } + } + + // try to assign B button to UI back + if (add_button_assignment(assignments, IPT_UI_BACK, { buttonitems[SDL_CONTROLLER_BUTTON_B] })) + { + buttonitems[SDL_CONTROLLER_BUTTON_X] = ITEM_ID_INVALID; + } + else + { + // otherwise try to find an unassigned button + for (auto [item, button, axis] : numberedbuttons) + { + if ((SDL_CONTROLLER_BUTTON_INVALID != button) && (ITEM_ID_INVALID != buttonitems[button])) + { + add_button_assignment(assignments, IPT_UI_CLEAR, { item }); + buttonitems[button] = ITEM_ID_INVALID; + break; + } + else if ((SDL_CONTROLLER_AXIS_INVALID != axis) && (ITEM_ID_INVALID != axisitems[axis].second)) + { + add_button_assignment(assignments, IPT_UI_CLEAR, { item }); + axisitems[axis].second = ITEM_ID_INVALID; + break; + } + } + } + + // try to assign Y button to UI help + if (add_button_assignment(assignments, IPT_UI_HELP, { buttonitems[SDL_CONTROLLER_BUTTON_Y] })) + { + buttonitems[SDL_CONTROLLER_BUTTON_Y] = ITEM_ID_INVALID; + } + else + { + // otherwise try to find an unassigned button + for (auto [item, button, axis] : numberedbuttons) + { + if ((SDL_CONTROLLER_BUTTON_INVALID != button) && (ITEM_ID_INVALID != buttonitems[button])) + { + add_button_assignment(assignments, IPT_UI_HELP, { item }); + buttonitems[button] = ITEM_ID_INVALID; + break; + } + else if ((SDL_CONTROLLER_AXIS_INVALID != axis) && (ITEM_ID_INVALID != axisitems[axis].second)) + { + add_button_assignment(assignments, IPT_UI_HELP, { item }); + axisitems[axis].second = ITEM_ID_INVALID; + break; + } + } + } + + // put focus previous/next on the shoulder buttons if available - this can be overloaded with zoom + if (add_button_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, buttonitems[SDL_CONTROLLER_BUTTON_LEFTSHOULDER], buttonitems[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER])) + { + // took shoulder buttons + } + else if (add_axis_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, diraxis[1][0])) + { + // took secondary X + } + else if (add_axis_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, diraxis[1][1])) + { + // took secondary Y + } + + // put zoom on the secondary stick if available, or fall back to shoulder buttons + if (add_axis_pair_assignment(assignments, IPT_UI_ZOOM_OUT, IPT_UI_ZOOM_IN, diraxis[1][0])) + { + // took secondary X + if (axisitems[SDL_CONTROLLER_AXIS_LEFTX].first == diraxis[1][0]) + add_button_assignment(assignments, IPT_UI_ZOOM_DEFAULT, { buttonitems[SDL_CONTROLLER_BUTTON_LEFTSTICK] }); + else if (axisitems[SDL_CONTROLLER_AXIS_RIGHTX].first == diraxis[1][0]) + add_button_assignment(assignments, IPT_UI_ZOOM_DEFAULT, { buttonitems[SDL_CONTROLLER_BUTTON_RIGHTSTICK] }); + diraxis[1][0] = ITEM_ID_INVALID; + } + else if (add_axis_pair_assignment(assignments, IPT_UI_ZOOM_IN, IPT_UI_ZOOM_OUT, diraxis[1][1])) + { + // took secondary Y + if (axisitems[SDL_CONTROLLER_AXIS_LEFTY].first == diraxis[1][1]) + add_button_assignment(assignments, IPT_UI_ZOOM_DEFAULT, { buttonitems[SDL_CONTROLLER_BUTTON_LEFTSTICK] }); + else if (axisitems[SDL_CONTROLLER_AXIS_RIGHTY].first == diraxis[1][1]) + add_button_assignment(assignments, IPT_UI_ZOOM_DEFAULT, { buttonitems[SDL_CONTROLLER_BUTTON_RIGHTSTICK] }); + diraxis[1][1] = ITEM_ID_INVALID; + } + else if (consume_button_pair(assignments, IPT_UI_ZOOM_OUT, IPT_UI_ZOOM_IN, buttonitems[SDL_CONTROLLER_BUTTON_LEFTSHOULDER], buttonitems[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER])) + { + // took shoulder buttons + } + + // set default assignments + device.set_default_assignments(std::move(assignments)); } - void exit() override + virtual void reset() override { - // unsubscribe for events - sdl_event_manager::instance().unsubscribe(this); + sdl_joystick_device_base::reset(); + clear_buffer(); + } + + virtual void process_event(SDL_Event const &event) override + { + if (!m_ctrldevice) + return; + + switch (event.type) + { + case SDL_CONTROLLERAXISMOTION: + if (event.caxis.axis < SDL_CONTROLLER_AXIS_MAX) + { + switch (event.caxis.axis) + { + case SDL_CONTROLLER_AXIS_TRIGGERLEFT: // MAME wants negative values for triggers + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: + m_controller.axes[event.caxis.axis] = -normalize_absolute_axis(event.caxis.value, -32'767, 32'767); + break; + default: + m_controller.axes[event.caxis.axis] = normalize_absolute_axis(event.caxis.value, -32'767, 32'767); + } + } + break; - input_module_base::exit(); + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + if (event.cbutton.button < SDL_CONTROLLER_BUTTON_MAX) + m_controller.buttons[event.cbutton.button] = (event.cbutton.state == SDL_PRESSED) ? 0x80 : 0x00; + break; + + case SDL_CONTROLLERDEVICEREMOVED: + osd_printf_verbose("Game Controller: %s [ID %s] disconnected\n", name(), id()); + clear_instance(); + clear_buffer(); + close_device(); + break; + } } - void before_poll(running_machine& machine) override + void attach_device(SDL_GameController *ctrl) { - // Tell the event manager to process events and push them to the devices - sdl_event_manager::instance().process_events(machine); + assert(ctrl); + assert(!m_ctrldevice); + + set_instance(SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(ctrl))); + m_ctrldevice = ctrl; + + osd_printf_verbose("Game Controller: %s [ID %s] reconnected\n", name(), id()); } - bool should_poll_devices(running_machine& machine) override +private: + // state information for a game controller + struct sdl_controller_state { - return sdl_event_manager::instance().has_focus() && input_enabled(); + s32 axes[SDL_CONTROLLER_AXIS_MAX]; + s32 buttons[SDL_CONTROLLER_BUTTON_MAX]; + }; + + sdl_controller_state m_controller; + SDL_GameController *m_ctrldevice; + + void clear_buffer() + { + memset(&m_controller, 0, sizeof(m_controller)); } - void handle_event(SDL_Event &sdlevent) override + void close_device() { - // By default dispatch event to every device - devicelist()->for_each_device([&sdlevent](auto device) { - downcast<sdl_device*>(device)->queue_events(&sdlevent, 1); - }); + if (m_ctrldevice) + { + SDL_GameControllerClose(m_ctrldevice); + m_ctrldevice = nullptr; + } } }; + //============================================================ -// sdl_keyboard_module +// sdl_input_module //============================================================ -class sdl_keyboard_module : public sdl_input_module +template <typename Info> +class sdl_input_module : + public input_module_impl<Info, sdl_osd_interface>, + protected sdl_event_manager::subscriber { - keyboard_trans_table * m_key_trans_table; public: - sdl_keyboard_module() - : sdl_input_module(OSD_KEYBOARDINPUT_PROVIDER), m_key_trans_table(nullptr) + sdl_input_module(char const *type, char const *name) : + input_module_impl<Info, sdl_osd_interface>(type, name) { } - void input_init(running_machine &machine) override + virtual void exit() override { - sdl_input_module::input_init(machine); + // unsubscribe for events + unsubscribe(); - static int event_types[] = { - static_cast<int>(SDL_KEYDOWN), - static_cast<int>(SDL_KEYUP), - static_cast<int>(SDL_TEXTINPUT) - }; + input_module_impl<Info, sdl_osd_interface>::exit(); + } - sdl_event_manager::instance().subscribe(event_types, ARRAY_LENGTH(event_types), this); +protected: + virtual void handle_event(SDL_Event const &event) override + { + // dispatch event to every device by default + this->devicelist().for_each_device( + [&event] (auto &device) { device.queue_events(&event, 1); }); + } +}; - sdl_keyboard_device *devinfo; - // Read our keymap and store a pointer to our table - m_key_trans_table = sdlinput_read_keymap(machine); +//============================================================ +// sdl_keyboard_module +//============================================================ - keyboard_trans_table& local_table = *m_key_trans_table; +class sdl_keyboard_module : public sdl_input_module<sdl_keyboard_device> +{ +public: + sdl_keyboard_module() : + sdl_input_module<sdl_keyboard_device>(OSD_KEYBOARDINPUT_PROVIDER, "sdl") + { + } - osd_printf_verbose("Keyboard: Start initialization\n"); + virtual void input_init(running_machine &machine) override + { + sdl_input_module<sdl_keyboard_device>::input_init(machine); - // SDL only has 1 keyboard add it now - devinfo = devicelist()->create_device<sdl_keyboard_device>(machine, "System keyboard", "System keyboard", *this); + constexpr int event_types[] = { + int(SDL_KEYDOWN), + int(SDL_KEYUP) }; - // populate it - for (int keynum = 0; local_table[keynum].mame_key != ITEM_ID_INVALID; keynum++) - { - input_item_id itemid = local_table[keynum].mame_key; + subscribe(osd(), event_types); + + // Read our keymap and store a pointer to our table + sdlinput_read_keymap(); - // generate the default / modified name - char defname[20]; - snprintf(defname, sizeof(defname) - 1, "%s", local_table[keynum].ui_name); + osd_printf_verbose("Keyboard: Start initialization\n"); - devinfo->device()->add_item(defname, itemid, generic_button_get_state<std::int32_t>, &devinfo->keyboard.state[local_table[keynum].sdl_scancode]); - } + // SDL only has 1 keyboard add it now + auto &devinfo = create_device<sdl_keyboard_device>( + DEVICE_CLASS_KEYBOARD, + "System keyboard", + "System keyboard", + *m_key_trans_table); - osd_printf_verbose("Keyboard: Registered %s\n", devinfo->name()); + osd_printf_verbose("Keyboard: Registered %s\n", devinfo.name()); osd_printf_verbose("Keyboard: End initialization\n"); } private: - static keyboard_trans_table* sdlinput_read_keymap(running_machine &machine) + void sdlinput_read_keymap() { - char *keymap_filename; - FILE *keymap_file; - int line = 1; - int sdl2section = 0; - keyboard_trans_table &default_table = keyboard_trans_table::instance(); - if (!machine.options().bool_value(SDLOPTION_KEYMAP)) - return &default_table; - - keymap_filename = (char *)downcast<sdl_options &>(machine.options()).keymap_file(); - osd_printf_verbose("Keymap: Start reading keymap_file %s\n", keymap_filename); - - keymap_file = fopen(keymap_filename, "r"); - if (keymap_file == nullptr) - { - osd_printf_warning("Keymap: Unable to open keymap %s, using default\n", keymap_filename); - return &default_table; - } - // Allocate a block of translation entries big enough to hold what's in the default table - auto key_trans_entries = std::make_unique<key_trans_entry[]>(default_table.size()); + auto key_trans_entries = std::make_unique<key_trans_entry []>(default_table.size()); - // copy the elements from the default table + // copy the elements from the default table and ask SDL for key names for (int i = 0; i < default_table.size(); i++) + { key_trans_entries[i] = default_table[i]; + char const *const name = SDL_GetScancodeName(SDL_Scancode(default_table[i].sdl_scancode)); + if (name && *name) + key_trans_entries[i].ui_name = name; + } // Allocate the trans table to be associated with the machine so we don't have to free it - keyboard_trans_table *custom_table = auto_alloc(machine, keyboard_trans_table(std::move(key_trans_entries), default_table.size())); + m_key_trans_table = std::make_unique<keyboard_trans_table>(std::move(key_trans_entries), default_table.size()); + + if (!options()->bool_value(SDLOPTION_KEYMAP)) + return; + + const char *const keymap_filename = dynamic_cast<sdl_options const &>(*options()).keymap_file(); + osd_printf_verbose("Keymap: Start reading keymap_file %s\n", keymap_filename); + + FILE *const keymap_file = fopen(keymap_filename, "r"); + if (!keymap_file) + { + osd_printf_warning("Keymap: Unable to open keymap %s, using default\n", keymap_filename); + return; + } + int line = 1; + int sdl2section = 0; while (!feof(keymap_file)) { char buf[256]; @@ -864,34 +2266,36 @@ private: if (sk >= 0 && index >= 0) { - key_trans_entry &entry = (*custom_table)[index]; + key_trans_entry &entry = (*m_key_trans_table)[index]; entry.sdl_scancode = sk; - entry.ui_name = auto_alloc_array(machine, char, strlen(kns) + 1); - strcpy(entry.ui_name, kns); + entry.ui_name = const_cast<char *>(m_ui_names.emplace_back(kns).c_str()); osd_printf_verbose("Keymap: Mapped <%s> to <%s> with ui-text <%s>\n", sks, mks, kns); } else + { osd_printf_error("Keymap: Error on line %d - %s key not found: %s\n", line, (sk<0) ? "sdl" : "mame", buf); + } } } line++; } fclose(keymap_file); osd_printf_verbose("Keymap: Processed %d lines\n", line); - - return custom_table; } + + std::unique_ptr<keyboard_trans_table> m_key_trans_table; + std::list<std::string> m_ui_names; }; + //============================================================ // sdl_mouse_module //============================================================ -class sdl_mouse_module : public sdl_input_module +class sdl_mouse_module : public sdl_input_module<sdl_mouse_device> { public: - sdl_mouse_module() - : sdl_input_module(OSD_MOUSEINPUT_PROVIDER) + sdl_mouse_module() : sdl_input_module<sdl_mouse_device>(OSD_MOUSEINPUT_PROVIDER, "sdl") { } @@ -899,285 +2303,578 @@ public: { sdl_input_module::input_init(machine); - static int event_types[] = { - static_cast<int>(SDL_MOUSEMOTION), - static_cast<int>(SDL_MOUSEBUTTONDOWN), - static_cast<int>(SDL_MOUSEBUTTONUP), - static_cast<int>(SDL_MOUSEWHEEL) - }; - - sdl_event_manager::instance().subscribe(event_types, ARRAY_LENGTH(event_types), this); + constexpr int event_types[] = { + int(SDL_MOUSEMOTION), + int(SDL_MOUSEBUTTONDOWN), + int(SDL_MOUSEBUTTONUP), + int(SDL_MOUSEWHEEL) }; - sdl_mouse_device *devinfo; - char defname[20]; - int button; + subscribe(osd(), event_types); osd_printf_verbose("Mouse: Start initialization\n"); // SDL currently only supports one mouse - devinfo = devicelist()->create_device<sdl_mouse_device>(machine, "System mouse", "System mouse", *this); - - // add the axes - devinfo->device()->add_item("X", ITEM_ID_XAXIS, generic_axis_get_state<std::int32_t>, &devinfo->mouse.lX); - devinfo->device()->add_item("Y", ITEM_ID_YAXIS, generic_axis_get_state<std::int32_t>, &devinfo->mouse.lY); + auto &devinfo = create_device<sdl_mouse_device>( + DEVICE_CLASS_MOUSE, + "System mouse", + "System mouse"); - for (button = 0; button < 4; button++) - { - input_item_id itemid = (input_item_id)(ITEM_ID_BUTTON1 + button); - snprintf(defname, sizeof(defname), "B%d", button + 1); - - devinfo->device()->add_item(defname, itemid, generic_button_get_state<std::int32_t>, &devinfo->mouse.buttons[button]); - } - - osd_printf_verbose("Mouse: Registered %s\n", devinfo->name()); + osd_printf_verbose("Mouse: Registered %s\n", devinfo.name()); osd_printf_verbose("Mouse: End initialization\n"); } }; -static void devmap_register(device_map_t &devmap, int physical_idx, const std::string &name) -{ - // Attempt to find the entry by name - auto entry = std::find_if(std::begin(devmap.map), std::end(devmap.map), [&name](auto &item) - { - return item.name == name && item.physical < 0; - }); +//============================================================ +// sdl_lightgun_module +//============================================================ - // If we didn't find it by name, find the first free slot - if (entry == std::end(devmap.map)) +class sdl_lightgun_module : public sdl_input_module<sdl_mouse_device_base> +{ +public: + sdl_lightgun_module() : sdl_input_module<sdl_mouse_device_base>(OSD_LIGHTGUNINPUT_PROVIDER, "sdl") { - entry = std::find_if(std::begin(devmap.map), std::end(devmap.map), [](auto &item) { return item.name.empty(); }); } - if (entry != std::end(devmap.map)) + virtual void input_init(running_machine &machine) override { - entry->physical = physical_idx; - entry->name = name; - int logical_idx = std::distance(std::begin(devmap.map), entry); - devmap.logical[physical_idx] = logical_idx; + auto &sdlopts = dynamic_cast<sdl_options const &>(*options()); + sdl_input_module::input_init(machine); + bool const dual(sdlopts.dual_lightgun()); + + if (!dual) + { + constexpr int event_types[] = { + int(SDL_MOUSEMOTION), + int(SDL_MOUSEBUTTONDOWN), + int(SDL_MOUSEBUTTONUP), + int(SDL_MOUSEWHEEL), + int(SDL_WINDOWEVENT) }; + subscribe(osd(), event_types); + } + else + { + constexpr int event_types[] = { + int(SDL_MOUSEBUTTONDOWN), + int(SDL_MOUSEBUTTONUP) }; + subscribe(osd(), event_types); + } + + osd_printf_verbose("Lightgun: Start initialization\n"); + + if (!dual) + { + auto &devinfo = create_device<sdl_lightgun_device>( + DEVICE_CLASS_LIGHTGUN, + "System pointer gun 1", + "System pointer gun 1"); + osd_printf_verbose("Lightgun: Registered %s\n", devinfo.name()); + } + else + { + auto &dev1info = create_device<sdl_dual_lightgun_device>( + DEVICE_CLASS_LIGHTGUN, + "System pointer gun 1", + "System pointer gun 1", + 0); + osd_printf_verbose("Lightgun: Registered %s\n", dev1info.name()); + + auto &dev2info = create_device<sdl_dual_lightgun_device>( + DEVICE_CLASS_LIGHTGUN, + "System pointer gun 2", + "System pointer gun 2", + 1); + osd_printf_verbose("Lightgun: Registered %s\n", dev2info.name()); + } + + osd_printf_verbose("Lightgun: End initialization\n"); } -} +}; + //============================================================ -// sdl_joystick_module +// sdl_joystick_module_base //============================================================ -class sdl_joystick_module : public sdl_input_module +class sdl_joystick_module_base : public sdl_input_module<sdl_joystick_device_base> { -private: - device_map_t m_joy_map; - bool m_initialized_joystick; - bool m_initialized_haptic; - bool m_sixaxis_mode; -public: - sdl_joystick_module() - : sdl_input_module(OSD_JOYSTICKINPUT_PROVIDER), m_initialized_joystick(false), m_initialized_haptic(false), m_sixaxis_mode(false) +protected: + sdl_joystick_module_base(char const *name) : + sdl_input_module<sdl_joystick_device_base>(OSD_JOYSTICKINPUT_PROVIDER, name), + m_initialized_joystick(false), + m_initialized_haptic(false) { } - virtual void exit() override + virtual ~sdl_joystick_module_base() + { + assert(!m_initialized_joystick); + assert(!m_initialized_haptic); + } + + bool have_joystick() const { return m_initialized_joystick; } + bool have_haptic() const { return m_initialized_haptic; } + + void init_joystick() { - sdl_input_module::exit(); + assert(!m_initialized_joystick); + assert(!m_initialized_haptic); + + m_initialized_joystick = !SDL_InitSubSystem(SDL_INIT_JOYSTICK); + if (!m_initialized_joystick) + { + osd_printf_error("Could not initialize SDL Joystick subsystem: %s.\n", SDL_GetError()); + return; + } + m_initialized_haptic = !SDL_InitSubSystem(SDL_INIT_HAPTIC); + if (!m_initialized_haptic) + osd_printf_verbose("Could not initialize SDL Haptic subsystem: %s.\n", SDL_GetError()); + } + + void quit_joystick() + { if (m_initialized_joystick) { SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + m_initialized_joystick = false; } if (m_initialized_haptic) { SDL_QuitSubSystem(SDL_INIT_HAPTIC); + m_initialized_haptic = false; } } - virtual void input_init(running_machine &machine) override + sdl_joystick_device *create_joystick_device(int index, bool sixaxis) { - SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); - - m_initialized_joystick = !SDL_InitSubSystem(SDL_INIT_JOYSTICK); - if (!m_initialized_joystick) + // open the joystick device + SDL_Joystick *const joy = SDL_JoystickOpen(index); + if (!joy) { - osd_printf_error("Could not initialize SDL Joystick: %s.\n", SDL_GetError()); - return; + osd_printf_error("Joystick: Could not open SDL joystick %d: %s.\n", index, SDL_GetError()); + return nullptr; } - m_initialized_haptic = !SDL_InitSubSystem(SDL_INIT_HAPTIC); - if (!m_initialized_haptic) + // get basic info + char const *const name = SDL_JoystickName(joy); + SDL_JoystickGUID guid = SDL_JoystickGetGUID(joy); + char guid_str[256]; + guid_str[0] = '\0'; + SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str) - 1); + char const *const serial = SDL_JoystickGetSerial(joy); + std::string id(guid_str); + if (serial) + id.append(1, '-').append(serial); + + // print some diagnostic info + osd_printf_verbose("Joystick: %s [GUID %s] Vendor ID %04X, Product ID %04X, Revision %04X, Serial %s\n", + name ? name : "<nullptr>", + guid_str, + SDL_JoystickGetVendor(joy), + SDL_JoystickGetProduct(joy), + SDL_JoystickGetProductVersion(joy), + serial ? serial : "<nullptr>"); + osd_printf_verbose("Joystick: ... %d axes, %d buttons %d hats %d balls\n", + SDL_JoystickNumAxes(joy), + SDL_JoystickNumButtons(joy), + SDL_JoystickNumHats(joy), + SDL_JoystickNumBalls(joy)); + if (SDL_JoystickNumButtons(joy) > MAX_BUTTONS) + osd_printf_verbose("Joystick: ... Has %d buttons which exceeds supported %d buttons\n", SDL_JoystickNumButtons(joy), MAX_BUTTONS); + + // instantiate device + sdl_joystick_device &devinfo = sixaxis + ? create_device<sdl_sixaxis_joystick_device>(DEVICE_CLASS_JOYSTICK, name ? name : guid_str, guid_str, joy, serial) + : create_device<sdl_joystick_device>(DEVICE_CLASS_JOYSTICK, name ? name : guid_str, guid_str, joy, serial); + + if (devinfo.has_haptic()) + osd_printf_verbose("Joystick: ... Has haptic capability\n"); + else + osd_printf_verbose("Joystick: ... Does not have haptic capability\n"); + + return &devinfo; + } + + void dispatch_joystick_event(SDL_Event const &event) + { + // figure out which joystick this event is destined for + sdl_joystick_device_base *const target_device = find_joystick(event.jdevice.which); // FIXME: this depends on SDL_JoystickID being the same size as Sint32 + + // if we find a matching joystick, dispatch the event to the joystick + if (target_device) + target_device->queue_events(&event, 1); + } + + device_info *find_reconnect_match(SDL_JoystickGUID const &guid, char const *serial) + { + char guid_str[256]; + guid_str[0] = '\0'; + SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str) - 1); + auto target_device = std::find_if( + devicelist().begin(), + devicelist().end(), + [&guid_str, &serial] (auto const &device) + { + return device->reconnect_match(guid_str, serial); + }); + return (devicelist().end() != target_device) ? target_device->get() : nullptr; + } + + sdl_joystick_device_base *find_joystick(SDL_JoystickID instance) + { + for (auto &device : devicelist()) { - osd_printf_verbose("Could not initialize SDL Haptic subsystem: %s.\n", SDL_GetError()); + if (device->is_instance(instance)) + return device.get(); } + return nullptr; + } - sdl_input_module::input_init(machine); +private: + bool m_initialized_joystick; + bool m_initialized_haptic; +}; + + +//============================================================ +// sdl_joystick_module +//============================================================ + +class sdl_joystick_module : public sdl_joystick_module_base +{ +public: + sdl_joystick_module() : sdl_joystick_module_base("sdljoy") + { + } - char tempname[512]; + virtual void exit() override + { + sdl_joystick_module_base::exit(); - m_sixaxis_mode = downcast<const sdl_options*>(options())->sixaxis(); + quit_joystick(); + } - devmap_init(machine, &m_joy_map, SDLOPTION_JOYINDEX, 8, "Joystick mapping"); + virtual void input_init(running_machine &machine) override + { + auto &sdlopts = dynamic_cast<sdl_options const &>(*options()); + bool const sixaxis_mode = sdlopts.sixaxis(); - osd_printf_verbose("Joystick: Start initialization\n"); - int physical_stick; - for (physical_stick = 0; physical_stick < SDL_NumJoysticks(); physical_stick++) - { - std::string joy_name = remove_spaces(SDL_JoystickNameForIndex(physical_stick)); - devmap_register(m_joy_map, physical_stick, joy_name); - } + if (!sdlopts.debug() && sdlopts.background_input()) + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); + SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); - for (int stick = 0; stick < MAX_DEVMAP_ENTRIES; stick++) - { - sdl_joystick_device *devinfo = create_joystick_device(machine, &m_joy_map, stick, DEVICE_CLASS_JOYSTICK); + init_joystick(); + if (!have_joystick()) + return; - if (devinfo == nullptr) - continue; + sdl_joystick_module_base::input_init(machine); - physical_stick = m_joy_map.map[stick].physical; - SDL_Joystick *joy = SDL_JoystickOpen(physical_stick); - devinfo->sdl_state.device = joy; - devinfo->sdl_state.joystick_id = SDL_JoystickInstanceID(joy); - devinfo->sdl_state.hapdevice = SDL_HapticOpenFromJoystick(joy); + osd_printf_verbose("Joystick: Start initialization\n"); + for (int physical_stick = 0; physical_stick < SDL_NumJoysticks(); physical_stick++) + create_joystick_device(physical_stick, sixaxis_mode); + + constexpr int event_types[] = { + int(SDL_JOYAXISMOTION), + int(SDL_JOYBALLMOTION), + int(SDL_JOYHATMOTION), + int(SDL_JOYBUTTONDOWN), + int(SDL_JOYBUTTONUP), + int(SDL_JOYDEVICEADDED), + int(SDL_JOYDEVICEREMOVED) }; + subscribe(osd(), event_types); - osd_printf_verbose("Joystick: %s\n", SDL_JoystickNameForIndex(physical_stick)); - osd_printf_verbose("Joystick: ... %d axes, %d buttons %d hats %d balls\n", SDL_JoystickNumAxes(joy), SDL_JoystickNumButtons(joy), SDL_JoystickNumHats(joy), SDL_JoystickNumBalls(joy)); - osd_printf_verbose("Joystick: ... Physical id %d mapped to logical id %d\n", physical_stick, stick + 1); - if (devinfo->sdl_state.hapdevice != nullptr) + osd_printf_verbose("Joystick: End initialization\n"); + } + + virtual void handle_event(SDL_Event const &event) override + { + if (SDL_JOYDEVICEADDED == event.type) + { + SDL_Joystick *const joy = SDL_JoystickOpen(event.jdevice.which); + if (!joy) { - osd_printf_verbose("Joystick: ... Has haptic capability\n"); + osd_printf_error("Joystick: Could not open SDL joystick %d: %s.\n", event.jdevice.which, SDL_GetError()); } else { - osd_printf_verbose("Joystick: ... Does not have haptic capability\n"); + SDL_JoystickGUID guid = SDL_JoystickGetGUID(joy); + char const *const serial = SDL_JoystickGetSerial(joy); + auto *const target_device = find_reconnect_match(guid, serial); + if (target_device) + { + auto &devinfo = dynamic_cast<sdl_joystick_device &>(*target_device); + devinfo.attach_device(joy); + } + else + { + SDL_JoystickClose(joy); + } } + } + else + { + dispatch_joystick_event(event); + } + } +}; - // loop over all axes - for (int axis = 0; axis < SDL_JoystickNumAxes(joy); axis++) - { - input_item_id itemid; - if (axis < INPUT_MAX_AXIS) - itemid = (input_item_id)(ITEM_ID_XAXIS + axis); - else if (axis < INPUT_MAX_AXIS + INPUT_MAX_ADD_ABSOLUTE) - itemid = (input_item_id)(ITEM_ID_ADD_ABSOLUTE1 - INPUT_MAX_AXIS + axis); - else - itemid = ITEM_ID_OTHER_AXIS_ABSOLUTE; +//============================================================ +// sdl_game_controller_module +//============================================================ - snprintf(tempname, sizeof(tempname), "A%d %s", axis, devinfo->name()); - devinfo->device()->add_item(tempname, itemid, generic_axis_get_state<std::int32_t>, &devinfo->joystick.axes[axis]); - } +class sdl_game_controller_module : public sdl_joystick_module_base +{ +public: + sdl_game_controller_module() : + sdl_joystick_module_base("sdlgame"), + m_initialized_game_controller(false) + { + } - // loop over all buttons - for (int button = 0; button < SDL_JoystickNumButtons(joy); button++) - { - input_item_id itemid; + virtual void exit() override + { + sdl_joystick_module_base::exit(); - devinfo->joystick.buttons[button] = 0; + if (m_initialized_game_controller) + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); - if (button < INPUT_MAX_BUTTONS) - itemid = (input_item_id)(ITEM_ID_BUTTON1 + button); - else if (button < INPUT_MAX_BUTTONS + INPUT_MAX_ADD_SWITCH) - itemid = (input_item_id)(ITEM_ID_ADD_SWITCH1 - INPUT_MAX_BUTTONS + button); - else - itemid = ITEM_ID_OTHER_SWITCH; + quit_joystick(); + } - snprintf(tempname, sizeof(tempname), "button %d", button); - devinfo->device()->add_item(tempname, itemid, generic_button_get_state<std::int32_t>, &devinfo->joystick.buttons[button]); - } + virtual void input_init(running_machine &machine) override + { + auto &sdlopts = dynamic_cast<sdl_options const &>(*options()); + bool const sixaxis_mode = sdlopts.sixaxis(); - // loop over all hats - for (int hat = 0; hat < SDL_JoystickNumHats(joy); hat++) - { - input_item_id itemid; - - snprintf(tempname, sizeof(tempname), "hat %d Up", hat); - itemid = (input_item_id)((hat < INPUT_MAX_HATS) ? ITEM_ID_HAT1UP + 4 * hat : ITEM_ID_OTHER_SWITCH); - devinfo->device()->add_item(tempname, itemid, generic_button_get_state<std::int32_t>, &devinfo->joystick.hatsU[hat]); - snprintf(tempname, sizeof(tempname), "hat %d Down", hat); - itemid = (input_item_id)((hat < INPUT_MAX_HATS) ? ITEM_ID_HAT1DOWN + 4 * hat : ITEM_ID_OTHER_SWITCH); - devinfo->device()->add_item(tempname, itemid, generic_button_get_state<std::int32_t>, &devinfo->joystick.hatsD[hat]); - snprintf(tempname, sizeof(tempname), "hat %d Left", hat); - itemid = (input_item_id)((hat < INPUT_MAX_HATS) ? ITEM_ID_HAT1LEFT + 4 * hat : ITEM_ID_OTHER_SWITCH); - devinfo->device()->add_item(tempname, itemid, generic_button_get_state<std::int32_t>, &devinfo->joystick.hatsL[hat]); - snprintf(tempname, sizeof(tempname), "hat %d Right", hat); - itemid = (input_item_id)((hat < INPUT_MAX_HATS) ? ITEM_ID_HAT1RIGHT + 4 * hat : ITEM_ID_OTHER_SWITCH); - devinfo->device()->add_item(tempname, itemid, generic_button_get_state<std::int32_t>, &devinfo->joystick.hatsR[hat]); - } + if (!sdlopts.debug() && sdlopts.background_input()) + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); + SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); - // loop over all (track)balls - for (int ball = 0; ball < SDL_JoystickNumBalls(joy); ball++) - { - int itemid; + init_joystick(); + if (!have_joystick()) + return; - if (ball * 2 < INPUT_MAX_ADD_RELATIVE) - itemid = ITEM_ID_ADD_RELATIVE1 + ball * 2; + m_initialized_game_controller = !SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER); + if (m_initialized_game_controller) + { + char const *const mapfile = sdlopts.controller_mapping_file(); + if (mapfile && *mapfile && std::strcmp(mapfile, OSDOPTVAL_NONE)) + { + auto const count = SDL_GameControllerAddMappingsFromFile(mapfile); + if (0 <= count) + osd_printf_verbose("Game Controller: %d controller mapping(s) added from file [%s].\n", count, mapfile); else - itemid = ITEM_ID_OTHER_AXIS_RELATIVE; - - snprintf(tempname, sizeof(tempname), "R%d %s", ball * 2, devinfo->name()); - devinfo->device()->add_item(tempname, (input_item_id)itemid, generic_axis_get_state<std::int32_t>, &devinfo->joystick.balls[ball * 2]); - snprintf(tempname, sizeof(tempname), "R%d %s", ball * 2 + 1, devinfo->name()); - devinfo->device()->add_item(tempname, (input_item_id)(itemid + 1), generic_axis_get_state<std::int32_t>, &devinfo->joystick.balls[ball * 2 + 1]); + osd_printf_error("Game Controller: Error adding mappings from file [%s]: %s.\n", mapfile, SDL_GetError()); } } + else + { + osd_printf_warning("Could not initialize SDL Game Controller: %s.\n", SDL_GetError()); + } - static int event_types[] = { - static_cast<int>(SDL_JOYAXISMOTION), - static_cast<int>(SDL_JOYBALLMOTION), - static_cast<int>(SDL_JOYHATMOTION), - static_cast<int>(SDL_JOYBUTTONDOWN), - static_cast<int>(SDL_JOYBUTTONUP) - }; + sdl_joystick_module_base::input_init(machine); - sdl_event_manager::instance().subscribe(event_types, ARRAY_LENGTH(event_types), this); + osd_printf_verbose("Game Controller: Start initialization\n"); + for (int physical_stick = 0; physical_stick < SDL_NumJoysticks(); physical_stick++) + { + // try to open as a game controller + SDL_GameController *ctrl = nullptr; + if (m_initialized_game_controller && SDL_IsGameController(physical_stick)) + { + ctrl = SDL_GameControllerOpen(physical_stick); + if (!ctrl) + osd_printf_warning("Game Controller: Could not open SDL game controller %d: %s.\n", physical_stick, SDL_GetError()); + } - osd_printf_verbose("Joystick: End initialization\n"); + // fall back to joystick API if necessary + if (!ctrl) + create_joystick_device(physical_stick, sixaxis_mode); + else + create_game_controller_device(physical_stick, ctrl); + } + + constexpr int joy_event_types[] = { + int(SDL_JOYAXISMOTION), + int(SDL_JOYBALLMOTION), + int(SDL_JOYHATMOTION), + int(SDL_JOYBUTTONDOWN), + int(SDL_JOYBUTTONUP), + int(SDL_JOYDEVICEADDED), + int(SDL_JOYDEVICEREMOVED) }; + constexpr int event_types[] = { + int(SDL_JOYAXISMOTION), + int(SDL_JOYBALLMOTION), + int(SDL_JOYHATMOTION), + int(SDL_JOYBUTTONDOWN), + int(SDL_JOYBUTTONUP), + int(SDL_JOYDEVICEADDED), + int(SDL_JOYDEVICEREMOVED), + int(SDL_CONTROLLERAXISMOTION), + int(SDL_CONTROLLERBUTTONDOWN), + int(SDL_CONTROLLERBUTTONUP), + int(SDL_CONTROLLERDEVICEADDED), + int(SDL_CONTROLLERDEVICEREMOVED) }; + if (m_initialized_game_controller) + subscribe(osd(), event_types); + else + subscribe(osd(), joy_event_types); + + osd_printf_verbose("Game Controller: End initialization\n"); } - virtual void handle_event(SDL_Event &sdlevent) override + virtual void handle_event(SDL_Event const &event) override { - // Figure out which joystick this event id destined for - auto target_device = std::find_if(devicelist()->begin(), devicelist()->end(), [&sdlevent](auto &device) + switch (event.type) { - std::unique_ptr<device_info> &ptr = device; - return downcast<sdl_joystick_device*>(ptr.get())->sdl_state.joystick_id == sdlevent.jdevice.which; - }); + case SDL_JOYDEVICEADDED: + { + // make sure this isn't an event for a reconnected game controller + auto const controller = find_joystick(SDL_JoystickGetDeviceInstanceID(event.jdevice.which)); + if (find_joystick(SDL_JoystickGetDeviceInstanceID(event.jdevice.which))) + { + osd_printf_verbose( + "Game Controller: Got SDL joystick added event for reconnected game controller %s [ID %s]\n", + controller->name(), + controller->id()); + break; + } - // If we find a matching joystick, dispatch the event to the joystick - if (target_device != devicelist()->end()) - { - downcast<sdl_joystick_device*>((*target_device).get())->queue_events(&sdlevent, 1); + SDL_Joystick *const joy = SDL_JoystickOpen(event.jdevice.which); + if (!joy) + { + osd_printf_error("Joystick: Could not open SDL joystick %d: %s.\n", event.jdevice.which, SDL_GetError()); + break; + } + + SDL_JoystickGUID guid = SDL_JoystickGetGUID(joy); + char const *const serial = SDL_JoystickGetSerial(joy); + auto *const target_device = find_reconnect_match(guid, serial); + if (target_device) + { + // if this downcast fails, opening as a game controller worked initially but failed on reconnection + auto *const devinfo = dynamic_cast<sdl_joystick_device *>(target_device); + if (devinfo) + devinfo->attach_device(joy); + else + SDL_JoystickClose(joy); + } + else + { + SDL_JoystickClose(joy); + } + } + break; + + // for devices supported by the game controller API, this is received before the corresponding SDL_JOYDEVICEADDED + case SDL_CONTROLLERDEVICEADDED: + if (m_initialized_game_controller) + { + SDL_GameController *const ctrl = SDL_GameControllerOpen(event.cdevice.which); + if (!ctrl) + { + osd_printf_error("Game Controller: Could not open SDL game controller %d: %s.\n", event.cdevice.which, SDL_GetError()); + break; + } + + SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(event.cdevice.which); + char const *const serial = SDL_GameControllerGetSerial(ctrl); + auto *const target_device = find_reconnect_match(guid, serial); + if (target_device) + { + // downcast can fail if there was an error opening the device as a game controller the first time + auto *const devinfo = dynamic_cast<sdl_game_controller_device *>(target_device); + if (devinfo) + devinfo->attach_device(ctrl); + else + SDL_GameControllerClose(ctrl); + } + else + { + SDL_GameControllerClose(ctrl); + } + } + break; + + default: + dispatch_joystick_event(event); } } private: - sdl_joystick_device* create_joystick_device(running_machine &machine, device_map_t *devmap, int index, input_device_class devclass) + sdl_game_controller_device *create_game_controller_device(int index, SDL_GameController *ctrl) { - char tempname[20]; - - if (devmap->map[index].name.empty()) + // get basic info + char const *const name = SDL_GameControllerName(ctrl); + SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(index); + char guid_str[256]; + guid_str[0] = '\0'; + SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str) - 1); + char const *const serial = SDL_GameControllerGetSerial(ctrl); + std::string id(guid_str); + if (serial) + id.append(1, '-').append(serial); + + // print some diagnostic info + osd_printf_verbose("Game Controller: %s [GUID %s] Vendor ID %04X, Product ID %04X, Revision %04X, Serial %s\n", + name ? name : "<nullptr>", + guid_str, + SDL_GameControllerGetVendor(ctrl), + SDL_GameControllerGetProduct(ctrl), + SDL_GameControllerGetProductVersion(ctrl), + serial ? serial : "<nullptr>"); + char *const mapping = SDL_GameControllerMapping(ctrl); + if (mapping) { - // only map place holders if there were mappings specified - if (devmap->initialized) - { - snprintf(tempname, ARRAY_LENGTH(tempname), "NC%d", index); - m_sixaxis_mode - ? devicelist()->create_device<sdl_sixaxis_joystick_device>(machine, tempname, tempname, *this) - : devicelist()->create_device<sdl_joystick_device>(machine, tempname, tempname, *this); - } - - return nullptr; + osd_printf_verbose("Game Controller: ... mapping [%s]\n", mapping); + SDL_free(mapping); + } + else + { + osd_printf_verbose("Game Controller: ... no mapping\n"); } - return m_sixaxis_mode - ? devicelist()->create_device<sdl_sixaxis_joystick_device>(machine, devmap->map[index].name.c_str(), devmap->map[index].name.c_str(), *this) - : devicelist()->create_device<sdl_joystick_device>(machine, devmap->map[index].name.c_str(), devmap->map[index].name.c_str(), *this); + // instantiate device + sdl_game_controller_device &devinfo = create_device<sdl_game_controller_device>( + DEVICE_CLASS_JOYSTICK, + name ? name : guid_str, + guid_str, + ctrl, + serial); + return &devinfo; } + + bool m_initialized_game_controller; }; -#else +} // anonymous namespace + +} // namespace osd + + +#else // defined(SDLMAME_SDL2) + +namespace osd { + +namespace { + MODULE_NOT_SUPPORTED(sdl_keyboard_module, OSD_KEYBOARDINPUT_PROVIDER, "sdl") MODULE_NOT_SUPPORTED(sdl_mouse_module, OSD_MOUSEINPUT_PROVIDER, "sdl") -MODULE_NOT_SUPPORTED(sdl_joystick_module, OSD_JOYSTICKINPUT_PROVIDER, "sdl") -#endif +MODULE_NOT_SUPPORTED(sdl_lightgun_module, OSD_LIGHTGUNINPUT_PROVIDER, "sdl") +MODULE_NOT_SUPPORTED(sdl_joystick_module, OSD_JOYSTICKINPUT_PROVIDER, "sdljoy") +MODULE_NOT_SUPPORTED(sdl_game_controller_module, OSD_JOYSTICKINPUT_PROVIDER, "sdlgame") + +} // anonymous namespace + +} // namespace osd + +#endif // defined(SDLMAME_SDL2) + -MODULE_DEFINITION(KEYBOARDINPUT_SDL, sdl_keyboard_module) -MODULE_DEFINITION(MOUSEINPUT_SDL, sdl_mouse_module) -MODULE_DEFINITION(JOYSTICKINPUT_SDL, sdl_joystick_module) +MODULE_DEFINITION(KEYBOARDINPUT_SDL, osd::sdl_keyboard_module) +MODULE_DEFINITION(MOUSEINPUT_SDL, osd::sdl_mouse_module) +MODULE_DEFINITION(LIGHTGUNINPUT_SDL, osd::sdl_lightgun_module) +MODULE_DEFINITION(JOYSTICKINPUT_SDLJOY, osd::sdl_joystick_module) +MODULE_DEFINITION(JOYSTICKINPUT_SDLGAME, osd::sdl_game_controller_module) |