Expose separate keyboard and mouse grab support

This adds SDL_SetWindowKeyboardGrab(), SDL_GetWindowKeyboardGrab(),
SDL_SetWindowMouseGrab(), SDL_GetWindowMouseGrab(), and new
SDL_WINDOW_KEYBOARD_GRABBED flag. It also updates the test harness to exercise
this functionality and makes a minor fix to X11 that I missed in
https://hg.libsdl.org/SDL/rev/02a2d609369b

To fit in with this new support, SDL_WINDOW_INPUT_CAPTURE has been renamed to
SDL_WINDOW_MOUSE_CAPTURE with the old name remaining as an alias for backwards
compatibility with older code.
diff --git a/include/SDL_video.h b/include/SDL_video.h
index 2f684be..0860ea0 100644
--- a/include/SDL_video.h
+++ b/include/SDL_video.h
@@ -68,6 +68,8 @@
  *  \sa SDL_GetWindowData()
  *  \sa SDL_GetWindowFlags()
  *  \sa SDL_GetWindowGrab()
+ *  \sa SDL_GetWindowKeyboardGrab()
+ *  \sa SDL_GetWindowMouseGrab()
  *  \sa SDL_GetWindowPosition()
  *  \sa SDL_GetWindowSize()
  *  \sa SDL_GetWindowTitle()
@@ -79,6 +81,8 @@
  *  \sa SDL_SetWindowData()
  *  \sa SDL_SetWindowFullscreen()
  *  \sa SDL_SetWindowGrab()
+ *  \sa SDL_SetWindowKeyboardGrab()
+ *  \sa SDL_SetWindowMouseGrab()
  *  \sa SDL_SetWindowIcon()
  *  \sa SDL_SetWindowPosition()
  *  \sa SDL_SetWindowSize()
@@ -104,7 +108,7 @@
     SDL_WINDOW_RESIZABLE = 0x00000020,          /**< window can be resized */
     SDL_WINDOW_MINIMIZED = 0x00000040,          /**< window is minimized */
     SDL_WINDOW_MAXIMIZED = 0x00000080,          /**< window is maximized */
-    SDL_WINDOW_INPUT_GRABBED = 0x00000100,      /**< window has grabbed input focus */
+    SDL_WINDOW_MOUSE_GRABBED = 0x00000100,      /**< window has grabbed mouse input */
     SDL_WINDOW_INPUT_FOCUS = 0x00000200,        /**< window has input focus */
     SDL_WINDOW_MOUSE_FOCUS = 0x00000400,        /**< window has mouse focus */
     SDL_WINDOW_FULLSCREEN_DESKTOP = ( SDL_WINDOW_FULLSCREEN | 0x00001000 ),
@@ -112,14 +116,17 @@
     SDL_WINDOW_ALLOW_HIGHDPI = 0x00002000,      /**< window should be created in high-DPI mode if supported.
                                                      On macOS NSHighResolutionCapable must be set true in the
                                                      application's Info.plist for this to have any effect. */
-    SDL_WINDOW_MOUSE_CAPTURE = 0x00004000,      /**< window has mouse captured (unrelated to INPUT_GRABBED) */
-    SDL_WINDOW_ALWAYS_ON_TOP = 0x00008000,      /**< window should always be above others */
-    SDL_WINDOW_SKIP_TASKBAR  = 0x00010000,      /**< window should not be added to the taskbar */
-    SDL_WINDOW_UTILITY       = 0x00020000,      /**< window should be treated as a utility window */
-    SDL_WINDOW_TOOLTIP       = 0x00040000,      /**< window should be treated as a tooltip */
-    SDL_WINDOW_POPUP_MENU    = 0x00080000,      /**< window should be treated as a popup menu */
-    SDL_WINDOW_VULKAN        = 0x10000000,      /**< window usable for Vulkan surface */
-    SDL_WINDOW_METAL         = 0x20000000       /**< window usable for Metal view */
+    SDL_WINDOW_MOUSE_CAPTURE    = 0x00004000,   /**< window has mouse captured (unrelated to MOUSE_GRABBED) */
+    SDL_WINDOW_ALWAYS_ON_TOP    = 0x00008000,   /**< window should always be above others */
+    SDL_WINDOW_SKIP_TASKBAR     = 0x00010000,   /**< window should not be added to the taskbar */
+    SDL_WINDOW_UTILITY          = 0x00020000,   /**< window should be treated as a utility window */
+    SDL_WINDOW_TOOLTIP          = 0x00040000,   /**< window should be treated as a tooltip */
+    SDL_WINDOW_POPUP_MENU       = 0x00080000,   /**< window should be treated as a popup menu */
+    SDL_WINDOW_KEYBOARD_GRABBED = 0x00100000,   /**< window has grabbed keyboard input */
+    SDL_WINDOW_VULKAN           = 0x10000000,   /**< window usable for Vulkan surface */
+    SDL_WINDOW_METAL            = 0x20000000,   /**< window usable for Metal view */
+
+    SDL_WINDOW_INPUT_GRABBED = SDL_WINDOW_MOUSE_GRABBED /**< equivalent to SDL_WINDOW_MOUSE_GRABBED for compatibility */
 } SDL_WindowFlags;
 
 /**
@@ -486,9 +493,9 @@
  *               ::SDL_WINDOW_FULLSCREEN,    ::SDL_WINDOW_OPENGL,
  *               ::SDL_WINDOW_HIDDEN,        ::SDL_WINDOW_BORDERLESS,
  *               ::SDL_WINDOW_RESIZABLE,     ::SDL_WINDOW_MAXIMIZED,
- *               ::SDL_WINDOW_MINIMIZED,     ::SDL_WINDOW_INPUT_GRABBED,
- *               ::SDL_WINDOW_ALLOW_HIGHDPI, ::SDL_WINDOW_VULKAN
- *               ::SDL_WINDOW_METAL.
+ *               ::SDL_WINDOW_MINIMIZED,     ::SDL_WINDOW_MOUSE_GRABBED,
+ *               ::SDL_WINDOW_ALLOW_HIGHDPI, ::SDL_WINDOW_KEYBOARD_GRABBED,
+ *               ::SDL_WINDOW_VULKAN,        ::SDL_WINDOW_METAL.
  *
  *  \return The created window, or NULL if window creation failed.
  *
@@ -881,21 +888,79 @@
  *  If the caller enables a grab while another window is currently grabbed,
  *  the other window loses its grab in favor of the caller's window.
  *
+ *  If SDL_HINT_GRAB_KEYBOARD=1, this also grabs keyboard input.
+ *
  *  \sa SDL_GetWindowGrab()
+ *  \sa SDL_SetWindowKeyboardGrab()
+ *  \sa SDL_SetWindowMouseGrab()
  */
 extern DECLSPEC void SDLCALL SDL_SetWindowGrab(SDL_Window * window,
                                                SDL_bool grabbed);
 
 /**
- *  \brief Get a window's input grab mode.
+ *  \brief Set a window's keyboard grab mode.
  *
- *  \return This returns SDL_TRUE if input is grabbed, and SDL_FALSE otherwise.
+ *  \param window The window for which the keyboard grab mode should be set.
+ *  \param grabbed This is SDL_TRUE to grab keyboard, and SDL_FALSE to release keyboard grab.
  *
+ *  If the caller enables a grab while another window is currently grabbed,
+ *  the other window loses its grab in favor of the caller's window.
+ *
+ *  \sa SDL_GetWindowKeyboardGrab()
+ *  \sa SDL_SetWindowMouseGrab()
  *  \sa SDL_SetWindowGrab()
  */
