IOS/USB: Add "Connect USB Keyboard" support for HID (Windows only)

This commit is contained in:
Sepalani 2025-04-14 22:57:36 +04:00
parent 8a7a8b6179
commit 95c11323b0
6 changed files with 699 additions and 0 deletions

View File

@ -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

View File

@ -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,

View File

@ -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 <algorithm>
#include <ranges>
#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 <windows.h>
#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<int, u8>;
std::vector<VkHidPair> 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<VkHidPair> 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<VkHidPair> CleanVkHidMap(const std::vector<VkHidPair>& map)
{
// Ignore unsupported/ignored keys
std::vector<VkHidPair> 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<u8> 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<int> s_missing_hid_for_vk;
s_missing_hid_for_vk.emplace_back(virtual_key);
}
#endif
}
return cleaned_map;
}
static const std::vector<VkHidPair> 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<ConfigDescriptor> Keyboard::GetConfigurations() const
{
return m_config_descriptor;
}
std::vector<InterfaceDescriptor> Keyboard::GetInterfaces(u8 config) const
{
return m_interface_descriptor;
}
std::vector<EndpointDescriptor> 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<CtrlMessage> 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<u8>(cmd->index) != m_active_interface)
{
const int ret = ChangeInterface(static_cast<u8>(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<u8>(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<HIDProtocol>(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<BulkMessage> 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<IntrMessage> 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<const u8*>(&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<IsoMessage> 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<TransferCommand> command, std::span<const u8> data,
u64 expected_time_us)
{
command->FillBuffer(data.data(), data.size());
command->ScheduleTransferCompletion(static_cast<s32>(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<VkHidPair> 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

View File

@ -0,0 +1,89 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <memory>
#include <span>
#include <vector>
#include "Common/CommonTypes.h"
#include "Core/IOS/USB/Common.h"
#include "Core/System.h"
namespace IOS::HLE::USB
{
using HIDPressedKeys = std::array<u8, 6>;
#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<ConfigDescriptor> GetConfigurations() const override;
std::vector<InterfaceDescriptor> GetInterfaces(u8 config) const override;
std::vector<EndpointDescriptor> 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<CtrlMessage> message) override;
int SubmitTransfer(std::unique_ptr<BulkMessage> message) override;
int SubmitTransfer(std::unique_ptr<IntrMessage> message) override;
int SubmitTransfer(std::unique_ptr<IsoMessage> message) override;
private:
void ScheduleTransfer(std::unique_ptr<TransferCommand> command, std::span<const u8> 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<ConfigDescriptor> m_config_descriptor{
{0x09, 0x02, 0x003B, /*0x02*/ 0x01, 0x01, 0x00, 0xA0}};
const std::vector<InterfaceDescriptor> m_interface_descriptor{
{0x09, 0x04, 0x00, 0x00, 0x01, 0x03, 0x01, 0x01, 0x00},
// {0x09, 0x04, 0x01, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00}
};
const std::vector<EndpointDescriptor> m_endpoint_descriptor{
{0x07, 0x05, 0x81, 0x03, 0x0008, 0x0A},
// {0x07, 0x05, 0x82, 0x03, 0x0004, 0x0A}
};
};
} // namespace IOS::HLE::USB

View File

@ -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<u64>& new_devices, DeviceChangeHooks&
auto infinity_base = std::make_unique<USB::InfinityUSB>(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<USB::Keyboard>(GetEmulationKernel());
CheckAndAddDevice(std::move(keyboard), new_devices, hooks, always_add_hooks);
}
}
void USBHost::CheckAndAddDevice(std::unique_ptr<USB::Device> device, std::set<u64>& new_devices,

View File

@ -401,6 +401,7 @@
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.h" />
<ClInclude Include="Core\IOS\USB\Common.h" />
<ClInclude Include="Core\IOS\USB\Emulated\Infinity.h" />
<ClInclude Include="Core\IOS\USB\Emulated\Keyboard.h" />
<ClInclude Include="Core\IOS\USB\Emulated\Skylanders\Skylander.h" />
<ClInclude Include="Core\IOS\USB\Emulated\Skylanders\SkylanderCrypto.h" />
<ClInclude Include="Core\IOS\USB\Emulated\Skylanders\SkylanderFigure.h" />
@ -1064,6 +1065,7 @@
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.cpp" />
<ClCompile Include="Core\IOS\USB\Common.cpp" />
<ClCompile Include="Core\IOS\USB\Emulated\Infinity.cpp" />
<ClCompile Include="Core\IOS\USB\Emulated\Keyboard.cpp" />
<ClCompile Include="Core\IOS\USB\Emulated\Skylanders\Skylander.cpp" />
<ClCompile Include="Core\IOS\USB\Emulated\Skylanders\SkylanderCrypto.cpp" />
<ClCompile Include="Core\IOS\USB\Emulated\Skylanders\SkylanderFigure.cpp" />