From d285a9fdeb5e7f4688ebbd8f8fa8f101b01b5bea Mon Sep 17 00:00:00 2001 From: Rokas Kupstys Date: Mon, 30 Sep 2019 15:44:43 +0300 Subject: [PATCH] Add support for mouse input transparency This adds the GLFW_MOUSE_PASSTHROUGH window hint and attribute for controlling whether mouse input passes through the window to whatever window is behind it. Fixes #1236. Closes #1568. --- include/GLFW/glfw3.h | 8 ++++++++ src/cocoa_window.m | 8 ++++++++ src/internal.h | 3 +++ src/null_window.c | 4 ++++ src/win32_window.c | 12 ++++++++++++ src/window.c | 9 +++++++++ src/wl_window.c | 17 +++++++++++++++++ src/x11_init.c | 33 +++++++++++++++++++++++++++++++++ src/x11_platform.h | 28 ++++++++++++++++++++++++++++ src/x11_window.c | 26 ++++++++++++++++++++++++++ 10 files changed, 148 insertions(+) diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index d489f2a0..e218d3a5 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -895,6 +895,13 @@ extern "C" { */ #define GLFW_FOCUS_ON_SHOW 0x0002000C +/*! @brief Forward mouse input to window behind. + * + * Mouse input forwarding[window hint](@ref GLFW_MOUSE_PASSTHROUGH_hint) or + * [window attribute](@ref GLFW_MOUSE_PASSTHROUGH_attrib). + */ +#define GLFW_MOUSE_PASSTHROUGH 0x0002000D + /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_RED_BITS). @@ -3656,6 +3663,7 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* window, int attrib); * [GLFW_FLOATING](@ref GLFW_FLOATING_attrib), * [GLFW_AUTO_ICONIFY](@ref GLFW_AUTO_ICONIFY_attrib) and * [GLFW_FOCUS_ON_SHOW](@ref GLFW_FOCUS_ON_SHOW_attrib). + * [GLFW_MOUSE_PASSTHROUGH](@ref GLFW_MOUSE_PASSTHROUGH_attrib) * * Some of these attributes are ignored for full screen windows. The new * value will take effect if the window is later made windowed. diff --git a/src/cocoa_window.m b/src/cocoa_window.m index e34fb876..84908967 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -1371,6 +1371,14 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) } // autoreleasepool } +void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, GLFWbool enabled) +{ + window->mousePassthrough = enabled; + @autoreleasepool { + [window->ns.object setIgnoresMouseEvents:enabled]; + } +} + float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) { @autoreleasepool { diff --git a/src/internal.h b/src/internal.h index 73d2d823..9fd1a8e9 100644 --- a/src/internal.h +++ b/src/internal.h @@ -270,6 +270,7 @@ struct _GLFWwndconfig GLFWbool maximized; GLFWbool centerCursor; GLFWbool focusOnShow; + GLFWbool mousePassthrough; GLFWbool scaleToMonitor; struct { GLFWbool retina; @@ -380,6 +381,7 @@ struct _GLFWwindow GLFWbool autoIconify; GLFWbool floating; GLFWbool focusOnShow; + GLFWbool mousePassthrough; GLFWbool shouldClose; void* userPointer; GLFWvidmode videoMode; @@ -678,6 +680,7 @@ float _glfwPlatformGetWindowOpacity(_GLFWwindow* window); void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled); void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled); void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled); +void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, GLFWbool enabled); void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity); void _glfwPlatformPollEvents(void); diff --git a/src/null_window.c b/src/null_window.c index ba85571b..61a17da7 100644 --- a/src/null_window.c +++ b/src/null_window.c @@ -362,6 +362,10 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) window->null.floating = enabled; } +void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, GLFWbool enabled) +{ +} + float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) { return window->null.opacity; diff --git a/src/win32_window.c b/src/win32_window.c index 9dc52bab..0b5ccb5a 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -1201,6 +1201,13 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, DragFinish(drop); return 0; } + + case WM_NCHITTEST: + { + if (window->mousePassthrough) + return HTTRANSPARENT; + break; + } } return DefWindowProcW(hWnd, uMsg, wParam, lParam); @@ -1854,6 +1861,11 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); } +void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, GLFWbool enabled) +{ + window->mousePassthrough = enabled; +} + float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) { BYTE alpha; diff --git a/src/window.c b/src/window.c index 4716cd09..94a4ac68 100644 --- a/src/window.c +++ b/src/window.c @@ -243,6 +243,8 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, } } + _glfwPlatformSetWindowMousePassthrough(window, wndconfig.mousePassthrough); + return (GLFWwindow*) window; } @@ -378,6 +380,9 @@ GLFWAPI void glfwWindowHint(int hint, int value) case GLFW_FOCUS_ON_SHOW: _glfw.hints.window.focusOnShow = value ? GLFW_TRUE : GLFW_FALSE; return; + case GLFW_MOUSE_PASSTHROUGH: + _glfw.hints.window.mousePassthrough = value ? GLFW_TRUE : GLFW_FALSE; + return; case GLFW_CLIENT_API: _glfw.hints.context.client = value; return; @@ -822,6 +827,8 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* handle, int attrib) return _glfwPlatformWindowHovered(window); case GLFW_FOCUS_ON_SHOW: return window->focusOnShow; + case GLFW_MOUSE_PASSTHROUGH: + return window->mousePassthrough; case GLFW_TRANSPARENT_FRAMEBUFFER: return _glfwPlatformFramebufferTransparent(window); case GLFW_RESIZABLE: @@ -900,6 +907,8 @@ GLFWAPI void glfwSetWindowAttrib(GLFWwindow* handle, int attrib, int value) } else if (attrib == GLFW_FOCUS_ON_SHOW) window->focusOnShow = value; + else if (attrib == GLFW_MOUSE_PASSTHROUGH) + _glfwPlatformSetWindowMousePassthrough(window, value); else _glfwInputError(GLFW_INVALID_ENUM, "Invalid window attribute 0x%08X", attrib); } diff --git a/src/wl_window.c b/src/wl_window.c index d1dad065..13d801b0 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -1127,6 +1127,23 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) "Wayland: Window attribute setting not implemented yet"); } +void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, GLFWbool enabled) +{ + if (enabled == window->mousePassthrough) + return; + + if (enabled) + { + struct wl_region* region = wl_compositor_create_region(_glfw.wl.compositor); + wl_surface_set_input_region(window->wl.surface, region); + wl_region_destroy(region); + } + else + wl_surface_set_input_region(window->wl.surface, 0); + wl_surface_commit(window->wl.surface); + window->mousePassthrough = enabled; +} + float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) { return 1.f; diff --git a/src/x11_init.c b/src/x11_init.c index 80b9bb42..fd1ccb91 100644 --- a/src/x11_init.c +++ b/src/x11_init.c @@ -851,6 +851,33 @@ static GLFWbool initExtensions(void) } } +#if defined(__CYGWIN__) + _glfw.x11.xshape.handle = _glfw_dlopen("libXext-6.so"); +#else + _glfw.x11.xshape.handle = _glfw_dlopen("libXext.so.6"); +#endif + if (_glfw.x11.xshape.handle) + { + _glfw.x11.xshape.QueryExtension = (PFN_XShapeQueryExtension) + _glfw_dlsym(_glfw.x11.xshape.handle, "XShapeQueryExtension"); + _glfw.x11.xshape.ShapeCombineRegion = (PFN_XShapeCombineRegion) + _glfw_dlsym(_glfw.x11.xshape.handle, "XShapeCombineRegion"); + _glfw.x11.xshape.QueryVersion = (PFN_XShapeQueryVersion) + _glfw_dlsym(_glfw.x11.xshape.handle, "XShapeQueryVersion"); + + if (XShapeQueryExtension(_glfw.x11.display, + &_glfw.x11.xshape.errorBase, + &_glfw.x11.xshape.eventBase)) + { + if (XShapeQueryVersion(_glfw.x11.display, + &_glfw.x11.xshape.major, + &_glfw.x11.xshape.minor)) + { + _glfw.x11.xshape.available = GLFW_TRUE; + } + } + } + // Update the key code LUT // FIXME: We should listen to XkbMapNotify events to track changes to // the keyboard mapping. @@ -1122,6 +1149,8 @@ int _glfwPlatformInit(void) _glfw_dlsym(_glfw.x11.xlib.handle, "XCreateFontCursor"); _glfw.x11.xlib.CreateIC = (PFN_XCreateIC) _glfw_dlsym(_glfw.x11.xlib.handle, "XCreateIC"); + _glfw.x11.xlib.CreateRegion = (PFN_XCreateRegion) + _glfw_dlsym(_glfw.x11.xlib.handle, "XCreateRegion"); _glfw.x11.xlib.CreateWindow = (PFN_XCreateWindow) _glfw_dlsym(_glfw.x11.xlib.handle, "XCreateWindow"); _glfw.x11.xlib.DefineCursor = (PFN_XDefineCursor) @@ -1132,6 +1161,8 @@ int _glfwPlatformInit(void) _glfw_dlsym(_glfw.x11.xlib.handle, "XDeleteProperty"); _glfw.x11.xlib.DestroyIC = (PFN_XDestroyIC) _glfw_dlsym(_glfw.x11.xlib.handle, "XDestroyIC"); + _glfw.x11.xlib.DestroyRegion = (PFN_XDestroyRegion) + _glfw_dlsym(_glfw.x11.xlib.handle, "XDestroyRegion"); _glfw.x11.xlib.DestroyWindow = (PFN_XDestroyWindow) _glfw_dlsym(_glfw.x11.xlib.handle, "XDestroyWindow"); _glfw.x11.xlib.DisplayKeycodes = (PFN_XDisplayKeycodes) @@ -1254,6 +1285,8 @@ int _glfwPlatformInit(void) _glfw_dlsym(_glfw.x11.xlib.handle, "XUndefineCursor"); _glfw.x11.xlib.UngrabPointer = (PFN_XUngrabPointer) _glfw_dlsym(_glfw.x11.xlib.handle, "XUngrabPointer"); + _glfw.x11.xlib.UnionRectWithRegion = (PFN_XUnionRectWithRegion) + _glfw_dlsym(_glfw.x11.xlib.handle, "XUnionRectWithRegion"); _glfw.x11.xlib.UnmapWindow = (PFN_XUnmapWindow) _glfw_dlsym(_glfw.x11.xlib.handle, "XUnmapWindow"); _glfw.x11.xlib.UnsetICFocus = (PFN_XUnsetICFocus) diff --git a/src/x11_platform.h b/src/x11_platform.h index f55d9d1c..ab2f3aec 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -61,11 +61,13 @@ typedef int (* PFN_XConvertSelection)(Display*,Atom,Atom,Atom,Window,Time); typedef Colormap (* PFN_XCreateColormap)(Display*,Window,Visual*,int); typedef Cursor (* PFN_XCreateFontCursor)(Display*,unsigned int); typedef XIC (* PFN_XCreateIC)(XIM,...); +typedef Region (* PFN_XCreateRegion)(void); typedef Window (* PFN_XCreateWindow)(Display*,Window,int,int,unsigned int,unsigned int,unsigned int,int,unsigned int,Visual*,unsigned long,XSetWindowAttributes*); typedef int (* PFN_XDefineCursor)(Display*,Window,Cursor); typedef int (* PFN_XDeleteContext)(Display*,XID,XContext); typedef int (* PFN_XDeleteProperty)(Display*,Window,Atom); typedef void (* PFN_XDestroyIC)(XIC); +typedef int (* PFN_XDestroyRegion)(Region); typedef int (* PFN_XDestroyWindow)(Display*,Window); typedef int (* PFN_XDisplayKeycodes)(Display*,int*,int*); typedef int (* PFN_XEventsQueued)(Display*,int); @@ -127,6 +129,7 @@ typedef int (* PFN_XSync)(Display*,Bool); typedef Bool (* PFN_XTranslateCoordinates)(Display*,Window,Window,int,int,int*,int*,Window*); typedef int (* PFN_XUndefineCursor)(Display*,Window); typedef int (* PFN_XUngrabPointer)(Display*,Time); +typedef int (* PFN_XUnionRectWithRegion)(XRectangle*,Region,Region); typedef int (* PFN_XUnmapWindow)(Display*,Window); typedef void (* PFN_XUnsetICFocus)(XIC); typedef VisualID (* PFN_XVisualIDFromVisual)(Visual*); @@ -161,11 +164,13 @@ typedef void (* PFN_Xutf8SetWMProperties)(Display*,Window,const char*,const char #define XCreateColormap _glfw.x11.xlib.CreateColormap #define XCreateFontCursor _glfw.x11.xlib.CreateFontCursor #define XCreateIC _glfw.x11.xlib.CreateIC +#define XCreateRegion _glfw.x11.xlib.CreateRegion #define XCreateWindow _glfw.x11.xlib.CreateWindow #define XDefineCursor _glfw.x11.xlib.DefineCursor #define XDeleteContext _glfw.x11.xlib.DeleteContext #define XDeleteProperty _glfw.x11.xlib.DeleteProperty #define XDestroyIC _glfw.x11.xlib.DestroyIC +#define XDestroyRegion _glfw.x11.xlib.DestroyRegion #define XDestroyWindow _glfw.x11.xlib.DestroyWindow #define XDisplayKeycodes _glfw.x11.xlib.DisplayKeycodes #define XEventsQueued _glfw.x11.xlib.EventsQueued @@ -227,6 +232,7 @@ typedef void (* PFN_Xutf8SetWMProperties)(Display*,Window,const char*,const char #define XTranslateCoordinates _glfw.x11.xlib.TranslateCoordinates #define XUndefineCursor _glfw.x11.xlib.UndefineCursor #define XUngrabPointer _glfw.x11.xlib.UngrabPointer +#define XUnionRectWithRegion _glfw.x11.xlib.UnionRectWithRegion #define XUnmapWindow _glfw.x11.xlib.UnmapWindow #define XUnsetICFocus _glfw.x11.xlib.UnsetICFocus #define XVisualIDFromVisual _glfw.x11.xlib.VisualIDFromVisual @@ -331,6 +337,13 @@ typedef XRenderPictFormat* (* PFN_XRenderFindVisualFormat)(Display*,Visual const #define XRenderQueryVersion _glfw.x11.xrender.QueryVersion #define XRenderFindVisualFormat _glfw.x11.xrender.FindVisualFormat +typedef Bool (* PFN_XShapeQueryExtension)(Display*,int*,int*); +typedef Status (* PFN_XShapeQueryVersion)(Display*dpy,int*,int*); +typedef void (* PFN_XShapeCombineRegion)(Display*,Window,int,int,int,Region,int); +#define XShapeQueryExtension _glfw.x11.xshape.QueryExtension +#define XShapeQueryVersion _glfw.x11.xshape.QueryVersion +#define XShapeCombineRegion _glfw.x11.xshape.ShapeCombineRegion + typedef VkFlags VkXlibSurfaceCreateFlagsKHR; typedef VkFlags VkXcbSurfaceCreateFlagsKHR; @@ -515,11 +528,13 @@ typedef struct _GLFWlibraryX11 PFN_XCreateColormap CreateColormap; PFN_XCreateFontCursor CreateFontCursor; PFN_XCreateIC CreateIC; + PFN_XCreateRegion CreateRegion; PFN_XCreateWindow CreateWindow; PFN_XDefineCursor DefineCursor; PFN_XDeleteContext DeleteContext; PFN_XDeleteProperty DeleteProperty; PFN_XDestroyIC DestroyIC; + PFN_XDestroyRegion DestroyRegion; PFN_XDestroyWindow DestroyWindow; PFN_XDisplayKeycodes DisplayKeycodes; PFN_XEventsQueued EventsQueued; @@ -581,6 +596,7 @@ typedef struct _GLFWlibraryX11 PFN_XTranslateCoordinates TranslateCoordinates; PFN_XUndefineCursor UndefineCursor; PFN_XUngrabPointer UngrabPointer; + PFN_XUnionRectWithRegion UnionRectWithRegion; PFN_XUnmapWindow UnmapWindow; PFN_XUnsetICFocus UnsetICFocus; PFN_XVisualIDFromVisual VisualIDFromVisual; @@ -720,6 +736,18 @@ typedef struct _GLFWlibraryX11 PFN_XRenderFindVisualFormat FindVisualFormat; } xrender; + struct { + GLFWbool available; + void* handle; + int major; + int minor; + int eventBase; + int errorBase; + PFN_XShapeQueryExtension QueryExtension; + PFN_XShapeCombineRegion ShapeCombineRegion; + PFN_XShapeQueryVersion QueryVersion; + } xshape; + } _GLFWlibraryX11; // X11-specific per-monitor data diff --git a/src/x11_window.c b/src/x11_window.c index f88a45c4..bbbff0d5 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -2702,6 +2702,32 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) XFlush(_glfw.x11.display); } +void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, GLFWbool enabled) +{ + if (!_glfw.x11.xshape.available) + return; + + if (enabled == window->mousePassthrough) + return; + + int width = 0; + int height = 0; + if (!enabled) + _glfwPlatformGetWindowSize(window, &width, &height); + + XRectangle rect; + rect.x = 0; + rect.y = 0; + rect.width = (unsigned short)width; + rect.height = (unsigned short)height; + + Region region = XCreateRegion(); + XUnionRectWithRegion(&rect, region, region); + XShapeCombineRegion(_glfw.x11.display, window->x11.handle, 2/*ShapeInput*/, 0, 0, region, 0/*ShapeSet*/); + XDestroyRegion(region); + window->mousePassthrough = enabled; +} + float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) { float opacity = 1.f;