This commit is contained in:
sepalani 2025-08-12 17:03:04 +04:00 committed by GitHub
commit 7ed68dfe85
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 961 additions and 272 deletions

View file

@ -95,6 +95,8 @@ add_library(common
JitRegister.h
JsonUtil.h
JsonUtil.cpp
Keyboard.h
Keyboard.cpp
Lazy.h
LinearDiskCache.h
Logging/ConsoleListener.h
@ -352,6 +354,11 @@ if(OPROFILE_FOUND)
target_link_libraries(common PRIVATE OProfile::OProfile)
endif()
if(ENABLE_SDL)
target_link_libraries(common PRIVATE SDL3::SDL3)
target_compile_definitions(common PRIVATE -DHAVE_SDL3)
endif()
if(ENABLE_LLVM)
find_package(LLVM CONFIG)
if(LLVM_FOUND)

View file

@ -0,0 +1,509 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Common/Keyboard.h"
#include <array>
#include <map>
#include <mutex>
#include <ranges>
#include <utility>
#ifdef __APPLE__
#include <Carbon/Carbon.h>
#include "Common/ScopeGuard.h"
#endif
#ifdef HAVE_SDL3
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_keyboard.h>
// Will be overridden by Dolphin's SDL InputBackend
u32 Common::KeyboardContext::s_sdl_init_event_type(-1);
u32 Common::KeyboardContext::s_sdl_update_event_type(-1);
u32 Common::KeyboardContext::s_sdl_quit_event_type(-1);
#endif
#include "Core/Config/MainSettings.h"
#include "Core/Config/SYSCONFSettings.h"
#include "DiscIO/Enums.h"
namespace
{
// Translate HID usage ID based on the host and the game keyboard layout:
// - we need to take into account the host layout as we receive raw scan codes
// - we need to consider the game layout as it might be different from the host one
u8 TranslateUsageID(u8 usage_id, int host_layout, int game_layout)
{
if (host_layout == game_layout)
return usage_id;
// Currently, the translation is partial (i.e. alpha only)
if (usage_id != Common::HIDUsageID::M_AZERTY &&
(usage_id < Common::HIDUsageID::A || usage_id > Common::HIDUsageID::Z))
{
return usage_id;
}
switch (host_layout | game_layout)
{
case Common::KeyboardLayout::AZERTY_QWERTZ:
{
static const std::map<u8, u8> TO_QWERTZ{
{Common::HIDUsageID::A_AZERTY, Common::HIDUsageID::A},
{Common::HIDUsageID::Z_AZERTY, Common::HIDUsageID::Z_QWERTZ},
{Common::HIDUsageID::Y, Common::HIDUsageID::Y_QWERTZ},
{Common::HIDUsageID::Q_AZERTY, Common::HIDUsageID::Q},
{Common::HIDUsageID::M_AZERTY, Common::HIDUsageID::M},
{Common::HIDUsageID::W_AZERTY, Common::HIDUsageID::W},
{Common::HIDUsageID::M, Common::HIDUsageID::M_AZERTY},
};
static const std::map<u8, u8> TO_AZERTY{
{Common::HIDUsageID::Q, Common::HIDUsageID::Q_AZERTY},
{Common::HIDUsageID::W, Common::HIDUsageID::W_AZERTY},
{Common::HIDUsageID::Z_QWERTZ, Common::HIDUsageID::Z_AZERTY},
{Common::HIDUsageID::A, Common::HIDUsageID::A_AZERTY},
{Common::HIDUsageID::M_AZERTY, Common::HIDUsageID::M},
{Common::HIDUsageID::Y_QWERTZ, Common::HIDUsageID::Y},
{Common::HIDUsageID::M, Common::HIDUsageID::M_AZERTY},
};
const auto& map = game_layout == Common::KeyboardLayout::QWERTZ ? TO_QWERTZ : TO_AZERTY;
if (const auto it{map.find(usage_id)}; it != map.end())
return it->second;
break;
}
case Common::KeyboardLayout::QWERTY_AZERTY:
{
static constexpr std::array<std::pair<u8, u8>, 3> BI_MAP{
{{Common::HIDUsageID::Q, Common::HIDUsageID::A},
{Common::HIDUsageID::W, Common::HIDUsageID::Z},
{Common::HIDUsageID::M, Common::HIDUsageID::M_AZERTY}}};
for (const auto& [a, b] : BI_MAP)
{
if (usage_id == a)
return b;
else if (usage_id == b)
return a;
}
break;
}
case Common::KeyboardLayout::QWERTY_QWERTZ:
{
if (usage_id == Common::HIDUsageID::Y)
return Common::HIDUsageID::Z;
else if (usage_id == Common::HIDUsageID::Z)
return Common::HIDUsageID::Y;
break;
}
default:
// Shouldn't happen
break;
}
return usage_id;
}
int GetHostLayout()
{
const int layout = Config::Get(Config::MAIN_WII_KEYBOARD_HOST_LAYOUT);
if (layout != Common::KeyboardLayout::AUTO)
return layout;
#ifdef __APPLE__
auto current_layout = TISCopyCurrentKeyboardLayoutInputSource();
Common::ScopeGuard guard([&current_layout] { CFRelease(current_layout); });
auto layout_data = static_cast<CFDataRef>(
TISGetInputSourceProperty(current_layout, kTISPropertyUnicodeKeyLayoutData));
if (!layout_data)
return Common::KeyboardLayout::QWERTY;
auto unicode_layout = reinterpret_cast<const UCKeyboardLayout*>(CFDataGetBytePtr(layout_data));
using VkToLayout = std::tuple<CGKeyCode, char, Common::KeyboardLayout::LayoutEnum>;
for (auto& [key_code, c, l] :
std::array{VkToLayout(kVK_ANSI_Y, 'z', Common::KeyboardLayout::QWERTZ),
VkToLayout(kVK_ANSI_Q, 'a', Common::KeyboardLayout::AZERTY)})
{
UInt32 dead_key_state = 0;
std::array<UniChar, 4> str{};
UniCharCount real_length = 0;
UCKeyTranslate(unicode_layout, key_code, kUCKeyActionDown, 0, LMGetKbdType(),
kUCKeyTranslateNoDeadKeysMask, &dead_key_state, str.size(), &real_length,
str.data());
if (real_length && (str[0] == c || str[0] == (c - 0x20)))
return l;
}
#elif defined(HAVE_SDL3)
if (const SDL_Keycode key_code = SDL_GetKeyFromScancode(SDL_SCANCODE_Y, SDL_KMOD_NONE, false);
key_code == SDLK_Z)
{
return Common::KeyboardLayout::QWERTZ;
}
if (const SDL_Keycode key_code = SDL_GetKeyFromScancode(SDL_SCANCODE_Q, SDL_KMOD_NONE, false);
key_code == SDLK_A)
{
return Common::KeyboardLayout::AZERTY;
}
#endif
return Common::KeyboardLayout::QWERTY;
}
int GetGameLayout()
{
const int layout = Config::Get(Config::MAIN_WII_KEYBOARD_GAME_LAYOUT);
if (layout != Common::KeyboardLayout::AUTO)
return layout;
const DiscIO::Language language =
static_cast<DiscIO::Language>(Config::Get(Config::SYSCONF_LANGUAGE));
switch (language)
{
case DiscIO::Language::French:
return Common::KeyboardLayout::AZERTY;
case DiscIO::Language::German:
return Common::KeyboardLayout::QWERTZ;
default:
return Common::KeyboardLayout::QWERTY;
}
}
u8 MapVirtualKeyToHID(u8 virtual_key, int host_layout, int game_layout)
{
#ifdef __APPLE__
static const std::map<u8, u8> VK_TO_HID{{kVK_ANSI_A, 0x04},
{kVK_ANSI_B, 0x05},
{kVK_ANSI_C, 0x06},
{kVK_ANSI_D, 0x07},
{kVK_ANSI_E, 0x08},
{kVK_ANSI_F, 0x09},
{kVK_ANSI_G, 0x0a},
{kVK_ANSI_H, 0x0b},
{kVK_ANSI_I, 0x0c},
{kVK_ANSI_J, 0x0d},
{kVK_ANSI_K, 0x0e},
{kVK_ANSI_L, 0x0f},
{kVK_ANSI_M, 0x10},
{kVK_ANSI_N, 0x11},
{kVK_ANSI_O, 0x12},
{kVK_ANSI_P, 0x13},
{kVK_ANSI_Q, 0x14},
{kVK_ANSI_R, 0x15},
{kVK_ANSI_S, 0x16},
{kVK_ANSI_T, 0x17},
{kVK_ANSI_U, 0x18},
{kVK_ANSI_V, 0x19},
{kVK_ANSI_W, 0x1a},
{kVK_ANSI_X, 0x1b},
{kVK_ANSI_Y, 0x1c},
{kVK_ANSI_Z, 0x1d},
{kVK_ANSI_1, 0x1e},
{kVK_ANSI_2, 0x1f},
{kVK_ANSI_3, 0x20},
{kVK_ANSI_4, 0x21},
{kVK_ANSI_5, 0x22},
{kVK_ANSI_6, 0x23},
{kVK_ANSI_7, 0x24},
{kVK_ANSI_8, 0x25},
{kVK_ANSI_9, 0x26},
{kVK_ANSI_0, 0x27},
{kVK_Return, 0x28},
{kVK_Escape, 0x29},
{kVK_Delete, 0x2a},
{kVK_Tab, 0x2b},
{kVK_Space, 0x2c},
{kVK_ANSI_Minus, 0x2d},
{kVK_ANSI_Equal, 0x2e},
{kVK_ANSI_LeftBracket, 0x2f},
{kVK_ANSI_RightBracket, 0x30},
{kVK_ANSI_Backslash, 0x31},
// Missing: Non-US #
{kVK_ANSI_Semicolon, 0x33},
{kVK_ANSI_Quote, 0x34},
{kVK_ANSI_Grave, 0x35},
{kVK_ANSI_Comma, 0x36},
{kVK_ANSI_Period, 0x37},
{kVK_ANSI_Slash, 0x38},
{kVK_CapsLock, 0x39},
{kVK_F1, 0x3a},
{kVK_F2, 0x3b},
{kVK_F3, 0x3c},
{kVK_F4, 0x3d},
{kVK_F5, 0x3e},
{kVK_F6, 0x3f},
{kVK_F7, 0x40},
{kVK_F8, 0x41},
{kVK_F9, 0x42},
{kVK_F10, 0x43},
{kVK_F11, 0x44},
{kVK_F12, 0x45},
// Missing: PrintScreen, Scroll Lock, Pause
{kVK_Help, 0x49},
{kVK_Home, 0x4a},
{kVK_PageUp, 0x4b},
{kVK_ForwardDelete, 0x4c},
{kVK_End, 0x4d},
{kVK_PageDown, 0x4e},
{kVK_RightArrow, 0x4f},
{kVK_LeftArrow, 0x50},
{kVK_DownArrow, 0x51},
{kVK_UpArrow, 0x52},
{kVK_ANSI_KeypadClear, 0x53},
{kVK_ANSI_KeypadDivide, 0x54},
{kVK_ANSI_KeypadMultiply, 0x55},
{kVK_ANSI_KeypadMinus, 0x56},
{kVK_ANSI_KeypadPlus, 0x57},
{kVK_ANSI_KeypadEnter, 0x58},
{kVK_ANSI_Keypad1, 0x59},
{kVK_ANSI_Keypad2, 0x5a},
{kVK_ANSI_Keypad3, 0x5b},
{kVK_ANSI_Keypad4, 0x5c},
{kVK_ANSI_Keypad5, 0x5d},
{kVK_ANSI_Keypad6, 0x5e},
{kVK_ANSI_Keypad7, 0x5f},
{kVK_ANSI_Keypad8, 0x60},
{kVK_ANSI_Keypad9, 0x61},
{kVK_ANSI_Keypad0, 0x62},
{kVK_ANSI_KeypadDecimal, 0x63},
// Missing: Non-US |, Application, Power
{kVK_ANSI_KeypadEquals, 0x67},
{kVK_Control, 0xe0},
{kVK_Shift, 0xe1},
{kVK_Option, 0xe2},
{kVK_Command, 0xe3},
{kVK_RightControl, 0xe4},
{kVK_RightShift, 0xe5},
{kVK_RightOption, 0xe6},
{kVK_RightCommand, 0xe7}};
const auto it{VK_TO_HID.find(virtual_key)};
u8 usage_id = (it == VK_TO_HID.end()) ? 0 : it->second;
#else
// SDL3 keyboard state uses scan codes already based on HID usage id
u8 usage_id = virtual_key;
#endif
if (Config::Get(Config::MAIN_WII_KEYBOARD_TRANSLATION))
usage_id = TranslateUsageID(usage_id, host_layout, game_layout);
return usage_id;
}
std::weak_ptr<Common::KeyboardContext> s_keyboard_context;
std::mutex s_keyboard_context_mutex;
// Will be updated by DolphinQt's Host:
// - SetRenderHandle
// - SetFullscreen
Common::KeyboardContext::HandlerState s_handler_state{};
} // Anonymous namespace
namespace Common
{
KeyboardContext::KeyboardContext()
{
if (Config::Get(Config::MAIN_WII_KEYBOARD))
Init();
}
KeyboardContext::~KeyboardContext()
{
if (Config::Get(Config::MAIN_WII_KEYBOARD))
Quit();
}
void KeyboardContext::Init()
{
#ifdef __APPLE__
CFNotificationCenterAddObserver(
CFNotificationCenterGetDistributedCenter(), this,
[](CFNotificationCenterRef /* center */, void* /* observer */, CFStringRef /* name */,
const void* /* object */,
CFDictionaryRef /* userInfo */) { KeyboardContext::UpdateLayout(); },
kTISNotifySelectedKeyboardInputSourceChanged, nullptr,
CFNotificationSuspensionBehaviorDeliverImmediately);
#elif defined(HAVE_SDL3)
SDL_Event event{s_sdl_init_event_type};
SDL_PushEvent(&event);
m_keyboard_state = SDL_GetKeyboardState(nullptr);
#endif
UpdateLayout();
m_is_ready = true;
}
void KeyboardContext::Quit()
{
m_is_ready = false;
#ifdef __APPLE__
CFNotificationCenterRemoveEveryObserver(CFNotificationCenterGetDistributedCenter(), this);
#elif defined(HAVE_SDL3)
SDL_Event event{s_sdl_quit_event_type};
SDL_PushEvent(&event);
#endif
}
void* KeyboardContext::HandlerState::GetHandle() const
{
#ifdef _WIN32
if (is_rendering_to_main && !is_fullscreen)
return main_handle;
#endif
return renderer_handle;
}
void KeyboardContext::NotifyInit()
{
if (auto self = s_keyboard_context.lock())
self->Init();
}
void KeyboardContext::NotifyHandlerChanged(const KeyboardContext::HandlerState& state)
{
s_handler_state = state;
if (s_keyboard_context.expired())
return;
#ifdef __APPLE__
// Quartz doesn't seem impacted
#elif defined(HAVE_SDL3)
SDL_Event event{s_sdl_update_event_type};
SDL_PushEvent(&event);
#endif
}
void KeyboardContext::NotifyQuit()
{
if (auto self = s_keyboard_context.lock())
self->Quit();
}
void KeyboardContext::UpdateLayout()
{
if (auto self = s_keyboard_context.lock())
{
self->m_host_layout = GetHostLayout();
self->m_game_layout = GetGameLayout();
}
}
void* KeyboardContext::GetWindowHandle()
{
return s_handler_state.GetHandle();
}
std::shared_ptr<KeyboardContext> KeyboardContext::GetInstance()
{
const std::lock_guard guard(s_keyboard_context_mutex);
std::shared_ptr<KeyboardContext> ptr = s_keyboard_context.lock();
if (!ptr)
{
ptr = std::shared_ptr<KeyboardContext>(new KeyboardContext);
s_keyboard_context = ptr;
}
return ptr;
}
HIDPressedState KeyboardContext::GetPressedState() const
{
return m_is_ready ? HIDPressedState{.modifiers = PollHIDModifiers(),
.pressed_keys = PollHIDPressedKeys()} :
HIDPressedState{};
}
bool KeyboardContext::IsVirtualKeyPressed(int virtual_key) const
{
#ifdef __APPLE__
return CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, CGKeyCode(virtual_key));
#elif defined(HAVE_SDL3)
if (virtual_key >= SDL_SCANCODE_COUNT)
return false;
return m_keyboard_state[virtual_key];
#else
// TODO: Android implementation
return false;
#endif
}
u8 KeyboardContext::PollHIDModifiers() const
{
u8 modifiers = 0;
using VkHidPair = std::pair<int, u8>;
// References:
// https://stackoverflow.com/a/16125341
// https://wiki.libsdl.org/SDL3/SDL_Scancode
// https://www.usb.org/document-library/device-class-definition-hid-111
//
// HID modifiers:
// Bit 0 - LEFT CTRL
// Bit 1 - LEFT SHIFT
// Bit 2 - LEFT ALT
// Bit 3 - LEFT GUI
// Bit 4 - RIGHT CTRL
// Bit 5 - RIGHT SHIFT
// Bit 6 - RIGHT ALT
// Bit 7 - RIGHT GUI
static const std::vector<VkHidPair> MODIFIERS_MAP{
#ifdef __APPLE__
{kVK_Control, 0x01}, {kVK_Shift, 0x02}, {kVK_Option, 0x04},
{kVK_Command, 0x08}, {kVK_RightControl, 0x10}, {kVK_RightShift, 0x20},
{kVK_RightOption, 0x40}, {kVK_RightCommand, 0x80}
#elif defined(HAVE_SDL3)
{SDL_SCANCODE_LCTRL, 0x01}, {SDL_SCANCODE_LSHIFT, 0x02}, {SDL_SCANCODE_LALT, 0x04},
{SDL_SCANCODE_LGUI, 0x08}, {SDL_SCANCODE_RCTRL, 0x10}, {SDL_SCANCODE_RSHIFT, 0x20},
{SDL_SCANCODE_RALT, 0x40}, {SDL_SCANCODE_RGUI, 0x80}
#else
// TODO: Android implementation
#endif
};
for (const auto& [virtual_key, hid_modifier] : MODIFIERS_MAP)
{
if (IsVirtualKeyPressed(virtual_key))
modifiers |= hid_modifier;
}
return modifiers;
}
HIDPressedKeys KeyboardContext::PollHIDPressedKeys() const
{
HIDPressedKeys pressed_keys{};
auto it = pressed_keys.begin();
#ifdef __APPLE__
// See comment about <HIToolbox/Events.h> keycodes in QuartzKeyboardAndMouse.mm
static auto KEYCODES = std::ranges::filter_view(std::ranges::iota_view{0, 0x7f}, [](int keycode) {
switch (keycode)
{
case kVK_Command:
case kVK_RightCommand:
case kVK_Shift:
case kVK_RightShift:
case kVK_Option:
case kVK_RightOption:
case kVK_Control:
case kVK_RightControl:
return false;
default:
return true;
}
});
static const std::vector<int> VIRTUAL_KEYS{KEYCODES.begin(), KEYCODES.end()};
#elif defined(HAVE_SDL3)
static constexpr std::ranges::iota_view SCANCODES{int(SDL_SCANCODE_A), int(SDL_SCANCODE_LCTRL)};
static const std::vector<int> VIRTUAL_KEYS{SCANCODES.begin(), SCANCODES.end()};
#else
static constexpr std::vector<int> VIRTUAL_KEYS{};
#endif
for (int virtual_key : VIRTUAL_KEYS)
{
if (!IsVirtualKeyPressed(virtual_key))
continue;
*it = MapVirtualKeyToHID(static_cast<u8>(virtual_key), m_host_layout, m_game_layout);
if (++it == pressed_keys.end())
break;
}
return pressed_keys;
}
} // namespace Common