+extern DECLSPEC void SDLCALL SDL_SetWindowKeyboardGrab(SDL_Window * window,
+                                                       SDL_bool grabbed);
+
+/**
+ *  \brief Set a window's mouse grab mode.
+ *
+ *  \param window The window for which the mouse grab mode should be set.
+ *  \param grabbed This is SDL_TRUE to grab mouse, and SDL_FALSE to release mouse grab.
+ *
+ *  If the caller enables a grab while another window is currently grabbed,
+ *  the other window loses its grab in favor of the caller's window.
+ *
+ *  \sa SDL_GetWindowMouseGrab()
+ *  \sa SDL_SetWindowKeyboardGrab()
+ *  \sa SDL_SetWindowGrab()
+ */
+extern DECLSPEC void SDLCALL SDL_SetWindowMouseGrab(SDL_Window * window,
+                                                    SDL_bool grabbed);
+
+/**
+ *  \brief Get a window's input grab mode.
+ *
+ *  \return This returns SDL_TRUE if keyboard or mouse input is grabbed, and SDL_FALSE otherwise.
+ *
+ *  \sa SDL_SetWindowGrab()
+ *  \sa SDL_GetWindowMouseGrab()
+ *  \sa SDL_GetWindowKeyboardGrab()
+ */
 extern DECLSPEC SDL_bool SDLCALL SDL_GetWindowGrab(SDL_Window * window);
 
 /**
+ *  \brief Get a window's keyboard grab mode.
+ *
+ *  \return This returns SDL_TRUE if keyboard is grabbed, and SDL_FALSE otherwise.
+ *
+ *  \sa SDL_SetWindowKeyboardGrab()
+ *  \sa SDL_GetWindowGrab()
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_GetWindowKeyboardGrab(SDL_Window * window);
+
+/**
+ *  \brief Get a window's mouse grab mode.
+ *
+ *  \return This returns SDL_TRUE if mouse is grabbed, and SDL_FALSE otherwise.
+ *
+ *  \sa SDL_SetWindowKeyboardGrab()
+ *  \sa SDL_GetWindowGrab()
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_GetWindowMouseGrab(SDL_Window * window);
+
+/**
  *  \brief Get the window that currently has an input grab enabled.
  *
  *  \return This returns the window if input is grabbed, and NULL otherwise.
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index e6d682d..bb592c0 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -795,3 +795,7 @@
 #define SDL_SoftStretchLinear SDL_SoftStretchLinear_REAL
 #define SDL_RenderGetD3D11Device SDL_RenderGetD3D11Device_REAL
 #define SDL_UpdateNVTexture SDL_UpdateNVTexture_REAL
+#define SDL_SetWindowKeyboardGrab SDL_SetWindowKeyboardGrab_REAL

+#define SDL_SetWindowMouseGrab SDL_SetWindowMouseGrab_REAL

+#define SDL_GetWindowKeyboardGrab SDL_GetWindowKeyboardGrab_REAL

+#define SDL_GetWindowMouseGrab SDL_GetWindowMouseGrab_REAL

diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index ce1ce72..b461985 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -858,3 +858,7 @@
 SDL_DYNAPI_PROC(ID3D11Device*,SDL_RenderGetD3D11Device,(SDL_Renderer *a),(a),return)
 #endif
 SDL_DYNAPI_PROC(int,SDL_UpdateNVTexture,(SDL_Texture *a, const SDL_Rect *b, const Uint8 *c, int d, const Uint8 *e, int f),(a,b,c,d,e,f),return)
+SDL_DYNAPI_PROC(void,SDL_SetWindowKeyboardGrab,(SDL_Window *a, SDL_bool b),(a,b),)

+SDL_DYNAPI_PROC(void,SDL_SetWindowMouseGrab,(SDL_Window *a, SDL_bool b),(a,b),)

+SDL_DYNAPI_PROC(SDL_bool,SDL_GetWindowKeyboardGrab,(SDL_Window *a),(a),return)

+SDL_DYNAPI_PROC(SDL_bool,SDL_GetWindowMouseGrab,(SDL_Window *a),(a),return)

diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c
index 13bec3a..3384a82 100644
--- a/src/test/SDL_test_common.c
+++ b/src/test/SDL_test_common.c
@@ -34,7 +34,7 @@
     "[--icon icon.bmp]", "[--center | --position X,Y]", "[--geometry WxH]",
     "[--min-geometry WxH]", "[--max-geometry WxH]", "[--logical WxH]",
     "[--scale N]", "[--depth N]", "[--refresh R]", "[--vsync]", "[--noframe]",
-    "[--resize]", "[--minimize]", "[--maximize]", "[--grab]",
+    "[--resize]", "[--minimize]", "[--maximize]", "[--grab]", "[--keyboard-grab]",
     "[--allow-highdpi]", "[--usable-bounds]"
 };
 
@@ -412,7 +412,11 @@
         return 1;
     }
     if (SDL_strcasecmp(argv[index], "--grab") == 0) {
-        state->window_flags |= SDL_WINDOW_INPUT_GRABBED;
+        state->window_flags |= SDL_WINDOW_MOUSE_GRABBED;
+        return 1;
+    }
+    if (SDL_strcasecmp(argv[index], "--keyboard-grab") == 0) {
+        state->window_flags |= SDL_WINDOW_KEYBOARD_GRABBED;
         return 1;
     }
     if (SDL_strcasecmp(argv[index], "--rate") == 0) {
@@ -1764,13 +1768,22 @@
             break;
         case SDLK_g:
             if (withControl) {
-                /* Ctrl-G toggle grab */
+                /* Ctrl-G toggle mouse grab */
                 SDL_Window *window = SDL_GetWindowFromID(event->key.windowID);
                 if (window) {
                     SDL_SetWindowGrab(window, !SDL_GetWindowGrab(window) ? SDL_TRUE : SDL_FALSE);
                 }
             }
             break;
