summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCameron Gutman <aicommander@gmail.com>2021-01-22 19:40:26 -0600
committerCameron Gutman <aicommander@gmail.com>2021-01-22 19:40:26 -0600
commit89b1c85fd143e4aacf3b9c41c69a45699e230bcb (patch)
treead497f6cf3863772b55247fc235626c752038881
parent386bd71ee92ebccd87e27a122fe8acfdd7d28baa (diff)
downloadsdl-89b1c85fd143e4aacf3b9c41c69a45699e230bcb.tar.gz
Implement keyboard grab support for Windows
This is implemented via a low-level keyboard hook. Unfortunately, this is rather invasive, but it's how Microsoft recommends that it be done [0]. We want to do as little as possible in the hook, so we only intercept a few crucial modifier keys there, while leaving other keys to the normal event processing flow. We will only install this hook if SDL_HINT_GRAB_KEYBOARD=1, which is not the default. This will reduce any compatibility concerns to just the SDL applications that explicitly ask for this behavior. We also remove the hook when the grab is terminated to ensure that we're not unnecessarily staying involved in key event processing when it's not required anymore. [0]: https://docs.microsoft.com/en-us/windows/win32/dxtecharts/disabling-shortcut-keys-in-games
-rw-r--r--src/video/windows/SDL_windowsevents.c41
-rw-r--r--src/video/windows/SDL_windowsevents.h1
-rw-r--r--src/video/windows/SDL_windowswindow.c50
-rw-r--r--src/video/windows/SDL_windowswindow.h1
4 files changed, 93 insertions, 0 deletions
diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c
index 2557d8628..acb52c3ce 100644
--- a/src/video/windows/SDL_windowsevents.c
+++ b/src/video/windows/SDL_windowsevents.c
@@ -429,6 +429,47 @@ static SDL_MOUSE_EVENT_SOURCE GetMouseMessageSource()
}
LRESULT CALLBACK
+WIN_KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam)
+{
+ if (nCode < 0 || nCode != HC_ACTION) {
+ return CallNextHookEx(NULL, nCode, wParam, lParam);
+ }
+
+ KBDLLHOOKSTRUCT* hookData = (KBDLLHOOKSTRUCT*)lParam;
+ SDL_Scancode scanCode;
+ switch (hookData->vkCode) {
+ case VK_LWIN:
+ scanCode = SDL_SCANCODE_LGUI;
+ break;
+ case VK_RWIN:
+ scanCode = SDL_SCANCODE_RGUI;
+ break;
+ case VK_LMENU:
+ scanCode = SDL_SCANCODE_LALT;
+ break;
+ case VK_RMENU:
+ scanCode = SDL_SCANCODE_RALT;
+ break;
+ case VK_LCONTROL:
+ scanCode = SDL_SCANCODE_LCTRL;
+ break;
+ case VK_RCONTROL:
+ scanCode = SDL_SCANCODE_RCTRL;
+ break;
+ default:
+ return CallNextHookEx(NULL, nCode, wParam, lParam);
+ }
+
+ if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
+ SDL_SendKeyboardKey(SDL_PRESSED, scanCode);
+ } else {
+ SDL_SendKeyboardKey(SDL_RELEASED, scanCode);
+ }
+
+ return 1;
+}
+
+LRESULT CALLBACK
WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
SDL_WindowData *data;
diff --git a/src/video/windows/SDL_windowsevents.h b/src/video/windows/SDL_windowsevents.h
index 9407f72bc..fcd716dcf 100644
--- a/src/video/windows/SDL_windowsevents.h
+++ b/src/video/windows/SDL_windowsevents.h
@@ -27,6 +27,7 @@ extern LPTSTR SDL_Appname;
extern Uint32 SDL_Appstyle;
extern HINSTANCE SDL_Instance;
+extern LRESULT CALLBACK WIN_KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam);
extern LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam,
LPARAM lParam);
extern void WIN_PumpEvents(_THIS);
diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c
index 2a54eb2ab..cae93f769 100644
--- a/src/video/windows/SDL_windowswindow.c
+++ b/src/video/windows/SDL_windowswindow.c
@@ -738,11 +738,58 @@ WIN_GetWindowGammaRamp(_THIS, SDL_Window * window, Uint16 * ramp)
return succeeded ? 0 : -1;
}
+static void WIN_GrabKeyboard(SDL_Window *window)
+{
+ SDL_WindowData *data = (SDL_WindowData *)window->driverdata;
+ HMODULE module;
+
+ if (data->keyboard_hook) {
+ return;
+ }
+
+ /* SetWindowsHookEx() needs to know which module contains the hook we
+ want to install. This is complicated by the fact that SDL can be
+ linked statically or dynamically. Fortunately XP and later provide
+ this nice API that will go through the loaded modules and find the
+ one containing our code.
+ */
+ if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
+ (LPTSTR)WIN_KeyboardHookProc,
+ &module)) {
+ return;
+ }
+
+ /* To grab the keyboard, we have to install a low-level keyboard hook to
+ intercept keys that would normally be captured by the OS. Intercepting
+ all key events on the system is rather invasive, but it's what Microsoft
+ actually documents that you do to capture these.
+ */
+ data->keyboard_hook = SetWindowsHookEx(WH_KEYBOARD_LL, WIN_KeyboardHookProc, module, 0);
+}
+
+void WIN_UngrabKeyboard(SDL_Window *window)
+{
+ SDL_WindowData *data = (SDL_WindowData *)window->driverdata;
+
+ if (data->keyboard_hook) {
+ UnhookWindowsHookEx(data->keyboard_hook);
+ data->keyboard_hook = NULL;
+ }
+}
+
void
WIN_SetWindowGrab(_THIS, SDL_Window * window, SDL_bool grabbed)
{
WIN_UpdateClipCursor(window);
+ if (grabbed) {
+ if (SDL_GetHintBoolean(SDL_HINT_GRAB_KEYBOARD, SDL_FALSE)) {
+ WIN_GrabKeyboard(window);
+ }
+ } else {
+ WIN_UngrabKeyboard(window);
+ }
+
if (window->flags & SDL_WINDOW_FULLSCREEN) {
UINT flags = SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOSIZE;
@@ -759,6 +806,9 @@ WIN_DestroyWindow(_THIS, SDL_Window * window)
SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
if (data) {
+ if (data->keyboard_hook) {
+ UnhookWindowsHookEx(data->keyboard_hook);
+ }
ReleaseDC(data->hwnd, data->hdc);
RemoveProp(data->hwnd, TEXT("SDL_WindowData"));
if (data->created) {
diff --git a/src/video/windows/SDL_windowswindow.h b/src/video/windows/SDL_windowswindow.h
index 69ae4a7f3..cbadc858a 100644
--- a/src/video/windows/SDL_windowswindow.h
+++ b/src/video/windows/SDL_windowswindow.h
@@ -37,6 +37,7 @@ typedef struct
HINSTANCE hinstance;
HBITMAP hbm;
WNDPROC wndproc;
+ HHOOK keyboard_hook;
SDL_bool created;
WPARAM mouse_button_flags;
LPARAM last_pointer_update;