View file

@ -0,0 +1,126 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <memory>
#include "Common/CommonTypes.h"
namespace Common
{
namespace HIDUsageID
{
// See HID Usage Tables - Keyboard (0x07):
// https://usb.org/sites/default/files/hut1_21.pdf
enum
{
A = 0x04,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
COLON = 0x33,
A_AZERTY = Q,
M_AZERTY = COLON,
Q_AZERTY = A,
W_AZERTY = Z,
Z_AZERTY = W,
Y_QWERTZ = Z,
Z_QWERTZ = Y,
};
} // namespace HIDUsageID
namespace KeyboardLayout
{
enum LayoutEnum
{
AUTO = 0,
QWERTY = 1,
AZERTY = 2,
QWERTZ = 4,
// Translation
QWERTY_AZERTY = QWERTY | AZERTY,
QWERTY_QWERTZ = QWERTY | QWERTZ,
AZERTY_QWERTZ = AZERTY | QWERTZ
};
}
using HIDPressedKeys = std::array<u8, 6>;
struct HIDPressedState
{
u8 modifiers = 0;
HIDPressedKeys pressed_keys{};
auto operator<=>(const HIDPressedState&) const = default;
};
class KeyboardContext
{
public:
~KeyboardContext();
struct HandlerState
{
void* main_handle = nullptr;
void* renderer_handle = nullptr;
bool is_fullscreen = false;
bool is_rendering_to_main = false;
void* GetHandle() const;
};
static void NotifyInit();
static void NotifyHandlerChanged(const HandlerState& state);
static void NotifyQuit();
static void UpdateLayout();
static void* GetWindowHandle();
static std::shared_ptr<KeyboardContext> GetInstance();
HIDPressedState GetPressedState() const;
#ifdef HAVE_SDL3
static u32 s_sdl_init_event_type;
static u32 s_sdl_update_event_type;
static u32 s_sdl_quit_event_type;
#endif
private:
KeyboardContext();
void Init();
void Quit();
bool IsVirtualKeyPressed(int virtual_key) const;
u8 PollHIDModifiers() const;
HIDPressedKeys PollHIDPressedKeys() const;
bool m_is_ready = false;
int m_host_layout = KeyboardLayout::AUTO;
int m_game_layout = KeyboardLayout::AUTO;
#ifdef HAVE_SDL3
const bool* m_keyboard_state = nullptr;
#endif
};
} // namespace Common