+        case SDLK_k:
+            if (withControl) {
+                /* Ctrl-K toggle keyboard grab */
+                SDL_Window* window = SDL_GetWindowFromID(event->key.windowID);
+                if (window) {
+                    SDL_SetWindowKeyboardGrab(window, !SDL_GetWindowKeyboardGrab(window) ? SDL_TRUE : SDL_FALSE);
+                }
+            }
+            break;
         case SDLK_m:
             if (withControl) {
                 /* Ctrl-M maximize */
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index 7c5b819..d750a35 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -1433,9 +1433,17 @@
     if (flags & SDL_WINDOW_FULLSCREEN) {
         SDL_SetWindowFullscreen(window, flags);
     }
-    if (flags & SDL_WINDOW_INPUT_GRABBED) {
+    if (flags & SDL_WINDOW_MOUSE_GRABBED) {
+        /* We must specifically call SDL_SetWindowGrab() and not
+           SDL_SetWindowMouseGrab() here because older applications may use
+           this flag plus SDL_HINT_GRAB_KEYBOARD to indicate that they want
+           the keyboard grabbed too and SDL_SetWindowMouseGrab() won't do that.
+        */
         SDL_SetWindowGrab(window, SDL_TRUE);
     }
+    if (flags & SDL_WINDOW_KEYBOARD_GRABBED) {
+        SDL_SetWindowKeyboardGrab(window, SDL_TRUE);
+    }
     if (!(flags & SDL_WINDOW_HIDDEN)) {
         SDL_ShowWindow(window);
     }
@@ -2638,41 +2646,46 @@
 void
 SDL_UpdateWindowGrab(SDL_Window * window)
 {
-    SDL_Window *grabbed_window;
-    SDL_bool grabbed;
-    if ((SDL_GetMouse()->relative_mode || (window->flags & SDL_WINDOW_INPUT_GRABBED)) &&
-         (window->flags & SDL_WINDOW_INPUT_FOCUS)) {
-        grabbed = SDL_TRUE;
+    SDL_bool keyboard_grabbed, mouse_grabbed;
+
+    if (window->flags & SDL_WINDOW_INPUT_FOCUS) {
+        if (SDL_GetMouse()->relative_mode || (window->flags & SDL_WINDOW_MOUSE_GRABBED)) {
+            mouse_grabbed = SDL_TRUE;
+        } else {
+            mouse_grabbed = SDL_FALSE;
+        }
+
+        if (window->flags & SDL_WINDOW_KEYBOARD_GRABBED) {
+            keyboard_grabbed = SDL_TRUE;
+        } else {
+            keyboard_grabbed = SDL_FALSE;
+        }
     } else {
-        grabbed = SDL_FALSE;
+        mouse_grabbed = SDL_FALSE;
+        keyboard_grabbed = SDL_FALSE;
     }
 
-    grabbed_window = _this->grabbed_window;
-    if (grabbed) {
-        if (grabbed_window && (grabbed_window != window)) {
+    if (mouse_grabbed || keyboard_grabbed) {
+        if (_this->grabbed_window && (_this->grabbed_window != window)) {
             /* stealing a grab from another window! */
-            grabbed_window->flags &= ~SDL_WINDOW_INPUT_GRABBED;
+            _this->grabbed_window->flags &= ~(SDL_WINDOW_MOUSE_GRABBED | SDL_WINDOW_KEYBOARD_GRABBED);
             if (_this->SetWindowMouseGrab) {
-                _this->SetWindowMouseGrab(_this, grabbed_window, SDL_FALSE);
+                _this->SetWindowMouseGrab(_this, _this->grabbed_window, SDL_FALSE);
             }
             if (_this->SetWindowKeyboardGrab) {
-                _this->SetWindowKeyboardGrab(_this, grabbed_window, SDL_FALSE);
+                _this->SetWindowKeyboardGrab(_this, _this->grabbed_window, SDL_FALSE);
             }
         }
         _this->grabbed_window = window;
-    } else if (grabbed_window == window) {
-        _this->grabbed_window = NULL;  /* ungrabbing. */
+    } else if (_this->grabbed_window == window) {
+        _this->grabbed_window = NULL;  /* ungrabbing input. */
     }
 
     if (_this->SetWindowMouseGrab) {
-        _this->SetWindowMouseGrab(_this, window, grabbed);
+        _this->SetWindowMouseGrab(_this, window, mouse_grabbed);
     }
     if (_this->SetWindowKeyboardGrab) {
-        if (grabbed && SDL_GetHintBoolean(SDL_HINT_GRAB_KEYBOARD, SDL_FALSE)) {
-            _this->SetWindowKeyboardGrab(_this, window, SDL_TRUE);
-        } else {
-            _this->SetWindowKeyboardGrab(_this, window, SDL_FALSE);
-        }
+        _this->SetWindowKeyboardGrab(_this, window, keyboard_grabbed);
     }
 }
 
@@ -2681,13 +2694,41 @@
 {
     CHECK_WINDOW_MAGIC(window,);
 
-    if (!!grabbed == !!(window->flags & SDL_WINDOW_INPUT_GRABBED)) {
+    SDL_SetWindowMouseGrab(window, grabbed);
+
+    if (SDL_GetHintBoolean(SDL_HINT_GRAB_KEYBOARD, SDL_FALSE)) {
+        SDL_SetWindowKeyboardGrab(window, grabbed);
+    }
+}
+
+void
+SDL_SetWindowKeyboardGrab(SDL_Window * window, SDL_bool grabbed)
+{
+    CHECK_WINDOW_MAGIC(window,);
+
+    if (!!grabbed == !!(window->flags & SDL_WINDOW_KEYBOARD_GRABBED)) {
         return;
     }
     if (grabbed) {
-        window->flags |= SDL_WINDOW_INPUT_GRABBED;
+        window->flags |= SDL_WINDOW_KEYBOARD_GRABBED;
     } else {
-        window->flags &= ~SDL_WINDOW_INPUT_GRABBED;
+        window->flags &= ~SDL_WINDOW_KEYBOARD_GRABBED;
+    }
+    SDL_UpdateWindowGrab(window);
+}
+
+void
+SDL_SetWindowMouseGrab(SDL_Window * window, SDL_bool grabbed)
+{
+    CHECK_WINDOW_MAGIC(window,);
+
+    if (!!grabbed == !!(window->flags & SDL_WINDOW_MOUSE_GRABBED)) {
+        return;
+    }
+    if (grabbed) {
+        window->flags |= SDL_WINDOW_MOUSE_GRABBED;
+    } else {
+        window->flags &= ~SDL_WINDOW_MOUSE_GRABBED;
     }
     SDL_UpdateWindowGrab(window);
 }
@@ -2696,14 +2737,34 @@
 SDL_GetWindowGrab(SDL_Window * window)
 {
     CHECK_WINDOW_MAGIC(window, SDL_FALSE);
-    SDL_assert(!_this->grabbed_window || ((_this->grabbed_window->flags & SDL_WINDOW_INPUT_GRABBED) != 0));
+    SDL_assert(!_this->grabbed_window ||
+               ((_this->grabbed_window->flags & SDL_WINDOW_MOUSE_GRABBED) != 0) ||
+               ((_this->grabbed_window->flags & SDL_WINDOW_KEYBOARD_GRABBED) != 0));
     return window == _this->grabbed_window;
 }
 
+SDL_bool
+SDL_GetWindowKeyboardGrab(SDL_Window * window)
+{
+    CHECK_WINDOW_MAGIC(window, SDL_FALSE);
+    return window == _this->grabbed_window &&
+           ((_this->grabbed_window->flags & SDL_WINDOW_KEYBOARD_GRABBED) != 0);
+}
+
+SDL_bool
+SDL_GetWindowMouseGrab(SDL_Window * window)
+{
+    CHECK_WINDOW_MAGIC(window, SDL_FALSE);
+    return window == _this->grabbed_window &&
+           ((_this->grabbed_window->flags & SDL_WINDOW_MOUSE_GRABBED) != 0);
+}
+
 SDL_Window *
 SDL_GetGrabbedWindow(void)
 {
-    SDL_assert(!_this->grabbed_window || ((_this->grabbed_window->flags & SDL_WINDOW_INPUT_GRABBED) != 0));
+    SDL_assert(!_this->grabbed_window ||
+               ((_this->grabbed_window->flags & SDL_WINDOW_MOUSE_GRABBED) != 0) ||
+               ((_this->grabbed_window->flags & SDL_WINDOW_KEYBOARD_GRABBED) != 0));
     return _this->grabbed_window;
 }
 
diff --git a/src/video/cocoa/SDL_cocoamousetap.m b/src/video/cocoa/SDL_cocoamousetap.m
index 503a524..f1ed18a 100644
--- a/src/video/cocoa/SDL_cocoamousetap.m
+++ b/src/video/cocoa/SDL_cocoamousetap.m
@@ -90,7 +90,7 @@
         return event;
     }
 
-    if (!(window->flags & SDL_WINDOW_INPUT_GRABBED)) {
+    if (!(window->flags & SDL_WINDOW_MOUSE_GRABBED)) {
         return event;
     }
 
diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m
index f27c1f6..802cb3a 100644
--- a/src/video/cocoa/SDL_cocoawindow.m
+++ b/src/video/cocoa/SDL_cocoawindow.m
@@ -1109,7 +1109,7 @@
     x = (int)point.x;
     y = (int)(window->h - point.y);
 
-    if (window->flags & SDL_WINDOW_INPUT_GRABBED) {
+    if (window->flags & SDL_WINDOW_MOUSE_GRABBED) {
         if (x < 0 || x >= window->w || y < 0 || y >= window->h) {
             if (x < 0) {
                 x = 0;
diff --git a/src/video/directfb/SDL_DirectFB_events.c b/src/video/directfb/SDL_DirectFB_events.c
index 1d4c199..146bb99 100644
--- a/src/video/directfb/SDL_DirectFB_events.c
+++ b/src/video/directfb/SDL_DirectFB_events.c
@@ -212,7 +212,7 @@
         case DWET_MOTION:
             if (ClientXY(windata, &evt->x, &evt->y)) {
                 if (!devdata->use_linux_input) {
-                    if (!(sdlwin->flags & SDL_WINDOW_INPUT_GRABBED))
+                    if (!(sdlwin->flags & SDL_WINDOW_MOUSE_GRABBED))
                         SDL_SendMouseMotion_ex(sdlwin, devdata->mouse_id[0], 0,
                                             evt->x, evt->y, 0);
                 } else {
diff --git a/src/video/os2/SDL_os2video.c b/src/video/os2/SDL_os2video.c
index 5ae2c71..0ec0f1e 100644
--- a/src/video/os2/SDL_os2video.c
+++ b/src/video/os2/SDL_os2video.c
@@ -183,7 +183,7 @@
 {
     SDL_Mouse *pSDLMouse = SDL_GetMouse();
 
-    if ((pSDLMouse->relative_mode || (pWinData->window->flags & SDL_WINDOW_INPUT_GRABBED) != 0) &&
+    if ((pSDLMouse->relative_mode || (pWinData->window->flags & SDL_WINDOW_MOUSE_GRABBED) != 0) &&
         ((pWinData->window->flags & SDL_WINDOW_INPUT_FOCUS) != 0)) {
         /* We will make a real capture in _wmMouseButton() */
     } else {
@@ -232,7 +232,7 @@
 
     if (!pSDLMouse->relative_mode || pSDLMouse->relative_mode_warp) {
         if (!pSDLMouse->relative_mode && fWinActive &&
-            ((pWinData->window->flags & SDL_WINDOW_INPUT_GRABBED) != 0) &&
+            ((pWinData->window->flags & SDL_WINDOW_MOUSE_GRABBED) != 0) &&
             (WinQueryCapture(HWND_DESKTOP) == pWinData->hwnd)) {
 
             pointl.x = lX;
@@ -281,7 +281,7 @@
                                       SDL_BUTTON_MIDDLE };
     SDL_Mouse *pSDLMouse = SDL_GetMouse();
 
-    if ((pSDLMouse->relative_mode || ((pWinData->window->flags & SDL_WINDOW_INPUT_GRABBED) != 0)) &&
+    if ((pSDLMouse->relative_mode || ((pWinData->window->flags & SDL_WINDOW_MOUSE_GRABBED) != 0)) &&
         ((pWinData->window->flags & SDL_WINDOW_INPUT_FOCUS) != 0) &&
         (WinQueryCapture(HWND_DESKTOP) != pWinData->hwnd)) {
         /* Mouse should be captured. */
diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c
index c8b57cd..6eb26eb 100644
--- a/src/video/windows/SDL_windowswindow.c
+++ b/src/video/windows/SDL_windowswindow.c
@@ -282,7 +282,7 @@
         window->flags |= SDL_WINDOW_INPUT_FOCUS;
         SDL_SetKeyboardFocus(data->window);
 
-        if (window->flags & SDL_WINDOW_INPUT_GRABBED) {
+        if (window->flags & SDL_WINDOW_MOUSE_GRABBED) {
             RECT rect;
             GetClientRect(hwnd, &rect);
             ClientToScreen(hwnd, (LPPOINT) & rect);
@@ -973,7 +973,7 @@
         return;
     }
 
-    if ((mouse->relative_mode || (window->flags & SDL_WINDOW_INPUT_GRABBED)) &&
+    if ((mouse->relative_mode || (window->flags & SDL_WINDOW_MOUSE_GRABBED)) &&
         (window->flags & SDL_WINDOW_INPUT_FOCUS)) {
         if (mouse->relative_mode && !mouse->relative_mode_warp) {
             if (GetWindowRect(data->hwnd, &rect)) {
diff --git a/src/video/x11/SDL_x11window.c b/src/video/x11/SDL_x11window.c
index dbe114f..3b090f7 100644
--- a/src/video/x11/SDL_x11window.c
+++ b/src/video/x11/SDL_x11window.c
@@ -325,7 +325,7 @@
             SDL_SetKeyboardFocus(data->window);
         }
 
-        if (window->flags & SDL_WINDOW_INPUT_GRABBED) {
+        if (window->flags & SDL_WINDOW_MOUSE_GRABBED) {
             /* Tell x11 to clip mouse */
         }
     }
@@ -1623,6 +1623,13 @@
     Display *display = data->videodata->display;
 
     if (grabbed) {
+        /* If the window is unmapped, XGrab calls return GrabNotViewable,
+           so when we get a MapNotify later, we'll try to update the grab as
+           appropriate. */
+        if (window->flags & SDL_WINDOW_HIDDEN) {
+            return;
+        }
+
         X11_XGrabKeyboard(display, data->xwindow, True, GrabModeAsync,
                           GrabModeAsync, CurrentTime);
     } else {
diff --git a/test/testautomation_video.c b/test/testautomation_video.c
index 7b86cfb..bd59e8e 100644
--- a/test/testautomation_video.c
+++ b/test/testautomation_video.c
@@ -265,7 +265,7 @@
   w = SDLTest_RandomIntegerInRange(320, 1024);
   h = SDLTest_RandomIntegerInRange(320, 768);
 
-  for (fVariation = 0; fVariation < 13; fVariation++) {
+  for (fVariation = 0; fVariation < 14; fVariation++) {
     switch(fVariation) {
      case 0:
       flags = SDL_WINDOW_FULLSCREEN;
@@ -299,7 +299,7 @@
       flags = SDL_WINDOW_MAXIMIZED;
       break;
      case 9:
-      flags = SDL_WINDOW_INPUT_GRABBED;
+      flags = SDL_WINDOW_MOUSE_GRABBED;
       break;
      case 10:
       flags = SDL_WINDOW_INPUT_FOCUS;
@@ -310,6 +310,9 @@
      case 12:
       flags = SDL_WINDOW_FOREIGN;
       break;
+     case 13:
+      flags = SDL_WINDOW_KEYBOARD_GRABBED;
+      break;
     }
 
     window = SDL_CreateWindow(title, x, y, w, h, flags);
@@ -744,87 +747,213 @@
   return TEST_COMPLETED;
 }
 
-/* Helper for setting and checking the window grab state */
+/* Helper for setting and checking the window mouse grab state */
 void
-_setAndCheckWindowGrabState(SDL_Window* window, SDL_bool desiredState)
+_setAndCheckWindowMouseGrabState(SDL_Window* window, SDL_bool desiredState)
 {
   SDL_bool currentState;
 
   /* Set state */
-  SDL_SetWindowGrab(window, desiredState);
-  SDLTest_AssertPass("Call to SDL_SetWindowGrab(%s)", (desiredState == SDL_FALSE) ? "SDL_FALSE" : "SDL_TRUE");
+  SDL_SetWindowMouseGrab(window, desiredState);
+  SDLTest_AssertPass("Call to SDL_SetWindowMouseGrab(%s)", (desiredState == SDL_FALSE) ? "SDL_FALSE" : "SDL_TRUE");
 
   /* Get and check state */
-  currentState = SDL_GetWindowGrab(window);
-  SDLTest_AssertPass("Call to SDL_GetWindowGrab()");
+  currentState = SDL_GetWindowMouseGrab(window);
+  SDLTest_AssertPass("Call to SDL_GetWindowMouseGrab()");
   SDLTest_AssertCheck(
       currentState == desiredState,
       "Validate returned state; expected: %s, got: %s",
       (desiredState == SDL_FALSE) ? "SDL_FALSE" : "SDL_TRUE",
       (currentState == SDL_FALSE) ? "SDL_FALSE" : "SDL_TRUE");
+
+  if (desiredState) {
+    SDLTest_AssertCheck(
+      SDL_GetGrabbedWindow() == window,
+      "Grabbed window should be to our window");
+    SDLTest_AssertCheck(
+      SDL_GetWindowGrab(window),
+      "SDL_GetWindowGrab() should return SDL_TRUE");
+    SDLTest_AssertCheck(
+      SDL_GetWindowFlags(window) & SDL_WINDOW_MOUSE_GRABBED,
+      "SDL_WINDOW_MOUSE_GRABBED should be set");
+  } else {
+    SDLTest_AssertCheck(
+      !(SDL_GetWindowFlags(window) & SDL_WINDOW_MOUSE_GRABBED),
+      "SDL_WINDOW_MOUSE_GRABBED should be unset");
+  }
+}
+
+/* Helper for setting and checking the window keyboard grab state */
+void
+_setAndCheckWindowKeyboardGrabState(SDL_Window* window, SDL_bool desiredState)
+{
+  SDL_bool currentState;
+
+  /* Set state */
+  SDL_SetWindowKeyboardGrab(window, desiredState);
+  SDLTest_AssertPass("Call to SDL_SetWindowKeyboardGrab(%s)", (desiredState == SDL_FALSE) ? "SDL_FALSE" : "SDL_TRUE");
+
+  /* Get and check state */
+  currentState = SDL_GetWindowKeyboardGrab(window);
+  SDLTest_AssertPass("Call to SDL_GetWindowKeyboardGrab()");
+  SDLTest_AssertCheck(
+      currentState == desiredState,
+      "Validate returned state; expected: %s, got: %s",
+      (desiredState == SDL_FALSE) ? "SDL_FALSE" : "SDL_TRUE",
+      (currentState == SDL_FALSE) ? "SDL_FALSE" : "SDL_TRUE");
+
+  if (desiredState) {
+    SDLTest_AssertCheck(
+      SDL_GetGrabbedWindow() == window,
+      "Grabbed window should be set to our window");
+    SDLTest_AssertCheck(
+      SDL_GetWindowGrab(window),
+      "SDL_GetWindowGrab() should return SDL_TRUE");
+    SDLTest_AssertCheck(
+      SDL_GetWindowFlags(window) & SDL_WINDOW_KEYBOARD_GRABBED,
+      "SDL_WINDOW_KEYBOARD_GRABBED should be set");
+  } else {
+    SDLTest_AssertCheck(
+      !(SDL_GetWindowFlags(window) & SDL_WINDOW_KEYBOARD_GRABBED),
+      "SDL_WINDOW_KEYBOARD_GRABBED should be unset");
+  }
 }
 
 /**
- * @brief Tests call to SDL_GetWindowGrab and SDL_SetWindowGrab
+ * @brief Tests keyboard and mouse grab support
  *
- * @sa http://wiki.libsdl.org/moin.fcg/SDL_GetWindowGrab
- * @sa http://wiki.libsdl.org/moin.fcg/SDL_SetWindowGrab
+ * @sa http://wiki.libsdl.org/SDL_GetWindowGrab
+ * @sa http://wiki.libsdl.org/SDL_SetWindowGrab
  */
 int
 video_getSetWindowGrab(void *arg)
 {
   const char* title = "video_getSetWindowGrab Test Window";
   SDL_Window* window;
-  SDL_bool originalState, dummyState, currentState, desiredState;
+  SDL_bool originalMouseState, originalKeyboardState, dummyState;
 
   /* Call against new test window */
   window = _createVideoSuiteTestWindow(title);
   if (window == NULL) return TEST_ABORTED;
 
   /* Get state */
-  originalState = SDL_GetWindowGrab(window);
-  SDLTest_AssertPass("Call to SDL_GetWindowGrab()");
+  originalMouseState = SDL_GetWindowMouseGrab(window);
+  SDLTest_AssertPass("Call to SDL_GetWindowMouseGrab()");
+  originalKeyboardState = SDL_GetWindowKeyboardGrab(window);
+  SDLTest_AssertPass("Call to SDL_GetWindowKeyboardGrab()");
 
   /* F */
-  _setAndCheckWindowGrabState(window, SDL_FALSE);
+  _setAndCheckWindowKeyboardGrabState(window, SDL_FALSE);
+  _setAndCheckWindowMouseGrabState(window, SDL_FALSE);
+  SDLTest_AssertCheck(!SDL_GetWindowGrab(window),
+                      "SDL_GetWindowGrab should return SDL_FALSE");
+  SDLTest_AssertCheck(SDL_GetGrabbedWindow() == NULL,
+                      "Expected NULL grabbed window");
 
   /* F --> F */
-  _setAndCheckWindowGrabState(window, SDL_FALSE);
+  _setAndCheckWindowMouseGrabState(window, SDL_FALSE);
+  _setAndCheckWindowKeyboardGrabState(window, SDL_FALSE);
+  SDLTest_AssertCheck(SDL_GetGrabbedWindow() == NULL,
+                      "Expected NULL grabbed window");
 
   /* F --> T */
-  _setAndCheckWindowGrabState(window, SDL_TRUE);
+  _setAndCheckWindowMouseGrabState(window, SDL_TRUE);
+  _setAndCheckWindowKeyboardGrabState(window, SDL_TRUE);
+  SDLTest_AssertCheck(SDL_GetWindowGrab(window),
+                      "SDL_GetWindowGrab() should return SDL_TRUE");
 
   /* T --> T */
-  _setAndCheckWindowGrabState(window, SDL_TRUE);
+  _setAndCheckWindowKeyboardGrabState(window, SDL_TRUE);
+  _setAndCheckWindowMouseGrabState(window, SDL_TRUE);
+  SDLTest_AssertCheck(SDL_GetWindowGrab(window),
+                      "SDL_GetWindowGrab() should return SDL_TRUE");
 
-  /* T --> F */
-  _setAndCheckWindowGrabState(window, SDL_FALSE);
+  /* M: T --> F */
+  /* K: T --> T */
+  _setAndCheckWindowKeyboardGrabState(window, SDL_TRUE);
+  _setAndCheckWindowMouseGrabState(window, SDL_FALSE);
+  SDLTest_AssertCheck(SDL_GetWindowGrab(window),
+                      "SDL_GetWindowGrab() should return SDL_TRUE");
+
+  /* M: F --> T */
+  /* K: T --> F */
+  _setAndCheckWindowMouseGrabState(window, SDL_TRUE);
+  _setAndCheckWindowKeyboardGrabState(window, SDL_FALSE);
+  SDLTest_AssertCheck(SDL_GetWindowGrab(window),
+                      "SDL_GetWindowGrab() should return SDL_TRUE");
+
+  /* M: T --> F */
+  /* K: F --> F */
+  _setAndCheckWindowMouseGrabState(window, SDL_FALSE);
+  _setAndCheckWindowKeyboardGrabState(window, SDL_FALSE);
+  SDLTest_AssertCheck(!SDL_GetWindowGrab(window),
+                      "SDL_GetWindowGrab() should return SDL_FALSE");
+  SDLTest_AssertCheck(SDL_GetGrabbedWindow() == NULL,
+                      "Expected NULL grabbed window");
+
+  /* Using the older SDL_SetWindowGrab API should only grab mouse by default */
+  SDL_SetWindowGrab(window, SDL_TRUE);
+  SDLTest_AssertPass("Call to SDL_SetWindowGrab(SDL_TRUE)");
+  SDLTest_AssertCheck(SDL_GetWindowGrab(window),
+                      "SDL_GetWindowGrab() should return SDL_TRUE");
+  SDLTest_AssertCheck(SDL_GetWindowMouseGrab(window),
+                      "SDL_GetWindowMouseGrab() should return SDL_TRUE");
+  SDLTest_AssertCheck(!SDL_GetWindowKeyboardGrab(window),
+                      "SDL_GetWindowKeyboardGrab() should return SDL_FALSE");
+  SDL_SetWindowGrab(window, SDL_FALSE);
+  SDLTest_AssertCheck(!SDL_GetWindowGrab(window),
+                      "SDL_GetWindowGrab() should return SDL_FALSE");
+  SDLTest_AssertCheck(!SDL_GetWindowMouseGrab(window),
+                      "SDL_GetWindowMouseGrab() should return SDL_FALSE");
+  SDLTest_AssertCheck(!SDL_GetWindowKeyboardGrab(window),
+                      "SDL_GetWindowKeyboardGrab() should return SDL_FALSE");
+
+  /* Now test with SDL_HINT_GRAB_KEYBOARD set. We should get keyboard grab now. */
+  SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1");
+  SDL_SetWindowGrab(window, SDL_TRUE);
+  SDLTest_AssertPass("Call to SDL_SetWindowGrab(SDL_TRUE)");
+  SDLTest_AssertCheck(SDL_GetWindowGrab(window),
+                      "SDL_GetWindowGrab() should return SDL_TRUE");
+  SDLTest_AssertCheck(SDL_GetWindowMouseGrab(window),
+                      "SDL_GetWindowMouseGrab() should return SDL_TRUE");
+  SDLTest_AssertCheck(SDL_GetWindowKeyboardGrab(window),
+                      "SDL_GetWindowKeyboardGrab() should return SDL_TRUE");
+  SDL_SetWindowGrab(window, SDL_FALSE);
+  SDLTest_AssertCheck(!SDL_GetWindowGrab(window),
+                      "SDL_GetWindowGrab() should return SDL_FALSE");
+  SDLTest_AssertCheck(!SDL_GetWindowMouseGrab(window),
+                      "SDL_GetWindowMouseGrab() should return SDL_FALSE");
+  SDLTest_AssertCheck(!SDL_GetWindowKeyboardGrab(window),
+                      "SDL_GetWindowKeyboardGrab() should return SDL_FALSE");
 
   /* Negative tests */
   dummyState = SDL_GetWindowGrab(NULL);
   SDLTest_AssertPass("Call to SDL_GetWindowGrab(window=NULL)");
   _checkInvalidWindowError();
 
+  dummyState = SDL_GetWindowKeyboardGrab(NULL);
+  SDLTest_AssertPass("Call to SDL_GetWindowKeyboardGrab(window=NULL)");
+  _checkInvalidWindowError();
+
   SDL_SetWindowGrab(NULL, SDL_FALSE);
   SDLTest_AssertPass("Call to SDL_SetWindowGrab(window=NULL,SDL_FALSE)");
   _checkInvalidWindowError();
 
-  SDL_SetWindowGrab(NULL, SDL_TRUE);
-  SDLTest_AssertPass("Call to SDL_SetWindowGrab(window=NULL,SDL_FALSE)");
+  SDL_SetWindowKeyboardGrab(NULL, SDL_FALSE);
+  SDLTest_AssertPass("Call to SDL_SetWindowKeyboardGrab(window=NULL,SDL_FALSE)");
   _checkInvalidWindowError();
 
-  /* State should still be F */
-  desiredState = SDL_FALSE;
-  currentState = SDL_GetWindowGrab(window);
-  SDLTest_AssertPass("Call to SDL_GetWindowGrab()");
-  SDLTest_AssertCheck(
-      currentState == desiredState,
-      "Validate returned state; expected: %s, got: %s",
-      (desiredState == SDL_FALSE) ? "SDL_FALSE" : "SDL_TRUE",
-      (currentState == SDL_FALSE) ? "SDL_FALSE" : "SDL_TRUE");
+  SDL_SetWindowGrab(NULL, SDL_TRUE);
+  SDLTest_AssertPass("Call to SDL_SetWindowGrab(window=NULL,SDL_TRUE)");
+  _checkInvalidWindowError();
+
+  SDL_SetWindowKeyboardGrab(NULL, SDL_TRUE);
+  SDLTest_AssertPass("Call to SDL_SetWindowKeyboardGrab(window=NULL,SDL_TRUE)");
+  _checkInvalidWindowError();
 
   /* Restore state */
-  _setAndCheckWindowGrabState(window, originalState);
+  _setAndCheckWindowMouseGrabState(window, originalMouseState);
+  _setAndCheckWindowKeyboardGrabState(window, originalKeyboardState);
 
   /* Clean up */
   _destroyVideoSuiteTestWindow(window);