diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index b4437747e8..0a11e95947 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -427,6 +427,8 @@ add_library(core IOS/USB/Common.h IOS/USB/Emulated/Infinity.cpp IOS/USB/Emulated/Infinity.h + IOS/USB/Emulated/Keyboard.cpp + IOS/USB/Emulated/Keyboard.h IOS/USB/Emulated/Skylanders/Skylander.cpp IOS/USB/Emulated/Skylanders/Skylander.h IOS/USB/Emulated/Skylanders/SkylanderCrypto.cpp diff --git a/Source/Core/Core/IOS/USB/Common.h b/Source/Core/Core/IOS/USB/Common.h index 1c5fd07670..99684e474b 100644 --- a/Source/Core/Core/IOS/USB/Common.h +++ b/Source/Core/Core/IOS/USB/Common.h @@ -24,11 +24,28 @@ enum StandardDeviceRequestCodes REQUEST_SET_INTERFACE = 11, }; +// See USB HID specification under "Class-Specific Requests": +// - https://www.usb.org/sites/default/files/documents/hid1_11.pdf +namespace HIDRequestCodes +{ +enum +{ + GET_REPORT = 1, + GET_IDLE = 2, + GET_PROTOCOL = 3, + // 0x04~0x08 - Reserved + SET_REPORT = 9, + SET_IDLE = 10, + SET_PROTOCOL = 11, +}; +} + enum ControlRequestTypes { DIR_HOST2DEVICE = 0, DIR_DEVICE2HOST = 1, TYPE_STANDARD = 0, + TYPE_CLASS = 1, TYPE_VENDOR = 2, REC_DEVICE = 0, REC_INTERFACE = 1, diff --git a/Source/Core/Core/IOS/USB/Emulated/Keyboard.cpp b/Source/Core/Core/IOS/USB/Emulated/Keyboard.cpp new file mode 100644 index 0000000000..3a36cc43b3 --- /dev/null +++ b/Source/Core/Core/IOS/USB/Emulated/Keyboard.cpp @@ -0,0 +1,583 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/IOS/USB/Emulated/Keyboard.h" + +#include +#include + +#include "Common/Assert.h" +#include "Common/Projection.h" +#include "Core/Config/MainSettings.h" +#include "Core/HW/Memmap.h" +#include "InputCommon/ControlReference/ControlReference.h" + +#ifdef _WIN32 +#include +#endif + +namespace IOS::HLE::USB +{ +namespace +{ +#ifdef _WIN32 +// Map Windows virtual key to HID key code +// +// NB: +// - It seems Windows API is able to convert PS/2 scan code <-> virtual key +// - I couldn't find something equivalent for HID usage id +// - The following posts might be worth mentioning: +// https://stackoverflow.com/a/69600455 +// https://stackoverflow.com/a/72869289 +using VkHidPair = std::pair; +std::vector GetVkHidMap() +{ + // These constants are added for clarity + static constexpr int VK_UNKNOWN = 0; + static constexpr u16 HID_UNKNOWN = 0; + static constexpr u16 HID_MODIFIER = 0; + static constexpr auto DEPRECATED = [](auto) -> u8 { return 0; }; + + // References: + // - Keyboard scan codes: + // https://learn.microsoft.com/windows/win32/inputdev/about-keyboard-input + // - Windows virtual key codes: + // https://learn.microsoft.com/windows/win32/inputdev/virtual-key-codes + // https://learn.microsoft.com/globalization/windows-keyboard-layouts + // - HID Usage Tables, Keyboard/Keypad page (0x07): + // https://usb.org/sites/default/files/hut1_21.pdf + std::vector map; + + // Windows virtual keys 0x01 to 0x07 + for (const int& virtual_key : + {VK_LBUTTON, VK_RBUTTON, VK_CANCEL, VK_MBUTTON, VK_XBUTTON1, VK_XBUTTON2, 0x07}) + { + map.emplace_back(virtual_key, HID_UNKNOWN); + } + + // Windows virtual keys 0x08 and 0x09 + map.emplace_back(VK_BACK, 0x2A); // Backspace key + map.emplace_back(VK_TAB, 0x2B); + + // Windows (reserved) virtual keys 0x0A and 0x0B + for (const int& virtual_key : {0x0A, 0x0B}) + map.emplace_back(virtual_key, HID_UNKNOWN); + + // Windows virtual keys 0x0C and 0x0D + map.emplace_back(VK_CLEAR, 0x9C); + map.emplace_back(VK_RETURN, 0x28); // Enter key + + // Windows (unassigned) virtual keys 0x0E and 0x0F + for (const int& virtual_key : {0x0E, 0x0F}) + map.emplace_back(virtual_key, HID_UNKNOWN); + + // Windows virtual keys 0x10 and 0x14 + map.emplace_back(VK_SHIFT, HID_MODIFIER); + map.emplace_back(VK_CONTROL, HID_MODIFIER); + map.emplace_back(VK_MENU, HID_MODIFIER); // Alt key + map.emplace_back(VK_PAUSE, 0x48); + map.emplace_back(VK_CAPITAL, 0x39); + + // Windows virtual keys 0x15 to 0x1A + // TODO: Handle regional keys (kana, kanji, ...) + for (const int& virtual_key : {0x15, VK_IME_ON, VK_JUNJA, VK_FINAL, 0x19, VK_IME_OFF}) + map.emplace_back(virtual_key, HID_UNKNOWN); + + // Windows virtual key 0x1B + map.emplace_back(VK_ESCAPE, 0x29); + + // Windows virtual keys 0x1C to 0x1F + // TODO: Handle regional keys (kana, kanji, ...) + for (const int& virtual_key : {VK_CONVERT, VK_NONCONVERT, VK_ACCEPT, VK_MODECHANGE}) + map.emplace_back(virtual_key, HID_UNKNOWN); + + // Windows virtual keys 0x20 to 0x28 + map.emplace_back(VK_SPACE, 0x2C); + map.emplace_back(VK_PRIOR, 0x4B); // Page up key + map.emplace_back(VK_NEXT, 0x4E); // Page down key + map.emplace_back(VK_END, 0x4D); + map.emplace_back(VK_HOME, 0x4A); + map.emplace_back(VK_LEFT, 0x50); + map.emplace_back(VK_UP, 0x52); + map.emplace_back(VK_RIGHT, 0x4F); + map.emplace_back(VK_DOWN, 0x51); + + // Windows virtual keys 0x29 to 0x2B + // NB: Modern keyboards don't have these keys so let's silent them + map.emplace_back(VK_SELECT, DEPRECATED(0x77)); + map.emplace_back(VK_PRINT, DEPRECATED(HID_UNKNOWN)); + map.emplace_back(VK_EXECUTE, DEPRECATED(0x74)); + + // Windows virtual keys 0x2C to 0x2F + map.emplace_back(VK_SNAPSHOT, 0x46); + map.emplace_back(VK_INSERT, 0x49); + map.emplace_back(VK_DELETE, 0x4C); + map.emplace_back(VK_HELP, 0x75); + + // Windows virtual keys `0` (0x30) to `9` (0x39) + map.emplace_back('0', 0x27); + map.emplace_back('1', 0x1E); + map.emplace_back('2', 0x1F); + map.emplace_back('3', 0x20); + map.emplace_back('4', 0x21); + map.emplace_back('5', 0x22); + map.emplace_back('6', 0x23); + map.emplace_back('7', 0x24); + map.emplace_back('8', 0x25); + map.emplace_back('9', 0x26); + + // Windows (undefined) virtual keys 0x3A to 0x40 + for (int virtual_key = 0x3A; virtual_key <= 0x40; ++virtual_key) + map.emplace_back(virtual_key, HID_UNKNOWN); + + // Windows virtual keys `A` (0x41) to `Z` (0x5A) + for (int i = 0; i < 26; ++i) + map.emplace_back('A' + i, 0x04 + i); + + // Windows virtual keys 0x5B to 0x5F + map.emplace_back(VK_LWIN, HID_MODIFIER); + map.emplace_back(VK_RWIN, HID_MODIFIER); + map.emplace_back(VK_APPS, 0x65); + map.emplace_back(0x5E, HID_UNKNOWN); // Reserved virtual key + map.emplace_back(VK_SLEEP, HID_UNKNOWN); + + // Windows virtual keys 0x60 to 0x69 + map.emplace_back(VK_NUMPAD0, 0x62); + map.emplace_back(VK_NUMPAD1, 0x59); + map.emplace_back(VK_NUMPAD2, 0x5A); + map.emplace_back(VK_NUMPAD3, 0x5B); + map.emplace_back(VK_NUMPAD4, 0x5C); + map.emplace_back(VK_NUMPAD5, 0x5D); + map.emplace_back(VK_NUMPAD6, 0x5E); + map.emplace_back(VK_NUMPAD7, 0x5F); + map.emplace_back(VK_NUMPAD8, 0x60); + map.emplace_back(VK_NUMPAD9, 0x61); + + // Windows virtual keys 0x6A to 0x6F + map.emplace_back(VK_MULTIPLY, 0x55); + map.emplace_back(VK_ADD, 0x57); + map.emplace_back(VK_SEPARATOR, 0x85); + map.emplace_back(VK_SUBTRACT, 0x56); + map.emplace_back(VK_DECIMAL, 0x63); + map.emplace_back(VK_DIVIDE, 0x54); + + // Windows virtual keys 0x70 to 0x7B + for (int i = 0; i < 12; ++i) + map.emplace_back(VK_F1 + i, 0x3A + i); + + // Windows virtual keys 0x7C to 0x87 + // NB: Modern keyboards don't have these keys so let's silent them + for (int i = 0; i < 12; ++i) + map.emplace_back(VK_F13 + i, DEPRECATED(0x68 + i)); + + // Windows (reserved) virtual keys 0x88 to 0x8F + for (int virtual_key = 0x88; virtual_key <= 0x8F; ++virtual_key) + map.emplace_back(virtual_key, HID_UNKNOWN); + + // Windows virtual keys 0x90 and 0x91 + map.emplace_back(VK_NUMLOCK, 0x53); + map.emplace_back(VK_SCROLL, 0x47); + + // Windows (OEM specific) virtual keys 0x92 to 0x96 + for (int virtual_key = 0x92; virtual_key <= 0x96; ++virtual_key) + map.emplace_back(virtual_key, HID_UNKNOWN); + + // Windows (unassigned) virtual keys 0x97 to 0x9F + for (int virtual_key = 0x97; virtual_key <= 0x9F; ++virtual_key) + map.emplace_back(virtual_key, HID_UNKNOWN); + + // Windows virtual keys 0xA0 to 0xA5 + for (const int& virtual_key : + {VK_LSHIFT, VK_RSHIFT, VK_LCONTROL, VK_RCONTROL, VK_LMENU, VK_RMENU}) + { + map.emplace_back(virtual_key, HID_MODIFIER); + } + + // Windows virtual keys 0xA6 to 0xAC + for (const int& virtual_key : + {VK_BROWSER_BACK, VK_BROWSER_FORWARD, VK_BROWSER_REFRESH, VK_BROWSER_STOP, VK_BROWSER_SEARCH, + VK_BROWSER_FAVORITES, VK_BROWSER_HOME}) + { + map.emplace_back(virtual_key, HID_UNKNOWN); + } + + // Windows virtual keys 0xAD to 0xAF + for (const int& virtual_key : {VK_VOLUME_MUTE, VK_VOLUME_DOWN, VK_VOLUME_UP}) + map.emplace_back(virtual_key, HID_UNKNOWN); + + // Windows virtual keys 0xB0 to 0xB3 + for (const int& virtual_key : + {VK_MEDIA_NEXT_TRACK, VK_MEDIA_PREV_TRACK, VK_MEDIA_STOP, VK_MEDIA_PLAY_PAUSE}) + { + map.emplace_back(virtual_key, HID_UNKNOWN); + } + + // Windows virtual keys 0xB4 to 0xB7 + for (const int& virtual_key : + {VK_LAUNCH_MAIL, VK_LAUNCH_MEDIA_SELECT, VK_LAUNCH_APP1, VK_LAUNCH_APP2}) + { + map.emplace_back(virtual_key, HID_UNKNOWN); + } + + // Windows (reserved) virtual keys 0xB8 and 0xB9 + for (const int& virtual_key : {0xB8, 0xB9}) + map.emplace_back(virtual_key, HID_UNKNOWN); + + // NB: The ones below are used for miscellaneous characters and can vary by keyboard. + // TODO: Implement regional variants (e.g. Europe and Japan) + + // Windows virtual keys 0xBA to 0xC0 + map.emplace_back(VK_OEM_1, 0x33); // US: ;: key + map.emplace_back(VK_OEM_PLUS, 0x2E); + map.emplace_back(VK_OEM_COMMA, 0x36); + map.emplace_back(VK_OEM_MINUS, 0x2D); + map.emplace_back(VK_OEM_PERIOD, 0x37); + map.emplace_back(VK_OEM_2, 0x38); // US: /? key + map.emplace_back(VK_OEM_3, 0x35); // US: `~ key + + // Windows (reserved) virtual keys 0xC1 to 0xDA + for (int virtual_key = 0xC1; virtual_key <= 0xDA; ++virtual_key) + map.emplace_back(virtual_key, HID_UNKNOWN); + + // Windows virtual keys 0xDB to 0xE2 + map.emplace_back(VK_OEM_4, 0x2F); // US: [{ key + map.emplace_back(VK_OEM_5, 0x32); // US: \| key + map.emplace_back(VK_OEM_6, 0x30); // US: ]} key + map.emplace_back(VK_OEM_7, 0x34); // US: '" key + map.emplace_back(VK_OEM_8, HID_UNKNOWN); + map.emplace_back(0xE0, HID_UNKNOWN); // Reserved virtual key + map.emplace_back(0xE1, HID_UNKNOWN); // OEM specific virtual key + map.emplace_back(VK_OEM_102, 0x31); // US: \| key + + // Nothing interesting past this point. + ASSERT_MSG(IOS_USB, map[VK_OEM_102].first != VK_OEM_102, + "Failed to generate emulated keyboard layout"); + +#ifdef HLE_KEYBOARD_DEBUG + // Might be worth adding for debugging purpose + for (u16 hid_usage_id = 1; hid_usage_id < 0xE8; ++hid_usage_id) + { + if (std::ranges::find(map, hid_usage_id, Common::Projection::Second{}) == map.end()) + { + map.emplace_back(VK_UNKNOWN, hid_usage_id); + } + } +#endif + + return map; +} + +std::vector CleanVkHidMap(const std::vector& map) +{ + // Ignore unsupported/ignored keys + std::vector cleaned_map; + for (const auto& [virtual_key, hid_usage_id] : map) + { + if (virtual_key != 0 && hid_usage_id != 0) + { + cleaned_map.emplace_back(virtual_key, hid_usage_id); + } +#ifdef HLE_KEYBOARD_DEBUG + else if (virtual_key == 0) + { + // Missing virtual key for HID usage ID + static std::vector s_missing_vk_for_hid; + s_missing_vk_for_hid.emplace_back(hid_usage_id); + } + else if (hid_usage_id == 0) + { + // Missing HID usage ID for virtual key + static std::vector s_missing_hid_for_vk; + s_missing_hid_for_vk.emplace_back(virtual_key); + } +#endif + } + return cleaned_map; +} + +static const std::vector VK_HID_MAP = CleanVkHidMap(GetVkHidMap()); +#endif +} // namespace + +Keyboard::Keyboard(IOS::HLE::EmulationKernel& ios) : m_ios(ios) +{ + m_id = u64(m_vid) << 32 | u64(m_pid) << 16 | u64(9) << 8 | u64(1); +} + +Keyboard::~Keyboard() = default; + +DeviceDescriptor Keyboard::GetDeviceDescriptor() const +{ + return m_device_descriptor; +} + +std::vector Keyboard::GetConfigurations() const +{ + return m_config_descriptor; +} + +std::vector Keyboard::GetInterfaces(u8 config) const +{ + return m_interface_descriptor; +} + +std::vector Keyboard::GetEndpoints(u8 config, u8 interface, u8 alt) const +{ + return m_endpoint_descriptor; +} + +bool Keyboard::Attach() +{ + if (m_device_attached) + return true; + + DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x}] Opening emulated keyboard", m_vid, m_pid); + m_device_attached = true; + return true; +} + +bool Keyboard::AttachAndChangeInterface(const u8 interface) +{ + if (!Attach()) + return false; + + if (interface != m_active_interface) + return ChangeInterface(interface) == 0; + + return true; +} + +int Keyboard::CancelTransfer(const u8 endpoint) +{ + INFO_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Cancelling transfers (endpoint {:#x})", m_vid, m_pid, + m_active_interface, endpoint); + + return IPC_SUCCESS; +} + +int Keyboard::ChangeInterface(const u8 interface) +{ + DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Changing interface to {}", m_vid, m_pid, + m_active_interface, interface); + m_active_interface = interface; + return 0; +} + +int Keyboard::GetNumberOfAltSettings(u8 interface) +{ + return 0; +} + +int Keyboard::SetAltSetting(u8 alt_setting) +{ + return 0; +} + +int Keyboard::SubmitTransfer(std::unique_ptr cmd) +{ + DEBUG_LOG_FMT(IOS_USB, + "[{:04x}:{:04x} {}] Control: bRequestType={:02x} bRequest={:02x} wValue={:04x}" + " wIndex={:04x} wLength={:04x}", + m_vid, m_pid, m_active_interface, cmd->request_type, cmd->request, cmd->value, + cmd->index, cmd->length); + + switch (cmd->request_type << 8 | cmd->request) + { + case USBHDR(DIR_DEVICE2HOST, TYPE_STANDARD, REC_INTERFACE, REQUEST_GET_INTERFACE): + { + const u8 data{1}; + cmd->FillBuffer(&data, sizeof(data)); + cmd->ScheduleTransferCompletion(1, 100); + break; + } + case USBHDR(DIR_HOST2DEVICE, TYPE_STANDARD, REC_INTERFACE, REQUEST_SET_INTERFACE): + { + INFO_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] REQUEST_SET_INTERFACE index={:04x} value={:04x}", + m_vid, m_pid, m_active_interface, cmd->index, cmd->value); + if (static_cast(cmd->index) != m_active_interface) + { + const int ret = ChangeInterface(static_cast(cmd->index)); + if (ret < 0) + { + ERROR_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Failed to change interface to {}", m_vid, m_pid, + m_active_interface, cmd->index); + return ret; + } + } + const int ret = SetAltSetting(static_cast(cmd->value)); + if (ret == 0) + m_ios.EnqueueIPCReply(cmd->ios_request, cmd->length); + return ret; + } + case USBHDR(DIR_HOST2DEVICE, TYPE_CLASS, REC_INTERFACE, HIDRequestCodes::SET_REPORT): + { + // According to the HID specification: + // - A device might choose to ignore input Set_Report requests as meaningless. + // - Alternatively these reports could be used to reset the origin of a control + // (that is, current position should report zero). + // - The effect of sent reports will also depend on whether the recipient controls + // are absolute or relative. + const u8 report_type = cmd->value >> 8; + const u8 report_id = cmd->value & 0xFF; + auto& system = m_ios.GetSystem(); + auto& memory = system.GetMemory(); + + // The data seems to report LED status for keys such as: + // - NUM LOCK, CAPS LOCK + u8* data = memory.GetPointerForRange(cmd->data_address, cmd->length); + INFO_LOG_FMT(IOS_USB, "SET_REPORT ignored (report_type={:02x}, report_id={:02x}, index={})\n{}", + report_type, report_id, cmd->index, HexDump(data, cmd->length)); + m_ios.EnqueueIPCReply(cmd->ios_request, IPC_SUCCESS); + break; + } + case USBHDR(DIR_HOST2DEVICE, TYPE_CLASS, REC_INTERFACE, HIDRequestCodes::SET_IDLE): + { + WARN_LOG_FMT(IOS_USB, "SET_IDLE not implemented (value={:04x}, index={})", cmd->value, + cmd->index); + // TODO: Handle idle duration and implement NAK + m_ios.EnqueueIPCReply(cmd->ios_request, IPC_SUCCESS); + break; + } + case USBHDR(DIR_HOST2DEVICE, TYPE_CLASS, REC_INTERFACE, HIDRequestCodes::SET_PROTOCOL): + { + INFO_LOG_FMT(IOS_USB, "SET_PROTOCOL: value={}, index={}", cmd->value, cmd->index); + const HIDProtocol protocol = static_cast(cmd->value); + switch (protocol) + { + case HIDProtocol::Boot: + case HIDProtocol::Report: + m_current_protocol = protocol; + break; + default: + WARN_LOG_FMT(IOS_USB, "SET_PROTOCOL: Unknown protocol {} for interface {}", cmd->value, + cmd->index); + } + m_ios.EnqueueIPCReply(cmd->ios_request, IPC_SUCCESS); + break; + } + default: + WARN_LOG_FMT(IOS_USB, "Unknown command, req={:02x}, type={:02x}", cmd->request, + cmd->request_type); + m_ios.EnqueueIPCReply(cmd->ios_request, IPC_SUCCESS); + } + + return IPC_SUCCESS; +} + +int Keyboard::SubmitTransfer(std::unique_ptr cmd) +{ + DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Bulk: length={:04x} endpoint={:02x}", m_vid, m_pid, + m_active_interface, cmd->length, cmd->endpoint); + m_ios.EnqueueIPCReply(cmd->ios_request, IPC_SUCCESS); + return IPC_SUCCESS; +} + +int Keyboard::SubmitTransfer(std::unique_ptr cmd) +{ + static auto start_time = std::chrono::steady_clock::now(); + const auto current_time = std::chrono::steady_clock::now(); + const bool should_poll = (current_time - start_time) >= std::chrono::milliseconds(1); + + const HIDKeyboardReport report = should_poll ? PollInputs() : m_last_report; + cmd->FillBuffer(reinterpret_cast(&report), sizeof(report)); + m_ios.EnqueueIPCReply(cmd->ios_request, IPC_SUCCESS); + + if (should_poll) + { + m_last_report = std::move(report); + start_time = std::chrono::steady_clock::now(); + } + return IPC_SUCCESS; +} + +int Keyboard::SubmitTransfer(std::unique_ptr cmd) +{ + DEBUG_LOG_FMT(IOS_USB, + "[{:04x}:{:04x} {}] Isochronous: length={:04x} endpoint={:02x} num_packets={:02x}", + m_vid, m_pid, m_active_interface, cmd->length, cmd->endpoint, cmd->num_packets); + cmd->ScheduleTransferCompletion(IPC_SUCCESS, 2000); + return IPC_SUCCESS; +} + +void Keyboard::ScheduleTransfer(std::unique_ptr command, std::span data, + u64 expected_time_us) +{ + command->FillBuffer(data.data(), data.size()); + command->ScheduleTransferCompletion(static_cast(data.size()), expected_time_us); +} + +bool Keyboard::IsKeyPressed(int key) const +{ +#ifdef _WIN32 + return (GetAsyncKeyState(key) & 0x8000) != 0; +#else + // TODO: do it for non-Windows platforms + return false; +#endif +} + +HIDPressedKeys Keyboard::PollHIDPressedKeys() +{ + HIDPressedKeys pressed_keys{}; + std::size_t pressed_keys_count = 0; + +#ifdef _WIN32 + for (const auto& [virtual_key, hid_usage_id] : VK_HID_MAP) + { + if (!IsKeyPressed(virtual_key)) + continue; + + pressed_keys[pressed_keys_count++] = hid_usage_id; + if (pressed_keys_count == pressed_keys.size()) + break; + } +#else + // TODO: Implementation for non-Windows platforms +#endif + return pressed_keys; +} + +u8 Keyboard::PollHIDModifiers() +{ + u8 modifiers = 0; +#ifdef _WIN32 + // References: + // https://learn.microsoft.com/windows/win32/inputdev/virtual-key-codes + // https://www.usb.org/document-library/device-class-definition-hid-111 + static const std::vector MODIFIERS_MAP{ + {VK_LCONTROL, 0x01}, // HID modifier: Bit 0 - LEFT CTRL + {VK_LSHIFT, 0x02}, // HID modifier: Bit 1 - LEFT SHIFT + {VK_LMENU, 0x04}, // HID modifier: Bit 2 - LEFT ALT + {VK_LWIN, 0x08}, // HID modifier: Bit 3 - LEFT GUI + {VK_RCONTROL, 0x10}, // HID modifier: Bit 4 - RIGHT CTRL + {VK_RSHIFT, 0x20}, // HID modifier: Bit 5 - RIGHT SHIFT + {VK_RMENU, 0x40}, // HID modifier: Bit 6 - RIGHT ALT + {VK_RWIN, 0x80} // HID modifier: Bit 7 - RIGHT GUI + }; + for (const auto& [virtual_key, hid_modifier] : MODIFIERS_MAP) + { + if (IsKeyPressed(virtual_key)) + modifiers |= hid_modifier; + } +#else + // TODO: Implementation for non-Windows platforms +#endif + return modifiers; +} + +// WARNING: Current implementation kills performance +HIDKeyboardReport Keyboard::PollInputs() +{ + if (!Config::Get(Config::MAIN_WII_KEYBOARD) || + // Check if input should be captured + !ControlReference::GetInputGate()) + { + return {}; + } + + const HIDKeyboardReport result{ + .modifiers = PollHIDModifiers(), .oem = 0, .pressed_keys = PollHIDPressedKeys()}; + + return result; +} +} // namespace IOS::HLE::USB diff --git a/Source/Core/Core/IOS/USB/Emulated/Keyboard.h b/Source/Core/Core/IOS/USB/Emulated/Keyboard.h new file mode 100644 index 0000000000..89f0424d9a --- /dev/null +++ b/Source/Core/Core/IOS/USB/Emulated/Keyboard.h @@ -0,0 +1,89 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "Core/IOS/USB/Common.h" +#include "Core/System.h" + +namespace IOS::HLE::USB +{ +using HIDPressedKeys = std::array; + +#pragma pack(push, 1) +struct HIDKeyboardReport +{ + u8 modifiers = 0; + u8 oem = 0; + HIDPressedKeys pressed_keys{}; +}; +#pragma pack(pop) + +enum class HIDProtocol : u16 +{ + Boot = 0, + Report = 1, +}; + +class Keyboard final : public Device +{ +public: + Keyboard(EmulationKernel& ios); + ~Keyboard(); + + DeviceDescriptor GetDeviceDescriptor() const override; + std::vector GetConfigurations() const override; + std::vector GetInterfaces(u8 config) const override; + std::vector GetEndpoints(u8 config, u8 interface, u8 alt) const override; + bool Attach() override; + bool AttachAndChangeInterface(u8 interface) override; + int CancelTransfer(u8 endpoint) override; + int ChangeInterface(u8 interface) override; + int GetNumberOfAltSettings(u8 interface) override; + int SetAltSetting(u8 alt_setting) override; + int SubmitTransfer(std::unique_ptr message) override; + int SubmitTransfer(std::unique_ptr message) override; + int SubmitTransfer(std::unique_ptr message) override; + int SubmitTransfer(std::unique_ptr message) override; + +private: + void ScheduleTransfer(std::unique_ptr command, std::span data, + u64 expected_time_us); + bool IsKeyPressed(int virtual_key) const; + HIDPressedKeys PollHIDPressedKeys(); + u8 PollHIDModifiers(); + HIDKeyboardReport PollInputs(); + + EmulationKernel& m_ios; + HIDKeyboardReport m_last_report; + HIDProtocol m_current_protocol = HIDProtocol::Report; + + // Apple Extended Keyboard [Mitsumi] + // - Model A1058 / USB 1.1 + const u16 m_vid = 0x05ac; + const u16 m_pid = 0x020c; + u8 m_active_interface = 0; + bool m_device_attached = false; + bool init = false; + const DeviceDescriptor m_device_descriptor{0x12, 0x01, 0x0110, 0x00, 0x00, 0x00, 0x08, + 0x05AC, 0x020C, 0x0395, 0x01, 0x03, 0x00, 0x01}; + // Uncommenting these lines crashes MH3 + // Note to self: this keyboard was working on real hardware, though :v/ + const std::vector m_config_descriptor{ + {0x09, 0x02, 0x003B, /*0x02*/ 0x01, 0x01, 0x00, 0xA0}}; + const std::vector m_interface_descriptor{ + {0x09, 0x04, 0x00, 0x00, 0x01, 0x03, 0x01, 0x01, 0x00}, + // {0x09, 0x04, 0x01, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00} + }; + const std::vector m_endpoint_descriptor{ + {0x07, 0x05, 0x81, 0x03, 0x0008, 0x0A}, + // {0x07, 0x05, 0x82, 0x03, 0x0004, 0x0A} + }; +}; +} // namespace IOS::HLE::USB diff --git a/Source/Core/Core/IOS/USB/Host.cpp b/Source/Core/Core/IOS/USB/Host.cpp index c8d55a0bf5..cc3beb41a7 100644 --- a/Source/Core/Core/IOS/USB/Host.cpp +++ b/Source/Core/Core/IOS/USB/Host.cpp @@ -23,6 +23,7 @@ #include "Core/Core.h" #include "Core/IOS/USB/Common.h" #include "Core/IOS/USB/Emulated/Infinity.h" +#include "Core/IOS/USB/Emulated/Keyboard.h" #include "Core/IOS/USB/Emulated/Skylanders/Skylander.h" #include "Core/IOS/USB/LibusbDevice.h" #include "Core/NetPlayProto.h" @@ -195,6 +196,11 @@ void USBHost::AddEmulatedDevices(std::set& new_devices, DeviceChangeHooks& auto infinity_base = std::make_unique(GetEmulationKernel()); CheckAndAddDevice(std::move(infinity_base), new_devices, hooks, always_add_hooks); } + if (Config::Get(Config::MAIN_WII_KEYBOARD) && !NetPlay::IsNetPlayRunning()) + { + auto keyboard = std::make_unique(GetEmulationKernel()); + CheckAndAddDevice(std::move(keyboard), new_devices, hooks, always_add_hooks); + } } void USBHost::CheckAndAddDevice(std::unique_ptr device, std::set& new_devices, diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 388367afd1..b5d08429ed 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -401,6 +401,7 @@ + @@ -1064,6 +1065,7 @@ +