View file

@ -196,6 +196,10 @@ const Info<bool> MAIN_WII_SD_CARD_ENABLE_FOLDER_SYNC{
{System::Main, "Core", "WiiSDCardEnableFolderSync"}, false};
const Info<u64> MAIN_WII_SD_CARD_FILESIZE{{System::Main, "Core", "WiiSDCardFilesize"}, 0};
const Info<bool> MAIN_WII_KEYBOARD{{System::Main, "Core", "WiiKeyboard"}, false};
const Info<int> MAIN_WII_KEYBOARD_HOST_LAYOUT{{System::Main, "Core", "WiiKeyboardHostLayout"}, 0};
const Info<int> MAIN_WII_KEYBOARD_GAME_LAYOUT{{System::Main, "Core", "WiiKeyboardGameLayout"}, 0};
const Info<bool> MAIN_WII_KEYBOARD_TRANSLATION{{System::Main, "Core", "WiiKeyboardTranslation"},
false};
const Info<bool> MAIN_WIIMOTE_CONTINUOUS_SCANNING{
{System::Main, "Core", "WiimoteContinuousScanning"}, false};
const Info<std::string> MAIN_WIIMOTE_AUTO_CONNECT_ADDRESSES{

View file

@ -108,6 +108,9 @@ extern const Info<bool> MAIN_WII_SD_CARD;
extern const Info<bool> MAIN_WII_SD_CARD_ENABLE_FOLDER_SYNC;
extern const Info<u64> MAIN_WII_SD_CARD_FILESIZE;
extern const Info<bool> MAIN_WII_KEYBOARD;
extern const Info<int> MAIN_WII_KEYBOARD_HOST_LAYOUT;
extern const Info<int> MAIN_WII_KEYBOARD_GAME_LAYOUT;
extern const Info<bool> MAIN_WII_KEYBOARD_TRANSLATION;
extern const Info<bool> MAIN_WIIMOTE_CONTINUOUS_SCANNING;
extern const Info<std::string> MAIN_WIIMOTE_AUTO_CONNECT_ADDRESSES;
extern const Info<bool> MAIN_WIIMOTE_ENABLE_SPEAKER;

View file

@ -3,9 +3,6 @@
#include "Core/IOS/USB/USB_KBD.h"
#include <array>
#include <queue>
#include "Common/FileUtil.h"
#include "Common/IniFile.h"
#include "Common/Logging/Log.h"
@ -16,169 +13,11 @@
#include "Core/System.h"
#include "InputCommon/ControlReference/ControlReference.h" // For background input check
#ifdef _WIN32
#include <windows.h>
#endif
namespace IOS::HLE
{
namespace
{
// Crazy ugly
#ifdef _WIN32
constexpr std::array<u8, 256> s_key_codes_qwerty{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2A, // Backspace
0x2B, // Tab
0x00, 0x00,
0x00, // Clear
0x28, // Return
0x00, 0x00,
0x00, // Shift
0x00, // Control
0x00, // ALT
0x48, // Pause
0x39, // Capital
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x29, // Escape
0x00, 0x00, 0x00, 0x00,
0x2C, // Space
0x4B, // Prior
0x4E, // Next
0x4D, // End
0x4A, // Home
0x50, // Left
0x52, // Up
0x4F, // Right
0x51, // Down
0x00, 0x00, 0x00,
0x46, // Print screen
0x49, // Insert
0x4C, // Delete
0x00,
// 0 -> 9
0x27, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
// A -> Z
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13,
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00,
// Numpad 0 -> 9
0x62, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61,
0x55, // Multiply
0x57, // Add
0x00, // Separator
0x56, // Subtract
0x63, // Decimal
0x54, // Divide
// F1 -> F12
0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
// F13 -> F24
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x53, // Numlock
0x47, // Scroll lock
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Modifier keys
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x33, // ';'
0x2E, // Plus
0x36, // Comma
0x2D, // Minus
0x37, // Period
0x38, // '/'
0x35, // '~'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2F, // '['
0x32, // '\'
0x30, // ']'
0x34, // '''
0x00, //
0x00, // Nothing interesting past this point.
};
constexpr std::array<u8, 256> s_key_codes_azerty{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2A, // Backspace
0x2B, // Tab
0x00, 0x00,
0x00, // Clear
0x28, // Return
0x00, 0x00,
0x00, // Shift
0x00, // Control
0x00, // ALT
0x48, // Pause
0x39, // Capital
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x29, // Escape
0x00, 0x00, 0x00, 0x00,
0x2C, // Space
0x4B, // Prior
0x4E, // Next
0x4D, // End
0x4A, // Home
0x50, // Left
0x52, // Up
0x4F, // Right
0x51, // Down
0x00, 0x00, 0x00,
0x46, // Print screen
0x49, // Insert
0x4C, // Delete
0x00,
// 0 -> 9
0x27, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
// A -> Z
0x14, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x33, 0x11, 0x12, 0x13,
0x04, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1D, 0x1B, 0x1C, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00,
// Numpad 0 -> 9
0x62, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61,
0x55, // Multiply
0x57, // Add
0x00, // Separator
0x56, // Substract
0x63, // Decimal
0x54, // Divide
// F1 -> F12
0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
// F13 -> F24
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x53, // Numlock
0x47, // Scroll lock
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Modifier keys
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x30, // '$'
0x2E, // Plus
0x10, // Comma
0x00, // Minus
0x36, // Period
0x37, // '/'
0x34, // ' '
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2D, // ')'
0x32, // '\'
0x2F, // '^'
0x00, // ' '
0x38, // '!'
0x00, // Nothing interesting past this point.
};
#else
constexpr std::array<u8, 256> s_key_codes_qwerty{};
constexpr std::array<u8, 256> s_key_codes_azerty{};
#endif
} // Anonymous namespace
USB_KBD::MessageData::MessageData(MessageType type, u8 modifiers_, PressedKeyData pressed_keys_)
: msg_type{Common::swap32(static_cast<u32>(type))}, modifiers{modifiers_},
pressed_keys{pressed_keys_}
USB_KBD::MessageData::MessageData(MessageType type, const Common::HIDPressedState& state)
: msg_type{Common::swap32(static_cast<u32>(type))}, modifiers{state.modifiers},
pressed_keys{state.pressed_keys}
{
}
@ -192,18 +31,24 @@ USB_KBD::USB_KBD(EmulationKernel& ios, const std::string& device_name)
std::optional<IPCReply> USB_KBD::Open(const OpenRequest& request)
{
INFO_LOG_FMT(IOS, "USB_KBD: Open");
Common::IniFile ini;
ini.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX));
ini.GetOrCreateSection("USB Keyboard")->Get("Layout", &m_keyboard_layout, KBD_LAYOUT_QWERTY);
m_message_queue = {};
m_old_key_buffer.fill(false);
m_old_modifiers = 0x00;
m_previous_state = {};
m_keyboard_context = Common::KeyboardContext::GetInstance();
// m_message_queue.emplace(MessageType::KeyboardConnect, 0, PressedKeyData{});
// m_message_queue.emplace(MessageType::KeyboardConnect, {});
return Device::Open(request);
}
std::optional<IPCReply> USB_KBD::Close(u32 fd)
{
INFO_LOG_FMT(IOS, "USB_KBD: Close");
m_keyboard_context.reset();
return Device::Close(fd);
}
std::optional<IPCReply> USB_KBD::Write(const ReadWriteRequest& request)
{
// Stubbed.
@ -223,91 +68,20 @@ std::optional<IPCReply> USB_KBD::IOCtl(const IOCtlRequest& request)
return IPCReply(IPC_SUCCESS);
}
bool USB_KBD::IsKeyPressed(int key) const
{
#ifdef _WIN32
return (GetAsyncKeyState(key) & 0x8000) != 0;
#else
// TODO: do it for non-Windows platforms
return false;
#endif
}
void USB_KBD::Update()
{
if (!Config::Get(Config::MAIN_WII_KEYBOARD) || Core::WantsDeterminism() || !m_is_active)
if (!Config::Get(Config::MAIN_WII_KEYBOARD) || Core::WantsDeterminism() || !m_is_active ||
!m_keyboard_context)
{
return;
}
const auto current_state = m_keyboard_context->GetPressedState();
if (current_state == m_previous_state)
return;
u8 modifiers = 0x00;
PressedKeyData pressed_keys{0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
bool got_event = false;
size_t num_pressed_keys = 0;
for (size_t i = 0; i < m_old_key_buffer.size(); i++)
{
const bool key_pressed_now = IsKeyPressed(static_cast<int>(i));
const bool key_pressed_before = m_old_key_buffer[i];
u8 key_code = 0;
if (key_pressed_now ^ key_pressed_before)
{
if (key_pressed_now)
{
switch (m_keyboard_layout)
{
case KBD_LAYOUT_QWERTY:
key_code = s_key_codes_qwerty[i];
break;
case KBD_LAYOUT_AZERTY:
key_code = s_key_codes_azerty[i];
break;
}
if (key_code == 0x00)
continue;
pressed_keys[num_pressed_keys] = key_code;
num_pressed_keys++;
if (num_pressed_keys == pressed_keys.size())
break;
}
got_event = true;
}
m_old_key_buffer[i] = key_pressed_now;
}
#ifdef _WIN32
if (GetAsyncKeyState(VK_LCONTROL) & 0x8000)
modifiers |= 0x01;
if (GetAsyncKeyState(VK_LSHIFT) & 0x8000)
modifiers |= 0x02;
if (GetAsyncKeyState(VK_MENU) & 0x8000)
modifiers |= 0x04;
if (GetAsyncKeyState(VK_LWIN) & 0x8000)
modifiers |= 0x08;
if (GetAsyncKeyState(VK_RCONTROL) & 0x8000)
modifiers |= 0x10;
if (GetAsyncKeyState(VK_RSHIFT) & 0x8000)
modifiers |= 0x20;
// TODO: VK_MENU is for ALT, not for ALT GR (ALT GR seems to work though...)
if (GetAsyncKeyState(VK_MENU) & 0x8000)
modifiers |= 0x40;
if (GetAsyncKeyState(VK_RWIN) & 0x8000)
modifiers |= 0x80;
#else
// TODO: modifiers for non-Windows platforms
#endif
if (modifiers ^ m_old_modifiers)
{
got_event = true;
m_old_modifiers = modifiers;
}
if (got_event)
m_message_queue.emplace(MessageType::Event, modifiers, pressed_keys);
m_message_queue.emplace(MessageType::Event, current_state);
m_previous_state = std::move(current_state);
}
} // namespace IOS::HLE

