diff --git a/README.md b/README.md
index cf4643e5a2df45d1b30090b25f20240d03510f30..34ec2b3a7a58469dd9707d7913dfabf49152fb22 100644
--- a/README.md
+++ b/README.md
@@ -162,6 +162,7 @@ information on what to include when reporting a bug.
 - Added definition of `GLAPIENTRY` to public header
 - Added `GLFW_TRANSPARENT_FRAMEBUFFER` window hint and attribute for controlling
   per-pixel framebuffer transparency (#197,#663,#715,#723,#1078)
+- Added `GLFW_HOVERED` window attribute for polling cursor hover state (#1166)
 - Added `GLFW_CENTER_CURSOR` window hint for controlling cursor centering
   (#749,#842)
 - Added `GLFW_JOYSTICK_HAT_BUTTONS` init hint (#889)
diff --git a/docs/input.dox b/docs/input.dox
index a72ba150afd35ce5ec34eeb0c920e2e5574c4053..1d8439d5516435e0638c5807ddb0bca995123df1 100644
--- a/docs/input.dox
+++ b/docs/input.dox
@@ -419,6 +419,16 @@ void cursor_enter_callback(GLFWwindow* window, int entered)
 }
 @endcode
 
+You can query whether the cursor is currently inside the client area of the
+window with the [GLFW_HOVERED](@ref GLFW_HOVERED_attrib) window attribute.
+
+@code
+if (glfwGetWindowAttrib(window, GLFW_HOVERED))
+{
+    highlight_interface();
+}
+@endcode
+
 
 @subsection input_mouse_button Mouse button input
 
diff --git a/docs/window.dox b/docs/window.dox
index 2f11f135b4f8dbc68d9b058c59164e98431883c9..9e97ce3bc6277522f4cd6d6e5913b7959982ba7f 100644
--- a/docs/window.dox
+++ b/docs/window.dox
@@ -1220,6 +1220,11 @@ See @ref window_iconify for details.
 __GLFW_MAXIMIZED__ indicates whether the specified window is maximized.  See
 @ref window_maximize for details.
 
+@anchor GLFW_HOVERED_attrib
+__GLFW_HOVERED__ indicates whether the cursor is currently directly over the
+client area of the window, with no other windows between.  See @ref cursor_enter
+for details.
+
 @anchor GLFW_VISIBLE_attrib
 __GLFW_VISIBLE__ indicates whether the specified window is visible.  See @ref
 window_hide for details.
diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h
index 167d99f294786413692f25e5667658b146151fa0..237dd55ef138268ac4af66c3e3571ac25f531818 100644
--- a/include/GLFW/glfw3.h
+++ b/include/GLFW/glfw3.h
@@ -814,6 +814,7 @@ extern "C" {
  *  [window attribute](@ref GLFW_TRANSPARENT_FRAMEBUFFER_attrib).
  */
 #define GLFW_TRANSPARENT_FRAMEBUFFER 0x0002000A
+#define GLFW_HOVERED                0x0002000B
 
 /*! @brief Framebuffer bit depth hint.
  *
diff --git a/src/cocoa_window.m b/src/cocoa_window.m
index 5b62bb5979256d3a4aa632d92aa8261b23b08352..10d4a0c13e23933010fa89fa2bbadf77e46e31a1 100644
--- a/src/cocoa_window.m
+++ b/src/cocoa_window.m
@@ -1488,6 +1488,20 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
     return [window->ns.object isZoomed];
 }
 
+int _glfwPlatformWindowHovered(_GLFWwindow* window)
+{
+    const NSPoint point = [NSEvent mouseLocation];
+
+    if ([NSWindow windowNumberAtPoint:point belowWindowWithWindowNumber:0] !=
+        [window->ns.object windowNumber])
+    {
+        return GLFW_FALSE;
+    }
+
+    return NSPointInRect(point,
+        [window->ns.object convertRectToScreen:[window->ns.view bounds]]);
+}
+
 int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
 {
     return ![window->ns.object isOpaque] && ![window->ns.view isOpaque];
diff --git a/src/internal.h b/src/internal.h
index 4de1b9f55f1ce59fac39e148768750660750e6c8..ea9c4ed1a59355e870a48bfe6d37501c14fe944e 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -690,6 +690,7 @@ int _glfwPlatformWindowFocused(_GLFWwindow* window);
 int _glfwPlatformWindowIconified(_GLFWwindow* window);
 int _glfwPlatformWindowVisible(_GLFWwindow* window);
 int _glfwPlatformWindowMaximized(_GLFWwindow* window);
+int _glfwPlatformWindowHovered(_GLFWwindow* window);
 int _glfwPlatformFramebufferTransparent(_GLFWwindow* window);
 float _glfwPlatformGetWindowOpacity(_GLFWwindow* window);
 void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled);
diff --git a/src/mir_window.c b/src/mir_window.c
index ad06cb073d468cfd1aeccd767bb9f2dff5596ad7..a0d17db2e10b28c25e101ca7f6c8dc397a184b95 100644
--- a/src/mir_window.c
+++ b/src/mir_window.c
@@ -627,6 +627,13 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
     return mir_window_get_state(window->mir.window) == mir_window_state_maximized;
 }
 
+int _glfwPlatformWindowHovered(_GLFWwindow* window)
+{
+    _glfwInputError(GLFW_PLATFORM_ERROR,
+                    "Mir: Unsupported function %s", __PRETTY_FUNCTION__);
+    return GLFW_FALSE;
+}
+
 int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
 {
     _glfwInputError(GLFW_PLATFORM_ERROR,
diff --git a/src/null_window.c b/src/null_window.c
index 6eb4ac6e209bfabbb5f7f805f30f68e017e6c24d..6a54cfe56e709e68ca92caaa47ff0c9b2a1e84d6 100644
--- a/src/null_window.c
+++ b/src/null_window.c
@@ -165,6 +165,11 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
     return GLFW_FALSE;
 }
 
+int _glfwPlatformWindowHovered(_GLFWwindow* window)
+{
+    return GLFW_FALSE;
+}
+
 int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
 {
     return GLFW_FALSE;
diff --git a/src/win32_window.c b/src/win32_window.c
index 3e3fb63b9f86e0e532996b74979ffb169a56bcde..fbb4f2ebaad8f9f3a4aeb3e4742ef739f79e7180 100644
--- a/src/win32_window.c
+++ b/src/win32_window.c
@@ -1578,6 +1578,11 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
     return IsZoomed(window->win32.handle);
 }
 
+int _glfwPlatformWindowHovered(_GLFWwindow* window)
+{
+    return cursorInClientArea(window);
+}
+
 int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
 {
     return window->win32.transparent && _glfwIsCompositionEnabledWin32();
diff --git a/src/window.c b/src/window.c
index ae365560edf02de6894a1f1b020f2eba832b1869..e7352f472b304c9bac8b1a0a187b2fc75c922edc 100644
--- a/src/window.c
+++ b/src/window.c
@@ -785,6 +785,8 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* handle, int attrib)
             return _glfwPlatformWindowVisible(window);
         case GLFW_MAXIMIZED:
             return _glfwPlatformWindowMaximized(window);
+        case GLFW_HOVERED:
+            return _glfwPlatformWindowHovered(window);
         case GLFW_TRANSPARENT_FRAMEBUFFER:
             return _glfwPlatformFramebufferTransparent(window);
         case GLFW_RESIZABLE:
diff --git a/src/wl_init.c b/src/wl_init.c
index 8a5d98a466da908b71a59e4bb7edc95d49349e56..78d5901e1fe1cddfd7c902b28b2500ca8d47c347 100644
--- a/src/wl_init.c
+++ b/src/wl_init.c
@@ -54,6 +54,8 @@ static void pointerHandleEnter(void* data,
     _glfw.wl.pointerSerial = serial;
     _glfw.wl.pointerFocus = window;
 
+    window->wl.hovered = GLFW_TRUE;
+
     _glfwPlatformSetCursor(window, window->wl.currentCursor);
     _glfwInputCursorEnter(window, GLFW_TRUE);
 }
@@ -68,6 +70,8 @@ static void pointerHandleLeave(void* data,
     if (!window)
         return;
 
+    window->wl.hovered = GLFW_FALSE;
+
     _glfw.wl.pointerSerial = serial;
     _glfw.wl.pointerFocus = NULL;
     _glfwInputCursorEnter(window, GLFW_FALSE);
diff --git a/src/wl_platform.h b/src/wl_platform.h
index c3cebecf9d5194b89c4baf166eebd550e512973b..534998124cb2db670c9c4d3c10be9c5f326e47de 100644
--- a/src/wl_platform.h
+++ b/src/wl_platform.h
@@ -117,6 +117,7 @@ typedef struct _GLFWwindowWayland
     int                         width, height;
     GLFWbool                    visible;
     GLFWbool                    maximized;
+    GLFWbool                    hovered;
     GLFWbool                    transparent;
     struct wl_surface*          surface;
     struct wl_egl_window*       native;
diff --git a/src/wl_window.c b/src/wl_window.c
index 9759ba26fc15ddca99df5054efb593b6c0b5071c..daebf6723402d25b76a1af3ba9780043ef028e09 100644
--- a/src/wl_window.c
+++ b/src/wl_window.c
@@ -692,6 +692,11 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
     return window->wl.maximized;
 }
 
+int _glfwPlatformWindowHovered(_GLFWwindow* window)
+{
+    return window->wl.hovered;
+}
+
 int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
 {
     return window->wl.transparent;
diff --git a/src/x11_window.c b/src/x11_window.c
index a76005588c5a29af3af972809941f4dd64ea4f80..1520b82563436c381675a44e442f255d484329b0 100644
--- a/src/x11_window.c
+++ b/src/x11_window.c
@@ -2453,6 +2453,28 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
     return maximized;
 }
 
+int _glfwPlatformWindowHovered(_GLFWwindow* window)
+{
+    Window w = _glfw.x11.root;
+    while (w)
+    {
+        Window root;
+        int rootX, rootY, childX, childY;
+        unsigned int mask;
+
+        if (!XQueryPointer(_glfw.x11.display, w,
+                           &root, &w, &rootX, &rootY, &childX, &childY, &mask))
+        {
+            return GLFW_FALSE;
+        }
+
+        if (w == window->x11.handle)
+            return GLFW_TRUE;
+    }
+
+    return GLFW_FALSE;
+}
+
 int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
 {
     if (!window->x11.transparent)