[WinKey] Rebindable keyboard controls.
This commit is contained in:
parent
5384e0e174
commit
5e31429128
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
// This is a partial file designed to be included by other files when
|
||||
// constructing various tables.
|
||||
|
||||
// clang-format off
|
||||
XE_HID_WINKEY_BINDING(DpadLeft, "DPAD_LEFT" , keybind_dpad_left , "^A" )
|
||||
XE_HID_WINKEY_BINDING(DpadRight, "DPAD_RIGHT" , keybind_dpad_right , "^D" )
|
||||
XE_HID_WINKEY_BINDING(DpadDown, "DPAD_DOWN" , keybind_dpad_down , "^S" )
|
||||
XE_HID_WINKEY_BINDING(DpadUp, "DPAD_UP" , keybind_dpad_up , "^W" )
|
||||
XE_HID_WINKEY_BINDING(LThumbLeft, "LEFT_THUMB_LEFT" , keybind_left_thumb_left , "_A" )
|
||||
XE_HID_WINKEY_BINDING(LThumbRight, "LEFT_THUMB_RIGHT" , keybind_left_thumb_right , "_D" )
|
||||
XE_HID_WINKEY_BINDING(LThumbDown, "LEFT_THUMB_DOWN" , keybind_left_thumb_down , "_S" )
|
||||
XE_HID_WINKEY_BINDING(LThumbUp, "LEFT_THUMB_UP" , keybind_left_thumb_up , "_W" )
|
||||
XE_HID_WINKEY_BINDING(LThumbPress, "LEFT_THUMB_PRESSED" , keybind_left_thumb , "F" )
|
||||
XE_HID_WINKEY_BINDING(RThumbUp, "RIGHT_THUMB_UP" , keybind_right_thumb_up , "0x26")
|
||||
XE_HID_WINKEY_BINDING(RThumbLeft, "RIGHT_THUMB_DOWN" , keybind_right_thumb_down , "0x28")
|
||||
XE_HID_WINKEY_BINDING(RThumbRight, "RIGHT_THUMB_RIGHT" , keybind_right_thumb_right, "0x27")
|
||||
XE_HID_WINKEY_BINDING(RThumbLeft, "RIGHT_THUMB_LEFT" , keybind_right_thumb_left , "0x25")
|
||||
XE_HID_WINKEY_BINDING(RThumbPress, "RIGHT_THUMB_PRESSED", keybind_right_thumb , "K" )
|
||||
XE_HID_WINKEY_BINDING(X, "X" , keybind_x , "L" )
|
||||
XE_HID_WINKEY_BINDING(B, "B" , keybind_b , "0xDE")
|
||||
XE_HID_WINKEY_BINDING(A, "A" , keybind_a , "0xBA")
|
||||
XE_HID_WINKEY_BINDING(Y, "Y" , keybind_y , "P" )
|
||||
XE_HID_WINKEY_BINDING(LTrigger, "LEFT_TRIGGER" , keybind_left_trigger , "Q I" )
|
||||
XE_HID_WINKEY_BINDING(RTrigger, "RIGHT_TRIGGER" , keybind_right_trigger , "E O" )
|
||||
XE_HID_WINKEY_BINDING(Back, "BACK" , keybind_back , "Z" )
|
||||
XE_HID_WINKEY_BINDING(Start, "START" , keybind_start , "X" )
|
||||
XE_HID_WINKEY_BINDING(LShoulder, "LEFT_SHOULDER" , keybind_left_shoulder , "1" )
|
||||
XE_HID_WINKEY_BINDING(RShoulder, "RIGHT_SHOULDER" , keybind_right_shoulder , "3" )
|
||||
// clang-format on
|
|
@ -2,23 +2,84 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/hid/winkey/winkey_input_driver.h"
|
||||
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/platform_win.h"
|
||||
#include "xenia/hid/hid_flags.h"
|
||||
#include "xenia/hid/input_system.h"
|
||||
#include "xenia/ui/virtual_key.h"
|
||||
#include "xenia/ui/window.h"
|
||||
|
||||
#define XE_HID_WINKEY_BINDING(button, description, cvar_name, \
|
||||
cvar_default_value) \
|
||||
DEFINE_string(cvar_name, cvar_default_value, \
|
||||
"List of keys to bind to " description \
|
||||
", separated by spaces", \
|
||||
"HID.WinKey")
|
||||
#include "winkey_binding_table.inc"
|
||||
#undef XE_HID_WINKEY_BINDING
|
||||
|
||||
namespace xe {
|
||||
namespace hid {
|
||||
namespace winkey {
|
||||
|
||||
bool __inline IsKeyToggled(uint8_t key) {
|
||||
return (GetKeyState(key) & 0x1) == 0x1;
|
||||
}
|
||||
|
||||
bool __inline IsKeyDown(uint8_t key) {
|
||||
return (GetAsyncKeyState(key) & 0x8000) == 0x8000;
|
||||
}
|
||||
|
||||
bool __inline IsKeyDown(ui::VirtualKey virtual_key) {
|
||||
return IsKeyDown(static_cast<uint8_t>(virtual_key));
|
||||
}
|
||||
|
||||
void WinKeyInputDriver::ParseKeyBinding(ui::VirtualKey output_key,
|
||||
const std::string_view description,
|
||||
const std::string_view source_tokens) {
|
||||
for (const std::string_view source_token :
|
||||
utf8::split(source_tokens, " ", true)) {
|
||||
KeyBinding key_binding;
|
||||
key_binding.output_key = output_key;
|
||||
|
||||
std::string_view token = source_token;
|
||||
|
||||
if (utf8::starts_with(token, "_")) {
|
||||
key_binding.lowercase = true;
|
||||
token = token.substr(1);
|
||||
} else if (utf8::starts_with(token, "^")) {
|
||||
key_binding.uppercase = true;
|
||||
token = token.substr(1);
|
||||
}
|
||||
|
||||
if (utf8::starts_with(token, "0x")) {
|
||||
token = token.substr(2);
|
||||
key_binding.input_key = static_cast<ui::VirtualKey>(
|
||||
string_util::from_string<uint16_t>(token, true));
|
||||
} else if (token.size() == 1 && (token[0] >= 'A' && token[0] <= 'Z') ||
|
||||
(token[0] >= '0' && token[0] <= '9')) {
|
||||
key_binding.input_key = static_cast<ui::VirtualKey>(token[0]);
|
||||
}
|
||||
|
||||
if (key_binding.input_key == ui::VirtualKey::kNone) {
|
||||
XELOGW("winkey: failed to parse binding \"{}\" for controller input {}.",
|
||||
source_token, description);
|
||||
continue;
|
||||
}
|
||||
|
||||
key_bindings_.push_back(key_binding);
|
||||
XELOGI("winkey: \"{}\" binds key 0x{:X} to controller input {}.",
|
||||
source_token, key_binding.input_key, description);
|
||||
}
|
||||
}
|
||||
|
||||
WinKeyInputDriver::WinKeyInputDriver(xe::ui::Window* window)
|
||||
: InputDriver(window), packet_number_(1) {
|
||||
// Register a key listener.
|
||||
|
@ -50,6 +111,13 @@ WinKeyInputDriver::WinKeyInputDriver(xe::ui::Window* window)
|
|||
key.repeat_count = evt->repeat_count();
|
||||
key_events_.push(key);
|
||||
});
|
||||
|
||||
#define XE_HID_WINKEY_BINDING(button, description, cvar_name, \
|
||||
cvar_default_value) \
|
||||
ParseKeyBinding(xe::ui::VirtualKey::kXInputPad##button, description, \
|
||||
cvars::cvar_name);
|
||||
#include "winkey_binding_table.inc"
|
||||
#undef XE_HID_WINKEY_BINDING
|
||||
}
|
||||
|
||||
WinKeyInputDriver::~WinKeyInputDriver() = default;
|
||||
|
@ -78,9 +146,6 @@ X_RESULT WinKeyInputDriver::GetCapabilities(uint32_t user_index, uint32_t flags,
|
|||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
#define IS_KEY_TOGGLED(key) ((GetKeyState(key) & 0x1) == 0x1)
|
||||
#define IS_KEY_DOWN(key) ((GetAsyncKeyState(key) & 0x8000) == 0x8000)
|
||||
|
||||
X_RESULT WinKeyInputDriver::GetState(uint32_t user_index,
|
||||
X_INPUT_STATE* out_state) {
|
||||
if (user_index != 0) {
|
||||
|
@ -98,114 +163,86 @@ X_RESULT WinKeyInputDriver::GetState(uint32_t user_index,
|
|||
int16_t thumb_ry = 0;
|
||||
|
||||
if (window()->has_focus() && is_active()) {
|
||||
if (IS_KEY_TOGGLED(VK_CAPITAL) || IS_KEY_DOWN(VK_SHIFT)) {
|
||||
// dpad toggled
|
||||
if (IS_KEY_DOWN('A')) {
|
||||
// A
|
||||
buttons |= 0x0004; // XINPUT_GAMEPAD_DPAD_LEFT
|
||||
bool capital = IsKeyToggled(VK_CAPITAL) || IsKeyDown(VK_SHIFT);
|
||||
for (const KeyBinding& b : key_bindings_) {
|
||||
if (((b.lowercase == b.uppercase) || (b.lowercase && !capital) ||
|
||||
(b.uppercase && capital)) &&
|
||||
IsKeyDown(b.input_key)) {
|
||||
switch (b.output_key) {
|
||||
case ui::VirtualKey::kXInputPadA:
|
||||
buttons |= 0x1000; // XINPUT_GAMEPAD_A
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadY:
|
||||
buttons |= 0x8000; // XINPUT_GAMEPAD_Y
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadB:
|
||||
buttons |= 0x2000; // XINPUT_GAMEPAD_B
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadX:
|
||||
buttons |= 0x4000; // XINPUT_GAMEPAD_X
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadDpadLeft:
|
||||
buttons |= 0x0004; // XINPUT_GAMEPAD_DPAD_LEFT
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadDpadRight:
|
||||
buttons |= 0x0008; // XINPUT_GAMEPAD_DPAD_RIGHT
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadDpadDown:
|
||||
buttons |= 0x0002; // XINPUT_GAMEPAD_DPAD_DOWN
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadDpadUp:
|
||||
buttons |= 0x0001; // XINPUT_GAMEPAD_DPAD_UP
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadRThumbPress:
|
||||
buttons |= 0x0080; // XINPUT_GAMEPAD_RIGHT_THUMB
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadLThumbPress:
|
||||
buttons |= 0x0040; // XINPUT_GAMEPAD_LEFT_THUMB
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadBack:
|
||||
buttons |= 0x0020; // XINPUT_GAMEPAD_BACK
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadStart:
|
||||
buttons |= 0x0010; // XINPUT_GAMEPAD_START
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadLShoulder:
|
||||
buttons |= 0x0100; // XINPUT_GAMEPAD_LEFT_SHOULDER
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadRShoulder:
|
||||
buttons |= 0x0200; // XINPUT_GAMEPAD_RIGHT_SHOULDER
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadLTrigger:
|
||||
left_trigger = 0xFF;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadRTrigger:
|
||||
right_trigger = 0xFF;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadLThumbLeft:
|
||||
thumb_lx += SHRT_MIN;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadLThumbRight:
|
||||
thumb_lx += SHRT_MAX;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadLThumbDown:
|
||||
thumb_ly += SHRT_MIN;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadLThumbUp:
|
||||
thumb_ly += SHRT_MAX;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadRThumbUp:
|
||||
thumb_ry += SHRT_MAX;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadRThumbDown:
|
||||
thumb_ry += SHRT_MIN;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadRThumbRight:
|
||||
thumb_rx += SHRT_MAX;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadRThumbLeft:
|
||||
thumb_rx += SHRT_MIN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (IS_KEY_DOWN('D')) {
|
||||
// D
|
||||
buttons |= 0x0008; // XINPUT_GAMEPAD_DPAD_RIGHT
|
||||
}
|
||||
if (IS_KEY_DOWN('S')) {
|
||||
// S
|
||||
buttons |= 0x0002; // XINPUT_GAMEPAD_DPAD_DOWN
|
||||
}
|
||||
if (IS_KEY_DOWN('W')) {
|
||||
// W
|
||||
buttons |= 0x0001; // XINPUT_GAMEPAD_DPAD_UP
|
||||
}
|
||||
} else {
|
||||
// left stick
|
||||
if (IS_KEY_DOWN('A')) {
|
||||
// A
|
||||
thumb_lx += SHRT_MIN;
|
||||
}
|
||||
if (IS_KEY_DOWN('D')) {
|
||||
// D
|
||||
thumb_lx += SHRT_MAX;
|
||||
}
|
||||
if (IS_KEY_DOWN('S')) {
|
||||
// S
|
||||
thumb_ly += SHRT_MIN;
|
||||
}
|
||||
if (IS_KEY_DOWN('W')) {
|
||||
// W
|
||||
thumb_ly += SHRT_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
if (IS_KEY_DOWN('F')) {
|
||||
// F
|
||||
buttons |= 0x0040; // XINPUT_GAMEPAD_LEFT_THUMB
|
||||
}
|
||||
|
||||
// Right stick
|
||||
if (IS_KEY_DOWN(VK_UP)) {
|
||||
// Up
|
||||
thumb_ry += SHRT_MAX;
|
||||
}
|
||||
if (IS_KEY_DOWN(VK_DOWN)) {
|
||||
// Down
|
||||
thumb_ry += SHRT_MIN;
|
||||
}
|
||||
if (IS_KEY_DOWN(VK_RIGHT)) {
|
||||
// Right
|
||||
thumb_rx += SHRT_MAX;
|
||||
}
|
||||
if (IS_KEY_DOWN(VK_LEFT)) {
|
||||
// Left
|
||||
thumb_rx += SHRT_MIN;
|
||||
}
|
||||
|
||||
if (IS_KEY_DOWN('L')) {
|
||||
// L
|
||||
buttons |= 0x4000; // XINPUT_GAMEPAD_X
|
||||
}
|
||||
if (IS_KEY_DOWN(VK_OEM_7)) {
|
||||
// '
|
||||
buttons |= 0x2000; // XINPUT_GAMEPAD_B
|
||||
}
|
||||
if (IS_KEY_DOWN(VK_OEM_1)) {
|
||||
// ;
|
||||
buttons |= 0x1000; // XINPUT_GAMEPAD_A
|
||||
}
|
||||
if (IS_KEY_DOWN('P')) {
|
||||
// P
|
||||
buttons |= 0x8000; // XINPUT_GAMEPAD_Y
|
||||
}
|
||||
|
||||
if (IS_KEY_DOWN('K')) {
|
||||
// K
|
||||
buttons |= 0x0080; // XINPUT_GAMEPAD_RIGHT_THUMB
|
||||
}
|
||||
|
||||
if (IS_KEY_DOWN('Q') || IS_KEY_DOWN('I')) {
|
||||
// Q / I
|
||||
left_trigger = 0xFF;
|
||||
}
|
||||
|
||||
if (IS_KEY_DOWN('E') || IS_KEY_DOWN('O')) {
|
||||
// E / O
|
||||
right_trigger = 0xFF;
|
||||
}
|
||||
|
||||
if (IS_KEY_DOWN('Z')) {
|
||||
// Z
|
||||
buttons |= 0x0020; // XINPUT_GAMEPAD_BACK
|
||||
}
|
||||
if (IS_KEY_DOWN('X')) {
|
||||
// X
|
||||
buttons |= 0x0010; // XINPUT_GAMEPAD_START
|
||||
}
|
||||
if (IS_KEY_DOWN('1')) {
|
||||
// 1
|
||||
buttons |= 0x0100; // XINPUT_GAMEPAD_LEFT_SHOULDER
|
||||
}
|
||||
if (IS_KEY_DOWN('3')) {
|
||||
// 3
|
||||
buttons |= 0x0200; // XINPUT_GAMEPAD_RIGHT_SHOULDER
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -259,90 +296,13 @@ X_RESULT WinKeyInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
|
|||
key_events_.pop();
|
||||
}
|
||||
|
||||
switch (evt.virtual_key) {
|
||||
case ui::VirtualKey::kOem1: // ;
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadA;
|
||||
break;
|
||||
case ui::VirtualKey::kOem7: // '
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadB;
|
||||
break;
|
||||
case ui::VirtualKey::kL:
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadX;
|
||||
break;
|
||||
case ui::VirtualKey::kP:
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadY;
|
||||
break;
|
||||
case ui::VirtualKey::k3:
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadRShoulder;
|
||||
break;
|
||||
case ui::VirtualKey::k1:
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadLShoulder;
|
||||
break;
|
||||
case ui::VirtualKey::kQ:
|
||||
case ui::VirtualKey::kI:
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadLTrigger;
|
||||
break;
|
||||
case ui::VirtualKey::kE:
|
||||
case ui::VirtualKey::kO:
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadRTrigger;
|
||||
break;
|
||||
case ui::VirtualKey::kX:
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadStart;
|
||||
break;
|
||||
case ui::VirtualKey::kZ:
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadBack;
|
||||
break;
|
||||
case ui::VirtualKey::kUp:
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadRThumbUp;
|
||||
break;
|
||||
case ui::VirtualKey::kDown:
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadRThumbDown;
|
||||
break;
|
||||
case ui::VirtualKey::kRight:
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadRThumbRight;
|
||||
break;
|
||||
case ui::VirtualKey::kLeft:
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadRThumbLeft;
|
||||
break;
|
||||
default:
|
||||
// TODO(DrChat): Some other way to toggle this...
|
||||
if (IS_KEY_TOGGLED(VK_CAPITAL) || IS_KEY_DOWN(VK_SHIFT)) {
|
||||
// D-pad toggled.
|
||||
switch (evt.virtual_key) {
|
||||
case ui::VirtualKey::kW:
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadDpadUp;
|
||||
break;
|
||||
case ui::VirtualKey::kS:
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadDpadDown;
|
||||
break;
|
||||
case ui::VirtualKey::kA:
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadDpadLeft;
|
||||
break;
|
||||
case ui::VirtualKey::kD:
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadDpadRight;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Left thumbstick.
|
||||
switch (evt.virtual_key) {
|
||||
case ui::VirtualKey::kW:
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadLThumbUp;
|
||||
break;
|
||||
case ui::VirtualKey::kS:
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadLThumbDown;
|
||||
break;
|
||||
case ui::VirtualKey::kA:
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadLThumbLeft;
|
||||
break;
|
||||
case ui::VirtualKey::kD:
|
||||
xinput_virtual_key = ui::VirtualKey::kXInputPadLThumbRight;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool capital = IsKeyToggled(VK_CAPITAL) || IsKeyDown(VK_SHIFT);
|
||||
for (const KeyBinding& b : key_bindings_) {
|
||||
if (b.input_key == evt.virtual_key &&
|
||||
((b.lowercase == b.uppercase) || (b.lowercase && !capital) ||
|
||||
(b.uppercase && capital))) {
|
||||
xinput_virtual_key = b.output_key;
|
||||
}
|
||||
}
|
||||
|
||||
if (xinput_virtual_key != ui::VirtualKey::kNone) {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -42,10 +42,22 @@ class WinKeyInputDriver : public InputDriver {
|
|||
bool prev_state = false; // down(true) or up(false)
|
||||
};
|
||||
|
||||
struct KeyBinding {
|
||||
ui::VirtualKey input_key = ui::VirtualKey::kNone;
|
||||
ui::VirtualKey output_key = ui::VirtualKey::kNone;
|
||||
bool uppercase = false;
|
||||
bool lowercase = false;
|
||||
};
|
||||
|
||||
xe::global_critical_region global_critical_region_;
|
||||
std::queue<KeyEvent> key_events_;
|
||||
std::vector<KeyBinding> key_bindings_;
|
||||
|
||||
uint32_t packet_number_;
|
||||
|
||||
void ParseKeyBinding(ui::VirtualKey virtual_key,
|
||||
const std::string_view description,
|
||||
const std::string_view binding);
|
||||
};
|
||||
|
||||
} // namespace winkey
|
||||
|
|
Loading…
Reference in New Issue