View file

@ -3,12 +3,11 @@
#pragma once
#include <array>
#include <queue>
#include <string>
#include <type_traits>
#include "Common/CommonTypes.h"
#include "Common/Keyboard.h"
#include "Core/IOS/Device.h"
#include "Core/IOS/IOS.h"
@ -20,6 +19,7 @@ public:
USB_KBD(EmulationKernel& ios, const std::string& device_name);
std::optional<IPCReply> Open(const OpenRequest& request) override;
std::optional<IPCReply> Close(u32 fd) override;
std::optional<IPCReply> Write(const ReadWriteRequest& request) override;
std::optional<IPCReply> IOCtl(const IOCtlRequest& request) override;
void Update() override;
@ -32,8 +32,6 @@ private:
Event = 2
};
using PressedKeyData = std::array<u8, 6>;
#pragma pack(push, 1)
struct MessageData
{
@ -41,26 +39,15 @@ private:
u32 unk1 = 0;
u8 modifiers = 0;
u8 unk2 = 0;
PressedKeyData pressed_keys{};
Common::HIDPressedKeys pressed_keys{};
MessageData(MessageType msg_type, u8 modifiers, PressedKeyData pressed_keys);
MessageData(MessageType msg_type, const Common::HIDPressedState& state);
};
static_assert(std::is_trivially_copyable_v<MessageData>,
"MessageData must be trivially copyable, as it's copied into emulated memory.");
#pragma pack(pop)
std::queue<MessageData> m_message_queue;
std::array<bool, 256> m_old_key_buffer{};
u8 m_old_modifiers = 0;
bool IsKeyPressed(int key) const;
// This stuff should probably die
enum
{
KBD_LAYOUT_QWERTY = 0,
KBD_LAYOUT_AZERTY = 1
};
int m_keyboard_layout = KBD_LAYOUT_QWERTY;
Common::HIDPressedState m_previous_state;
std::shared_ptr<Common::KeyboardContext> m_keyboard_context;
};
} // namespace IOS::HLE

