Merge fc2ae3b880
into 53b54406bd
This commit is contained in:
commit
2b35d9b80c
|
@ -93,6 +93,8 @@ add_library(common
|
|||
JitRegister.h
|
||||
JsonUtil.h
|
||||
JsonUtil.cpp
|
||||
Keyboard.h
|
||||
Keyboard.cpp
|
||||
Lazy.h
|
||||
LinearDiskCache.h
|
||||
Logging/ConsoleListener.h
|
||||
|
@ -347,6 +349,11 @@ if(OPROFILE_FOUND)
|
|||
target_link_libraries(common PRIVATE OProfile::OProfile)
|
||||
endif()
|
||||
|
||||
if(ENABLE_SDL)
|
||||
target_link_libraries(common PRIVATE SDL2::SDL2)
|
||||
target_compile_definitions(common PRIVATE -DHAVE_SDL2)
|
||||
endif()
|
||||
|
||||
if(ENABLE_LLVM)
|
||||
find_package(LLVM CONFIG)
|
||||
if(LLVM_FOUND)
|
||||
|
|
|
@ -0,0 +1,326 @@
|
|||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Common/Keyboard.h"
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
#ifdef HAVE_SDL2
|
||||
#include <SDL_events.h>
|
||||
#include <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 HAVE_SDL2
|
||||
if (const SDL_Keycode key_code = SDL_GetKeyFromScancode(SDL_SCANCODE_Y); key_code == SDLK_z)
|
||||
{
|
||||
return Common::KeyboardLayout::QWERTZ;
|
||||
}
|
||||
if (const SDL_Keycode key_code = SDL_GetKeyFromScancode(SDL_SCANCODE_Q); 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)
|
||||
{
|
||||
// SDL2 keyboard state uses scan codes already based on HID usage id
|
||||
u8 usage_id = virtual_key;
|
||||
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 HAVE_SDL2
|
||||
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 HAVE_SDL2
|
||||
SDL_Event event{s_sdl_quit_event_type};
|
||||
SDL_PushEvent(&event);
|
||||
#endif
|
||||
}
|
||||
|
||||
const void* KeyboardContext::HandlerState::GetHandle() const
|
||||
{
|
||||
if (is_rendering_to_main && !is_fullscreen)
|
||||
return main_handle;
|
||||
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 HAVE_SDL2
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
const 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 HAVE_SDL2
|
||||
if (virtual_key >= SDL_NUM_SCANCODES)
|
||||
return false;
|
||||
return m_keyboard_state[virtual_key] == 1;
|
||||
#else
|
||||
// TODO: Android implementation
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
u8 KeyboardContext::PollHIDModifiers() const
|
||||
{
|
||||
u8 modifiers = 0;
|
||||
|
||||
using VkHidPair = std::pair<int, u8>;
|
||||
|
||||
// References:
|
||||
// https://wiki.libsdl.org/SDL2/SDL_Scancode
|
||||
// https://www.usb.org/document-library/device-class-definition-hid-111
|
||||
//
|
||||
// HID modifier: Bit 0 - LEFT CTRL
|
||||
// HID modifier: Bit 1 - LEFT SHIFT
|
||||
// HID modifier: Bit 2 - LEFT ALT
|
||||
// HID modifier: Bit 3 - LEFT GUI
|
||||
// HID modifier: Bit 4 - RIGHT CTRL
|
||||
// HID modifier: Bit 5 - RIGHT SHIFT
|
||||
// HID modifier: Bit 6 - RIGHT ALT
|
||||
// HID modifier: Bit 7 - RIGHT GUI
|
||||
static const std::vector<VkHidPair> MODIFIERS_MAP{
|
||||
#ifdef HAVE_SDL2
|
||||
{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 HAVE_SDL2
|
||||
const std::size_t begin = SDL_SCANCODE_A;
|
||||
const std::size_t end = SDL_SCANCODE_LCTRL;
|
||||
#else
|
||||
const std::size_t begin = 0;
|
||||
const std::size_t end = 0;
|
||||
#endif
|
||||
|
||||
for (std::size_t virtual_key = begin; virtual_key < end; ++virtual_key)
|
||||
{
|
||||
if (!IsVirtualKeyPressed(static_cast<int>(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
|
|
@ -0,0 +1,129 @@
|
|||
// 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
|
||||
{
|
||||
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>;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct HIDPressedState
|
||||
{
|
||||
u8 modifiers = 0;
|
||||
u8 oem = 0;
|
||||
HIDPressedKeys pressed_keys{};
|
||||
|
||||
auto operator<=>(const HIDPressedState&) const = default;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
class KeyboardContext
|
||||
{
|
||||
public:
|
||||
~KeyboardContext();
|
||||
|
||||
struct HandlerState
|
||||
{
|
||||
const void* main_handle = nullptr;
|
||||
const void* renderer_handle = nullptr;
|
||||
bool is_fullscreen = false;
|
||||
bool is_rendering_to_main = false;
|
||||
|
||||
const void* GetHandle() const;
|
||||
};
|
||||
|
||||
static void NotifyInit();
|
||||
static void NotifyHandlerChanged(const HandlerState& state);
|
||||
static void NotifyQuit();
|
||||
static void UpdateLayout();
|
||||
static const void* GetWindowHandle();
|
||||
static std::shared_ptr<KeyboardContext> GetInstance();
|
||||
|
||||
HIDPressedState GetPressedState() const;
|
||||
|
||||
#ifdef HAVE_SDL2
|
||||
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_SDL2
|
||||
const u8* m_keyboard_state = nullptr;
|
||||
#endif
|
||||
};
|
||||
} // namespace Common
|
|
@ -427,6 +427,8 @@ add_library(core
|
|||
IOS/USB/Bluetooth/WiimoteHIDAttr.h
|
||||
IOS/USB/Common.cpp
|
||||
IOS/USB/Common.h
|
||||
IOS/USB/Emulated/HIDKeyboard.cpp
|
||||
IOS/USB/Emulated/HIDKeyboard.h
|
||||
IOS/USB/Emulated/Infinity.cpp
|
||||
IOS/USB/Emulated/Infinity.h
|
||||
IOS/USB/Emulated/Microphone.cpp
|
||||
|
|
|
@ -188,6 +188,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{
|
||||
|
|
|
@ -106,6 +106,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;
|
||||
|
|
|
@ -24,11 +24,34 @@ 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 class HIDProtocol : u16
|
||||
{
|
||||
Boot = 0,
|
||||
Report = 1,
|
||||
};
|
||||
|
||||
enum ControlRequestTypes
|
||||
{
|
||||
DIR_HOST2DEVICE = 0,
|
||||
DIR_DEVICE2HOST = 1,
|
||||
TYPE_STANDARD = 0,
|
||||
TYPE_CLASS = 1,
|
||||
TYPE_VENDOR = 2,
|
||||
REC_DEVICE = 0,
|
||||
REC_INTERFACE = 1,
|
||||
|
|
|
@ -0,0 +1,316 @@
|
|||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/IOS/USB/Emulated/HIDKeyboard.h"
|
||||
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/System.h"
|
||||
#include "InputCommon/ControlReference/ControlReference.h"
|
||||
|
||||
namespace IOS::HLE::USB
|
||||
{
|
||||
HIDKeyboard::HIDKeyboard()
|
||||
{
|
||||
m_id = u64(m_vid) << 32 | u64(m_pid) << 16 | u64(8) << 8 | u64(1);
|
||||
}
|
||||
|
||||
HIDKeyboard::~HIDKeyboard()
|
||||
{
|
||||
if (!m_device_attached)
|
||||
return;
|
||||
CancelPendingTransfers();
|
||||
}
|
||||
|
||||
DeviceDescriptor HIDKeyboard::GetDeviceDescriptor() const
|
||||
{
|
||||
return m_device_descriptor;
|
||||
}
|
||||
|
||||
std::vector<ConfigDescriptor> HIDKeyboard::GetConfigurations() const
|
||||
{
|
||||
return m_config_descriptor;
|
||||
}
|
||||
|
||||
std::vector<InterfaceDescriptor> HIDKeyboard::GetInterfaces(u8 config) const
|
||||
{
|
||||
return m_interface_descriptor;
|
||||
}
|
||||
|
||||
std::vector<EndpointDescriptor> HIDKeyboard::GetEndpoints(u8 config, u8 interface, u8 alt) const
|
||||
{
|
||||
if (const auto it{m_endpoint_descriptor.find(interface)}; it != m_endpoint_descriptor.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool HIDKeyboard::Attach()
|
||||
{
|
||||
if (m_device_attached)
|
||||
return true;
|
||||
|
||||
INFO_LOG_FMT(IOS_USB, "[{:04x}:{:04x}] Opening emulated keyboard", m_vid, m_pid);
|
||||
m_keyboard_context = Common::KeyboardContext::GetInstance();
|
||||
m_worker.Reset("HID Keyboard", [this](auto transfer) { HandlePendingTransfer(transfer); });
|
||||
m_device_attached = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HIDKeyboard::AttachAndChangeInterface(const u8 interface)
|
||||
{
|
||||
if (!Attach())
|
||||
return false;
|
||||
|
||||
if (interface != m_active_interface)
|
||||
return ChangeInterface(interface) == 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int HIDKeyboard::CancelTransfer(const u8 endpoint)
|
||||
{
|
||||
if (endpoint != KEYBOARD_ENDPOINT)
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
IOS_USB,
|
||||
"[{:04x}:{:04x} {}] Cancelling transfers for invalid endpoint {:#x} (expected: {:#x})",
|
||||
m_vid, m_pid, m_active_interface, endpoint, KEYBOARD_ENDPOINT);
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
INFO_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Cancelling transfers (endpoint {:#x})", m_vid, m_pid,
|
||||
m_active_interface, endpoint);
|
||||
CancelPendingTransfers();
|
||||
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
int HIDKeyboard::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 HIDKeyboard::GetNumberOfAltSettings(u8 interface)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HIDKeyboard::SetAltSetting(u8 alt_setting)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HIDKeyboard::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);
|
||||
|
||||
auto& ios = cmd->GetEmulationKernel();
|
||||
|
||||
switch (cmd->request_type << 8 | cmd->request)
|
||||
{
|
||||
case USBHDR(DIR_DEVICE2HOST, TYPE_STANDARD, REC_INTERFACE, REQUEST_GET_INTERFACE):
|
||||
{
|
||||
constexpr 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)
|
||||
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& memory = ios.GetSystem().GetMemory();
|
||||
|
||||
// The data seems to report LED status for keys such as:
|
||||
// - NUM LOCK, CAPS LOCK
|
||||
const 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));
|
||||
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
|
||||
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);
|
||||
}
|
||||
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);
|
||||
ios.EnqueueIPCReply(cmd->ios_request, IPC_SUCCESS);
|
||||
}
|
||||
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
int HIDKeyboard::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);
|
||||
cmd->GetEmulationKernel().EnqueueIPCReply(cmd->ios_request, IPC_SUCCESS);
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
int HIDKeyboard::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) >= POLLING_RATE && ControlReference::GetInputGate();
|
||||
const Common::HIDPressedState state =
|
||||
should_poll ? m_keyboard_context->GetPressedState() : m_last_state;
|
||||
|
||||
// We can't use cmd->ScheduleTransferCompletion here as it might provoke
|
||||
// invalid memory access with scheduled transfers when CancelTransfer is called.
|
||||
EnqueueTransfer(std::move(cmd), state);
|
||||
|
||||
if (should_poll)
|
||||
{
|
||||
m_last_state = std::move(state);
|
||||
start_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
int HIDKeyboard::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 HIDKeyboard::EnqueueTransfer(std::unique_ptr<IntrMessage> msg,
|
||||
const Common::HIDPressedState& state)
|
||||
{
|
||||
msg->FillBuffer(reinterpret_cast<const u8*>(&state), sizeof(state));
|
||||
auto transfer = std::make_shared<PendingTransfer>(std::move(msg));
|
||||
m_worker.EmplaceItem(transfer);
|
||||
{
|
||||
std::lock_guard lock(m_pending_lock);
|
||||
m_pending_tranfers.insert(transfer);
|
||||
}
|
||||
}
|
||||
|
||||
void HIDKeyboard::HandlePendingTransfer(std::shared_ptr<PendingTransfer> transfer)
|
||||
{
|
||||
std::unique_lock lock(m_pending_lock);
|
||||
if (transfer->IsCanceled())
|
||||
return;
|
||||
|
||||
while (!transfer->IsReady())
|
||||
{
|
||||
lock.unlock();
|
||||
std::this_thread::sleep_for(POLLING_RATE / 2);
|
||||
lock.lock();
|
||||
|
||||
if (transfer->IsCanceled())
|
||||
return;
|
||||
}
|
||||
|
||||
transfer->Do();
|
||||
m_pending_tranfers.erase(transfer);
|
||||
}
|
||||
|
||||
void HIDKeyboard::CancelPendingTransfers()
|
||||
{
|
||||
m_worker.Cancel();
|
||||
{
|
||||
std::lock_guard lock(m_pending_lock);
|
||||
for (auto& transfer : m_pending_tranfers)
|
||||
transfer->Cancel();
|
||||
m_pending_tranfers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
HIDKeyboard::PendingTransfer::PendingTransfer(std::unique_ptr<IntrMessage> msg)
|
||||
{
|
||||
m_time = std::chrono::steady_clock::now();
|
||||
m_msg = std::move(msg);
|
||||
}
|
||||
|
||||
HIDKeyboard::PendingTransfer::~PendingTransfer()
|
||||
{
|
||||
if (!m_pending)
|
||||
return;
|
||||
// Value based on LibusbDevice's HandleTransfer implementation
|
||||
m_msg->ScheduleTransferCompletion(-5, 0);
|
||||
}
|
||||
|
||||
bool HIDKeyboard::PendingTransfer::IsReady() const
|
||||
{
|
||||
return (std::chrono::steady_clock::now() - m_time) >= POLLING_RATE;
|
||||
}
|
||||
|
||||
bool HIDKeyboard::PendingTransfer::IsCanceled() const
|
||||
{
|
||||
return m_is_canceled;
|
||||
}
|
||||
|
||||
void HIDKeyboard::PendingTransfer::Do()
|
||||
{
|
||||
m_msg->ScheduleTransferCompletion(IPC_SUCCESS, 0);
|
||||
m_pending = false;
|
||||
}
|
||||
|
||||
void HIDKeyboard::PendingTransfer::Cancel()
|
||||
{
|
||||
m_is_canceled = true;
|
||||
}
|
||||
} // namespace IOS::HLE::USB
|
|
@ -0,0 +1,96 @@
|
|||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Keyboard.h"
|
||||
#include "Common/WorkQueueThread.h"
|
||||
#include "Core/IOS/USB/Common.h"
|
||||
|
||||
namespace IOS::HLE::USB
|
||||
{
|
||||
class HIDKeyboard final : public Device
|
||||
{
|
||||
public:
|
||||
HIDKeyboard();
|
||||
~HIDKeyboard() override;
|
||||
|
||||
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;
|
||||
|
||||
static constexpr auto POLLING_RATE = std::chrono::milliseconds(8); // 125 Hz
|
||||
|
||||
private:
|
||||
class PendingTransfer
|
||||
{
|
||||
public:
|
||||
PendingTransfer(std::unique_ptr<IntrMessage> msg);
|
||||
~PendingTransfer();
|
||||
|
||||
bool IsReady() const;
|
||||
bool IsCanceled() const;
|
||||
void Do();
|
||||
void Cancel();
|
||||
|
||||
private:
|
||||
std::unique_ptr<IntrMessage> m_msg;
|
||||
std::chrono::steady_clock::time_point m_time;
|
||||
bool m_is_canceled = false;
|
||||
bool m_pending = true;
|
||||
};
|
||||
|
||||
void EnqueueTransfer(std::unique_ptr<IntrMessage> msg, const Common::HIDPressedState& state);
|
||||
void HandlePendingTransfer(std::shared_ptr<PendingTransfer> transfer);
|
||||
void CancelPendingTransfers();
|
||||
|
||||
Common::WorkQueueThreadSP<std::shared_ptr<PendingTransfer>> m_worker;
|
||||
std::mutex m_pending_lock;
|
||||
std::set<std::shared_ptr<PendingTransfer>> m_pending_tranfers;
|
||||
|
||||
HIDProtocol m_current_protocol = HIDProtocol::Report;
|
||||
Common::HIDPressedState m_last_state;
|
||||
std::shared_ptr<Common::KeyboardContext> m_keyboard_context;
|
||||
|
||||
// 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;
|
||||
const DeviceDescriptor m_device_descriptor{0x12, 0x01, 0x0110, 0x00, 0x00, 0x00, 0x08,
|
||||
0x05AC, 0x020C, 0x0395, 0x01, 0x03, 0x00, 0x01};
|
||||
const std::vector<ConfigDescriptor> m_config_descriptor{
|
||||
{0x09, 0x02, 0x003B, 0x02, 0x01, 0x00, 0xA0, 0x19}};
|
||||
static constexpr u8 INTERFACE_0 = 0;
|
||||
static constexpr u8 INTERFACE_1 = 1;
|
||||
static constexpr u8 KEYBOARD_ENDPOINT = 0x81;
|
||||
static constexpr u8 HUB_ENDPOINT = 0x82;
|
||||
const std::vector<InterfaceDescriptor> m_interface_descriptor{
|
||||
{0x09, 0x04, INTERFACE_0, 0x00, 0x01, 0x03, 0x01, 0x01, 0x00},
|
||||
{0x09, 0x04, INTERFACE_1, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00}};
|
||||
const std::map<u8, std::vector<EndpointDescriptor>> m_endpoint_descriptor{
|
||||
{INTERFACE_0, {{0x07, 0x05, KEYBOARD_ENDPOINT, 0x03, 0x0008, 0x0A}}},
|
||||
{INTERFACE_1, {{0x07, 0x05, HUB_ENDPOINT, 0x03, 0x0004, 0x0A}}},
|
||||
};
|
||||
};
|
||||
} // namespace IOS::HLE::USB
|
|
@ -21,6 +21,7 @@
|
|||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/IOS/USB/Common.h"
|
||||
#include "Core/IOS/USB/Emulated/HIDKeyboard.h"
|
||||
#include "Core/IOS/USB/Emulated/Infinity.h"
|
||||
#include "Core/IOS/USB/Emulated/Skylanders/Skylander.h"
|
||||
#include "Core/IOS/USB/Emulated/WiiSpeak.h"
|
||||
|
@ -187,6 +188,11 @@ void USBScanner::AddEmulatedDevices(DeviceMap* new_devices)
|
|||
auto wii_speak = std::make_unique<USB::WiiSpeak>();
|
||||
AddDevice(std::move(wii_speak), new_devices);
|
||||
}
|
||||
if (Config::Get(Config::MAIN_WII_KEYBOARD) && !NetPlay::IsNetPlayRunning())
|
||||
{
|
||||
auto keyboard = std::make_unique<USB::HIDKeyboard>();
|
||||
AddDevice(std::move(keyboard), new_devices);
|
||||
}
|
||||
}
|
||||
|
||||
void USBScanner::WakeupSantrollerDevice(libusb_device* device)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -132,6 +132,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" />
|
||||
|
@ -406,6 +407,7 @@
|
|||
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteDevice.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Common.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Emulated\HIDKeyboard.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Emulated\Infinity.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Emulated\Microphone.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Emulated\Skylanders\Skylander.h" />
|
||||
|
@ -830,6 +832,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" />
|
||||
|
@ -1076,6 +1079,7 @@
|
|||
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteDevice.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Common.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Emulated\HIDKeyboard.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Emulated\Infinity.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Emulated\Microphone.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Emulated\Skylanders\Skylander.cpp" />
|
||||
|
|
|
@ -247,6 +247,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
|
||||
|
|
|
@ -157,6 +157,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" />
|
||||
|
@ -376,6 +377,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" />
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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); });
|
||||
|
|
|
@ -95,6 +95,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"
|
||||
|
@ -583,6 +584,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
|
||||
|
@ -1454,6 +1456,19 @@ void MainWindow::ShowWiiSpeakWindow()
|
|||
m_wii_speak_window->activateWindow();
|
||||
}
|
||||
|
||||
void MainWindow::ShowKeyboard()
|
||||
{
|
||||
if (!m_keyboard_window)
|
||||
{
|
||||
m_keyboard_window = new KeyboardWindow();
|
||||
}
|
||||
|
||||
SetQWidgetWindowDecorations(m_keyboard_window);
|
||||
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()) ?
|
||||
|
|
|
@ -38,6 +38,7 @@ class GCTASInputWindow;
|
|||
class GraphicsWindow;
|
||||
class HotkeyScheduler;
|
||||
class InfinityBaseWindow;
|
||||
class KeyboardWindow;
|
||||
class JITWidget;
|
||||
class LogConfigWidget;
|
||||
class LogWidget;
|
||||
|
@ -179,6 +180,7 @@ private:
|
|||
void ShowSkylanderPortal();
|
||||
void ShowInfinityBase();
|
||||
void ShowWiiSpeakWindow();
|
||||
void ShowKeyboard();
|
||||
void ShowMemcardManager();
|
||||
void ShowResourcePackManager();
|
||||
void ShowCheatsManager();
|
||||
|
@ -253,6 +255,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;
|
||||
|
||||
|
|
|
@ -282,6 +282,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();
|
||||
|
|
|
@ -95,6 +95,7 @@ signals:
|
|||
void ShowSkylanderPortal();
|
||||
void ShowInfinityBase();
|
||||
void ShowWiiSpeakWindow();
|
||||
void ShowKeyboard();
|
||||
void ConnectWiiRemote(int id);
|
||||
|
||||
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||
|
|
|
@ -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"
|
||||
|
@ -791,6 +792,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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "InputCommon/ControllerInterface/SDL/SDL.h"
|
||||
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
|
@ -13,12 +14,46 @@
|
|||
#include <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*)>;
|
||||
|
||||
std::optional<const char*> UpdateKeyboardHandle(UniqueSDLWindow* unique_window)
|
||||
{
|
||||
std::optional<const char*> error;
|
||||
|
||||
// Doesn't seem to work with X11 until SDL 3.10:
|
||||
// - https://github.com/libsdl-org/SDL/pull/10467
|
||||
const 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, SDL_TRUE);
|
||||
}
|
||||
|
||||
Common::KeyboardContext::UpdateLayout();
|
||||
|
||||
return error;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace ciface::SDL
|
||||
{
|
||||
|
||||
|
@ -39,6 +74,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)
|
||||
|
@ -145,7 +181,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");
|
||||
|
@ -153,6 +189,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_
|
||||
|
@ -276,6 +315,44 @@ bool InputBackend::HandleEventAndContinue(const SDL_Event& e)
|
|||
{
|
||||
return false;
|
||||
}
|
||||
else if (e.type == Common::KeyboardContext::s_sdl_init_event_type)
|
||||
{
|
||||
if (const int error = SDL_InitSubSystem(SDL_INIT_VIDEO); error != 0)
|
||||
{
|
||||
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_KEYMAPCHANGED)
|
||||
{
|
||||
Common::KeyboardContext::UpdateLayout();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue