// license:BSD-3-Clause // copyright-holders:Brad Hughes //============================================================ // // input_uwp.cpp - UWP input implementation // //============================================================ #include "input_module.h" #include "modules/osdmodule.h" #if defined(OSD_UWP) #include #include #include #undef interface // MAME headers #include "emu.h" #include "uiinput.h" #include "strconv.h" // MAMEOS headers #include "winmain.h" #include "input_common.h" #include "input_windows.h" #define UWP_BUTTON_COUNT 32 using namespace concurrency; using namespace Windows::UI::Core; using namespace Windows::Foundation; using namespace Windows::Foundation::Collections; using namespace Windows::Gaming::Input; //============================================================ // UWP Base device/module implementation //============================================================ //============================================================ // UwpInputDevice - base class for implementing an input // device in C++/CX. To be used with uwp_input_device //============================================================ private ref class UwpInputDevice { private: running_machine & m_machine; std::string m_name; std::string m_id; input_device_class m_devclass; input_module & m_module; input_device *m_inputdevice; internal: UwpInputDevice(running_machine &machine, const char *name, const char *id, input_device_class deviceclass, input_module &module) : m_machine(machine), m_name(name), m_id(id), m_devclass(deviceclass), m_module(module), m_inputdevice(nullptr) { } property running_machine & Machine { running_machine & get() { return m_machine; } } property const std::string & Name { const std::string & get() { return m_name; } } property const std::string & Id { const std::string & get() { return m_id; } } property input_device_class DeviceClass { input_device_class get() { return m_devclass; } } property input_module & Module { input_module & get() { return m_module; } } property input_device* InputDevice { input_device* get() { return m_inputdevice; } void set(input_device* value) { m_inputdevice = value; } } virtual void Poll() { } virtual void Reset() { } }; //============================================================ // uwp_input_device - a device that can be used to wrap a // C++/CX ref class for an input device implementation //============================================================ class uwp_input_device : public device_info { private: UwpInputDevice ^m_wrapped_device; public: uwp_input_device(UwpInputDevice ^device) : device_info(device->Machine, device->Name.c_str(), device->Id.c_str(), device->DeviceClass, device->Module), m_wrapped_device(device) { } void poll() override { m_wrapped_device->Poll(); } void reset() override { m_wrapped_device->Reset(); } }; //============================================================ // UwpInputModule - a base class that can be used to // implement an input module with a C++/CX class. // normally used with uwp_wininput_module //============================================================ class uwp_input_module; private ref class UwpInputModule { private: const std::string m_type; const std::string m_name; uwp_input_module *m_module; internal: UwpInputModule(const char *type, const char *name) : m_type(type), m_name(name), m_module(nullptr) { } property const std::string & Type { const std::string & get() { return m_type; } } property const std::string & Name { const std::string & get() { return m_name; } } property uwp_input_module * NativeModule { uwp_input_module * get() { return m_module; } void set(uwp_input_module * value) { m_module = value; } } virtual void input_init(running_machine &machine) { } }; //============================================================ // uwp_input_module - an input module that can be // used to create an input module with a C++/CX ref class //============================================================ class uwp_input_module : public wininput_module { private: UwpInputModule^ m_refmodule; public: uwp_input_module(UwpInputModule^ refmodule) : wininput_module(refmodule->Type.c_str(), refmodule->Name.c_str()), m_refmodule(refmodule) { refmodule->NativeModule = this; } void input_init(running_machine &machine) override { m_refmodule->input_init(machine); } }; //============================================================ // UWP Keyboard Implementation //============================================================ //============================================================ // UwpKeyboardDevice //============================================================ private ref class UwpKeyboardDevice : public UwpInputDevice { private: keyboard_state keyboard; Platform::Agile m_coreWindow; std::mutex m_state_lock; internal: UwpKeyboardDevice(Platform::Agile coreWindow, running_machine& machine, char *name, const char *id, input_module &module) : UwpInputDevice(machine, name, id, DEVICE_CLASS_KEYBOARD, module), keyboard({{0}}), m_coreWindow(coreWindow) { coreWindow->Dispatcher->AcceleratorKeyActivated += ref new TypedEventHandler(this, &UwpKeyboardDevice::OnAcceleratorKeyActivated); coreWindow->KeyDown += ref new TypedEventHandler(this, &UwpKeyboardDevice::OnKeyDown); coreWindow->KeyUp += ref new TypedEventHandler(this, &UwpKeyboardDevice::OnKeyUp); coreWindow->CharacterReceived += ref new TypedEventHandler(this, &UwpKeyboardDevice::OnCharacterReceived); } void Reset() override { std::lock_guard scope_lock(m_state_lock); memset(&keyboard, 0, sizeof(keyboard)); } void Configure() { keyboard_trans_table &table = keyboard_trans_table::instance(); // populate it indexed by the scancode for (int keynum = KEY_UNKNOWN + 1; keynum < MAX_KEYS; keynum++) { input_item_id itemid = table.map_di_scancode_to_itemid(keynum); const char *keyname = table.ui_label_for_mame_key(itemid); char temp[256]; if (keyname == nullptr) { snprintf(temp, ARRAY_LENGTH(temp), "Scan%03d", keynum); keyname = temp; } // add the item to the device this->InputDevice->add_item(keyname, itemid, generic_button_get_state, &keyboard.state[keynum]); } } void OnKeyDown(CoreWindow^ win, KeyEventArgs^ args) { std::lock_guard scope_lock(m_state_lock); CorePhysicalKeyStatus status = args->KeyStatus; int discancode = (status.ScanCode & 0x7f) | (status.IsExtendedKey ? 0x80 : 0x00); keyboard.state[discancode] = 0x80; } void OnKeyUp(CoreWindow^ win, KeyEventArgs^ args) { std::lock_guard scope_lock(m_state_lock); CorePhysicalKeyStatus status = args->KeyStatus; int discancode = (status.ScanCode & 0x7f) | (status.IsExtendedKey ? 0x80 : 0x00); keyboard.state[discancode] = 0; } void OnCharacterReceived(CoreWindow ^sender, CharacterReceivedEventArgs ^args) { this->Machine.ui_input().push_char_event(osd_common_t::s_window_list.front()->target(), args->KeyCode); } void OnAcceleratorKeyActivated(CoreDispatcher ^sender, AcceleratorKeyEventArgs ^args) { std::lock_guard scope_lock(m_state_lock); auto eventType = args->EventType; if (eventType == CoreAcceleratorKeyEventType::SystemKeyDown || eventType == CoreAcceleratorKeyEventType::SystemKeyUp) { CorePhysicalKeyStatus status = args->KeyStatus; int discancode = (status.ScanCode & 0x7f) | (status.IsExtendedKey ? 0x80 : 0x00); keyboard.state[discancode] = eventType == CoreAcceleratorKeyEventType::SystemKeyDown ? 0x80 : 0; } } }; //============================================================ // UwpKeyboardModule //============================================================ private ref class UwpKeyboardModule : public UwpInputModule { private: running_machine *m_machine; internal: UwpKeyboardModule() : UwpInputModule(OSD_KEYBOARDINPUT_PROVIDER, "uwp") { } void input_init(running_machine &machine) override { auto first_window = std::static_pointer_cast(osd_common_t::s_window_list.front()); auto coreWindow = first_window->platform_window(); // allocate the UWP implementation of the device object UwpKeyboardDevice ^refdevice = ref new UwpKeyboardDevice(coreWindow, machine, "UWP Keyboard 1", "UWP Keyboard 1", *this->NativeModule); // Allocate the wrapper and add it to the list auto created_devinfo = std::make_unique(refdevice); uwp_input_device *devinfo = NativeModule->devicelist()->add_device(machine, std::move(created_devinfo)); // Give the UWP implementation a handle to the input_device refdevice->InputDevice = devinfo->device(); // Configure the device refdevice->Configure(); } }; //============================================================ // uwp_keyboard_module //============================================================ class uwp_keyboard_module : public uwp_input_module { public: uwp_keyboard_module() : uwp_input_module(ref new UwpKeyboardModule()) { } }; // default axis names static const char *const uwp_axis_name[] = { "LSX", "LSY", "RSX", "RSY" }; static const input_item_id uwp_axis_ids[] = { ITEM_ID_XAXIS, ITEM_ID_YAXIS, ITEM_ID_RXAXIS, ITEM_ID_RYAXIS }; struct gamepad_state { BYTE buttons[UWP_BUTTON_COUNT]; LONG left_trigger; LONG right_trigger; LONG left_thumb_x; LONG left_thumb_y; LONG right_thumb_x; LONG right_thumb_y; }; // Maps different UWP GameControllerButtonLabels to a halfway-sane input_item_id in many cases static input_item_id buttonlabel_to_itemid[] = { input_item_id::ITEM_ID_INVALID, // GameControllerButtonLabel::None input_item_id::ITEM_ID_SELECT, // GameControllerButtonLabel::XboxBack input_item_id::ITEM_ID_START, // GameControllerButtonLabel::XboxStart input_item_id::ITEM_ID_START, // GameControllerButtonLabel::XboxMenu input_item_id::ITEM_ID_SELECT, // GameControllerButtonLabel::XboxView input_item_id::ITEM_ID_HAT1UP, // GameControllerButtonLabel::XboxUp input_item_id::ITEM_ID_HAT1DOWN, // GameControllerButtonLabel::XboxDown input_item_id::ITEM_ID_HAT1LEFT, // GameControllerButtonLabel::XboxLeft input_item_id::ITEM_ID_HAT1RIGHT, // GameControllerButtonLabel::XboxRight input_item_id::ITEM_ID_BUTTON1, // GameControllerButtonLabel::XboxA input_item_id::ITEM_ID_BUTTON2, // GameControllerButtonLabel::XboxB input_item_id::ITEM_ID_BUTTON3, // GameControllerButtonLabel::XboxX input_item_id::ITEM_ID_BUTTON4, // GameControllerButtonLabel::XboxY input_item_id::ITEM_ID_BUTTON5, // GameControllerButtonLabel::XboxLeftBumper input_item_id::ITEM_ID_ZAXIS, // GameControllerButtonLabel::XboxLeftTrigger input_item_id::ITEM_ID_BUTTON7, // GameControllerButtonLabel::XboxLeftStickButton input_item_id::ITEM_ID_BUTTON6, // GameControllerButtonLabel::XboxRightBumper input_item_id::ITEM_ID_RZAXIS, // GameControllerButtonLabel::XboxRightTrigger input_item_id::ITEM_ID_BUTTON8, // GameControllerButtonLabel::XboxRightStickButton input_item_id::ITEM_ID_BUTTON9, // GameControllerButtonLabel::XboxPaddle1 input_item_id::ITEM_ID_BUTTON10, // GameControllerButtonLabel::XboxPaddle2 input_item_id::ITEM_ID_BUTTON11, // GameControllerButtonLabel::XboxPaddle3 input_item_id::ITEM_ID_BUTTON12, // GameControllerButtonLabel::XboxPaddle4 input_item_id::ITEM_ID_INVALID, // GameControllerButtonLabel_Mode input_item_id::ITEM_ID_SELECT, // GameControllerButtonLabel_Select input_item_id::ITEM_ID_START, // GameControllerButtonLabel_Menu input_item_id::ITEM_ID_SELECT, // GameControllerButtonLabel_View input_item_id::ITEM_ID_SELECT, // GameControllerButtonLabel_Back input_item_id::ITEM_ID_START, // GameControllerButtonLabel_Start input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Options input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Share input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Up input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Down input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Left input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Right input_item_id::ITEM_ID_BUTTON1, // GameControllerButtonLabel_LetterA input_item_id::ITEM_ID_BUTTON2, // GameControllerButtonLabel_LetterB input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_LetterC input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_LetterL input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_LetterR input_item_id::ITEM_ID_BUTTON3, // GameControllerButtonLabel_LetterX input_item_id::ITEM_ID_BUTTON4, // GameControllerButtonLabel_LetterY input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_LetterZ input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Cross input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Circle input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Square input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Triangle input_item_id::ITEM_ID_BUTTON5, // GameControllerButtonLabel_LeftBumper input_item_id::ITEM_ID_ZAXIS, // GameControllerButtonLabel_LeftTrigger input_item_id::ITEM_ID_BUTTON7, // GameControllerButtonLabel_LeftStickButton input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Left1 input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Left2 input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Left3 input_item_id::ITEM_ID_BUTTON6, // GameControllerButtonLabel_RightBumper input_item_id::ITEM_ID_RZAXIS, // GameControllerButtonLabel_RightTrigger input_item_id::ITEM_ID_BUTTON8, // GameControllerButtonLabel_RightStickButton input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Right1 input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Right2 input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Right3 input_item_id::ITEM_ID_BUTTON9, // GameControllerButtonLabel_Paddle1 input_item_id::ITEM_ID_BUTTON10, // GameControllerButtonLabel_Paddle2 input_item_id::ITEM_ID_BUTTON11, // GameControllerButtonLabel_Paddle3 input_item_id::ITEM_ID_BUTTON12, // GameControllerButtonLabel_Paddle4 input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Plus input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Minus input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_DownLeftArrow input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_DialLeft input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_DialRight input_item_id::ITEM_ID_OTHER_SWITCH, // GameControllerButtonLabel_Suspension }; //============================================================ // UwpJoystickDevice //============================================================ private ref class UwpJoystickDevice : public UwpInputDevice { private: Gamepad ^m_pad; bool m_configured; gamepad_state state; internal: UwpJoystickDevice(Gamepad^ pad, running_machine &machine, const char *name, const char *id, input_module &module) : UwpInputDevice(machine, name, id, DEVICE_CLASS_JOYSTICK, module), m_pad(pad), m_configured(false), state({0}) {} void Poll() override { // If the device hasn't been configured, don't poll if (!m_configured) return; GamepadReading reading = m_pad->GetCurrentReading(); for (int butnum = 0; butnum < UWP_BUTTON_COUNT; butnum++) { GamepadButtons currentButton = GamepadButtons(1 << butnum); state.buttons[butnum] = (reading.Buttons & currentButton) != GamepadButtons::None ? 0xFF : 0; } // Now grab the axis values // Each of the thumbstick axis members is a signed value between -32768 and 32767 describing the position of the thumbstick // However, the Y axis values are inverted from what MAME expects, so negate the value state.left_thumb_x = normalize_absolute_axis(reading.LeftThumbstickX, -1, 1); state.left_thumb_y = -normalize_absolute_axis(reading.LeftThumbstickY, -1, 1); state.right_thumb_x = normalize_absolute_axis(reading.RightThumbstickX, -1, 1); state.right_thumb_y = -normalize_absolute_axis(reading.RightThumbstickY, -1, 1); // Get the trigger values state.left_trigger = normalize_absolute_axis(reading.LeftTrigger, 0.0, 1.0); state.right_trigger = normalize_absolute_axis(reading.RightTrigger, 0.0, 1.0); // For the UI, triggering UI_CONFIGURE is odd. It requires a EVENT_CHAR first static constexpr int menuhotkey = (int)(GamepadButtons::View | GamepadButtons::X); if (((int)reading.Buttons & menuhotkey) == menuhotkey) { ui_event uiev; memset(&uiev, 0, sizeof(uiev)); uiev.event_type = ui_event::IME_CHAR; this->Machine.ui_input().push_event(uiev); } } void Reset() override { memset(&state, 0, sizeof(state)); } void Configure() { // If the device has already been configured, don't do it again if (m_configured) return; GamepadReading r = m_pad->GetCurrentReading(); // Add the axes for (int axisnum = 0; axisnum < 4; axisnum++) { this->InputDevice->add_item( uwp_axis_name[axisnum], uwp_axis_ids[axisnum], generic_axis_get_state, &state.left_thumb_x + axisnum); } // populate the buttons for (int butnum = 0; butnum < UWP_BUTTON_COUNT; butnum++) { GamepadButtons button = GamepadButtons(1 << butnum); auto label = m_pad->GetButtonLabel(button); if (label != GameControllerButtonLabel::None) { std::string desc = osd::text::from_wstring(label.ToString()->Data()); this->InputDevice->add_item( desc.c_str(), buttonlabel_to_itemid[static_cast(label)], generic_button_get_state, &state.buttons[butnum]); } } this->InputDevice->add_item( "Left Trigger", ITEM_ID_ZAXIS, generic_axis_get_state, &state.left_trigger); this->InputDevice->add_item( "Right Trigger", ITEM_ID_RZAXIS, generic_axis_get_state, &state.right_trigger); m_configured = true; } }; //============================================================ // UwpJoystickModule //============================================================ private ref class UwpJoystickModule : public UwpInputModule { private: boolean m_joysticks_discovered; internal: UwpJoystickModule() : UwpInputModule(OSD_JOYSTICKINPUT_PROVIDER, "uwp"), m_joysticks_discovered(false) { } void input_init(running_machine &machine) override { PerformGamepadDiscovery(); auto pads = Gamepad::Gamepads; int padindex = 0; std::for_each(begin(pads), end(pads), [&](Gamepad^ pad) { uwp_input_device *devinfo; std::ostringstream namestream; namestream << "UWP Gamepad " << (padindex + 1); auto name = namestream.str(); // allocate the UWP implementation of the device object UwpJoystickDevice ^refdevice = ref new UwpJoystickDevice(pad, machine, name.c_str(), name.c_str(), *this->NativeModule); // Allocate the wrapper and add it to the list auto created_devinfo = std::make_unique(refdevice); devinfo = NativeModule->devicelist()->add_device(machine, std::move(created_devinfo)); // Give the UWP implementation a handle to the input_device refdevice->InputDevice = devinfo->device(); // Configure the device refdevice->Configure(); padindex++; }); } private: void PerformGamepadDiscovery() { Gamepad::GamepadAdded += ref new EventHandler(this, &UwpJoystickModule::OnGamepadAdded); auto start = std::chrono::system_clock::now(); // We need to pause a bit and pump events so gamepads get discovered while (std::chrono::system_clock::now() - start < std::chrono::milliseconds(1000)) CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent); m_joysticks_discovered = true; } void OnGamepadAdded(Platform::Object ^sender, Gamepad ^pad) { if (m_joysticks_discovered) { osd_printf_error("Input: UWP Compatible %s gamepad plugged in AFTER discovery complete!\n", pad->IsWireless ? "Wireless" : "Wired"); } else { osd_printf_verbose("Input: UWP Compatible %s gamepad discovered.\n", pad->IsWireless ? "Wireless" : "Wired"); } } }; //============================================================ // uwp_joystick_module //============================================================ class uwp_joystick_module : public uwp_input_module { public: uwp_joystick_module() : uwp_input_module(ref new UwpJoystickModule()) { } }; #else MODULE_NOT_SUPPORTED(uwp_keyboard_module, OSD_KEYBOARDINPUT_PROVIDER, "uwp") MODULE_NOT_SUPPORTED(uwp_joystick_module, OSD_JOYSTICKINPUT_PROVIDER, "uwp") #endif MODULE_DEFINITION(KEYBOARDINPUT_UWP, uwp_keyboard_module) MODULE_DEFINITION(JOYSTICKINPUT_UWP, uwp_joystick_module)