View file

@ -133,6 +133,7 @@
<ClInclude Include="Common\IOFile.h" />
<ClInclude Include="Common\JitRegister.h" />
<ClInclude Include="Common\JsonUtil.h" />
<ClInclude Include="Common\Keyboard.h" />
<ClInclude Include="Common\Lazy.h" />
<ClInclude Include="Common\LdrWatcher.h" />
<ClInclude Include="Common\LinearDiskCache.h" />
@ -838,6 +839,7 @@
<ClCompile Include="Common\IOFile.cpp" />
<ClCompile Include="Common\JitRegister.cpp" />
<ClCompile Include="Common\JsonUtil.cpp" />
<ClCompile Include="Common\Keyboard.cpp" />
<ClCompile Include="Common\LdrWatcher.cpp" />
<ClCompile Include="Common\Logging\ConsoleListenerWin.cpp" />
<ClCompile Include="Common\Logging\LogManager.cpp" />

View file

@ -249,6 +249,8 @@ add_executable(dolphin-emu
DiscordHandler.h
DiscordJoinRequestDialog.cpp
DiscordJoinRequestDialog.h
EmulatedUSB/Keyboard.cpp
EmulatedUSB/Keyboard.h
EmulatedUSB/WiiSpeakWindow.cpp
EmulatedUSB/WiiSpeakWindow.h
FIFO/FIFOAnalyzer.cpp

View file

@ -158,6 +158,7 @@
<ClCompile Include="Debugger\WatchWidget.cpp" />
<ClCompile Include="DiscordHandler.cpp" />
<ClCompile Include="DiscordJoinRequestDialog.cpp" />
<ClCompile Include="EmulatedUSB\Keyboard.cpp" />
<ClCompile Include="EmulatedUSB\WiiSpeakWindow.cpp" />
<ClCompile Include="FIFO\FIFOAnalyzer.cpp" />
<ClCompile Include="FIFO\FIFOPlayerWindow.cpp" />
@ -380,6 +381,7 @@
<QtMoc Include="Debugger\WatchWidget.h" />
<QtMoc Include="DiscordHandler.h" />
<QtMoc Include="DiscordJoinRequestDialog.h" />
<QtMoc Include="EmulatedUSB\Keyboard.h" />
<QtMoc Include="EmulatedUSB\WiiSpeakWindow.h" />
<QtMoc Include="FIFO\FIFOAnalyzer.h" />
<QtMoc Include="FIFO\FIFOPlayerWindow.h" />

View file

@ -0,0 +1,90 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/EmulatedUSB/Keyboard.h"
#include <QCheckBox>
#include <QComboBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QVBoxLayout>
#include "Common/Keyboard.h"
#include "Core/Config/MainSettings.h"
#include "DolphinQt/Config/ConfigControls/ConfigBool.h"
#include "DolphinQt/Settings.h"
KeyboardWindow::KeyboardWindow(QWidget* parent) : QWidget(parent)
{
// i18n: Window for managing the Wii keyboard emulation
setWindowTitle(tr("Keyboard Manager"));
setObjectName(QStringLiteral("keyboard_manager"));
setMinimumSize(QSize(500, 200));
auto* main_layout = new QVBoxLayout();
{
auto* group = new QGroupBox();
auto* hbox_layout = new QHBoxLayout();
hbox_layout->setAlignment(Qt::AlignHCenter);
auto* checkbox_emulate = new QCheckBox(tr("Emulate USB keyboard"), this);
checkbox_emulate->setChecked(Settings::Instance().IsUSBKeyboardConnected());
connect(checkbox_emulate, &QCheckBox::toggled, this, &KeyboardWindow::EmulateKeyboard);
connect(&Settings::Instance(), &Settings::USBKeyboardConnectionChanged, checkbox_emulate,
&QCheckBox::setChecked);
hbox_layout->addWidget(checkbox_emulate);
group->setLayout(hbox_layout);
main_layout->addWidget(group);
}
{
auto* group = new QGroupBox(tr("Layout Configuration"));
auto* grid_layout = new QGridLayout();
auto* checkbox_translate =
new ConfigBool(tr("Enable partial translation"), Config::MAIN_WII_KEYBOARD_TRANSLATION);
grid_layout->addWidget(checkbox_translate, 0, 0, 1, 2, Qt::AlignLeft);
auto create_combo = [checkbox_translate, grid_layout](int row, const QString& name,
const auto& config_info) {
grid_layout->addWidget(new QLabel(name), row, 0);
auto* combo = new QComboBox();
combo->addItem(tr("Automatic detection"), Common::KeyboardLayout::AUTO);
combo->addItem(QStringLiteral("QWERTY"), Common::KeyboardLayout::QWERTY);
combo->addItem(QStringLiteral("AZERTY"), Common::KeyboardLayout::AZERTY);
combo->addItem(QStringLiteral("QWERTZ"), Common::KeyboardLayout::QWERTZ);
combo->setCurrentIndex(combo->findData(Config::Get(config_info)));
combo->setEnabled(checkbox_translate->isChecked());
connect(combo, &QComboBox::currentIndexChanged, combo, [combo, config_info] {
const auto keyboard_layout = combo->currentData();
if (!keyboard_layout.isValid())
return;
Config::SetBaseOrCurrent(config_info, keyboard_layout.toInt());
Common::KeyboardContext::UpdateLayout();
});
connect(checkbox_translate, &QCheckBox::toggled, combo, &QComboBox::setEnabled);
grid_layout->addWidget(combo, row, 1);
};
create_combo(1, tr("Host layout:"), Config::MAIN_WII_KEYBOARD_HOST_LAYOUT);
create_combo(2, tr("Game layout:"), Config::MAIN_WII_KEYBOARD_GAME_LAYOUT);
group->setLayout(grid_layout);
main_layout->addWidget(group);
}
setLayout(main_layout);
}
KeyboardWindow::~KeyboardWindow() = default;
void KeyboardWindow::EmulateKeyboard(bool emulate) const
{
Settings::Instance().SetUSBKeyboardConnected(emulate);
}

View file

@ -0,0 +1,17 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QWidget>
class KeyboardWindow : public QWidget
{
Q_OBJECT
public:
explicit KeyboardWindow(QWidget* parent = nullptr);
~KeyboardWindow() override;
private:
void EmulateKeyboard(bool emulate) const;
};

View file

@ -16,6 +16,7 @@
#endif
#include "Common/Common.h"
#include "Common/Keyboard.h"
#include "Core/Config/MainSettings.h"
#include "Core/ConfigManager.h"
@ -62,6 +63,11 @@ void Host::SetRenderHandle(void* handle)
{
m_render_to_main = Config::Get(Config::MAIN_RENDER_TO_MAIN);
Common::KeyboardContext::NotifyHandlerChanged({.main_handle = m_main_window_handle.load(),
.renderer_handle = handle,
.is_fullscreen = m_render_fullscreen.load(),
.is_rendering_to_main = m_render_to_main.load()});
if (m_render_handle == handle)
return;
@ -176,6 +182,11 @@ void Host::SetRenderFullscreen(bool fullscreen)
{
m_render_fullscreen = fullscreen;
Common::KeyboardContext::NotifyHandlerChanged({.main_handle = m_main_window_handle.load(),
.renderer_handle = m_render_handle.load(),
.is_fullscreen = fullscreen,
.is_rendering_to_main = m_render_to_main.load()});
if (g_gfx && g_gfx->IsFullscreen() != fullscreen && g_ActiveConfig.ExclusiveFullscreenEnabled())
{
RunWithGPUThreadInactive([fullscreen] { g_gfx->SetFullscreen(fullscreen); });

View file

@ -93,6 +93,7 @@
#include "DolphinQt/Debugger/ThreadWidget.h"
#include "DolphinQt/Debugger/WatchWidget.h"
#include "DolphinQt/DiscordHandler.h"
#include "DolphinQt/EmulatedUSB/Keyboard.h"
#include "DolphinQt/EmulatedUSB/WiiSpeakWindow.h"
#include "DolphinQt/FIFO/FIFOPlayerWindow.h"
#include "DolphinQt/GCMemcardManager.h"
@ -579,6 +580,7 @@ void MainWindow::ConnectMenuBar()
connect(m_menu_bar, &MenuBar::ShowSkylanderPortal, this, &MainWindow::ShowSkylanderPortal);
connect(m_menu_bar, &MenuBar::ShowInfinityBase, this, &MainWindow::ShowInfinityBase);
connect(m_menu_bar, &MenuBar::ShowWiiSpeakWindow, this, &MainWindow::ShowWiiSpeakWindow);
connect(m_menu_bar, &MenuBar::ShowKeyboard, this, &MainWindow::ShowKeyboard);
connect(m_menu_bar, &MenuBar::ConnectWiiRemote, this, &MainWindow::OnConnectWiiRemote);
#ifdef USE_RETRO_ACHIEVEMENTS
@ -1425,6 +1427,18 @@ void MainWindow::ShowWiiSpeakWindow()
m_wii_speak_window->activateWindow();
}
void MainWindow::ShowKeyboard()
{
if (!m_keyboard_window)
{
m_keyboard_window = new KeyboardWindow();
}
m_keyboard_window->show();
m_keyboard_window->raise();
m_keyboard_window->activateWindow();
}
void MainWindow::StateLoad()
{
QString dialog_path = (Config::Get(Config::MAIN_CURRENT_STATE_PATH).empty()) ?

View file

@ -36,6 +36,7 @@ class GBATASInputWindow;
class GCTASInputWindow;
class HotkeyScheduler;
class InfinityBaseWindow;
class KeyboardWindow;
class JITWidget;
class LogConfigWidget;
class LogWidget;
@ -177,6 +178,7 @@ private:
void ShowSkylanderPortal();
void ShowInfinityBase();
void ShowWiiSpeakWindow();
void ShowKeyboard();
void ShowMemcardManager();
void ShowResourcePackManager();
void ShowCheatsManager();
@ -249,6 +251,7 @@ private:
SkylanderPortalWindow* m_skylander_window = nullptr;
InfinityBaseWindow* m_infinity_window = nullptr;
WiiSpeakWindow* m_wii_speak_window = nullptr;
KeyboardWindow* m_keyboard_window = nullptr;
MappingWindow* m_hotkey_window = nullptr;
FreeLookWindow* m_freelook_window = nullptr;

View file

@ -280,6 +280,7 @@ void MenuBar::AddToolsMenu()
usb_device_menu->addAction(tr("&Skylanders Portal"), this, &MenuBar::ShowSkylanderPortal);
usb_device_menu->addAction(tr("&Infinity Base"), this, &MenuBar::ShowInfinityBase);
usb_device_menu->addAction(tr("&Wii Speak"), this, &MenuBar::ShowWiiSpeakWindow);
usb_device_menu->addAction(tr("&Keyboard"), this, &MenuBar::ShowKeyboard);
tools_menu->addMenu(usb_device_menu);
tools_menu->addSeparator();

View file

@ -95,6 +95,7 @@ signals:
void ShowSkylanderPortal();
void ShowInfinityBase();
void ShowWiiSpeakWindow();
void ShowKeyboard();
void ConnectWiiRemote(int id);
#ifdef USE_RETRO_ACHIEVEMENTS

View file

@ -24,6 +24,7 @@
#include "Common/Config/Config.h"
#include "Common/Contains.h"
#include "Common/FileUtil.h"
#include "Common/Keyboard.h"
#include "Common/StringUtil.h"
#include "Core/AchievementManager.h"
@ -789,6 +790,10 @@ void Settings::SetUSBKeyboardConnected(bool connected)
if (IsUSBKeyboardConnected() != connected)
{
Config::SetBaseOrCurrent(Config::MAIN_WII_KEYBOARD, connected);
if (connected)
Common::KeyboardContext::NotifyInit();
else
Common::KeyboardContext::NotifyQuit();
emit USBKeyboardConnectionChanged(connected);
}
}

View file

@ -3,6 +3,7 @@
#include "InputCommon/ControllerInterface/SDL/SDL.h"
#include <optional>
#include <span>
#include <thread>
#include <vector>
@ -14,12 +15,89 @@
#include <SDL3/SDL.h>
#include "Common/Event.h"
#include "Common/Keyboard.h"
#include "Common/Logging/Log.h"
#include "Common/ScopeGuard.h"
#include "Core/Host.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h"
#include "InputCommon/ControllerInterface/SDL/SDLGamepad.h"
namespace
{
using UniqueSDLWindow = std::unique_ptr<SDL_Window, void (*)(SDL_Window*)>;
// Based on sdl2-compat 76eb981a4c376bcaf615c0af37d46512ba45cfb8
SDL_Window* SDL_CreateWindowFrom(void* handle)
{
SDL_PropertiesID props = SDL_CreateProperties();
if (!props)
return nullptr;
if (const char* hint = SDL_GetHint("SDL_VIDEO_WINDOW_SHARE_PIXEL_FORMAT"); hint)
{
// This hint is a pointer (in string form) of the address of
// the window to share a pixel format with
SDL_Window* other_window = nullptr;
if (SDL_sscanf(hint, "%p", &other_window))
{
void* ptr = SDL_GetPointerProperty(SDL_GetWindowProperties(other_window),
SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
SDL_SetPointerProperty(props, SDL_PROP_WINDOW_CREATE_WIN32_PIXEL_FORMAT_HWND_POINTER, ptr);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true);
}
}
if (SDL_GetHintBoolean("SDL_VIDEO_FOREIGN_WINDOW_OPENGL", false))
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true);
if (SDL_GetHintBoolean("SDL_VIDEO_FOREIGN_WINDOW_VULKAN", false))
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_VULKAN_BOOLEAN, true);
SDL_SetPointerProperty(props, "sdl2-compat.external_window", handle);
SDL_Window* window = SDL_CreateWindowWithProperties(props);
SDL_DestroyProperties(props);
// SDL3 has per-window text input, so we must enable on this window if it's active
if (SDL_EventEnabled(SDL_EVENT_TEXT_INPUT))
{
props = SDL_CreateProperties();
SDL_SetNumberProperty(props, SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT);
SDL_SetNumberProperty(props, SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER, SDL_CAPITALIZE_NONE);
SDL_SetBooleanProperty(props, SDL_PROP_TEXTINPUT_AUTOCORRECT_BOOLEAN, false);
SDL_StartTextInputWithProperties(window, props);
SDL_DestroyProperties(props);
}
return window;
}
std::optional<const char*> UpdateKeyboardHandle(UniqueSDLWindow* unique_window)
{
std::optional<const char*> error;
void* keyboard_handle = Common::KeyboardContext::GetWindowHandle();
SDL_Window* keyboard_window = SDL_CreateWindowFrom(keyboard_handle);
if (keyboard_window == nullptr)
error = SDL_GetError();
unique_window->reset(keyboard_window);
if (error.has_value())
return error;
// SDL aggressive hooking might make the window borderless sometimes
if (!Host_RendererIsFullscreen())
{
SDL_SetWindowFullscreen(keyboard_window, 0);
SDL_SetWindowBordered(keyboard_window, true);
}
Common::KeyboardContext::UpdateLayout();
return error;
}
} // namespace
namespace ciface::SDL
{
@ -40,6 +118,7 @@ private:
Uint32 m_stop_event_type;
Uint32 m_populate_event_type;
std::thread m_hotplug_thread;
UniqueSDLWindow m_keyboard_window{nullptr, SDL_DestroyWindow};
};
std::unique_ptr<ciface::InputBackend> CreateInputBackend(ControllerInterface* controller_interface)
@ -155,7 +234,7 @@ InputBackend::InputBackend(ControllerInterface* controller_interface)
return;
}
const Uint32 custom_events_start = SDL_RegisterEvents(2);
const Uint32 custom_events_start = SDL_RegisterEvents(5);
if (custom_events_start == static_cast<Uint32>(-1))
{
ERROR_LOG_FMT(CONTROLLERINTERFACE, "SDL failed to register custom events");
@ -163,6 +242,9 @@ InputBackend::InputBackend(ControllerInterface* controller_interface)
}
m_stop_event_type = custom_events_start;
m_populate_event_type = custom_events_start + 1;
Common::KeyboardContext::s_sdl_init_event_type = custom_events_start + 2;
Common::KeyboardContext::s_sdl_update_event_type = custom_events_start + 3;
Common::KeyboardContext::s_sdl_quit_event_type = custom_events_start + 4;
// Drain all of the events and add the initial joysticks before returning. Otherwise, the
// individual joystick events as well as the custom populate event will be handled _after_
@ -285,6 +367,55 @@ bool InputBackend::HandleEventAndContinue(const SDL_Event& e)
{
return false;
}
else if (e.type == Common::KeyboardContext::s_sdl_init_event_type)
{
const auto wsi = GetControllerInterface().GetWindowSystemInfo();
if (wsi.type == WindowSystemType::X11)
{
// Avoid a crash with Xwayland when the wrong driver is picked
if (!SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "x11"))
{
WARN_LOG_FMT(IOS_USB, "SDL failed to pick driver to capture keyboard input: {}",
SDL_GetError());
}
}
if (!SDL_InitSubSystem(SDL_INIT_VIDEO))
{
ERROR_LOG_FMT(IOS_USB, "SDL failed to init subsystem to capture keyboard input: {}",
SDL_GetError());
return true;
}
if (const auto error = UpdateKeyboardHandle(&m_keyboard_window); error.has_value())
{
ERROR_LOG_FMT(IOS_USB, "SDL failed to attach window to capture keyboard input: {}", *error);
return true;
}
}
else if (e.type == Common::KeyboardContext::s_sdl_update_event_type)
{
if (!SDL_WasInit(SDL_INIT_VIDEO))
return true;
// Release previous SDLWindow
m_keyboard_window.reset();
if (const auto error = UpdateKeyboardHandle(&m_keyboard_window); error.has_value())
{
ERROR_LOG_FMT(IOS_USB, "SDL failed to switch window to capture keyboard input: {}", *error);
return true;
}
}
else if (e.type == Common::KeyboardContext::s_sdl_quit_event_type)
{
m_keyboard_window.reset();
SDL_QuitSubSystem(SDL_INIT_VIDEO);
}
else if (e.type == SDL_EVENT_KEYMAP_CHANGED)
{
Common::KeyboardContext::UpdateLayout();
}
return true;
}