Fixes for win32 key handling.
This commit is contained in:
parent
f3c70dc300
commit
4d11079095
|
@ -5,6 +5,7 @@
|
|||
#include "CThread.h"
|
||||
#include "CLog.h"
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
|
||||
//
|
||||
// CMSWindowsSecondaryScreen
|
||||
|
@ -40,7 +41,7 @@ static BOOL CALLBACK WINAPI debugProc(HWND, UINT msg, WPARAM wParam, LPARAM lPar
|
|||
|
||||
case WM_APP:
|
||||
if (!s_logMore.empty()) {
|
||||
if (s_log.size() > 20000)
|
||||
if (s_log.size() > 40000)
|
||||
s_log = s_logMore;
|
||||
else
|
||||
s_log += s_logMore;
|
||||
|
@ -189,15 +190,12 @@ void CMSWindowsSecondaryScreen::keyDown(
|
|||
|
||||
// get the sequence of keys to simulate key press and the final
|
||||
// modifier state.
|
||||
m_mask = mapKey(keys, virtualKey, key, mask, true);
|
||||
m_mask = mapKey(keys, virtualKey, key, mask, kPress);
|
||||
if (keys.empty())
|
||||
return;
|
||||
|
||||
// generate key events
|
||||
for (Keystrokes::const_iterator k = keys.begin(); k != keys.end(); ++k) {
|
||||
const UINT code = MapVirtualKey(k->first, 0);
|
||||
keybd_event(k->first, code, k->second ? 0 : KEYEVENTF_KEYUP, 0);
|
||||
}
|
||||
doKeystrokes(keys, 1);
|
||||
|
||||
// note that key is now down
|
||||
m_keys[virtualKey] |= 0x80;
|
||||
|
@ -209,22 +207,14 @@ void CMSWindowsSecondaryScreen::keyRepeat(
|
|||
Keystrokes keys;
|
||||
UINT virtualKey;
|
||||
|
||||
// get the sequence of keys to simulate key release and the final
|
||||
// get the sequence of keys to simulate key repeat and the final
|
||||
// modifier state.
|
||||
m_mask = mapKey(keys, virtualKey, key, mask, true);
|
||||
m_mask = mapKey(keys, virtualKey, key, mask, kRepeat);
|
||||
if (keys.empty())
|
||||
return;
|
||||
|
||||
// generate key events
|
||||
// YYY -- need to know which code in Keystrokes should be repeated;
|
||||
// then repeat only that key count times
|
||||
for (SInt32 i = 0; i < count; ++i) {
|
||||
for (Keystrokes::const_iterator k = keys.begin();
|
||||
k != keys.end(); ++k) {
|
||||
const UINT code = MapVirtualKey(k->first, 0);
|
||||
keybd_event(k->first, code, k->second ? 0 : KEYEVENTF_KEYUP, 0);
|
||||
}
|
||||
}
|
||||
doKeystrokes(keys, count);
|
||||
}
|
||||
|
||||
void CMSWindowsSecondaryScreen::keyUp(
|
||||
|
@ -235,15 +225,12 @@ void CMSWindowsSecondaryScreen::keyUp(
|
|||
|
||||
// get the sequence of keys to simulate key release and the final
|
||||
// modifier state.
|
||||
m_mask = mapKey(keys, virtualKey, key, mask, false);
|
||||
m_mask = mapKey(keys, virtualKey, key, mask, kRelease);
|
||||
if (keys.empty())
|
||||
return;
|
||||
|
||||
// generate key events
|
||||
for (Keystrokes::const_iterator k = keys.begin(); k != keys.end(); ++k) {
|
||||
const UINT code = MapVirtualKey(k->first, 0);
|
||||
keybd_event(k->first, code, k->second ? 0 : KEYEVENTF_KEYUP, 0);
|
||||
}
|
||||
doKeystrokes(keys, 1);
|
||||
|
||||
// note that key is now up
|
||||
m_keys[virtualKey] &= ~0x80;
|
||||
|
@ -252,7 +239,7 @@ void CMSWindowsSecondaryScreen::keyUp(
|
|||
void CMSWindowsSecondaryScreen::mouseDown(ButtonID button)
|
||||
{
|
||||
// map button id to button flag
|
||||
DWORD flags = mapButton(button);
|
||||
DWORD flags = mapButton(button, true);
|
||||
|
||||
// send event
|
||||
if (flags != 0)
|
||||
|
@ -262,7 +249,7 @@ void CMSWindowsSecondaryScreen::mouseDown(ButtonID button)
|
|||
void CMSWindowsSecondaryScreen::mouseUp(ButtonID button)
|
||||
{
|
||||
// map button id to button flag
|
||||
DWORD flags = mapButton(button);
|
||||
DWORD flags = mapButton(button, false);
|
||||
|
||||
// send event
|
||||
if (flags != 0)
|
||||
|
@ -786,7 +773,7 @@ static const UINT g_function[] =
|
|||
/* 0x08 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/* 0x10 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/* 0x18 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/* 0x20 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/* 0x20 */ VK_TAB, 0, 0, 0, 0, 0, 0, 0,
|
||||
/* 0x28 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/* 0x30 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/* 0x38 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
|
@ -815,10 +802,6 @@ static const UINT g_function[] =
|
|||
/* 0xf0 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/* 0xf8 */ 0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
// FIXME -- will this work?
|
||||
// 0x100 + = shift
|
||||
// 0x200 + = ctrl
|
||||
// 0x400 + = alt
|
||||
/* XK_KP_Space to XK_KP_Equal */
|
||||
static const UINT g_miscellany[] =
|
||||
{
|
||||
|
@ -919,18 +902,18 @@ static const UINT* g_mapTable[] =
|
|||
};
|
||||
|
||||
DWORD CMSWindowsSecondaryScreen::mapButton(
|
||||
ButtonID button) const
|
||||
ButtonID button, bool press) const
|
||||
{
|
||||
// map button id to button flag
|
||||
switch (button) {
|
||||
case kButtonLeft:
|
||||
return MOUSEEVENTF_LEFTDOWN;
|
||||
return press ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP;
|
||||
|
||||
case kButtonMiddle:
|
||||
return MOUSEEVENTF_MIDDLEDOWN;
|
||||
return press ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP;
|
||||
|
||||
case kButtonRight:
|
||||
return MOUSEEVENTF_RIGHTDOWN;
|
||||
return press ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
|
@ -941,7 +924,7 @@ KeyModifierMask CMSWindowsSecondaryScreen::mapKey(
|
|||
Keystrokes& keys,
|
||||
UINT& virtualKey,
|
||||
KeyID id, KeyModifierMask mask,
|
||||
bool press) const
|
||||
EKeyAction action) const
|
||||
{
|
||||
// lookup the key table
|
||||
const UInt32 mapID = ((id >> 8) & 0xff);
|
||||
|
@ -959,17 +942,20 @@ KeyModifierMask CMSWindowsSecondaryScreen::mapKey(
|
|||
(KeyModifierCapsLock |
|
||||
KeyModifierNumLock |
|
||||
KeyModifierScrollLock));
|
||||
log((CLOG_DEBUG2 "key id %d -> virtual key %d", id, virtualKey));
|
||||
|
||||
// if not in map then ask system to convert ascii character
|
||||
if (virtualKey == 0) {
|
||||
if (mapID != 0) {
|
||||
// not ascii
|
||||
log((CLOG_DEBUG2 "not ascii"));
|
||||
return m_mask;
|
||||
}
|
||||
|
||||
// translate. return no keys if unknown key.
|
||||
SHORT vk = VkKeyScan(code);
|
||||
if (vk == 0xffff) {
|
||||
log((CLOG_DEBUG2 "no virtual key for character %d", code));
|
||||
return m_mask;
|
||||
}
|
||||
|
||||
|
@ -980,11 +966,30 @@ KeyModifierMask CMSWindowsSecondaryScreen::mapKey(
|
|||
outMask |= KeyModifierControl;
|
||||
if (HIBYTE(vk) & 4)
|
||||
outMask |= KeyModifierAlt;
|
||||
log((CLOG_DEBUG2 "character %d to virtual key %d mask 0x%04x", code, LOBYTE(vk), outMask));
|
||||
|
||||
// if caps-lock is on and so is shift then turn off caps-lock
|
||||
if (outMask & (KeyModifierShift | KeyModifierCapsLock) ==
|
||||
(KeyModifierShift | KeyModifierCapsLock))
|
||||
outMask &= ~KeyModifierCapsLock;
|
||||
// handle combination of caps-lock and shift. if caps-lock is
|
||||
// off locally then use shift as necessary. if caps-lock is on
|
||||
// locally then shift reverses its meaning (for keys that are
|
||||
// subject to case conversion).
|
||||
if ((m_mask & KeyModifierCapsLock) != 0) {
|
||||
// caps-lock is on. note if shift is required.
|
||||
log((CLOG_DEBUG2 "caps-lock is on"));
|
||||
const bool needShift = ((outMask & KeyModifierShift) != 0);
|
||||
|
||||
// if needShift is true then see if the key is subject to
|
||||
// case conversion. if it is then caps-lock and shift
|
||||
// cancel out. if not then caps-lock is ignored and shift
|
||||
// is required.
|
||||
if (needShift) {
|
||||
// FIXME -- there should be some system call to test
|
||||
// if a key is subject to case conversion.
|
||||
if (tolower(code) != toupper(code)) {
|
||||
log((CLOG_DEBUG2 "turn off shift"));
|
||||
outMask &= ~KeyModifierShift;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get virtual key
|
||||
virtualKey = LOBYTE(vk);
|
||||
|
@ -1001,11 +1006,22 @@ KeyModifierMask CMSWindowsSecondaryScreen::mapKey(
|
|||
// virtual keys but not for numeric keys.
|
||||
if (virtualKey >= VK_NUMPAD0 && virtualKey <= VK_DIVIDE) {
|
||||
// set required shift state based on current numlock state
|
||||
if ((outMask & KeyModifierNumLock) == 0)
|
||||
if ((outMask & KeyModifierNumLock) == 0) {
|
||||
if ((m_mask & KeyModifierNumLock) == 0) {
|
||||
log((CLOG_DEBUG2 "turn on num lock for keypad key"));
|
||||
outMask |= KeyModifierNumLock;
|
||||
}
|
||||
else {
|
||||
log((CLOG_DEBUG2 "turn on shift for keypad key"));
|
||||
outMask |= KeyModifierShift;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME -- should check for LeftTab KeySym
|
||||
// check for ISO_Left_Tab
|
||||
else if (id == 0xfe20) {
|
||||
outMask |= KeyModifierShift;
|
||||
}
|
||||
}
|
||||
|
||||
// a list of modifier key info
|
||||
|
@ -1078,6 +1094,7 @@ KeyModifierMask CMSWindowsSecondaryScreen::mapKey(
|
|||
// a modifier key then skip this because modifiers should not
|
||||
// modify modifiers.
|
||||
Keystrokes undo;
|
||||
Keystroke keystroke;
|
||||
if (outMask != m_mask && !isModifier) {
|
||||
for (unsigned int i = 0; i < s_numModifiers; ++i) {
|
||||
KeyModifierMask bit = s_modifier[i].mask;
|
||||
|
@ -1087,15 +1104,20 @@ KeyModifierMask CMSWindowsSecondaryScreen::mapKey(
|
|||
// modifier is a toggle then toggle it on with a
|
||||
// press/release, otherwise activate it with a
|
||||
// press.
|
||||
const UINT modifierKey = s_modifier[i].virtualKey;
|
||||
keys.push_back(std::make_pair(modifierKey, true));
|
||||
keystroke.m_virtualKey = s_modifier[i].virtualKey;
|
||||
keystroke.m_press = true;
|
||||
keystroke.m_repeat = false;
|
||||
keys.push_back(keystroke);
|
||||
if (s_modifier[i].isToggle) {
|
||||
keys.push_back(std::make_pair(modifierKey, false));
|
||||
undo.push_back(std::make_pair(modifierKey, false));
|
||||
undo.push_back(std::make_pair(modifierKey, true));
|
||||
keystroke.m_press = false;
|
||||
keys.push_back(keystroke);
|
||||
undo.push_back(keystroke);
|
||||
keystroke.m_press = true;
|
||||
undo.push_back(keystroke);
|
||||
}
|
||||
else {
|
||||
undo.push_back(std::make_pair(modifierKey, false));
|
||||
keystroke.m_press = false;
|
||||
undo.push_back(keystroke);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1106,22 +1128,34 @@ KeyModifierMask CMSWindowsSecondaryScreen::mapKey(
|
|||
// release. we must check each keycode for the
|
||||
// modifier if not a toggle.
|
||||
if (s_modifier[i].isToggle) {
|
||||
const UINT modifierKey = s_modifier[i].virtualKey;
|
||||
keys.push_back(std::make_pair(modifierKey, true));
|
||||
keys.push_back(std::make_pair(modifierKey, false));
|
||||
undo.push_back(std::make_pair(modifierKey, false));
|
||||
undo.push_back(std::make_pair(modifierKey, true));
|
||||
keystroke.m_virtualKey = s_modifier[i].virtualKey;
|
||||
keystroke.m_press = true;
|
||||
keystroke.m_repeat = false;
|
||||
keys.push_back(keystroke);
|
||||
keystroke.m_press = false;
|
||||
keys.push_back(keystroke);
|
||||
undo.push_back(keystroke);
|
||||
keystroke.m_press = true;
|
||||
undo.push_back(keystroke);
|
||||
}
|
||||
else {
|
||||
UINT key = s_modifier[i].virtualKey;
|
||||
if ((m_keys[key] & 0x80) != 0) {
|
||||
keys.push_back(std::make_pair(key, false));
|
||||
undo.push_back(std::make_pair(key, true));
|
||||
keystroke.m_virtualKey = key;
|
||||
keystroke.m_press = false;
|
||||
keystroke.m_repeat = false;
|
||||
keys.push_back(keystroke);
|
||||
keystroke.m_press = true;
|
||||
undo.push_back(keystroke);
|
||||
}
|
||||
key = s_modifier[i].virtualKey2;
|
||||
if ((m_keys[key] & 0x80) != 0) {
|
||||
keys.push_back(std::make_pair(key, false));
|
||||
undo.push_back(std::make_pair(key, true));
|
||||
if (key != 0 && (m_keys[key] & 0x80) != 0) {
|
||||
keystroke.m_virtualKey = key;
|
||||
keystroke.m_press = false;
|
||||
keystroke.m_repeat = false;
|
||||
keys.push_back(keystroke);
|
||||
keystroke.m_press = true;
|
||||
undo.push_back(keystroke);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1130,7 +1164,26 @@ KeyModifierMask CMSWindowsSecondaryScreen::mapKey(
|
|||
}
|
||||
|
||||
// add the key event
|
||||
keys.push_back(std::make_pair(virtualKey, press));
|
||||
keystroke.m_virtualKey = virtualKey;
|
||||
switch (action) {
|
||||
case kPress:
|
||||
keystroke.m_press = true;
|
||||
keystroke.m_repeat = false;
|
||||
keys.push_back(keystroke);
|
||||
break;
|
||||
|
||||
case kRelease:
|
||||
keystroke.m_press = false;
|
||||
keystroke.m_repeat = false;
|
||||
keys.push_back(keystroke);
|
||||
break;
|
||||
|
||||
case kRepeat:
|
||||
keystroke.m_press = true;
|
||||
keystroke.m_repeat = true;
|
||||
keys.push_back(keystroke);
|
||||
break;
|
||||
}
|
||||
|
||||
// add key events to restore the modifier state. apply events in
|
||||
// the reverse order that they're stored in undo.
|
||||
|
@ -1142,16 +1195,15 @@ KeyModifierMask CMSWindowsSecondaryScreen::mapKey(
|
|||
// if the key is a modifier key then compute the modifier mask after
|
||||
// this key is pressed.
|
||||
mask = m_mask;
|
||||
if (isModifier) {
|
||||
// toggle keys modify the state on press if toggling on and on
|
||||
// release if toggling off. other keys set the bit on press
|
||||
// and clear the bit on release.
|
||||
// FIXME -- verify if that's true on win32
|
||||
if (isModifier && action != kRepeat) {
|
||||
// toggle keys modify the state on release. other keys set
|
||||
// the bit on press and clear the bit on release.
|
||||
if (s_modifier[modifierIndex].isToggle) {
|
||||
if (((mask & s_modifier[modifierIndex].mask) == 0) == press)
|
||||
if (action == kRelease) {
|
||||
mask ^= s_modifier[modifierIndex].mask;
|
||||
}
|
||||
else if (press) {
|
||||
}
|
||||
else if (action == kPress) {
|
||||
mask |= s_modifier[modifierIndex].mask;
|
||||
}
|
||||
else {
|
||||
|
@ -1161,7 +1213,8 @@ KeyModifierMask CMSWindowsSecondaryScreen::mapKey(
|
|||
if ((m_keys[s_modifier[modifierIndex].virtualKey] & 0x80) != 0) {
|
||||
down = true;
|
||||
}
|
||||
if ((m_keys[s_modifier[modifierIndex].virtualKey2] & 0x80) != 0) {
|
||||
if (s_modifier[modifierIndex].virtualKey2 != 0 &&
|
||||
(m_keys[s_modifier[modifierIndex].virtualKey2] & 0x80) != 0) {
|
||||
down = true;
|
||||
}
|
||||
if (!down)
|
||||
|
@ -1169,12 +1222,65 @@ KeyModifierMask CMSWindowsSecondaryScreen::mapKey(
|
|||
}
|
||||
}
|
||||
|
||||
log((CLOG_DEBUG2 "previous modifiers 0x%04x, final modifiers 0x%04x", m_mask, mask));
|
||||
return mask;
|
||||
}
|
||||
|
||||
void CMSWindowsSecondaryScreen::doKeystrokes(
|
||||
const Keystrokes& keys, SInt32 count)
|
||||
{
|
||||
// do nothing if no keys or no repeats
|
||||
if (count < 1 || keys.empty())
|
||||
return;
|
||||
|
||||
// generate key events
|
||||
for (Keystrokes::const_iterator k = keys.begin(); k != keys.end(); ) {
|
||||
if (k->m_repeat) {
|
||||
// repeat from here up to but not including the next key
|
||||
// with m_repeat == false count times.
|
||||
Keystrokes::const_iterator start = k;
|
||||
for (; count > 0; --count) {
|
||||
// send repeating events
|
||||
for (k = start; k != keys.end() && k->m_repeat; ++k) {
|
||||
const UINT code = MapVirtualKey(k->m_virtualKey, 0);
|
||||
keybd_event(k->m_virtualKey, code,
|
||||
k->m_press ? 0 : KEYEVENTF_KEYUP, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// note -- k is now on the first non-repeat key after the
|
||||
// repeat keys, exactly where we'd like to continue from.
|
||||
}
|
||||
else {
|
||||
// send event
|
||||
const UINT code = MapVirtualKey(k->m_virtualKey, 0);
|
||||
keybd_event(k->m_virtualKey, code,
|
||||
k->m_press ? 0 : KEYEVENTF_KEYUP, 0);
|
||||
|
||||
// next key
|
||||
++k;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CMSWindowsSecondaryScreen::updateKeys()
|
||||
{
|
||||
GetKeyboardState(m_keys);
|
||||
// GetKeyboardKeys() doesn't seem to do the expected thing
|
||||
memset(m_keys, 0, sizeof(m_keys));
|
||||
m_keys[VK_LSHIFT] = GetKeyState(VK_LSHIFT);
|
||||
m_keys[VK_RSHIFT] = GetKeyState(VK_RSHIFT);
|
||||
m_keys[VK_SHIFT] = GetKeyState(VK_SHIFT);
|
||||
m_keys[VK_LCONTROL] = GetKeyState(VK_LCONTROL);
|
||||
m_keys[VK_RCONTROL] = GetKeyState(VK_RCONTROL);
|
||||
m_keys[VK_CONTROL] = GetKeyState(VK_CONTROL);
|
||||
m_keys[VK_LMENU] = GetKeyState(VK_LMENU);
|
||||
m_keys[VK_RMENU] = GetKeyState(VK_RMENU);
|
||||
m_keys[VK_MENU] = GetKeyState(VK_MENU);
|
||||
m_keys[VK_LWIN] = GetKeyState(VK_LWIN);
|
||||
m_keys[VK_RWIN] = GetKeyState(VK_RWIN);
|
||||
m_keys[VK_CAPITAL] = GetKeyState(VK_CAPITAL);
|
||||
m_keys[VK_NUMLOCK] = GetKeyState(VK_NUMLOCK);
|
||||
m_keys[VK_SCROLL] = GetKeyState(VK_SCROLL);
|
||||
}
|
||||
|
||||
void CMSWindowsSecondaryScreen::updateModifiers()
|
||||
|
@ -1195,6 +1301,7 @@ void CMSWindowsSecondaryScreen::updateModifiers()
|
|||
m_mask |= KeyModifierNumLock;
|
||||
if ((m_keys[VK_SCROLL] & 0x01) != 0)
|
||||
m_mask |= KeyModifierScrollLock;
|
||||
log((CLOG_DEBUG2 "modifiers on update: 0x%04x", m_mask));
|
||||
}
|
||||
|
||||
void CMSWindowsSecondaryScreen::toggleKey(
|
||||
|
|
|
@ -40,12 +40,19 @@ protected:
|
|||
virtual void onCloseDisplay();
|
||||
|
||||
private:
|
||||
typedef std::pair<UINT, bool> Keystroke;
|
||||
enum EKeyAction { kPress, kRelease, kRepeat };
|
||||
class Keystroke {
|
||||
public:
|
||||
UINT m_virtualKey;
|
||||
bool m_press;
|
||||
bool m_repeat;
|
||||
};
|
||||
typedef std::vector<Keystroke> Keystrokes;
|
||||
|
||||
DWORD mapButton(ButtonID button) const;
|
||||
DWORD mapButton(ButtonID button, bool press) const;
|
||||
KeyModifierMask mapKey(Keystrokes&, UINT& virtualKey, KeyID,
|
||||
KeyModifierMask, bool press) const;
|
||||
KeyModifierMask, EKeyAction) const;
|
||||
void doKeystrokes(const Keystrokes&, SInt32 count);
|
||||
|
||||
void updateKeys();
|
||||
void updateModifiers();
|
||||
|
|
|
@ -135,9 +135,11 @@ void CTCPSocket::init()
|
|||
CNetwork::TCPNoDelayType flag = 1;
|
||||
CNetwork::setsockopt(m_fd, SOL_TCP, TCP_NODELAY, &flag, sizeof(flag));
|
||||
|
||||
#if !defined(CONFIG_PLATFORM_WIN32)
|
||||
// don't buffer sends, we merge messages ourself
|
||||
int data = 0;
|
||||
CNetwork::setsockopt(m_fd, SOL_SOCKET, SO_SNDBUF, &data, sizeof(data));
|
||||
#endif
|
||||
}
|
||||
|
||||
void CTCPSocket::ioThread(void*)
|
||||
|
|
|
@ -252,7 +252,7 @@ void CMSWindowsPrimaryScreen::getClipboard(
|
|||
CClipboard::copy(dst, &src);
|
||||
}
|
||||
|
||||
KeyModifierMask CXWindowsPrimaryScreen::getToggleMask() const
|
||||
KeyModifierMask CMSWindowsPrimaryScreen::getToggleMask() const
|
||||
{
|
||||
KeyModifierMask mask;
|
||||
if ((m_keys[VK_CAPITAL] & 0x01) != 0)
|
||||
|
|
Loading…
Reference in New Issue