Skip to content

Commit 9aeb60f

Browse files
committed
release pressed keys on unfocus
1 parent 05bf789 commit 9aeb60f

File tree

2 files changed

+186
-109
lines changed

2 files changed

+186
-109
lines changed

loader/include/Geode/cocos/platform/win32/CCEGLView.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,8 @@ class CC_DLL CCEGLView : public CCEGLViewProtocol, public CCObject
265265
void onGLFWWindowPosCallback(GLFWwindow* window, int x, int y);
266266
// @note RobTop Addition
267267
void onGLFWWindowSizeFunCallback(GLFWwindow* window, int width, int height);
268+
// @note RobTop Addition
269+
void onGLFWWindowFocus(GLFWwindow* window, int focused);
268270
};
269271

270272
NS_CC_END

loader/src/platform/windows/input.cpp

Lines changed: 184 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ class RawInputQueue {
9393
}
9494
};
9595

96+
struct KeyInfo {
97+
uint16_t vkey;
98+
uint16_t scanCode;
99+
bool isE0;
100+
};
101+
96102
class KeyStateTracker {
97103
private:
98104
std::unordered_map<uint32_t, bool> m_keyStates;
@@ -103,6 +109,12 @@ class KeyStateTracker {
103109
(isE0 ? 1 : 0);
104110
}
105111

112+
static void unmakeKey(uint32_t key, uint16_t& vkey, uint16_t& scanCode, bool& isE0) {
113+
vkey = static_cast<uint16_t>(key >> 16);
114+
scanCode = static_cast<uint16_t>((key >> 1) & 0xFFFF);
115+
isE0 = (key & 1) != 0;
116+
}
117+
106118
public:
107119
static KeyStateTracker& get() {
108120
static KeyStateTracker instance;
@@ -153,6 +165,25 @@ class KeyStateTracker {
153165
m_keyStates[key] = isDown;
154166
return wasDown && isDown;
155167
}
168+
169+
std::vector<KeyInfo> getPressedKeys() {
170+
std::vector<KeyInfo> pressed;
171+
for (auto const& [key, isDown] : m_keyStates) {
172+
if (isDown) {
173+
uint16_t vkey, scanCode;
174+
bool isE0;
175+
unmakeKey(key, vkey, scanCode, isE0);
176+
pressed.push_back({vkey, scanCode, isE0});
177+
}
178+
}
179+
180+
return pressed;
181+
}
182+
183+
void clear() {
184+
m_keyStates.clear();
185+
m_currentMods = KeyboardModifier::None;
186+
}
156187
};
157188

158189
static HWND g_rawInputHWND, g_mainWindowHWND;
@@ -402,6 +433,10 @@ class DummyEGLView : public CCEGLView {
402433
void onGLFWCharCallback(GLFWwindow* window, unsigned int c) {
403434
CCEGLView::onGLFWCharCallback(window, c);
404435
}
436+
437+
void onGLFWWindowFocus(GLFWwindow* window, int focused) {
438+
CCEGLView::onGLFWWindowFocus(window, focused);
439+
}
405440
};
406441

407442
static void GLFWScrollCallback(GLFWwindow* window, double xoffset, double yoffset) {
@@ -415,7 +450,124 @@ static void GLFWCharCallback(GLFWwindow* window, unsigned int c) {
415450
static_cast<DummyEGLView*>(CCEGLView::get())->onGLFWCharCallback(window, c);
416451
}
417452

453+
static void GLFWFocusCallback(GLFWwindow* window, int focused);
454+
418455
struct GeodeRawInput : Modify<GeodeRawInput, CCEGLView> {
456+
void handleKeyboardEvent(RawInputEvent const& evt) {
457+
using enum KeyboardInputData::Action;
458+
bool isDown = evt.type == RawInputEvent::Type::KeyDown;
459+
460+
enumKeyCodes keyCode = keyToKeyCode(
461+
evt.keyboard.vkey,
462+
evt.keyboard.isE0
463+
);
464+
465+
KeyboardInputData data(
466+
keyCode,
467+
isDown ? (evt.keyboard.isRepeat ? Repeat : Press) : Release,
468+
{evt.keyboard.vkey, evt.keyboard.scanCode},
469+
evt.timestamp,
470+
evt.mods
471+
);
472+
473+
auto result = KeyboardInputEvent(keyCode).send(data);
474+
475+
// copy values from event, if someone modifies it
476+
isDown = data.action != Release;
477+
keyCode = data.key;
478+
479+
if (result == ListenerResult::Propagate && keyCode != KEY_Unknown) {
480+
auto* ime = CCIMEDispatcher::sharedDispatcher();
481+
if (keyCode == enumKeyCodes::KEY_Backspace && isDown) {
482+
ime->dispatchDeleteBackward();
483+
} else if (keyCode == enumKeyCodes::KEY_Delete && isDown) {
484+
ime->dispatchDeleteForward();
485+
}
486+
487+
auto* keyboardDispatcher = CCKeyboardDispatcher::get();
488+
489+
keyboardDispatcher->updateModifierKeys(
490+
data.modifiers & KeyboardModifier::Shift,
491+
data.modifiers & KeyboardModifier::Control,
492+
data.modifiers & KeyboardModifier::Alt,
493+
data.modifiers & KeyboardModifier::Super
494+
);
495+
496+
if (!ime->hasDelegate() || keyCode == KEY_Escape || keyCode == KEY_Enter) {
497+
keyboardDispatcher->dispatchKeyboardMSG(
498+
keyCode,
499+
isDown,
500+
data.action == Repeat,
501+
data.timestamp
502+
);
503+
}
504+
505+
// text pasting
506+
if (data.modifiers & KeyboardModifier::Control && keyCode == enumKeyCodes::KEY_V && isDown) {
507+
if (ime->hasDelegate()) {
508+
this->performSafeClipboardPaste();
509+
}
510+
}
511+
}
512+
}
513+
514+
void handleMouseEvent(RawInputEvent const& evt) {
515+
using enum MouseInputData::Action;
516+
using enum MouseInputData::Button;
517+
518+
struct Btn {
519+
USHORT down, up;
520+
MouseInputData::Button btn;
521+
};
522+
523+
constexpr Btn btns[] = {
524+
{RI_MOUSE_BUTTON_1_DOWN, RI_MOUSE_BUTTON_1_UP, Left},
525+
{RI_MOUSE_BUTTON_2_DOWN, RI_MOUSE_BUTTON_2_UP, Right},
526+
{RI_MOUSE_BUTTON_3_DOWN, RI_MOUSE_BUTTON_3_UP, Middle},
527+
{RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_4_UP, Button4},
528+
{RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_5_UP, Button5},
529+
};
530+
531+
// WinAPI can combine multiple button events into one
532+
for (auto const& b : btns) {
533+
bool isDown = (evt.mouse.flags & b.down) != 0;
534+
bool isUp = (evt.mouse.flags & b.up) != 0;
535+
if (isDown || isUp) {
536+
MouseInputData data(
537+
b.btn,
538+
isDown ? Press : Release,
539+
evt.timestamp,
540+
evt.mods
541+
);
542+
543+
auto result = MouseInputEvent().send(data);
544+
isDown = data.action == Press;
545+
546+
// handle cocos touches
547+
if (data.button == Left && result == ListenerResult::Propagate) {
548+
int id = 0;
549+
if (isDown) {
550+
m_bCaptured = true;
551+
this->handleTouchesBegin(
552+
1, &id,
553+
&m_fMouseX,
554+
&m_fMouseY,
555+
data.timestamp
556+
);
557+
} else {
558+
m_bCaptured = false;
559+
this->handleTouchesEnd(
560+
1, &id,
561+
&m_fMouseX,
562+
&m_fMouseY,
563+
data.timestamp
564+
);
565+
}
566+
}
567+
}
568+
}
569+
}
570+
419571
void pumpRawInput() {
420572
bool isForeground = GetForegroundWindow() == g_mainWindowHWND;
421573
if (!isForeground) {
@@ -455,118 +607,11 @@ struct GeodeRawInput : Modify<GeodeRawInput, CCEGLView> {
455607
switch (evt.type) {
456608
case RawInputEvent::Type::KeyDown:
457609
case RawInputEvent::Type::KeyUp: {
458-
using enum KeyboardInputData::Action;
459-
bool isDown = evt.type == RawInputEvent::Type::KeyDown;
460-
461-
enumKeyCodes keyCode = keyToKeyCode(
462-
evt.keyboard.vkey,
463-
evt.keyboard.isE0
464-
);
465-
466-
KeyboardInputData data(
467-
keyCode,
468-
isDown ? (evt.keyboard.isRepeat ? Repeat : Press) : Release,
469-
{evt.keyboard.vkey, evt.keyboard.scanCode},
470-
evt.timestamp,
471-
evt.mods
472-
);
473-
474-
auto result = KeyboardInputEvent(keyCode).send(data);
475-
476-
// copy values from event, if someone modifies it
477-
isDown = data.action != Release;
478-
keyCode = data.key;
479-
480-
if (result == ListenerResult::Propagate && keyCode != KEY_Unknown) {
481-
auto* ime = CCIMEDispatcher::sharedDispatcher();
482-
if (keyCode == enumKeyCodes::KEY_Backspace && isDown) {
483-
ime->dispatchDeleteBackward();
484-
} else if (keyCode == enumKeyCodes::KEY_Delete && isDown) {
485-
ime->dispatchDeleteForward();
486-
}
487-
488-
auto* keyboardDispatcher = CCKeyboardDispatcher::get();
489-
490-
keyboardDispatcher->updateModifierKeys(
491-
data.modifiers & KeyboardModifier::Shift,
492-
data.modifiers & KeyboardModifier::Control,
493-
data.modifiers & KeyboardModifier::Alt,
494-
data.modifiers & KeyboardModifier::Super
495-
);
496-
497-
if (!ime->hasDelegate() || keyCode == KEY_Escape || keyCode == KEY_Enter) {
498-
keyboardDispatcher->dispatchKeyboardMSG(
499-
keyCode,
500-
isDown,
501-
data.action == Repeat,
502-
data.timestamp
503-
);
504-
}
505-
506-
// text pasting
507-
if (data.modifiers & KeyboardModifier::Control && keyCode == enumKeyCodes::KEY_V && isDown) {
508-
if (ime->hasDelegate()) {
509-
this->performSafeClipboardPaste();
510-
}
511-
}
512-
}
610+
this->handleKeyboardEvent(evt);
513611
break;
514612
}
515613
case RawInputEvent::Type::MouseButton: {
516-
using enum MouseInputData::Action;
517-
using enum MouseInputData::Button;
518-
519-
struct Btn {
520-
USHORT down, up;
521-
MouseInputData::Button btn;
522-
};
523-
524-
constexpr Btn btns[] = {
525-
{RI_MOUSE_BUTTON_1_DOWN, RI_MOUSE_BUTTON_1_UP, Left},
526-
{RI_MOUSE_BUTTON_2_DOWN, RI_MOUSE_BUTTON_2_UP, Right},
527-
{RI_MOUSE_BUTTON_3_DOWN, RI_MOUSE_BUTTON_3_UP, Middle},
528-
{RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_4_UP, Button4},
529-
{RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_5_UP, Button5},
530-
};
531-
532-
// WinAPI can combine multiple button events into one
533-
for (auto const& b : btns) {
534-
bool isDown = (evt.mouse.flags & b.down) != 0;
535-
bool isUp = (evt.mouse.flags & b.up) != 0;
536-
if (isDown || isUp) {
537-
MouseInputData data(
538-
b.btn,
539-
isDown ? Press : Release,
540-
evt.timestamp,
541-
evt.mods
542-
);
543-
544-
auto result = MouseInputEvent().send(data);
545-
isDown = data.action == Press;
546-
547-
// handle cocos touches
548-
if (data.button == Left && result == ListenerResult::Propagate) {
549-
int id = 0;
550-
if (isDown) {
551-
m_bCaptured = true;
552-
this->handleTouchesBegin(
553-
1, &id,
554-
&m_fMouseX,
555-
&m_fMouseY,
556-
data.timestamp
557-
);
558-
} else {
559-
m_bCaptured = false;
560-
this->handleTouchesEnd(
561-
1, &id,
562-
&m_fMouseX,
563-
&m_fMouseY,
564-
data.timestamp
565-
);
566-
}
567-
}
568-
}
569-
}
614+
this->handleMouseEvent(evt);
570615
break;
571616
}
572617
default:
@@ -609,12 +654,42 @@ struct GeodeRawInput : Modify<GeodeRawInput, CCEGLView> {
609654
g_mainWindowHWND = *reinterpret_cast<HWND*>(reinterpret_cast<uintptr_t>(m_pMainWindow) + 0x370);
610655
*reinterpret_cast<GLFWscrollfun*>(reinterpret_cast<uintptr_t>(m_pMainWindow) + 0x340) = &GLFWScrollCallback;
611656
*reinterpret_cast<GLFWcharfun*>(reinterpret_cast<uintptr_t>(m_pMainWindow) + 0x350) = &GLFWCharCallback;
657+
*reinterpret_cast<GLFWwindowfocusfun*>(reinterpret_cast<uintptr_t>(m_pMainWindow) + 0x300) = &GLFWFocusCallback;
612658

613659
// window is created on a different thread, so it needs time to initialize
614660
queueInMainThread([]{ attemptHookRawInput(); });
615661
}
616662
};
617663

664+
static void GLFWFocusCallback(GLFWwindow* window, int focused) {
665+
auto view = CCEGLView::get();
666+
static_cast<DummyEGLView*>(view)->onGLFWWindowFocus(window, focused);
667+
if (!focused) {
668+
// technically a race condition, but you have to be the most unlucky person alive for it to happen,
669+
// and i really didn't want to add mutexes to KeyStateTracker just for window unfocus event
670+
auto& tracker = KeyStateTracker::get();
671+
auto pressedKeys = tracker.getPressedKeys();
672+
tracker.clear();
673+
674+
auto timestamp = getInputTimestamp();
675+
for (auto const& keyInfo : pressedKeys) {
676+
static_cast<GeodeRawInput*>(view)->handleKeyboardEvent(RawInputEvent{
677+
.timestamp = timestamp,
678+
.mods = KeyboardModifier::None,
679+
.keyboard = {
680+
.vkey = keyInfo.vkey,
681+
.scanCode = keyInfo.scanCode,
682+
.flags = 0,
683+
.isE0 = keyInfo.isE0,
684+
.isE1 = false,
685+
.isRepeat = false
686+
},
687+
.type = RawInputEvent::Type::KeyUp,
688+
});
689+
}
690+
}
691+
}
692+
618693
struct GeodeControllerInput : Modify<GeodeControllerInput, CCApplication> {
619694
void updateControllerKeys(CXBOXController* controller, int userIndex) {
620695
if (!controller) return;

0 commit comments

Comments
 (0)